Files
Aethera/mcp/core/Control_Service.cpp
T

998 lines
46 KiB
C++

#include "Control_Service.hpp"
#include "Control_Service.ipp"
#include "Control_Requests.hpp"
#include "Protocol_Type.hpp"
#include "runtime/Gallery_Plots.hpp"
#include "runtime/Datoviz_Observation_Json.hpp"
#include "runtime/Input_Event.hpp"
#include "runtime/Taskflow_Trace_Json.hpp"
#include <Frame_Policy/Fixed_Rate_Frame_Policy.hpp>
#include <Frame_Policy/Manual_Frame_Policy.hpp>
#include <Frame_Policy/Maximum_Rate_Frame_Policy.hpp>
#include <magic_enum/magic_enum.hpp>
#include <render_2D/plottable/Plottables.hpp>
#include <render_3D/Render_3D.hpp>
#include <render_3D/detail/Gpu_Completion_Service.hpp>
#include <render_common.hpp>
#include <array>
#include <chrono>
#include <cmath>
#include <optional>
#include <ranges>
#include <stdexcept>
#include <unordered_set>
#include <utility>
namespace aethera::mcp {
namespace {
using Invoke = Tool_Call_Output (*)(Control_Service&, const nlohmann::json&);
using Schema = nlohmann::json (*)();
struct Operation {
std::string_view name; /* MCP 稳定 tool name。 */
std::string_view description; /* 模型选择工具时使用的业务说明。 */
Schema schema; /* PFR 自动生成的输入结构。 */
Invoke invoke; /* 协议无关的业务调用入口。 */
};
template <typename Request>
[[nodiscard]] nlohmann::json request_schema() {
return describe_protocol_type<Request>();
}
template <typename Request, typename Callback>
[[nodiscard]] Tool_Call_Output decode_and_call(
const nlohmann::json& arguments, Callback&& callback) {
try {
Request request{};
decode_protocol_value(request, arguments);
return std::forward<Callback>(callback)(request);
} catch (const nlohmann::json::exception& failure) {
return {Tool_Call_Result::invalid_arguments, {}, failure.what()};
} catch (const std::invalid_argument& failure) {
return {Tool_Call_Result::invalid_arguments, {}, failure.what()};
}
}
[[nodiscard]] std::string_view pacing_mode_name(Frame_Policy_Type type) {
const auto name = magic_enum::enum_name(type);
if (name.empty()) throw std::logic_error("unknown frame policy type");
return name;
}
[[nodiscard]] nlohmann::json frame_policy_schema(
const Frame_Policy& policy) {
const auto* fixed = dynamic_cast<const Fixed_Rate_Frame_Policy*>(&policy);
return {
{"id", "frame-analysis"}, {"label", "渲染与媒体流水线"},
{"kind", "analysis"},
{"fields", nlohmann::json::array({
{{"key", "render_enabled"}, {"editor", "boolean"},
{"editable", true},
{"value", policy.get<&Frame_Policy::Prop::render_enabled>()}},
{{"key", "pixel_delivery_enabled"}, {"editor", "boolean"},
{"editable", true},
{"value", policy.get<&Frame_Policy::Prop::pixel_delivery_enabled>()}},
{{"key", "pacing_mode"}, {"editor", "select"},
{"editable", true}, {"value", pacing_mode_name(policy.type())},
{"options", nlohmann::json::array({
{{"value", "manual"}, {"label", "手动渲染"}},
{{"value", "fixed_rate"}, {"label", "固定帧率"}},
{{"value", "maximum_rate"}, {"label", "最大吞吐"}}})}},
{{"key", "fixed_rate_fps"}, {"editor", "number"},
{"editable", true}, {"minimum", 0.1}, {"maximum", 100.0},
{"value", fixed ? fixed->frame_rate() : 100.0}}
})}
};
}
[[nodiscard]] nlohmann::json frame_policy_state_json(
const Frame_Policy_State& state, const Frame_Policy& policy) {
const auto milliseconds = [](std::uint64_t value) {
return static_cast<double>(value) / 1'000'000.0;
};
const auto observed_ns = state.observed_until_ns > state.observation_started_ns
? state.observed_until_ns - state.observation_started_ns : 0U;
const auto observed_seconds = static_cast<double>(observed_ns) / 1e9;
const auto rate = [observed_seconds](std::uint64_t count) {
return observed_seconds == 0.0 ? 0.0 :
static_cast<double>(count) / observed_seconds;
};
const auto* fixed = dynamic_cast<const Fixed_Rate_Frame_Policy*>(&policy);
const double target = fixed ? fixed->frame_rate() : 0.0;
const auto completion_span =
state.last_completion_ns > state.first_completion_ns
? state.last_completion_ns - state.first_completion_ns : 0U;
const double completed_fps = completion_span != 0 &&
state.completed_frame_count > 1
? static_cast<double>(state.completed_frame_count - 1) * 1e9 /
static_cast<double>(completion_span)
: 0.0;
return {
{"generation", state.generation},
{"configuration", {
{"mode", pacing_mode_name(policy.type())},
{"render_enabled", policy.get<&Frame_Policy::Prop::render_enabled>()},
{"pixel_delivery_enabled", policy.get<&Frame_Policy::Prop::pixel_delivery_enabled>()},
{"fixed_rate_fps", target}}},
{"observation", {
{"duration_ms", milliseconds(observed_ns)},
{"request_count", state.request_count},
{"submitted_frame_count", state.submitted_frame_count},
{"completed_frame_count", state.completed_frame_count},
{"active_frame_count", state.active_frame_count}}},
{"lifecycle", {
{"state", magic_enum::enum_name(
policy.read_state<Frame_Policy::Base_Tag>().lifecycle)},
{"active_frame_count", state.active_frame_count},
{"published_frame_count", state.published_frame_count},
{"publication_failed_count", state.publication_failed_count}}},
{"throughput", {
{"request_rate_fps", rate(state.request_count)},
{"submission_rate_fps", rate(state.submitted_frame_count)},
{"completion_rate_fps", completed_fps},
{"target_achievement_ratio", target == 0.0 ? 0.0 : completed_fps / target},
{"latest_frame_interval_ms", milliseconds(state.latest_completion_interval_ns)}}},
{"latency", {
{"latest_tick_queue_ms", milliseconds(state.latest_tick_queue_ns)},
{"average_tick_queue_ms", milliseconds(
state.submitted_frame_count == 0 ? 0U :
state.tick_queue_total_ns / state.submitted_frame_count)},
{"maximum_tick_queue_ms", milliseconds(state.maximum_tick_queue_ns)},
{"latest_completion_ms", milliseconds(state.latest_completion_latency_ns)},
{"average_completion_ms", milliseconds(
state.completed_frame_count == 0 ? 0U :
state.completion_latency_total_ns /
state.completed_frame_count)},
{"maximum_completion_ms", milliseconds(state.maximum_completion_latency_ns)}}},
{"requests", {
{"periodic", state.periodic_request_count},
{"immediate", state.immediate_request_count},
{"maximum_rate", state.maximum_rate_request_count},
{"accepted", state.accepted_request_count},
{"policy_rejected", state.policy_rejection_count},
{"frame_slot_backpressure", state.frame_slot_backpressure_count},
{"scene_rejected", state.scene_rejection_count}}},
{"last_frame", {
{"sequence", state.last_frame_sequence},
{"request_source", magic_enum::enum_name(state.last_request_source)}}}
};
}
void append_frame_statistics_json(
nlohmann::json& output, const Frame_Statistics_State& state) {
for (const auto statistic : magic_enum::enum_values<Frame_Statistic>()) {
if (statistic == Frame_Statistic::count) continue;
const auto& value = state.values[static_cast<std::size_t>(statistic)];
if (value.count == 0) continue;
output[magic_enum::enum_name(statistic)] = {
{"count", value.count}, {"latest", value.latest},
{"minimum", value.minimum}, {"maximum", value.maximum},
{"average", value.average},
{"trimmed_average", value.trimmed_average},
{"variability", value.variability}, {"p50", value.p50},
{"p95", value.p95}, {"p99", value.p99}};
}
}
void append_event_statistics_json(
nlohmann::json& output, const Event_Statistics_State& state) {
for (const auto type : magic_enum::enum_values<Event_Type>()) {
auto& event = output[magic_enum::enum_name(type)];
const auto& values = state.values[static_cast<std::size_t>(type)];
for (const auto statistic : magic_enum::enum_values<Event_Statistic>()) {
if (statistic == Event_Statistic::count) continue;
const auto& value = values[static_cast<std::size_t>(statistic)];
if (value.count == 0) continue;
event[magic_enum::enum_name(statistic)] = {
{"count", value.count}, {"latest", value.latest},
{"minimum", value.minimum}, {"maximum", value.maximum},
{"average", value.average},
{"trimmed_average", value.trimmed_average},
{"variability", value.variability}, {"p50", value.p50},
{"p95", value.p95}, {"p99", value.p99}};
}
if (event.empty())
output.erase(std::string{magic_enum::enum_name(type)});
}
}
[[nodiscard]] Tool_Call_Output list_plots(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Empty_Request>(arguments, [&service](const auto&) {
return Tool_Call_Output{Tool_Call_Result::ok, service.plot_catalog(), {}};
});
}
[[nodiscard]] Tool_Call_Output plot_schema(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Plot_Request>(arguments, [&service](const auto& request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"};
return Tool_Call_Output{Tool_Call_Result::ok,
service.plot_schema(request.plot), {}};
});
}
[[nodiscard]] Tool_Call_Output plot_diagnostics(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Plot_Request>(arguments, [&service](const auto& request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"};
return Tool_Call_Output{Tool_Call_Result::ok,
service.plot_diagnostics(request.plot), {}};
});
}
[[nodiscard]] Tool_Call_Output reset_plot_diagnostics(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Plot_Request>(arguments, [&service](const auto& request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"};
service.reset_plot_diagnostics(request.plot);
return Tool_Call_Output{Tool_Call_Result::ok, {{"accepted", true}}, {}};
});
}
[[nodiscard]] Tool_Call_Output component_state(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Component_Request>(arguments, [&service](const auto& request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"};
auto result = service.plot_component_state(
request.plot, request.component);
if (!result.value("success", true))
return Tool_Call_Output{Tool_Call_Result::rejected, std::move(result), "unknown component"};
return Tool_Call_Output{Tool_Call_Result::ok, std::move(result), {}};
});
}
[[nodiscard]] Tool_Call_Output write_property(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Write_Property_Request>(arguments, [&service](const auto& request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"};
auto result = service.write_plot_property(
request.plot, request.component, request.property, request.value);
const bool success = result.value("success", false);
return Tool_Call_Output{success ? Tool_Call_Result::ok : Tool_Call_Result::rejected,
std::move(result), success ? "" : "property write rejected"};
});
}
[[nodiscard]] Tool_Call_Output generate_data(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Generate_Data_Request>(arguments, [&service](const auto& request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"};
return Tool_Call_Output{Tool_Call_Result::ok,
service.generate_plot_data(request.plot, request.input), {}};
});
}
[[nodiscard]] Tool_Call_Output submit_plot_input(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Plot_Input_Request>(
arguments, [&service](auto request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{
Tool_Call_Result::unknown_plot, {}, "unknown plot"};
const auto time_milliseconds =
request.event.at("time_milliseconds").get<double>();
service.submit_input(
request.plot, web::make_input_event(request.event));
return Tool_Call_Output{
Tool_Call_Result::ok,
{{"accepted", true},
{"plot", request.plot},
{"time_milliseconds", time_milliseconds}}, {}};
});
}
[[nodiscard]] Tool_Call_Output render_plot_at(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Plot_Render_Request>(
arguments, [&service](const auto& request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{
Tool_Call_Result::unknown_plot, {}, "unknown plot"};
service.request_frame(request.plot, Frame_Request{
.issued_at = std::chrono::steady_clock::now(),
.time_milliseconds = request.time_milliseconds,
.width = request.width,
.height = request.height,
.source = Frame_Request_Source::immediate});
return Tool_Call_Output{
Tool_Call_Result::ok,
{{"accepted", true},
{"plot", request.plot},
{"time_milliseconds", request.time_milliseconds},
{"width", request.width}, {"height", request.height}}, {}};
});
}
[[nodiscard]] Tool_Call_Output begin_frame_trace(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Frame_Trace_Request>(
arguments, [&service](const auto& request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{
Tool_Call_Result::unknown_plot, {}, "unknown plot"};
service.request_plot_taskflow_trace(
request.plot, request.frame_count);
return Tool_Call_Output{
Tool_Call_Result::ok,
{{"accepted", true}, {"plot", request.plot},
{"frame_count", request.frame_count},
{"status_tool", "aethera_frame_trace_read"}}, {}};
});
}
[[nodiscard]] Tool_Call_Output read_frame_trace(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Plot_Request>(
arguments, [&service](const auto& request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{
Tool_Call_Result::unknown_plot, {}, "unknown plot"};
return Tool_Call_Output{
Tool_Call_Result::ok,
service.plot_taskflow_trace(request.plot), {}};
});
}
[[nodiscard]] Tool_Call_Output begin_benchmark(
Control_Service& service, const nlohmann::json& arguments) {
return decode_and_call<Plot_Request>(arguments, [&service](const auto& request) {
if (!service.contains_plot(request.plot))
return Tool_Call_Output{Tool_Call_Result::unknown_plot, {}, "unknown plot"};
service.reset_plot_diagnostics(request.plot);
service.request_frame(request.plot, Frame_Request{
.issued_at = std::chrono::steady_clock::now(),
.source = Frame_Request_Source::immediate});
return Tool_Call_Output{Tool_Call_Result::ok,
{{"accepted", true}, {"plot", request.plot},
{"status_tool", "aethera_benchmark_read"}}, {}};
});
}
[[nodiscard]] nlohmann::json task_runtime_json() {
const auto state = task_runtime_state();
nlohmann::json workers = nlohmann::json::array();
for (const auto& worker : state.workers)
workers.push_back({
{"id", worker.id}, {"task_count", worker.task_count},
{"entry_queue_size", worker.current_queue_size},
{"entry_queue_capacity", worker.current_queue_capacity},
{"peak_queue_size", worker.peak_observed_queue_size},
{"max_queue_capacity", worker.max_observed_queue_capacity},
{"active_task", {{"native_id", std::to_string(worker.active_task_hash)},
{"type", worker.active_task_type},
{"time_ns", worker.active_task_time_ns}}},
{"task_time_ns", worker.task_time_ns},
{"busy_time_ns", worker.busy_time_ns},
{"cpu_time_ns", worker.cpu_time_ns},
{"cooperative_wait_count", worker.cooperative_wait_count},
{"cooperative_wait_time_ns", worker.cooperative_wait_time_ns},
{"idle_time_ns", worker.idle_time_ns},
{"min_task_time_ns", worker.min_task_time_ns},
{"max_task_time_ns", worker.max_task_time_ns},
{"utilization", worker.utilization},
{"cpu_utilization", worker.cpu_utilization}});
nlohmann::json task_types = nlohmann::json::array();
for (const auto& type : state.task_types)
task_types.push_back({
{"name", type.name}, {"count", type.count},
{"total_time_ns", type.total_time_ns},
{"min_time_ns", type.min_time_ns},
{"max_time_ns", type.max_time_ns}});
return {
{"protocol", "aethera.taskflow.runtime"}, {"version", 2},
{"worker_count", state.worker_count},
{"active_topologies", state.active_topology_count},
{"active_taskflows", state.active_taskflow_count},
{"peak_active_taskflows", state.peak_active_taskflow_count},
{"completed_taskflows", state.completed_taskflow_count},
{"failed_taskflows", state.failed_taskflow_count},
{"active_tasks", state.active_task_count},
{"peak_active_tasks", state.peak_active_task_count},
{"active_workers", state.active_worker_count},
{"peak_active_workers", state.peak_active_worker_count},
{"observed_tasks", state.observed_task_count},
{"named_tasks", state.named_task_count},
{"peak_worker_queue_size", state.peak_observed_worker_queue_size},
{"max_worker_queue_capacity", state.max_observed_worker_queue_capacity},
{"longest_task", {{"native_id", std::to_string(state.longest_task_hash)},
{"name", state.longest_task_name},
{"type", state.longest_task_type},
{"time_ns", state.longest_task_time_ns}}},
{"total_task_time_ns", state.total_task_time_ns},
{"worker_busy_time_ns", state.worker_busy_time_ns},
{"worker_cpu_time_ns", state.worker_cpu_time_ns},
{"cooperative_wait_count", state.cooperative_wait_count},
{"cooperative_wait_time_ns", state.cooperative_wait_time_ns},
{"observed_wall_time_ns", state.observed_wall_time_ns},
{"worker_utilization", state.worker_utilization},
{"worker_cpu_utilization", state.worker_cpu_utilization},
{"task_types", std::move(task_types)}, {"workers", std::move(workers)}};
}
[[nodiscard]] Tool_Call_Output task_runtime(
Control_Service&, const nlohmann::json& arguments) {
return decode_and_call<Empty_Request>(arguments, [](const auto&) {
return Tool_Call_Output{Tool_Call_Result::ok, task_runtime_json(), {}};
});
}
constexpr std::array operations{
Operation{"aethera_plot_list", "List every 2D and 3D gallery plot.",
&request_schema<Empty_Request>, &list_plots},
Operation{"aethera_plot_schema", "Describe a plot and its editable render components.",
&request_schema<Plot_Request>, &plot_schema},
Operation{"aethera_plot_diagnostics", "Read current frame, scene, GPU and Datoviz diagnostics.",
&request_schema<Plot_Request>, &plot_diagnostics},
Operation{"aethera_plot_diagnostics_reset", "Reset the plot's authoritative diagnostic counters.",
&request_schema<Plot_Request>, &reset_plot_diagnostics},
Operation{"aethera_component_state", "Read one render component's published state.",
&request_schema<Component_Request>, &component_state},
Operation{"aethera_component_write", "Write one editable render component property.",
&request_schema<Write_Property_Request>, &write_property},
Operation{"aethera_data_generate", "Generate input data for a gallery plot.",
&request_schema<Generate_Data_Request>, &generate_data},
Operation{"aethera_plot_input", "Submit one timestamped input event without waiting for rendering.",
&request_schema<Plot_Input_Request>, &submit_plot_input},
Operation{"aethera_plot_render_at", "Request one diagnostic frame at a caller-owned timeline time.",
&request_schema<Plot_Render_Request>, &render_plot_at},
Operation{"aethera_frame_trace_begin", "Capture existing per-frame Taskflow and Datoviz observations.",
&request_schema<Frame_Trace_Request>, &begin_frame_trace},
Operation{"aethera_frame_trace_read", "Read the current physical-frame trace capture.",
&request_schema<Plot_Request>, &read_frame_trace},
Operation{"aethera_benchmark_begin", "Reset diagnostics and asynchronously start rendering a plot.",
&request_schema<Plot_Request>, &begin_benchmark},
Operation{"aethera_benchmark_read", "Read benchmark results from the plot's current diagnostics.",
&request_schema<Plot_Request>, &plot_diagnostics},
Operation{"aethera_task_runtime", "Read Taskflow executor utilization and queue diagnostics.",
&request_schema<Empty_Request>, &task_runtime},
};
}
Control_Service::Private::Entry::Entry(
std::string value_id, web::Gallery_Build build,
Gallery_Output value_output)
: components(std::move(build.first)), id(std::move(value_id)),
output(std::move(value_output)) {
runtime.first = std::move(build.second);
}
Control_Service::Private::Entry::~Entry() = default;
std::shared_ptr<Frame_Policy>
Control_Service::Private::Entry::policy() const noexcept {
return runtime.second.load(std::memory_order_acquire);
}
void Control_Service::Private::Entry::fail(
std::exception_ptr failure) noexcept {
try {
std::string description{"unknown Gallery runtime failure"};
try {
if (failure) std::rethrow_exception(failure);
}
catch (const std::exception& error) { description = error.what(); }
catch (...) { description = "non-standard Gallery runtime failure"; }
terminal_failure.store(
std::make_shared<const std::string>(std::move(description)),
std::memory_order_release);
}
catch (...) {}
}
std::shared_ptr<Frame_Policy>
Control_Service::Private::Entry::create_policy(
Frame_Policy_Type type, double fixed_rate_fps) {
auto* scene = std::visit(
[](auto& value) -> Render_Frame_Scene* { return value.get(); },
runtime.first);
const bool is_3d = std::holds_alternative<
std::unique_ptr<render_3d::Render_Scene_3D>>(runtime.first);
const auto weak = weak_from_this();
Frame_Policy::Dependencies dependencies{
.scene = not_null{scene},
.create_frame = [is_3d]() -> std::unique_ptr<Render_Frame> {
if (is_3d)
return std::make_unique<render_3d::Frame_3D>(Frame_Identity{});
return std::make_unique<render_2d::Frame_2D>(Frame_Identity{});
},
.prepare_frame = [weak](not_null<Render_Frame*> frame,
const Frame_Production& production) {
if (const auto owner = weak.lock())
owner->prepare_frame(frame, production);
},
.publish_frame = [weak](not_null<Render_Frame*> frame,
const Frame_Production& production) {
if (const auto owner = weak.lock())
return owner->publish_frame(frame, production);
return Frame_Publication_Dispatch{};
},
.retain_owner = [weak]() -> std::shared_ptr<void> {
return weak.lock();
},
.report_failure = [weak](std::exception_ptr failure) {
if (const auto owner = weak.lock())
owner->fail(std::move(failure));
},
.frame_capacity = 3};
switch (type) {
case Frame_Policy_Type::manual:
return std::make_shared<Manual_Frame_Policy>(std::move(dependencies));
case Frame_Policy_Type::fixed_rate:
return std::make_shared<Fixed_Rate_Frame_Policy>(
std::move(dependencies), fixed_rate_fps);
case Frame_Policy_Type::maximum_rate:
return std::make_shared<Maximum_Rate_Frame_Policy>(
std::move(dependencies));
}
throw std::logic_error("unknown Frame Policy type");
}
void Control_Service::Private::Entry::start() {
auto next = create_policy(Frame_Policy_Type::fixed_rate, 100.0);
runtime.second.store(next, std::memory_order_release);
next->start();
}
void Control_Service::Private::Entry::stop() noexcept {
auto current = runtime.second.exchange({}, std::memory_order_acq_rel);
if (!current) return;
try {
current->stop([owner = shared_from_this(), current]() mutable {
current.reset();
owner.reset();
});
}
catch (...) { fail(std::current_exception()); }
}
void Control_Service::Private::Entry::replace_policy(
Frame_Policy_Type type) {
auto current = runtime.second.exchange({}, std::memory_order_acq_rel);
if (!current) throw std::logic_error("Frame Policy switch is active");
const auto* fixed = dynamic_cast<const Fixed_Rate_Frame_Policy*>(
current.get());
const auto fixed_rate_fps = fixed ? fixed->frame_rate() : 100.0;
current->stop([
owner = shared_from_this(), current, type, fixed_rate_fps]() mutable {
try {
auto next = owner->create_policy(type, fixed_rate_fps);
owner->runtime.second.store(next, std::memory_order_release);
next->start();
}
catch (...) { owner->fail(std::current_exception()); }
current.reset();
});
}
void Control_Service::Private::Entry::prepare_frame(
not_null<Render_Frame*> frame, const Frame_Production& production) {
auto request = production.request;
request.width = output.width;
request.height = output.height;
const Frame_Identity identity{
production.sequence,
request.sequence == 0 ? production.sequence : request.sequence};
if (auto* frame_2d = dynamic_cast<render_2d::Frame_2D*>(frame.get())) {
frame_2d->begin(identity, render_2d::Frame_2D::native_pixel_format,
request.source);
std::get<std::unique_ptr<render_2d::Render_Scene_2D>>(runtime.first)
->set<&render_2d::Render_Scene_2D::Prop::viewport>(
render_2d::Size{static_cast<int>(request.width),
static_cast<int>(request.height)});
}
else if (auto* frame_3d = dynamic_cast<render_3d::Frame_3D*>(frame.get())) {
frame_3d->begin(identity, render_3d::Frame_3D_Output::pixels,
render_3d::Frame_3D::native_pixel_format,
request.source);
std::get<std::unique_ptr<render_3d::Render_Scene_3D>>(runtime.first)
->set<&render_3d::Render_Scene_3D::Prop::viewport>(
render_3d::Extent{request.width, request.height});
}
else {
throw std::logic_error("Gallery policy created an unsupported Frame");
}
auto remaining = taskflow_trace_remaining.load(std::memory_order_acquire);
while (remaining != 0 &&
!taskflow_trace_remaining.compare_exchange_weak(
remaining, remaining - 1, std::memory_order_acq_rel,
std::memory_order_acquire)) {}
if (remaining != 0) frame->request_taskflow_trace();
frame->mark(Frame_Trace_Marker::plot_update_started);
const auto started = std::chrono::steady_clock::now();
components->update(request);
frame->mark(Frame_Trace_Marker::plot_update_finished);
const auto elapsed = std::chrono::steady_clock::now() - started;
frame->record(Frame_Trace_Measurement::plot_update_ns,
static_cast<std::uint64_t>(std::max<std::int64_t>(0,
std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed)
.count())));
}
Frame_Publication_Dispatch
Control_Service::Private::Entry::publish_frame(
not_null<Render_Frame*> frame, const Frame_Production& production) {
const auto current = policy();
const bool deliver_pixels = current && current->get<
&Frame_Policy::Prop::pixel_delivery_enabled>();
const auto identity = frame->identity();
auto rendered = identity;
std::shared_ptr<const std::vector<std::byte>> storage;
std::optional<render_3d::Datoviz_Frame_Observation> datoviz_observation;
web::Gallery_Pixel_Layout layout{web::Gallery_Pixel_Layout::rgba8};
std::uint32_t width{}, height{};
if (auto* frame_2d = dynamic_cast<render_2d::Frame_2D*>(frame.get())) {
layout = web::Gallery_Pixel_Layout::bgra8;
const auto image = frame_2d->image();
width = static_cast<std::uint32_t>(std::max(0, image.width));
height = static_cast<std::uint32_t>(std::max(0, image.height));
if (deliver_pixels) {
auto pixels = frame_2d->output_pixels();
width = static_cast<std::uint32_t>(pixels.width);
height = static_cast<std::uint32_t>(pixels.height);
storage = std::make_shared<const std::vector<std::byte>>(
std::move(pixels.bytes));
}
}
else if (auto* frame_3d = dynamic_cast<render_3d::Frame_3D*>(frame.get())) {
rendered = frame_3d->rendered_identity();
datoviz_observation = frame_3d->take_datoviz_observation();
const auto extent = frame_3d->extent();
width = extent.width;
height = extent.height;
if (deliver_pixels) storage = frame_3d->share_pixels();
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::duration<double, std::milli>(
production.request.time_milliseconds));
auto pixels = std::make_shared<const web::Gallery_Pixel_Frame>(
web::Gallery_Pixel_Frame{
std::move(storage), layout, timestamp, identity.sequence,
identity.correlation_id, rendered.sequence,
rendered.correlation_id, width, height});
auto publication = std::make_shared<const web::Gallery_Frame>(
web::Gallery_Frame{std::move(pixels)});
const auto started = std::chrono::steady_clock::now();
auto result = web::Gallery_Frame_Publication::ignored;
if (output.publish) result = output.publish(id, std::move(publication));
const auto completed = std::chrono::steady_clock::now();
frame->record(Frame_Trace_Measurement::plot_publish_ns,
static_cast<std::uint64_t>(std::max<std::int64_t>(0,
std::chrono::duration_cast<std::chrono::nanoseconds>(
completed - started).count())));
if (frame->taskflow_trace_requested()) {
const auto trace_value = frame->take_taskflow_trace();
std::unordered_set<std::uint64_t> executed;
for (const auto& task : trace_value.tasks)
executed.insert(task.native_id);
std::vector<std::string> component_ids;
for (const auto& graph : trace_value.graphs)
for (const auto& node : graph.nodes) {
if (!executed.contains(node.native_id)) continue;
const auto found = std::ranges::find(
node.attributes, "owner_component",
&std::pair<std::string, std::string>::first);
if (found != node.attributes.end() && !found->second.empty() &&
std::ranges::find(component_ids, found->second) ==
component_ids.end())
component_ids.push_back(found->second);
}
nlohmann::json captured_backend;
if (datoviz_observation)
captured_backend = datoviz_observation_json(*datoviz_observation);
const auto encoded = std::make_shared<const nlohmann::json>(
web::taskflow_trace_json(trace_value,
components->capture_components(component_ids),
captured_backend));
auto state = taskflow_trace_control.load(std::memory_order_acquire);
for (;;) {
const auto requested = static_cast<std::uint32_t>(state >> 32U);
const auto captured = static_cast<std::uint32_t>(state);
if (captured >= requested) break;
taskflow_trace_slots[captured].store(encoded,
std::memory_order_release);
const auto next = (static_cast<std::uint64_t>(requested) << 32U) |
static_cast<std::uint64_t>(captured + 1U);
if (taskflow_trace_control.compare_exchange_weak(
state, next, std::memory_order_release,
std::memory_order_acquire))
break;
}
}
return {
.started_at = started,
.completed_at = completed,
.asynchronous_feedback_count =
result == web::Gallery_Frame_Publication::asynchronous ? 1U : 0U,
.succeeded = result == web::Gallery_Frame_Publication::completed,
.pixel_width = width,
.pixel_height = height};
}
nlohmann::json Control_Service::Private::Entry::schema() const {
auto result = components->schema();
if (const auto current = policy()) {
auto analysis = frame_policy_schema(*current);
const auto generator = components->data_generator_schema();
if (!generator.is_null()) analysis["data_generator"] = generator;
result["frame_analysis"] = std::move(analysis);
}
return result;
}
nlohmann::json Control_Service::Private::Entry::diagnostics() const {
const auto current = policy();
if (!current)
return {{"protocol", "aethera.plot.diagnostics"}, {"version", 5},
{"available", false}};
Frame_Policy_State observation{};
current->access_state<Frame_Policy::Base_Tag>(
[&](const Frame_Policy::State& state) {
observation = state.observation;
});
nlohmann::json input_statistics = nlohmann::json::object();
nlohmann::json frame_statistics = nlohmann::json::object();
append_frame_statistics_json(
frame_statistics, observation.frame_statistics);
std::visit([&](const auto& scene) {
scene->template access_state<Scene::Base_Tag>(
[&](const Scene::State& state) {
append_event_statistics_json(
input_statistics, state.event_statistics);
});
}, runtime.first);
nlohmann::json result{
{"protocol", "aethera.plot.diagnostics"}, {"version", 5},
{"available", true}, {"plot", id},
{"dimension", std::holds_alternative<
std::unique_ptr<render_3d::Render_Scene_3D>>(runtime.first)
? "3D" : "2D"},
{"sequence", observation.last_frame_sequence},
{"pixel", {{"width", observation.pixel_width},
{"height", observation.pixel_height}}},
{"frame_policy", frame_policy_state_json(observation, *current)},
{"frame_lifecycle", magic_enum::enum_name(
current->read_state<Frame_Policy::Base_Tag>().lifecycle)},
{"frame_statistics", std::move(frame_statistics)},
{"input_statistics", std::move(input_statistics)}};
if (const auto failure = terminal_failure.load(std::memory_order_acquire))
result["terminal_failure"] = *failure;
return result;
}
void Control_Service::Private::Entry::request_trace(
std::size_t frame_count) {
if (frame_count == 0 || frame_count > maximum_taskflow_trace_frames)
throw std::invalid_argument(
"Taskflow trace frame_count must be between 1 and 120");
auto control = taskflow_trace_control.load(std::memory_order_acquire);
for (;;) {
const auto requested = static_cast<std::uint32_t>(control >> 32U);
const auto captured = static_cast<std::uint32_t>(control);
if (requested != captured)
throw std::logic_error("A Taskflow trace request is active");
const auto next = static_cast<std::uint64_t>(frame_count) << 32U;
if (taskflow_trace_control.compare_exchange_weak(
control, next, std::memory_order_release,
std::memory_order_acquire))
break;
}
for (auto& slot : taskflow_trace_slots)
slot.store({}, std::memory_order_release);
taskflow_trace_remaining.store(frame_count, std::memory_order_release);
}
nlohmann::json Control_Service::Private::Entry::trace() const {
nlohmann::json frames = nlohmann::json::array();
const auto state = taskflow_trace_control.load(std::memory_order_acquire);
const auto requested = static_cast<std::uint32_t>(state >> 32U);
const auto captured = static_cast<std::uint32_t>(state);
for (std::uint32_t index = 0; index < captured; ++index)
if (const auto value =
taskflow_trace_slots[index].load(std::memory_order_acquire))
frames.push_back(*value);
return {{"protocol", "aethera.taskflow.frames"}, {"version", 1},
{"requested", requested},
{"remaining", taskflow_trace_remaining.load(
std::memory_order_acquire)},
{"captured", frames.size()},
{"complete", requested != 0 && frames.size() == requested},
{"frames", std::move(frames)}};
}
Control_Service::Control_Service(Gallery_Output output)
: d(std::make_unique<Private>()) {
d->output = std::move(output);
}
Control_Service::~Control_Service() {
for (const auto& [id, entry] : d->plots) {
static_cast<void>(id);
entry->stop();
}
}
std::shared_ptr<Control_Service> Control_Service::create(
Gallery_Output output) {
auto service = std::shared_ptr<Control_Service>(
new Control_Service(std::move(output)));
service->d->plots.reserve(web::gallery_plot_definitions().size());
for (const auto& definition : web::gallery_plot_definitions()) {
auto entry = std::make_shared<Private::Entry>(
std::string{definition.id}, definition.create(),
service->d->output);
service->d->plots.emplace(entry->id, entry);
entry->start();
}
return service;
}
bool Control_Service::contains_plot(std::string_view id) const noexcept {
return d->plots.contains(std::string{id});
}
void Control_Service::submit_input(
std::string_view id, Scene::Event_Pointer event) {
if (!event) throw std::invalid_argument("Gallery input event is null");
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) throw std::invalid_argument("unknown plot");
std::visit([event = std::move(event)](auto& scene) mutable {
scene->submit_event(std::move(event));
}, found->second->runtime.first);
}
void Control_Service::request_frame(
std::string_view id, Frame_Request request) {
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) throw std::invalid_argument("unknown plot");
const auto current = found->second->policy();
if (!current) throw std::logic_error("Frame Policy switch is active");
current->request_frame(std::move(request));
}
void Control_Service::submit_publication_feedback(
std::string_view id, Frame_Publication_Feedback feedback) noexcept {
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) return;
if (const auto current = found->second->policy())
current->submit_publication_feedback(std::move(feedback));
}
nlohmann::json Control_Service::plot_schema(std::string_view id) const {
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) throw std::invalid_argument("unknown plot");
return found->second->schema();
}
nlohmann::json Control_Service::write_plot_property(
std::string_view id, std::string_view component, std::string_view key,
const nlohmann::json& value) {
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) throw std::invalid_argument("unknown plot");
auto& entry = *found->second;
if (component != "frame-analysis")
return entry.components->write_prop(component, key, value);
if (key == "pacing_mode") {
if (!value.is_string())
return {{"success", false},
{"error", "pacing_mode requires a string"}};
const auto parsed = magic_enum::enum_cast<Frame_Policy_Type>(
value.get_ref<const std::string&>());
if (!parsed)
return {{"success", false},
{"error", "unknown frame pacing mode"}};
entry.replace_policy(*parsed);
return {{"success", true}, {"component", component}, {"key", key},
{"value", pacing_mode_name(*parsed)}};
}
const auto current = entry.policy();
if (!current)
return {{"success", false}, {"error", "Frame Policy switch is active"}};
if (key == "render_enabled" || key == "pixel_delivery_enabled") {
if (!value.is_boolean())
return {{"success", false}, {"error", "boolean required"}};
const auto enabled = value.get<bool>();
if (key == "render_enabled")
current->set<&Frame_Policy::Prop::render_enabled>(enabled);
else
current->set<&Frame_Policy::Prop::pixel_delivery_enabled>(enabled);
return {{"success", true}, {"component", component}, {"key", key},
{"value", enabled}};
}
if (key == "fixed_rate_fps") {
if (!value.is_number())
return {{"success", false}, {"error", "number required"}};
const auto rate = value.get<double>();
auto* fixed = dynamic_cast<Fixed_Rate_Frame_Policy*>(current.get());
if (!fixed || !std::isfinite(rate) || rate < 0.1 || rate > 100.0)
return {{"success", false}, {"error", "invalid fixed rate"}};
fixed->set_frame_rate(rate);
return {{"success", true}, {"component", component}, {"key", key},
{"value", rate}};
}
return {{"success", false}, {"error", "unknown frame property"}};
}
nlohmann::json Control_Service::plot_component_state(
std::string_view id, std::string_view component) const {
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) throw std::invalid_argument("unknown plot");
return found->second->components->component_state(component);
}
nlohmann::json Control_Service::generate_plot_data(
std::string_view id, const nlohmann::json& input) {
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) throw std::invalid_argument("unknown plot");
return found->second->components->generate_data(input);
}
nlohmann::json Control_Service::plot_diagnostics(std::string_view id) const {
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) throw std::invalid_argument("unknown plot");
return found->second->diagnostics();
}
void Control_Service::reset_plot_diagnostics(std::string_view id) {
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) throw std::invalid_argument("unknown plot");
std::visit([](auto& scene) {
scene->template update_state<&Scene::State::event_statistics>(
Event_Statistics_State{});
}, found->second->runtime.first);
if (const auto current = found->second->policy())
current->reset_statistics();
}
void Control_Service::request_plot_taskflow_trace(
std::string_view id, std::size_t frame_count) {
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) throw std::invalid_argument("unknown plot");
found->second->request_trace(frame_count);
}
nlohmann::json Control_Service::plot_taskflow_trace(
std::string_view id) const {
const auto found = d->plots.find(std::string{id});
if (found == d->plots.end()) throw std::invalid_argument("unknown plot");
return found->second->trace();
}
nlohmann::json Control_Service::plot_catalog() const {
nlohmann::json result = nlohmann::json::array();
for (const auto& definition : web::gallery_plot_definitions())
result.push_back({
{"id", definition.id}, {"title", definition.title},
{"category", definition.category},
{"description", definition.description},
{"dimension", web::plot_dimension_name(definition.dimension)}});
return result;
}
nlohmann::json Control_Service::tool_catalog() const {
nlohmann::json result = nlohmann::json::array();
for (const auto& operation : operations)
result.push_back({{"name", operation.name},
{"description", operation.description},
{"inputSchema", operation.schema()}});
return result;
}
Tool_Call_Output Control_Service::call_tool(
std::string_view name, const nlohmann::json& arguments) {
for (const auto& operation : operations)
if (operation.name == name) return operation.invoke(*this, arguments);
return {Tool_Call_Result::unknown_tool, {}, "unknown tool"};
}
}