From 31c8076822b5aeb6d06a19f98e8db27c20b72b1a Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Sat, 29 Aug 2026 01:27:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mcp/core/runtime/Plot.cpp | 1141 +++++++++-------- .../detail/Gpu_Completion_Service.cpp | 149 +-- .../detail/Gpu_Completion_Service.hpp | 17 +- .../detail/Gpu_Completion_Service.ipp | 12 +- 4 files changed, 646 insertions(+), 673 deletions(-) diff --git a/mcp/core/runtime/Plot.cpp b/mcp/core/runtime/Plot.cpp index d936cc9..e635f19 100644 --- a/mcp/core/runtime/Plot.cpp +++ b/mcp/core/runtime/Plot.cpp @@ -29,7 +29,6 @@ #include #include #include - namespace aethera::web { namespace { using namespace render_2d; @@ -38,8 +37,6 @@ using Scene_2D = Render_Scene_2D; using Scene_3D = Render_Scene_3D; constexpr std::uint16_t plot_stream_protocol_version{9}; constexpr std::size_t diagnostic_window_capacity{600}; - - std::string exception_description(const std::exception_ptr& failure) { try { if (failure) std::rethrow_exception(failure); @@ -52,210 +49,258 @@ std::string exception_description(const std::exception_ptr& failure) { } return "empty Plot failure"; } - std::string_view pacing_mode_name(Frame_Pacing_Mode mode) { const auto name = magic_enum::enum_name(mode); if (name.empty()) throw std::logic_error("unknown frame pacing mode"); return name; } - std::optional parse_pacing_mode(std::string_view value) { return magic_enum::enum_cast(value); } - std::string_view pixel_format_name(render_2d::Pixel_Format format) { const auto name = magic_enum::enum_name(format); if (name.empty()) throw std::logic_error("unknown 2D pixel format"); return name; } - std::string_view pixel_format_name(render_3d::Pixel_Format format) { switch (format) { - case render_3d::Pixel_Format::rgba8_unorm: - return "rgba8"; + case render_3d::Pixel_Format::rgba8_unorm: return "rgba8"; } throw std::logic_error("unknown 3D pixel format"); } - const Frame_Policy::State& frame_policy_common_state( const Frame_Policy& policy) noexcept { return policy.read_state(); } - const Frame_Policy::State& frame_policy_common_state( const Frame_Policy_3D& policy) noexcept { return policy.read_state().common; } - template nlohmann::json frame_policy_schema(const Policy& policy) { const auto& current = frame_policy_common_state(policy); return { {"id", "frame-analysis"}, {"label", "渲染与媒体流水线"}, {"kind", "analysis"}, - {"fields", nlohmann::json::array({ - {{"key", "render_enabled"}, {"label", "持续渲染与采样"}, {"editor", "boolean"}, - {"editable", true}, {"description", "控制当前 Scene 的周期刷新;画面隐藏不会修改此项。"}, - {"technical_description", "Authoritative per-scene periodic render switch."}, - {"value", current.render_enabled}}, - {{"key", "video_enabled"}, {"label", "图集视频传输"}, {"editor", "boolean"}, - {"editable", true}, {"description", "控制完成帧是否进入页面级采样器;2D BGRA 与 3D RGBA 均保持原生格式。"}, - {"technical_description", "Authoritative tile publication switch for the shared gallery video."}, - {"value", current.video_enabled}}, - {{"key", "pacing_mode"}, {"label", "服务端帧策略"}, {"editor", "select"}, - {"editable", true}, {"description", "只控制 Scene::render(Frame*) 的调用节奏;Scene 的 Frame 所有权与接口保持不变。"}, - {"technical_description", "Per-scene frame pacing policy backed by the Kernel scheduler."}, - {"value", pacing_mode_name(current.mode)}, - {"options", nlohmann::json::array({ - {{"value", "manual"}, {"label", "手动渲染"}}, - {{"value", "fixed_rate"}, {"label", "固定频率"}}, - {{"value", "maximum_rate"}, {"label", "最大吞吐"}} - })}}, - {{"key", "fixed_rate_fps"}, {"label", "目标帧率"}, {"editor", "number"}, - {"editable", true}, {"minimum", 0.1}, {"maximum", 100.0}, {"step", 0.1}, - {"description", "仅 fixed_rate 使用;maximum_rate 在完成准入释放后异步自驱下一帧。"}, - {"technical_description", "Independent per-scene target frame rate."}, - {"value", current.fixed_rate_fps}} - })} + { + "fields", nlohmann::json::array({ + { + {"key", "render_enabled"}, {"label", "持续渲染与采样"}, {"editor", "boolean"}, + {"editable", true}, {"description", "控制当前 Scene 的周期刷新;画面隐藏不会修改此项。"}, + {"technical_description", "Authoritative per-scene periodic render switch."}, + {"value", current.render_enabled} + }, + { + {"key", "video_enabled"}, {"label", "图集视频传输"}, {"editor", "boolean"}, + {"editable", true}, {"description", "控制完成帧是否进入页面级采样器;2D BGRA 与 3D RGBA 均保持原生格式。"}, + {"technical_description", "Authoritative tile publication switch for the shared gallery video."}, + {"value", current.video_enabled} + }, + { + {"key", "pacing_mode"}, {"label", "服务端帧策略"}, {"editor", "select"}, + {"editable", true}, {"description", "只控制 Scene::render(Frame*) 的调用节奏;Scene 的 Frame 所有权与接口保持不变。"}, + {"technical_description", "Per-scene frame pacing policy backed by the Kernel scheduler."}, + {"value", pacing_mode_name(current.mode)}, + { + "options", nlohmann::json::array({ + {{"value", "manual"}, {"label", "手动渲染"}}, + {{"value", "fixed_rate"}, {"label", "固定频率"}}, + {{"value", "maximum_rate"}, {"label", "最大吞吐"}} + }) + } + }, + { + {"key", "fixed_rate_fps"}, {"label", "目标帧率"}, {"editor", "number"}, + {"editable", true}, {"minimum", 0.1}, {"maximum", 100.0}, {"step", 0.1}, + {"description", "仅 fixed_rate 使用;maximum_rate 在完成准入释放后异步自驱下一帧。"}, + {"technical_description", "Independent per-scene target frame rate."}, + {"value", current.fixed_rate_fps} + } + }) + } }; } - template nlohmann::json write_frame_policy_prop(Policy& pacing, std::string_view key, const nlohmann::json& value) { if (key == "render_enabled" || key == "video_enabled") { - if (!value.is_boolean()) - return {{"success", false}, {"error", "frame policy switch requires a boolean"}}; + if (!value.is_boolean()) return {{"success", false}, {"error", "frame policy switch requires a boolean"}}; const bool target = value.get(); if (key == "render_enabled") pacing.set_render_enabled(target); else pacing.set_video_enabled(target); - return {{"success", true}, {"component", "frame-analysis"}, {"key", key}, - {"value", target}}; + return { + {"success", true}, {"component", "frame-analysis"}, {"key", key}, + {"value", target} + }; } if (key == "pacing_mode") { - if (!value.is_string()) - return {{"success", false}, {"error", "pacing_mode requires a string"}}; + if (!value.is_string()) return {{"success", false}, {"error", "pacing_mode requires a string"}}; const auto parsed = parse_pacing_mode(value.get_ref()); - if (!parsed) - return {{"success", false}, {"error", "unknown frame pacing mode"}}; + if (!parsed) return {{"success", false}, {"error", "unknown frame pacing mode"}}; pacing.set_mode(*parsed); - return {{"success", true}, {"component", "frame-analysis"}, {"key", key}, - {"value", pacing_mode_name(*parsed)}}; + return { + {"success", true}, {"component", "frame-analysis"}, {"key", key}, + {"value", pacing_mode_name(*parsed)} + }; } if (key == "fixed_rate_fps") { - if (!value.is_number()) - return {{"success", false}, {"error", "fixed_rate_fps requires a number"}}; + if (!value.is_number()) return {{"success", false}, {"error", "fixed_rate_fps requires a number"}}; const double next = value.get(); - if (!std::isfinite(next) || next < 0.1 || next > 100.0) - return {{"success", false}, {"error", "fixed_rate_fps must be between 0.1 and 100"}}; + if (!std::isfinite(next) || next < 0.1 || next > 100.0) return {{"success", false}, {"error", "fixed_rate_fps must be between 0.1 and 100"}}; pacing.set_fixed_rate(next); - return {{"success", true}, {"component", "frame-analysis"}, {"key", key}, - {"value", next}}; + return { + {"success", true}, {"component", "frame-analysis"}, {"key", key}, + {"value", next} + }; } return {{"success", false}, {"error", "unknown frame runtime property"}}; } - nlohmann::json frame_policy_state_json(const Frame_Policy::State& state) { const auto milliseconds = [](std::uint64_t nanoseconds) { return static_cast(nanoseconds) / 1'000'000.0; }; const auto ratio = [](std::uint64_t numerator, std::uint64_t denominator) { - return denominator == 0 ? 0.0 : - static_cast(numerator) / static_cast(denominator); + return denominator == 0 ? 0.0 : static_cast(numerator) / static_cast(denominator); }; const auto observed_ns = state.observed_until_ns > state.observation_started_ns - ? state.observed_until_ns - state.observation_started_ns : 0U; + ? state.observed_until_ns - state.observation_started_ns + : 0U; const auto observed_seconds = static_cast(observed_ns) / 1'000'000'000.0; const auto rate = [observed_seconds](std::uint64_t count) { return observed_seconds > 0.0 - ? static_cast(count) / observed_seconds : 0.0; + ? static_cast(count) / observed_seconds + : 0.0; }; const auto completion_span = state.last_completion_ns > state.first_completion_ns - ? state.last_completion_ns - state.first_completion_ns : 0U; + ? state.last_completion_ns - state.first_completion_ns + : 0U; const auto effective_fps = completion_span != 0 && state.completed_frame_count > 1 - ? static_cast(state.completed_frame_count - 1U) * - 1'000'000'000.0 / static_cast(completion_span) - : 0.0; - const auto interval_mean = state.completion_interval_count == 0 ? 0.0 : - static_cast(state.completion_interval_total_ns) / - static_cast(state.completion_interval_count); - const auto interval_variance = state.completion_interval_count == 0 ? 0.0 : - std::max(0.0, - state.completion_interval_squared_total_ns2 / - static_cast(state.completion_interval_count) - - interval_mean * interval_mean); + ? static_cast(state.completed_frame_count - 1U) * + 1'000'000'000.0 / static_cast(completion_span) + : 0.0; + const auto interval_mean = state.completion_interval_count == 0 + ? 0.0 + : static_cast(state.completion_interval_total_ns) / + static_cast(state.completion_interval_count); + const auto interval_variance = state.completion_interval_count == 0 + ? 0.0 + : std::max(0.0, + state.completion_interval_squared_total_ns2 / + static_cast(state.completion_interval_count) - + interval_mean * interval_mean); const auto target_achievement = - state.mode == Frame_Pacing_Mode::fixed_rate && state.fixed_rate_fps > 0.0 - ? effective_fps / state.fixed_rate_fps : 0.0; + state.mode == Frame_Pacing_Mode::fixed_rate && state.fixed_rate_fps > 0.0 + ? effective_fps / state.fixed_rate_fps + : 0.0; return { {"generation", state.generation}, - {"configuration", { - {"mode", pacing_mode_name(state.mode)}, - {"render_enabled", state.render_enabled}, - {"video_enabled", state.video_enabled}, - {"fixed_rate_fps", state.fixed_rate_fps}}}, - {"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}}}, - {"throughput", { - {"request_rate_fps", rate(state.request_count)}, - {"submission_rate_fps", rate(state.submitted_frame_count)}, - {"completion_rate_fps", effective_fps}, - {"target_achievement_ratio", target_achievement}, - {"latest_frame_interval_ms", - milliseconds(state.latest_completion_interval_ns)}, - {"average_frame_interval_ms", milliseconds( - state.completion_interval_count == 0 ? 0U : - state.completion_interval_total_ns / - state.completion_interval_count)}, - {"frame_interval_jitter_ms", - std::sqrt(interval_variance) / 1'000'000.0}}}, - {"requests", { - {"periodic", state.periodic_request_count}, - {"immediate", state.immediate_request_count}, - {"maximum_rate", state.maximum_rate_request_count}, - {"accepted", state.accepted_request_count}, - {"coalesced", state.coalesced_request_count}, - {"policy_rejected", state.policy_rejection_count}, - {"frame_slot_backpressure", state.frame_slot_backpressure_count}, - {"scene_rejected", state.scene_rejection_count}, - {"acceptance_ratio", ratio( - state.accepted_request_count, state.request_count)}, - {"coalescing_ratio", ratio( - state.coalesced_request_count, state.request_count)}, - {"backpressure_ratio", ratio( - state.frame_slot_backpressure_count, - state.accepted_request_count)}}}, - {"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)}}}, - {"last_frame", { - {"sequence", state.last_frame_sequence}, - {"request_source", magic_enum::enum_name( - state.last_request_source)}}} + { + "configuration", { + {"mode", pacing_mode_name(state.mode)}, + {"render_enabled", state.render_enabled}, + {"video_enabled", state.video_enabled}, + {"fixed_rate_fps", state.fixed_rate_fps} + } + }, + { + "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} + } + }, + { + "throughput", { + {"request_rate_fps", rate(state.request_count)}, + {"submission_rate_fps", rate(state.submitted_frame_count)}, + {"completion_rate_fps", effective_fps}, + {"target_achievement_ratio", target_achievement}, + { + "latest_frame_interval_ms", + milliseconds(state.latest_completion_interval_ns) + }, + { + "average_frame_interval_ms", milliseconds( + state.completion_interval_count == 0 + ? 0U + : state.completion_interval_total_ns / + state.completion_interval_count) + }, + { + "frame_interval_jitter_ms", + std::sqrt(interval_variance) / 1'000'000.0 + } + } + }, + { + "requests", { + {"periodic", state.periodic_request_count}, + {"immediate", state.immediate_request_count}, + {"maximum_rate", state.maximum_rate_request_count}, + {"accepted", state.accepted_request_count}, + {"coalesced", state.coalesced_request_count}, + {"policy_rejected", state.policy_rejection_count}, + {"frame_slot_backpressure", state.frame_slot_backpressure_count}, + {"scene_rejected", state.scene_rejection_count}, + { + "acceptance_ratio", ratio( + state.accepted_request_count, state.request_count) + }, + { + "coalescing_ratio", ratio( + state.coalesced_request_count, state.request_count) + }, + { + "backpressure_ratio", ratio( + state.frame_slot_backpressure_count, + state.accepted_request_count) + } + } + }, + { + "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) + } + } + }, + { + "last_frame", { + {"sequence", state.last_frame_sequence}, + { + "request_source", magic_enum::enum_name( + state.last_request_source) + } + } + } }; } - void append_statistic_json(nlohmann::json& output, const Frame_Statistics_State& state) { for (const auto statistic : magic_enum::enum_values()) { if (statistic == Frame_Statistic::count) continue; const auto& value = - state.values[static_cast(statistic)]; + state.values[static_cast(statistic)]; if (value.count == 0) continue; output[magic_enum::enum_name(statistic)] = { {"count", value.count}, {"latest", value.latest}, @@ -263,10 +308,10 @@ void append_statistic_json(nlohmann::json& output, {"average", value.average}, {"trimmed_average", value.trimmed_average}, {"variability", value.variability}, {"p50", value.p50}, - {"p95", value.p95}, {"p99", value.p99}}; + {"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()) { @@ -282,12 +327,12 @@ void append_event_statistics_json(nlohmann::json& output, {"average", value.average}, {"trimmed_average", value.trimmed_average}, {"variability", value.variability}, {"p50", value.p50}, - {"p95", value.p95}, {"p99", value.p99}}; + {"p95", value.p95}, {"p99", value.p99} + }; } if (event.empty()) output.erase(std::string{magic_enum::enum_name(type)}); } } - nlohmann::json datoviz_observation_json( const Datoviz_Frame_Observation& value) { const auto milliseconds = [](std::uint64_t nanoseconds) { @@ -299,47 +344,65 @@ nlohmann::json datoviz_observation_json( {"gpu_timing_requested", value.gpu_timing_requested}, {"readback_requested", value.readback_requested}, {"controller_input_applied", value.controller_input_applied}, - {"prepare_released_after_submission", - value.prepare_released_after_submission}, - {"timings_ms", { - {"queue_submit_wait", - milliseconds(value.queue_submit_wait_ns)}, - {"apply", milliseconds(value.apply_ns)}, - {"emit", milliseconds(value.emit_ns)}, - {"execute", milliseconds(value.execute_ns)}, - {"submit", milliseconds(value.submit_ns)}, - {"gpu_completion_observation", - milliseconds(value.gpu_completion_observation_ns)}, - {"completion_task_queue", - milliseconds(value.completion_task_queue_ns)}, - {"readback", milliseconds(value.readback_ns)}}}, - {"traffic", { - {"uploaded_bytes", value.uploaded_bytes}, - {"readback_bytes", value.readback_bytes}}}, - {"frame_plan", { - {"resource_version", value.artifact_resource_version}, - {"frame_index", value.artifact_frame_index}, - {"status", value.artifact_status}}}, - {"validation", { - {"performed", value.validation_performed}, - {"ok", value.validation_ok}, - {"code", value.validation_code}, - {"command_index", value.validation_command_index}}} + { + "prepare_released_after_submission", + value.prepare_released_after_submission + }, + { + "timings_ms", { + { + "queue_submit_wait", + milliseconds(value.queue_submit_wait_ns) + }, + {"apply", milliseconds(value.apply_ns)}, + {"emit", milliseconds(value.emit_ns)}, + {"execute", milliseconds(value.execute_ns)}, + {"submit", milliseconds(value.submit_ns)}, + { + "gpu_completion_observation", + milliseconds(value.gpu_completion_observation_ns) + }, + { + "completion_task_queue", + milliseconds(value.completion_task_queue_ns) + }, + {"readback", milliseconds(value.readback_ns)} + } + }, + { + "traffic", { + {"uploaded_bytes", value.uploaded_bytes}, + {"readback_bytes", value.readback_bytes} + } + }, + { + "frame_plan", { + {"resource_version", value.artifact_resource_version}, + {"frame_index", value.artifact_frame_index}, + {"status", value.artifact_status} + } + }, + { + "validation", { + {"performed", value.validation_performed}, + {"ok", value.validation_ok}, + {"code", value.validation_code}, + {"command_index", value.validation_command_index} + } + } }; if (value.gpu) { result["gpu_ms"] = { {"render", milliseconds(value.gpu->render_ns)}, {"transition", milliseconds(value.gpu->transition_ns)}, {"copy", milliseconds(value.gpu->copy_ns)}, - {"total", milliseconds(value.gpu->total_ns)}}; + {"total", milliseconds(value.gpu->total_ns)} + }; } - if (!value.artifact_json.empty()) - result["frame_plan"]["artifact_json"] = value.artifact_json; + if (!value.artifact_json.empty()) result["frame_plan"]["artifact_json"] = value.artifact_json; return result; } - } - nlohmann::json taskflow_trace_json( const Taskflow_Frame_Trace& trace, const nlohmann::json& captured_components, @@ -347,11 +410,11 @@ nlohmann::json taskflow_trace_json( nlohmann::json markers = nlohmann::json::object(); for (const auto& marker : trace.markers) markers[magic_enum::enum_name(marker.marker)] = - static_cast(marker.elapsed_ns) / 1'000'000.0; + static_cast(marker.elapsed_ns) / 1'000'000.0; nlohmann::json measurements = nlohmann::json::object(); for (const auto& measurement : trace.measurements) measurements[magic_enum::enum_name(measurement.measurement)] = - static_cast(measurement.value_ns) / 1'000'000.0; + static_cast(measurement.value_ns) / 1'000'000.0; nlohmann::json graphs = nlohmann::json::array(); std::unordered_map node_ids; for (const auto& graph : trace.graphs) { @@ -359,20 +422,18 @@ nlohmann::json taskflow_trace_json( for (const auto& node : graph.nodes) { node_ids.emplace(node.native_id, node.node_id); nlohmann::json predecessors = nlohmann::json::array(); - for (const auto native_id : node.predecessors) - predecessors.push_back(std::to_string(native_id)); + for (const auto native_id : node.predecessors) predecessors.push_back(std::to_string(native_id)); nlohmann::json successors = nlohmann::json::array(); - for (const auto native_id : node.successors) - successors.push_back(std::to_string(native_id)); + for (const auto native_id : node.successors) successors.push_back(std::to_string(native_id)); nlohmann::json attributes = nlohmann::json::object(); - for (const auto& [key, value] : node.attributes) - attributes[key] = value; + for (const auto& [key, value] : node.attributes) attributes[key] = value; nlohmann::json encoded{ {"native_id", std::to_string(node.native_id)}, {"id", node.node_id}, {"parent_id", node.parent_node_id}, {"name", node.name}, {"type", node.type}, {"predecessors", std::move(predecessors)}, {"successors", std::move(successors)}, - {"attributes", std::move(attributes)}}; + {"attributes", std::move(attributes)} + }; const auto owner = encoded["attributes"].value( "owner_component", std::string{}); if (!owner.empty() && captured_components.contains(owner)) { @@ -380,7 +441,8 @@ nlohmann::json taskflow_trace_json( encoded["owner"] = { {"component", owner}, {"label", captured.value("label", owner)}, - {"kind", captured.value("kind", std::string{})}}; + {"kind", captured.value("kind", std::string{})} + }; encoded["prop"] = captured.value("prop", nlohmann::json::object()); encoded["state"] = captured.value("state", nlohmann::json::object()); } @@ -390,7 +452,8 @@ nlohmann::json taskflow_trace_json( {"stage", graph.stage}, {"name", graph.taskflow_name}, {"submitted_ms", graph.submitted_ms}, {"finished_ms", graph.finished_ms}, - {"completed", graph.completed}, {"nodes", std::move(nodes)}}); + {"completed", graph.completed}, {"nodes", std::move(nodes)} + }); } nlohmann::json executions = nlohmann::json::array(); for (const auto& task : trace.tasks) { @@ -408,7 +471,8 @@ nlohmann::json taskflow_trace_json( {"cooperative_wait_ms", task.cooperative_wait_ms}, {"observer_entry_ms", task.observer_entry_ms}, {"observer_exit_ms", task.observer_exit_ms}, - {"queue_wait_ms", task.queue_wait_ms}}); + {"queue_wait_ms", task.queue_wait_ms} + }); } nlohmann::json result{ {"sequence", trace.identity.sequence}, @@ -420,28 +484,28 @@ nlohmann::json taskflow_trace_json( {"markers", std::move(markers)}, {"measurements", std::move(measurements)}, {"graphs", std::move(graphs)}, - {"executions", std::move(executions)}}; + {"executions", std::move(executions)} + }; if (!captured_backend.empty()) result["datoviz"] = captured_backend; return result; } - namespace { - [[nodiscard]] Event_Timeline_Time input_timeline_time( double time_milliseconds) { constexpr long double nanoseconds_per_millisecond{1'000'000.0L}; constexpr long double maximum_milliseconds = - static_cast(std::numeric_limits::max()) / - nanoseconds_per_millisecond; + static_cast(std::numeric_limits::max()) / + nanoseconds_per_millisecond; if (!std::isfinite(time_milliseconds) || time_milliseconds < 0.0 || static_cast(time_milliseconds) > maximum_milliseconds) throw std::invalid_argument( "input time_milliseconds must be finite, non-negative and representable"); - return Event_Timeline_Time{static_cast( - static_cast(time_milliseconds) * - nanoseconds_per_millisecond)}; + return Event_Timeline_Time{ + static_cast( + static_cast(time_milliseconds) * + nanoseconds_per_millisecond) + }; } - template void dispatch_plot_input(Scene_Object& scene, const Plot_Input_Event& input) { const auto occurred_at = input_timeline_time(input.time_milliseconds); @@ -487,149 +551,130 @@ void dispatch_plot_input(Scene_Object& scene, const Plot_Input_Event& input) { dispatch(std::move(event)); break; } - default: - dispatch(scene.template make_event(input.type, occurred_at)); + default: dispatch(scene.template make_event(input.type, occurred_at)); break; } } } - struct Plot::Private { using Scene = std::variant, std::unique_ptr>; using Frame = std::variant, std::unique_ptr>; - enum struct Frame_State : std::uint8_t { available, - rendering, /* Scene::advance -> Plot pixel publish,不可重入。 */ - consuming /* 外接 Taskflow 正在消费已发布帧;允许下一帧渲染。 */ + rendering, /* Scene::advance -> Plot pixel publish,不可重入。 */ + consuming /* 外接 Taskflow 正在消费已发布帧;允许下一帧渲染。 */ }; - enum struct Render_Admission_State : std::uint8_t { ready, rendering, frame_slots_exhausted }; - struct Managed_Frame { - std::chrono::microseconds presentation_time{}; /* 共享页面时钟产生的媒体时间戳。 */ + std::chrono::microseconds presentation_time{}; /* 共享页面时钟产生的媒体时间戳。 */ std::chrono::steady_clock::time_point tick_issued_at{}; /* 本逻辑帧请求进入 Plot 的时刻。 */ std::chrono::steady_clock::time_point submitted_at{}; /* Scene 接受本逻辑帧的时刻。 */ - Frame frame{}; /* 三缓冲物理槽拥有且反复承载逻辑帧。 */ + Frame frame{}; /* 三缓冲物理槽拥有且反复承载逻辑帧。 */ std::atomic state{Frame_State::available}; /* 本槽唯一生命周期状态。 */ - std::atomic_size_t diagnostic_readers{}; /* 无锁诊断读取认领;非零时该槽不可复用。 */ - std::uint64_t statistics_generation{}; /* 与本槽中完成帧统计共同发布。 */ - Frame_Statistics_State statistics{}; /* 统计结果归属当前完成帧,不在 Plot 复制。 */ - std::atomic retired_next{}; /* 无锁退役队列的槽内侵入链接。 */ + std::atomic_size_t diagnostic_readers{}; /* 无锁诊断读取认领;非零时该槽不可复用。 */ + std::uint64_t statistics_generation{}; /* 与本槽中完成帧统计共同发布。 */ + Frame_Statistics_State statistics{}; /* 统计结果归属当前完成帧,不在 Plot 复制。 */ + std::atomic retired_next{}; /* 无锁退役队列的槽内侵入链接。 */ }; - struct Consumer { Stream_Handler handler; std::uint32_t width{}; std::uint32_t height{}; }; using Consumer_Map = std::unordered_map; - struct Stream_Snapshot { std::shared_ptr consumers; std::uint32_t width{}; std::uint32_t height{}; }; - std::unique_ptr view; std::once_flag start_once; - std::weak_ptr lifetime{}; /* 仅用于 completion 后重新投递 Taskflow,避免在 Scene callback 内重入 render。 */ + std::weak_ptr lifetime{}; /* 仅用于 completion 后重新投递 Taskflow,避免在 Scene callback 内重入 render。 */ std::atomic> frame_policy_lifetime{}; /* 任一物理帧被借用期间由策略保活整个 Plot;最后一槽归还后释放。 */ std::atomic> consumers{ - std::make_shared()}; /* 低频订阅修改发布不可变版本。 */ + std::make_shared() + }; /* 低频订阅修改发布不可变版本。 */ std::atomic_uint64_t next_stream_id{1}; std::atomic> terminal_failure{}; /* 首次 Plot Unknown Failure 的唯一终止状态。 */ std::uint64_t next_frame_sequence{1}; std::unique_ptr frame_policy_2d{}; std::unique_ptr frame_policy_3d{}; - Frame_Scheduler::Timer frame_timer{}; /* 每 Plot/Scene 只有轻量时间轮节点,不持有线程。 */ + Frame_Scheduler::Timer frame_timer{}; /* 每 Plot/Scene 只有轻量时间轮节点,不持有线程。 */ static constexpr std::size_t scene_frame_capacity{3}; std::array frame_slots{}; /* 帧策略拥有并反复调度的稳定三缓冲;Scene 只借用。 */ - std::atomic latest_statistics_frame{}; /* 只定位权威帧槽,不保存统计副本。 */ - std::atomic retired_frames{}; /* 完成回调返回、唯一帧策略写者消费的物理帧。 */ - Scene scene; /* 析构顺序保证 Scene 先停止,再释放物理帧。 */ + std::atomic latest_statistics_frame{}; /* 只定位权威帧槽,不保存统计副本。 */ + std::atomic retired_frames{}; /* 完成回调返回、唯一帧策略写者消费的物理帧。 */ + Scene scene; /* 析构顺序保证 Scene 先停止,再释放物理帧。 */ moodycamel::ConcurrentQueue tick_requests{}; /* 多生产者提交、唯一短任务消费的帧请求流。 */ - std::optional deferred_tick{}; /* 仅 tick consumer 任务访问的 latest 延后请求。 */ - std::atomic_uint64_t consumer_work_generation{}; /* tick 或退休帧入队后推进,关闭 consumer 尾部唤醒竞争窗口。 */ - std::atomic_bool tick_task_scheduled{}; /* 唯一短任务准入;不占用 Worker 等待。 */ + std::optional deferred_tick{}; /* 仅 tick consumer 任务访问的 latest 延后请求。 */ + std::atomic_uint64_t consumer_work_generation{}; /* tick 或退休帧入队后推进,关闭 consumer 尾部唤醒竞争窗口。 */ + std::atomic_bool tick_task_scheduled{}; /* 唯一短任务准入;不占用 Worker 等待。 */ std::atomic render_admission{Render_Admission_State::ready}; /* Plot 渲染准入及物理槽背压的唯一状态源。 */ std::chrono::steady_clock::time_point clock_origin{std::chrono::steady_clock::now()}; - std::atomic_size_t taskflow_trace_remaining{}; /* 尚待标记的实际渲染帧数。 */ + std::atomic_size_t taskflow_trace_remaining{}; /* 尚待标记的实际渲染帧数。 */ static constexpr std::size_t maximum_taskflow_trace_frames{120}; /* 高 32 位 requested,低 32 位 captured。每槽只发布一次不可变 Trace, * GET 直接读取已发布槽位,不复制或重排整个历史容器。 */ std::atomic_uint64_t taskflow_trace_control{}; std::array>, maximum_taskflow_trace_frames> taskflow_trace_slots{}; - std::atomic_size_t post_publish_trace_remaining{}; /* 仅捕获 publish 后外接 DAG 的剩余样本。 */ - std::atomic_uint64_t post_publish_trace_control{}; /* 高 32 位 requested,低 32 位 captured。 */ + std::atomic_size_t post_publish_trace_remaining{}; /* 仅捕获 publish 后外接 DAG 的剩余样本。 */ + std::atomic_uint64_t post_publish_trace_control{}; /* 高 32 位 requested,低 32 位 captured。 */ std::array>, maximum_taskflow_trace_frames> post_publish_trace_slots{}; - Task_Graph post_publish_graph{"plot.post_publish"}; /* publish 后外接 DAG;不再占用 Scene render admission。 */ + Task_Graph post_publish_graph{"plot.post_publish"}; /* publish 后外接 DAG;不再占用 Scene render admission。 */ Task_Node post_publish_tail{}; bool has_post_publish_tail{}; std::atomic_bool post_publish_busy{}; std::vector> completion_extensions{}; /* 生命周期覆盖 post-publish module 借用。 */ Frame_Statistics_Accumulator completed_frame_statistics{diagnostic_window_capacity}; - std::uint64_t applied_statistics_generation{}; /* 仅完成帧退役任务读写。 */ - std::atomic_uint64_t statistics_generation{}; /* reset 只推进代次,不触碰单写者累加器。 */ - + std::uint64_t applied_statistics_generation{}; /* 仅完成帧退役任务读写。 */ + std::atomic_uint64_t statistics_generation{}; /* reset 只推进代次,不触碰单写者累加器。 */ template Private(std::unique_ptr value_scene, - std::unique_ptr value_view) - : view(std::move(value_view)), scene(std::move(value_scene)) { + std::unique_ptr value_view) : view(std::move(value_view)), scene(std::move(value_scene)) { if constexpr (std::same_as) { auto built_policy = Frame_Policy::Builder{}.build(); - if (!built_policy) - throw std::logic_error("2D Frame Policy dependency graph is invalid"); + if (!built_policy) throw std::logic_error("2D Frame Policy dependency graph is invalid"); frame_policy_2d = std::move(*built_policy); } else { auto built_policy = - Frame_Policy_3D::Builder{}.build(); - if (!built_policy) - throw std::logic_error("3D Frame Policy dependency graph is invalid"); + Frame_Policy_3D::Builder{}.build(); + if (!built_policy) throw std::logic_error("3D Frame Policy dependency graph is invalid"); frame_policy_3d = std::move(*built_policy); frame_policy_3d->set_mode(Frame_Pacing_Mode::maximum_rate); static_cast(frame_policy_3d->consume_events()); } for (auto& slot : frame_slots) { - if constexpr (std::same_as) - slot.frame = std::make_unique(Frame_Identity{}); - else - slot.frame = std::make_unique(Frame_Identity{}); + if constexpr (std::same_as) slot.frame = std::make_unique(Frame_Identity{}); + else slot.frame = std::make_unique(Frame_Identity{}); } } - template decltype(auto) with_frame_policy(Callback&& callback) { - if (frame_policy_2d) - return std::forward(callback)(*frame_policy_2d); + if (frame_policy_2d) return std::forward(callback)(*frame_policy_2d); return std::forward(callback)(*frame_policy_3d); } - template decltype(auto) with_frame_policy(Callback&& callback) const { - if (frame_policy_2d) - return std::forward(callback)(*frame_policy_2d); + if (frame_policy_2d) return std::forward(callback)(*frame_policy_2d); return std::forward(callback)(*frame_policy_3d); } - [[nodiscard]] const Frame_Policy::State& pacing_state() const noexcept { - if (frame_policy_2d) - return frame_policy_2d->read_state(); + if (frame_policy_2d) return frame_policy_2d->read_state(); return frame_policy_3d->read_state().common; } - [[nodiscard]] bool consume_frame_policy_events() { return with_frame_policy( - [](auto& policy) { return policy.consume_events(); }); + [](auto& policy) { + return policy.consume_events(); + }); } - [[nodiscard]] nlohmann::json schema() const; [[nodiscard]] Stream_Snapshot stream_snapshot() const; void publish(std::shared_ptr frame) noexcept; @@ -664,31 +709,35 @@ struct Plot::Private { maximum_taskflow_trace_frames>& slots) const; void fail(std::exception_ptr failure) noexcept; }; - void Plot::Private::fail(std::exception_ptr failure) noexcept { try { auto description = std::make_shared( exception_description(failure)); std::shared_ptr empty; if (!terminal_failure.compare_exchange_strong( - empty, description, std::memory_order_acq_rel, - std::memory_order_acquire)) return; + empty, description, std::memory_order_acq_rel, + std::memory_order_acquire)) + return; const auto output = std::make_shared( - Plot_Stream_Frame{nlohmann::json{ - {"kind", "plot_error"}, - {"protocol", "aethera.video.frame"}, - {"version", plot_stream_protocol_version}, - {"message", *description} - }.dump(), {}}); + Plot_Stream_Frame{ + nlohmann::json{ + {"kind", "plot_error"}, + {"protocol", "aethera.video.frame"}, + {"version", plot_stream_protocol_version}, + {"message", *description} + }.dump(), + {} + }); publish(std::move(output)); } catch (...) {} } - nlohmann::json Plot::Private::schema() const { auto result = view->schema(); auto analysis = with_frame_policy( - [](const auto& policy) { return frame_policy_schema(policy); }); + [](const auto& policy) { + return frame_policy_schema(policy); + }); if (frame_policy_3d) { analysis["fields"].push_back({ {"key", "pipeline_capacity"}, {"label", "3D 帧槽容量"}, @@ -702,7 +751,6 @@ nlohmann::json Plot::Private::schema() const { result["frame_analysis"] = std::move(analysis); return result; } - Plot::Private::Stream_Snapshot Plot::Private::stream_snapshot() const { Stream_Snapshot result; result.consumers = consumers.load(std::memory_order_acquire); @@ -716,7 +764,6 @@ Plot::Private::Stream_Snapshot Plot::Private::stream_snapshot() const { result.height = std::clamp(result.height == 0 ? 192U : result.height, 120U, 1080U) & ~1U; return result; } - void Plot::Private::publish( std::shared_ptr frame) noexcept { if (!frame) return; @@ -739,14 +786,13 @@ void Plot::Private::publish( for (const auto id : failed_consumers) next->erase(id); std::shared_ptr desired = next; if (consumers.compare_exchange_weak( - current, desired, std::memory_order_release, - std::memory_order_acquire)) + current, desired, std::memory_order_release, + std::memory_order_acquire)) break; } } catch (...) {} } - void Plot::Private::refresh_schedule() { if (!frame_timer.valid()) return; const auto current_consumers = consumers.load(std::memory_order_acquire); @@ -766,21 +812,19 @@ void Plot::Private::refresh_schedule() { .issued_at = now, .time_milliseconds = std::chrono::duration( now - clock_origin).count(), - .source = Frame_Request_Source::maximum_rate}); + .source = Frame_Request_Source::maximum_rate + }); arm_tick_consumer(lifetime); } - void Plot::Private::submit_tick_request(Plot_Render_Tick tick) { const auto source = tick.source; const auto issued_at = tick.issued_at; - if (!tick_requests.enqueue(std::move(tick))) - throw std::bad_alloc{}; + if (!tick_requests.enqueue(std::move(tick))) throw std::bad_alloc{}; with_frame_policy([&](auto& policy) { policy.record_request(source, issued_at); }); consumer_work_generation.fetch_add(1, std::memory_order_release); } - void Plot::Private::keep_latest_tick(const Plot_Render_Tick& tick) { const auto priority = [](Frame_Request_Source source) { switch (source) { @@ -799,28 +843,30 @@ void Plot::Private::keep_latest_tick(const Plot_Render_Tick& tick) { }); if (current_priority > next_priority || (current_priority == next_priority && - deferred_tick->issued_at >= tick.issued_at)) + deferred_tick->issued_at >= tick.issued_at)) return; } deferred_tick = tick; } - void Plot::Private::arm_tick_consumer(std::weak_ptr lifetime) { if (terminal_failure.load(std::memory_order_acquire)) return; if (tick_task_scheduled.exchange(true, std::memory_order_acq_rel)) return; aethera::schedule_task("web.plot.tick.consume", [lifetime] { const auto plot = lifetime.lock(); if (!plot) return; - try { plot->d->consume_tick(lifetime); } - catch (...) { plot->d->fail(std::current_exception()); } + try { + plot->d->consume_tick(lifetime); + } + catch (...) { + plot->d->fail(std::current_exception()); + } }); } - void Plot::Private::release_render_admission(std::weak_ptr lifetime) { auto expected = Render_Admission_State::rendering; if (!render_admission.compare_exchange_strong( - expected, Render_Admission_State::ready, - std::memory_order_acq_rel, std::memory_order_acquire)) + expected, Render_Admission_State::ready, + std::memory_order_acq_rel, std::memory_order_acquire)) return; const auto& pacing = pacing_state(); if (pacing.render_enabled && pacing.mode == Frame_Pacing_Mode::maximum_rate) { @@ -831,12 +877,12 @@ void Plot::Private::release_render_admission(std::weak_ptr lifetime) { .issued_at = now, .time_milliseconds = std::chrono::duration( now - clock_origin).count(), - .source = Frame_Request_Source::maximum_rate}); + .source = Frame_Request_Source::maximum_rate + }); } } arm_tick_consumer(std::move(lifetime)); } - void Plot::Private::retain_frame_policy_lifetime() { if (frame_policy_lifetime.load(std::memory_order_acquire)) return; const auto owner = lifetime.lock(); @@ -847,24 +893,21 @@ void Plot::Private::retain_frame_policy_lifetime() { static_cast(frame_policy_lifetime.compare_exchange_strong( empty, owner, std::memory_order_release, std::memory_order_acquire)); } - void Plot::Private::release_frame_policy_lifetime_if_idle() { if (std::ranges::any_of(frame_slots, [](const Managed_Frame& slot) { - return slot.state.load(std::memory_order_acquire) != - Frame_State::available; - })) + return slot.state.load(std::memory_order_acquire) != + Frame_State::available; + })) return; frame_policy_lifetime.store({}, std::memory_order_release); } - void Plot::Private::consume_tick(std::weak_ptr lifetime) { const auto observed_generation = - consumer_work_generation.load(std::memory_order_acquire); + consumer_work_generation.load(std::memory_order_acquire); consume_retired_frames(); if (consume_frame_policy_events()) refresh_schedule(); Plot_Render_Tick requested; - while (tick_requests.try_dequeue(requested)) - keep_latest_tick(requested); + while (tick_requests.try_dequeue(requested)) keep_latest_tick(requested); if (render_admission.load(std::memory_order_acquire) == Render_Admission_State::ready) { auto tick = std::exchange(deferred_tick, {}); @@ -874,22 +917,21 @@ void Plot::Private::consume_tick(std::weak_ptr lifetime) { if (consume_frame_policy_events()) refresh_schedule(); tick_task_scheduled.store(false, std::memory_order_release); if (consumer_work_generation.load(std::memory_order_acquire) != - observed_generation || + observed_generation || retired_frames.load(std::memory_order_acquire) || (render_admission.load(std::memory_order_acquire) == - Render_Admission_State::ready && deferred_tick)) + Render_Admission_State::ready && deferred_tick)) arm_tick_consumer(std::move(lifetime)); } - void Plot::Private::clock_tick(const Plot_Render_Tick& tick) { if (terminal_failure.load(std::memory_order_acquire)) return; const auto& pacing = pacing_state(); const bool accepted = pacing.render_enabled && - (tick.source == Frame_Request_Source::immediate || - (tick.source == Frame_Request_Source::periodic && - pacing.mode == Frame_Pacing_Mode::fixed_rate) || - (tick.source == Frame_Request_Source::maximum_rate && - pacing.mode == Frame_Pacing_Mode::maximum_rate)); + (tick.source == Frame_Request_Source::immediate || + (tick.source == Frame_Request_Source::periodic && + pacing.mode == Frame_Pacing_Mode::fixed_rate) || + (tick.source == Frame_Request_Source::maximum_rate && + pacing.mode == Frame_Pacing_Mode::maximum_rate)); if (!accepted) { with_frame_policy([](auto& policy) { policy.record_policy_rejection(); @@ -901,31 +943,28 @@ void Plot::Private::clock_tick(const Plot_Render_Tick& tick) { }); render_frame(tick); } - bool Plot::Private::mark_taskflow_trace(Render_Frame& frame) { auto remaining = taskflow_trace_remaining.load(std::memory_order_acquire); while (remaining != 0) { if (taskflow_trace_remaining.compare_exchange_weak( - remaining, remaining - 1, std::memory_order_acq_rel, - std::memory_order_acquire)) { + remaining, remaining - 1, std::memory_order_acq_rel, + std::memory_order_acquire)) { frame.request_taskflow_trace(); return true; } } return false; } - bool Plot::Private::mark_post_publish_taskflow_trace() { auto remaining = post_publish_trace_remaining.load(std::memory_order_acquire); while (remaining != 0) { if (post_publish_trace_remaining.compare_exchange_weak( - remaining, remaining - 1, std::memory_order_acq_rel, - std::memory_order_acquire)) + remaining, remaining - 1, std::memory_order_acq_rel, + std::memory_order_acquire)) return true; } return false; } - void Plot::Private::store_trace( std::atomic_uint64_t& control, std::array>, @@ -942,14 +981,13 @@ void Plot::Private::store_trace( if (captured >= requested) return; slots[captured].store(trace, std::memory_order_release); const auto next = (static_cast(requested) << 32U) | - static_cast(captured + 1U); + static_cast(captured + 1U); if (control.compare_exchange_weak( - state, next, std::memory_order_release, - std::memory_order_acquire)) + state, next, std::memory_order_release, + std::memory_order_acquire)) return; } } - nlohmann::json Plot::Private::trace_response( const std::atomic_uint64_t& control, const std::atomic_size_t& remaining, @@ -959,38 +997,34 @@ nlohmann::json Plot::Private::trace_response( const auto state = control.load(std::memory_order_acquire); const auto requested = static_cast(state >> 32U); const auto captured = static_cast(state); - for (std::uint32_t index = 0; index < captured; ++index) - if (const auto trace = slots[index].load(std::memory_order_acquire)) - frames.push_back(*trace); + for (std::uint32_t index = 0; index < captured; ++index) if (const auto trace = slots[index].load(std::memory_order_acquire)) frames.push_back(*trace); const auto left = remaining.load(std::memory_order_acquire); return { {"protocol", "aethera.taskflow.frames"}, {"version", 1}, {"requested", requested}, {"remaining", left}, {"captured", frames.size()}, {"complete", requested != 0 && frames.size() == requested}, - {"frames", std::move(frames)}}; + {"frames", std::move(frames)} + }; } - void Plot::Private::render_frame(Plot_Render_Tick tick) { if (terminal_failure.load(std::memory_order_acquire)) return; const auto streams = stream_snapshot(); const auto& pacing = pacing_state(); const bool direct_diagnostics_frame = - streams.consumers->empty() && - tick.source == Frame_Request_Source::immediate; + streams.consumers->empty() && + tick.source == Frame_Request_Source::immediate; if (!pacing.render_enabled || (streams.consumers->empty() && !direct_diagnostics_frame)) return; - auto admission_expected = Render_Admission_State::ready; if (!render_admission.compare_exchange_strong( - admission_expected, Render_Admission_State::rendering, - std::memory_order_acq_rel, - std::memory_order_acquire)) { + admission_expected, Render_Admission_State::rendering, + std::memory_order_acq_rel, + std::memory_order_acquire)) { keep_latest_tick(tick); return; } - std::size_t slot_index{}; Managed_Frame* managed{}; /* @@ -1015,8 +1049,8 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { } auto expected = Frame_State::available; if (!candidate->state.compare_exchange_strong( - expected, Frame_State::rendering, - std::memory_order_acq_rel, std::memory_order_acquire)) { + expected, Frame_State::rendering, + std::memory_order_acq_rel, std::memory_order_acquire)) { if (was_latest) { Managed_Frame* empty{}; static_cast(latest_statistics_frame.compare_exchange_strong( @@ -1047,8 +1081,8 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { } retain_frame_policy_lifetime(); managed->presentation_time = - std::chrono::duration_cast( - std::chrono::duration(tick.time_milliseconds)); + std::chrono::duration_cast( + std::chrono::duration(tick.time_milliseconds)); managed->tick_issued_at = tick.issued_at; managed->submitted_at = {}; const auto rollback_unsubmitted = [this, slot_index] { @@ -1064,60 +1098,61 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { if (!std::exchange(taskflow_trace_claimed, false)) return; taskflow_trace_remaining.fetch_add(1, std::memory_order_release); }; - try { - if (streams.consumers->empty()) { - tick.width = std::clamp(tick.width, 160U, 1920U) & ~1U; - tick.height = std::clamp(tick.height, 120U, 1080U) & ~1U; - } - else { - tick.width = streams.width; - tick.height = streams.height; - } - const std::uint64_t sequence = next_frame_sequence++; - const Frame_Identity identity{ - sequence, tick.sequence == 0 ? sequence : tick.sequence}; - const auto request_source = tick.source; - const auto& policy_state = pacing_state(); - Render_Frame* logical_frame{}; - if (auto* frame_2d = std::get_if>(&managed->frame)) { - (*frame_2d)->begin(identity, Frame_2D::native_pixel_format, - request_source, policy_state); - logical_frame = frame_2d->get(); - } else { - auto& frame_3d = std::get>(managed->frame); - frame_3d->begin(identity, - pacing.video_enabled ? Frame_3D_Output::pixels - : Frame_3D_Output::diagnostics, - Frame_3D::native_pixel_format, request_source, - policy_state); - logical_frame = frame_3d.get(); - } - taskflow_trace_claimed = mark_taskflow_trace(*logical_frame); - /* - * 各图的采样、网格构造和属性读取都在 Plot 自己的准备域完成。 - * 进入 Scene::render 后只剩已经准备好的 Visual 批次与轻量提交; - * 共享 Render Domain 不承担业务数据生成。 - */ - logical_frame->mark(Frame_Trace_Marker::plot_update_started); - const auto update_started = std::chrono::steady_clock::now(); - view->update(tick); - const auto update_elapsed = std::chrono::steady_clock::now() - update_started; - logical_frame->mark(Frame_Trace_Marker::plot_update_finished); - const auto tick_queue_elapsed = tick.issued_at.time_since_epoch().count() == 0 - ? std::chrono::steady_clock::duration::zero() - : update_started - tick.issued_at; - const auto record_plot_measurements = [&](Render_Frame& frame) { - const auto nanoseconds = [](std::chrono::steady_clock::duration duration) { - return static_cast(std::max(0, - std::chrono::duration_cast(duration).count())); + if (streams.consumers->empty()) { + tick.width = std::clamp(tick.width, 160U, 1920U) & ~1U; + tick.height = std::clamp(tick.height, 120U, 1080U) & ~1U; + } + else { + tick.width = streams.width; + tick.height = streams.height; + } + const std::uint64_t sequence = next_frame_sequence++; + const Frame_Identity identity{ + sequence, tick.sequence == 0 ? sequence : tick.sequence + }; + const auto request_source = tick.source; + const auto& policy_state = pacing_state(); + Render_Frame* logical_frame{}; + if (auto* frame_2d = std::get_if>(&managed->frame)) { + (*frame_2d)->begin(identity, Frame_2D::native_pixel_format, + request_source, policy_state); + logical_frame = frame_2d->get(); + } + else { + auto& frame_3d = std::get>(managed->frame); + frame_3d->begin(identity, + pacing.video_enabled + ? Frame_3D_Output::pixels + : Frame_3D_Output::diagnostics, + Frame_3D::native_pixel_format, request_source, + policy_state); + logical_frame = frame_3d.get(); + } + taskflow_trace_claimed = mark_taskflow_trace(*logical_frame); + /* + * 各图的采样、网格构造和属性读取都在 Plot 自己的准备域完成。 + * 进入 Scene::render 后只剩已经准备好的 Visual 批次与轻量提交; + * 共享 Render Domain 不承担业务数据生成。 + */ + logical_frame->mark(Frame_Trace_Marker::plot_update_started); + const auto update_started = std::chrono::steady_clock::now(); + view->update(tick); + const auto update_elapsed = std::chrono::steady_clock::now() - update_started; + logical_frame->mark(Frame_Trace_Marker::plot_update_finished); + const auto tick_queue_elapsed = tick.issued_at.time_since_epoch().count() == 0 + ? std::chrono::steady_clock::duration::zero() + : update_started - tick.issued_at; + const auto record_plot_measurements = [&](Render_Frame& frame) { + const auto nanoseconds = [](std::chrono::steady_clock::duration duration) { + return static_cast(std::max(0, + std::chrono::duration_cast(duration).count())); + }; + frame.record(Frame_Trace_Measurement::plot_tick_queue_ns, + nanoseconds(tick_queue_elapsed)); + frame.record(Frame_Trace_Measurement::plot_update_ns, + nanoseconds(update_elapsed)); }; - frame.record(Frame_Trace_Measurement::plot_tick_queue_ns, - nanoseconds(tick_queue_elapsed)); - frame.record(Frame_Trace_Measurement::plot_update_ns, - nanoseconds(update_elapsed)); - }; - if (auto* scene_2d = std::get_if>(&scene)) { auto& output = *std::get>(managed->frame); record_plot_measurements(output); @@ -1144,7 +1179,8 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { rollback_unsubmitted(); restore_taskflow_trace_claim(); release_render_admission(lifetime); - } else { + } + else { taskflow_trace_claimed = false; managed->submitted_at = std::chrono::steady_clock::now(); with_frame_policy([&](auto& policy) { @@ -1165,8 +1201,12 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { Render_Scene_3D::Frame_Callbacks{ .submitted = [weak](not_null, bool) { if (auto owner = weak.lock()) { - try { owner->d->release_render_admission(weak); } - catch (...) { owner->d->fail(std::current_exception()); } + try { + owner->d->release_render_admission(weak); + } + catch (...) { + owner->d->fail(std::current_exception()); + } } }, .completed = [weak](not_null frame) { @@ -1176,9 +1216,12 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { owner->d->consume_completed_frame(frame); owner->d->retire_completed_frame(frame); } - catch (...) { owner->d->fail(std::current_exception()); } + catch (...) { + owner->d->fail(std::current_exception()); + } } - }}); + } + }); if (result == Render_Scene_3D::Render_Result::submitted) { taskflow_trace_claimed = false; managed->submitted_at = std::chrono::steady_clock::now(); @@ -1195,8 +1238,7 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { rollback_unsubmitted(); restore_taskflow_trace_claim(); release_render_admission(lifetime); - if (result == Render_Scene_3D::Render_Result::backend_unavailable) - throw std::runtime_error("3D render backend became unavailable before submission"); + if (result == Render_Scene_3D::Render_Result::backend_unavailable) throw std::runtime_error("3D render backend became unavailable before submission"); } catch (...) { rollback_unsubmitted(); @@ -1205,8 +1247,6 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { throw; } } - - void Plot::Private::publish_completed_frame( not_null completed) { Managed_Frame* managed{}; @@ -1220,13 +1260,11 @@ void Plot::Private::publish_completed_frame( managed = &slot; break; } - if (!managed) - throw std::logic_error("completed frame has no owning Plot policy slot"); + if (!managed) throw std::logic_error("completed frame has no owning Plot policy slot"); if (managed->state.load(std::memory_order_acquire) != Frame_State::rendering) throw std::logic_error("completed Plot frame is not rendering"); const auto frame = completed; - const auto& pacing = pacing_state(); const auto identity = frame->identity(); Frame_Identity rendered_identity = identity; @@ -1234,34 +1272,33 @@ void Plot::Private::publish_completed_frame( Plot_Pixel_Layout pixel_layout{Plot_Pixel_Layout::rgba8}; std::uint32_t width{}; std::uint32_t height{}; - if (auto *frame_2d = - std::get_if>(&managed->frame)) { - pixel_layout = Plot_Pixel_Layout::bgra8; - const auto image = (*frame_2d)->image(); - width = static_cast(image.width); - height = static_cast(image.height); - if (pacing.video_enabled) { - auto output = (*frame_2d)->output_pixels(); - pixel_storage = std::make_shared>( - std::move(output.bytes)); - width = static_cast(output.width); - height = static_cast(output.height); - } - } else { - auto &frame_3d = std::get>(managed->frame); - rendered_identity = frame_3d->rendered_identity(); - const auto extent = frame_3d->extent(); - width = extent.width; - height = extent.height; - if (pacing.video_enabled && frame_3d->output() == Frame_3D_Output::pixels) - pixel_storage = frame_3d->share_pixels(); + if (auto* frame_2d = + std::get_if>(&managed->frame)) { + pixel_layout = Plot_Pixel_Layout::bgra8; + const auto image = (*frame_2d)->image(); + width = static_cast(image.width); + height = static_cast(image.height); + if (pacing.video_enabled) { + auto output = (*frame_2d)->output_pixels(); + pixel_storage = std::make_shared>( + std::move(output.bytes)); + width = static_cast(output.width); + height = static_cast(output.height); + } + } + else { + auto& frame_3d = std::get>(managed->frame); + rendered_identity = frame_3d->rendered_identity(); + const auto extent = frame_3d->extent(); + width = extent.width; + height = extent.height; + if (pacing.video_enabled && frame_3d->output() == Frame_3D_Output::pixels) pixel_storage = frame_3d->share_pixels(); } - auto pixels = std::make_shared(Plot_Pixel_Frame{ std::move(pixel_storage), pixel_layout, managed->presentation_time, identity.sequence, identity.correlation_id, rendered_identity.sequence, - rendered_identity.correlation_id, width, height}); - + rendered_identity.correlation_id, width, height + }); const auto published = std::make_shared( Plot_Stream_Frame{{}, std::move(pixels)}); const auto publish_started = std::chrono::steady_clock::now(); @@ -1269,15 +1306,14 @@ void Plot::Private::publish_completed_frame( frame->record(Frame_Trace_Measurement::plot_publish_ns, static_cast(std::max( 0, std::chrono::duration_cast( - std::chrono::steady_clock::now() - publish_started) - .count()))); + std::chrono::steady_clock::now() - publish_started) + .count()))); auto expected = Frame_State::rendering; if (!managed->state.compare_exchange_strong( - expected, Frame_State::consuming, std::memory_order_acq_rel, - std::memory_order_acquire)) - throw std::logic_error("Plot frame left rendering before pixel publish"); + expected, Frame_State::consuming, std::memory_order_acq_rel, + std::memory_order_acquire)) + throw std::logic_error("Plot frame left rendering before pixel publish"); } - void Plot::Private::consume_completed_frame(not_null frame) { Managed_Frame* managed{}; for (auto& slot : frame_slots) { @@ -1287,29 +1323,23 @@ void Plot::Private::consume_completed_frame(not_null frame) { }, slot.frame); if (address.get() != frame.get()) continue; - if (slot.state.load(std::memory_order_acquire) != Frame_State::consuming) - throw std::logic_error("completed Plot frame was not published"); + if (slot.state.load(std::memory_order_acquire) != Frame_State::consuming) throw std::logic_error("completed Plot frame was not published"); managed = &slot; break; } - if (!managed) - throw std::logic_error("frame callback has no owned Plot frame"); - + if (!managed) throw std::logic_error("frame callback has no owned Plot frame"); /* * Scene 已在调用本 callback 前释放自己的 render admission;这里同步 * 释放 Plot 的 view/update 门,并立刻唤醒 busy 期间保留的 latest tick。 * 之后外接 DAG 仍在当前 Frame trace 内执行,但不会阻塞下一帧渲染。 */ - if (std::holds_alternative>(managed->frame)) - release_render_admission(lifetime); + if (std::holds_alternative>(managed->frame)) release_render_admission(lifetime); if (post_publish_graph.empty()) return; - bool expected = false; if (!post_publish_busy.compare_exchange_strong( - expected, true, std::memory_order_acq_rel, - std::memory_order_acquire)) + expected, true, std::memory_order_acq_rel, + std::memory_order_acquire)) return; - /* * 外接图异步提交;上一轮尚未完成时直接合并到 sampler 内的 latest,绝不 * 在 Taskflow Worker 内等待。诊断使用独立 Render_Frame 保存外接图观察 @@ -1317,13 +1347,13 @@ void Plot::Private::consume_completed_frame(not_null frame) { */ const bool local_post_publish_trace = mark_post_publish_taskflow_trace(); auto trace_frame = local_post_publish_trace - ? std::make_shared(frame->identity()) - : std::shared_ptr{}; + ? std::make_shared(frame->identity()) + : std::shared_ptr{}; bool local_trace_started{}; if (trace_frame) { trace_frame->request_taskflow_trace(); local_trace_started = - aethera::detail::begin_taskflow_trace(*trace_frame); + aethera::detail::begin_taskflow_trace(*trace_frame); } const auto weak = lifetime; auto completion = [weak, trace_frame, local_trace_started] { @@ -1348,15 +1378,12 @@ void Plot::Private::consume_completed_frame(not_null frame) { post_publish_graph, std::move(completion)); } catch (...) { - if (local_trace_started) - aethera::detail::finish_taskflow_trace(*trace_frame); - if (local_post_publish_trace) - post_publish_trace_remaining.fetch_add(1, std::memory_order_release); + if (local_trace_started) aethera::detail::finish_taskflow_trace(*trace_frame); + if (local_post_publish_trace) post_publish_trace_remaining.fetch_add(1, std::memory_order_release); post_publish_busy.store(false, std::memory_order_release); throw; } } - void Plot::Private::consume_retired_frames() { for (;;) { auto* list = retired_frames.exchange(nullptr, std::memory_order_acq_rel); @@ -1370,18 +1397,21 @@ void Plot::Private::consume_retired_frames() { } std::ranges::sort(frames, {}, [](const Managed_Frame* managed) { return std::visit( - [](const auto& value) { return value->identity().sequence; }, + [](const auto& value) { + return value->identity().sequence; + }, managed->frame); }); for (auto* managed : frames) { auto* frame = std::visit( - [](const auto& value) -> Render_Frame* { return value.get(); }, + [](const auto& value) -> Render_Frame* { + return value.get(); + }, managed->frame); finalize_retired_frame(frame); } } } - void Plot::Private::retire_completed_frame(not_null frame) { Managed_Frame* managed{}; for (auto& slot : frame_slots) { @@ -1394,19 +1424,17 @@ void Plot::Private::retire_completed_frame(not_null frame) { managed = &slot; break; } - if (!managed) - throw std::logic_error("retired frame has no owned Plot slot"); - + if (!managed) throw std::logic_error("retired frame has no owned Plot slot"); auto* head = retired_frames.load(std::memory_order_relaxed); do { managed->retired_next.store(head, std::memory_order_relaxed); - } while (!retired_frames.compare_exchange_weak( + } + while (!retired_frames.compare_exchange_weak( head, managed, std::memory_order_release, std::memory_order_relaxed)); consumer_work_generation.fetch_add(1, std::memory_order_release); arm_tick_consumer(lifetime); } - void Plot::Private::finalize_retired_frame(not_null frame) { Managed_Frame* managed{}; for (auto& slot : frame_slots) { @@ -1420,13 +1448,11 @@ void Plot::Private::finalize_retired_frame(not_null frame) { break; } } - if (!managed) - throw std::logic_error("retired frame has no owned Plot slot"); - + if (!managed) throw std::logic_error("retired frame has no owned Plot slot"); const auto completion_latency = - managed->submitted_at.time_since_epoch().count() == 0 - ? std::chrono::steady_clock::duration::zero() - : std::chrono::steady_clock::now() - managed->submitted_at; + managed->submitted_at.time_since_epoch().count() == 0 + ? std::chrono::steady_clock::duration::zero() + : std::chrono::steady_clock::now() - managed->submitted_at; std::optional datoviz_observation; if (auto* frame_3d = dynamic_cast(frame.get())) { datoviz_observation = frame_3d->take_datoviz_observation(); @@ -1441,24 +1467,21 @@ void Plot::Private::finalize_retired_frame(not_null frame) { frame_policy_2d->record_frame_completed( frame->identity().sequence, completion_latency); } - const auto statistics_generation_value = - statistics_generation.load(std::memory_order_acquire); + statistics_generation.load(std::memory_order_acquire); if (applied_statistics_generation != statistics_generation_value) { completed_frame_statistics.reset(); applied_statistics_generation = statistics_generation_value; } managed->statistics = completed_frame_statistics.submit(*frame); managed->statistics_generation = statistics_generation_value; - std::optional captured_trace; nlohmann::json captured_components; nlohmann::json captured_backend; if (frame->taskflow_trace_requested()) { captured_trace.emplace(frame->take_taskflow_trace()); std::unordered_set executed_nodes; - for (const auto& execution : captured_trace->tasks) - executed_nodes.insert(execution.native_id); + for (const auto& execution : captured_trace->tasks) executed_nodes.insert(execution.native_id); std::vector executed_components; for (const auto& graph : captured_trace->graphs) { for (const auto& node : graph.nodes) { @@ -1468,22 +1491,20 @@ void Plot::Private::finalize_retired_frame(not_null frame) { &std::pair::first); if (owner == node.attributes.end() || owner->second.empty() || std::ranges::find(executed_components, owner->second) != - executed_components.end()) continue; + executed_components.end()) + continue; executed_components.push_back(owner->second); } } captured_components = view->capture_components(executed_components); - if (datoviz_observation) - captured_backend = datoviz_observation_json(*datoviz_observation); + if (datoviz_observation) captured_backend = datoviz_observation_json(*datoviz_observation); } - auto expected = Frame_State::consuming; if (!managed->state.compare_exchange_strong( - expected, Frame_State::available, std::memory_order_acq_rel, - std::memory_order_acquire)) + expected, Frame_State::available, std::memory_order_acq_rel, + std::memory_order_acquire)) throw std::logic_error("retired Plot frame is not consuming"); latest_statistics_frame.store(managed, std::memory_order_release); - /* 物理槽是唯一背压原因;归还任意槽后只解除一次耗尽状态。 */ auto admission = Render_Admission_State::frame_slots_exhausted; static_cast(render_admission.compare_exchange_strong( @@ -1491,26 +1512,23 @@ void Plot::Private::finalize_retired_frame(not_null frame) { std::memory_order_acq_rel, std::memory_order_acquire)); arm_tick_consumer(lifetime); release_frame_policy_lifetime_if_idle(); - if (captured_trace) { auto owner = lifetime; schedule_task("plot.taskflow.serialize", [owner, - trace = std::move(*captured_trace), - components = std::move(captured_components), - backend = std::move(captured_backend)]() mutable { - if (const auto plot = owner.lock()) - plot->d->store_trace( - plot->d->taskflow_trace_control, - plot->d->taskflow_trace_slots, trace, components, - backend); - }); + trace = std::move(*captured_trace), + components = std::move(captured_components), + backend = std::move(captured_backend)]() mutable { + if (const auto plot = owner.lock()) + plot->d->store_trace( + plot->d->taskflow_trace_control, + plot->d->taskflow_trace_slots, trace, components, + backend); + }); } } - void Plot::Private::attach_completion( std::unique_ptr completion) { - if (!completion || completion->empty()) - throw std::invalid_argument("Plot completion pipeline is empty"); + if (!completion || completion->empty()) throw std::invalid_argument("Plot completion pipeline is empty"); completion_extensions.push_back(std::move(completion)); auto extension = post_publish_graph.compose( completion_extensions.back()->name(), *completion_extensions.back()); @@ -1520,20 +1538,15 @@ void Plot::Private::attach_completion( post_publish_tail = std::move(extension); has_post_publish_tail = true; } - Plot::Plot(std::unique_ptr scene, - std::unique_ptr view) - : d(std::make_unique(std::move(scene), std::move(view))) {} + std::unique_ptr view) : d(std::make_unique(std::move(scene), std::move(view))) {} Plot::Plot(std::unique_ptr scene, - std::unique_ptr view) - : d(std::make_unique(std::move(scene), std::move(view))) {} + std::unique_ptr view) : d(std::make_unique(std::move(scene), std::move(view))) {} Plot::~Plot() = default; - void Plot::attach_scene_completion( std::unique_ptr completion) { d->attach_completion(std::move(completion)); } - void Plot::ensure_started() { std::call_once(d->start_once, [this] { const auto weak = weak_from_this(); @@ -1545,16 +1558,15 @@ void Plot::ensure_started() { .issued_at = tick.issued_at, .sequence = tick.sequence, .time_milliseconds = tick.time_milliseconds, - .source = Frame_Request_Source::periodic}); + .source = Frame_Request_Source::periodic + }); } }); d->refresh_schedule(); }); } - Plot::Stream_Id Plot::subscribe(Stream_Handler handler) { - if (!handler) - throw std::invalid_argument("Plot subscription requires a handler"); + if (!handler) throw std::invalid_argument("Plot subscription requires a handler"); ensure_started(); const auto id = d->next_stream_id.fetch_add(1, std::memory_order_relaxed); const auto notification = handler; @@ -1564,8 +1576,8 @@ Plot::Stream_Id Plot::subscribe(Stream_Handler handler) { next->emplace(id, Private::Consumer{handler}); std::shared_ptr desired = next; if (d->consumers.compare_exchange_weak( - current, desired, std::memory_order_release, - std::memory_order_acquire)) + current, desired, std::memory_order_release, + std::memory_order_acquire)) break; } d->refresh_schedule(); @@ -1573,12 +1585,15 @@ Plot::Stream_Id Plot::subscribe(Stream_Handler handler) { const auto failure = d->terminal_failure.load(std::memory_order_acquire); try { notification(std::make_shared( - Plot_Stream_Frame{nlohmann::json{ - {"kind", "plot_error"}, - {"protocol", "aethera.video.frame"}, - {"version", plot_stream_protocol_version}, - {"message", failure ? *failure : "Plot unavailable"} - }.dump(), {}})); + Plot_Stream_Frame{ + nlohmann::json{ + {"kind", "plot_error"}, + {"protocol", "aethera.video.frame"}, + {"version", plot_stream_protocol_version}, + {"message", failure ? *failure : "Plot unavailable"} + }.dump(), + {} + })); } catch (...) { unsubscribe(id); @@ -1586,7 +1601,6 @@ Plot::Stream_Id Plot::subscribe(Stream_Handler handler) { } return id; } - void Plot::unsubscribe(Stream_Id stream) { auto current = d->consumers.load(std::memory_order_acquire); while (current->contains(stream)) { @@ -1594,13 +1608,12 @@ void Plot::unsubscribe(Stream_Id stream) { next->erase(stream); std::shared_ptr desired = next; if (d->consumers.compare_exchange_weak( - current, desired, std::memory_order_release, - std::memory_order_acquire)) + current, desired, std::memory_order_release, + std::memory_order_acquire)) break; } d->refresh_schedule(); } - void Plot::configure_stream(Stream_Id stream, std::uint32_t width, std::uint32_t height) { auto current = d->consumers.load(std::memory_order_acquire); @@ -1613,25 +1626,22 @@ void Plot::configure_stream(Stream_Id stream, std::uint32_t width, consumer.height = height; std::shared_ptr desired = next; if (d->consumers.compare_exchange_weak( - current, desired, std::memory_order_release, - std::memory_order_acquire)) + current, desired, std::memory_order_release, + std::memory_order_acquire)) return; } } - void Plot::schedule_render(Plot_Render_Tick tick) { if (!std::isfinite(tick.time_milliseconds) || tick.time_milliseconds < 0.0) throw std::invalid_argument( "render time_milliseconds must be finite and non-negative"); - if (tick.width == 0 || tick.height == 0) - throw std::invalid_argument("render viewport must be non-zero"); + if (tick.width == 0 || tick.height == 0) throw std::invalid_argument("render viewport must be non-zero"); ensure_started(); if (d->terminal_failure.load(std::memory_order_acquire)) return; d->submit_tick_request(std::move(tick)); d->arm_tick_consumer(weak_from_this()); } - void Plot::render_once() { ensure_started(); const auto now = std::chrono::steady_clock::now(); @@ -1639,10 +1649,10 @@ void Plot::render_once() { schedule_render(Plot_Render_Tick{ .issued_at = now, .time_milliseconds = - std::chrono::duration(elapsed).count(), - .source = Frame_Request_Source::immediate}); + std::chrono::duration(elapsed).count(), + .source = Frame_Request_Source::immediate + }); } - void Plot::submit_input(Plot_Input_Event event) { static_cast(input_timeline_time(event.time_milliseconds)); ensure_started(); @@ -1654,44 +1664,35 @@ void Plot::submit_input(Plot_Input_Event event) { * State 双缓冲发布;Web 层只在低频 diagnostics 请求中读取结果。 */ try { - if (auto* scene_2d = std::get_if>(&d->scene)) - dispatch_plot_input(**scene_2d, event); - else - dispatch_plot_input(*std::get>(d->scene), event); + if (auto* scene_2d = std::get_if>(&d->scene)) dispatch_plot_input(**scene_2d, event); + else dispatch_plot_input(*std::get>(d->scene), event); } catch (...) { d->fail(std::current_exception()); } } - nlohmann::json Plot::schema() { ensure_started(); return d->schema(); } - nlohmann::json Plot::write_prop(std::string_view component, std::string_view key, const nlohmann::json& value) { ensure_started(); - if (component != "frame-analysis") - return d->view->write_prop(component, key, value); + if (component != "frame-analysis") return d->view->write_prop(component, key, value); auto result = d->with_frame_policy([&](auto& policy) { return write_frame_policy_prop(policy, key, value); }); - if (result.value("success", false)) - d->arm_tick_consumer(weak_from_this()); + if (result.value("success", false)) d->arm_tick_consumer(weak_from_this()); return result; } - nlohmann::json Plot::component_state(std::string_view component) const { return d->view->component_state(component); } - nlohmann::json Plot::generate_data(const nlohmann::json& input) { ensure_started(); return d->view->generate_data(input); } - nlohmann::json Plot::diagnostics() const { nlohmann::json frame_statistics = nlohmann::json::object(); nlohmann::json input_statistics = nlohmann::json::object(); @@ -1710,34 +1711,34 @@ nlohmann::json Plot::diagnostics() const { if constexpr (std::same_as>) { scene->template access_state( read_scene_statistics); - } else { + } + else { is_3d = true; scene->template access_state( read_scene_statistics); } }, d->scene); - { const auto generation = - d->statistics_generation.load(std::memory_order_acquire); + d->statistics_generation.load(std::memory_order_acquire); auto* completed_frame = - d->latest_statistics_frame.load(std::memory_order_acquire); + d->latest_statistics_frame.load(std::memory_order_acquire); Frame_Statistics_State statistics{}; if (completed_frame) { completed_frame->diagnostic_readers.fetch_add( 1, std::memory_order_acq_rel); if (d->latest_statistics_frame.load(std::memory_order_acquire) == - completed_frame && + completed_frame && completed_frame->state.load(std::memory_order_acquire) == - Private::Frame_State::available && + Private::Frame_State::available && completed_frame->statistics_generation == generation) { statistics = completed_frame->statistics; std::visit([&](const auto& frame) { using Frame_Pointer = - std::remove_cvref_t; + std::remove_cvref_t; if constexpr (std::same_as< - Frame_Pointer, - std::unique_ptr>) { + Frame_Pointer, + std::unique_ptr>) { const auto image = frame->image(); completed_width = static_cast( std::max(0, image.width)); @@ -1761,9 +1762,9 @@ nlohmann::json Plot::diagnostics() const { const auto& interval = statistics.values[ static_cast(Frame_Statistic::frame_interval_ms)]; frame_rate = interval.trimmed_average > 0.0 - ? 1'000.0 / interval.trimmed_average : 0.0; + ? 1'000.0 / interval.trimmed_average + : 0.0; } - const auto& pacing = d->pacing_state(); const auto stream = d->stream_snapshot(); const auto admission = d->render_admission.load(std::memory_order_acquire); @@ -1771,31 +1772,32 @@ nlohmann::json Plot::diagnostics() const { switch (admission) { case Private::Render_Admission_State::ready: return "ready"; case Private::Render_Admission_State::rendering: return "rendering"; - case Private::Render_Admission_State::frame_slots_exhausted: - return "frame_slots_exhausted"; + case Private::Render_Admission_State::frame_slots_exhausted: return "frame_slots_exhausted"; } return "unknown"; }(); nlohmann::json supported_formats = nlohmann::json::array(); if (is_3d) { - for (const auto format : Frame_3D::supported_pixel_formats) - supported_formats.push_back(pixel_format_name(format)); - } else { - for (const auto format : Frame_2D::supported_pixel_formats) - supported_formats.push_back(pixel_format_name(format)); + for (const auto format : Frame_3D::supported_pixel_formats) supported_formats.push_back(pixel_format_name(format)); + } + else { + for (const auto format : Frame_2D::supported_pixel_formats) supported_formats.push_back(pixel_format_name(format)); } const auto format = is_3d - ? pixel_format_name(Frame_3D::native_pixel_format) - : pixel_format_name(Frame_2D::native_pixel_format); + ? pixel_format_name(Frame_3D::native_pixel_format) + : pixel_format_name(Frame_2D::native_pixel_format); const auto native_format = is_3d - ? pixel_format_name(Frame_3D::native_pixel_format) - : pixel_format_name(Frame_2D::native_pixel_format); + ? pixel_format_name(Frame_3D::native_pixel_format) + : pixel_format_name(Frame_2D::native_pixel_format); const auto pixel_width = completed_width == 0 - ? stream.width : completed_width; + ? stream.width + : completed_width; const auto pixel_height = completed_height == 0 - ? stream.height : completed_height; + ? stream.height + : completed_height; const std::size_t byte_length = pacing.video_enabled - ? static_cast(pixel_width) * pixel_height * 4U : 0U; + ? static_cast(pixel_width) * pixel_height * 4U + : 0U; nlohmann::json output{ {"protocol", "aethera.plot.diagnostics"}, {"version", 4}, {"dimension", is_3d ? "3D" : "2D"}, @@ -1803,33 +1805,42 @@ nlohmann::json Plot::diagnostics() const { {"correlation_id", identity.correlation_id}, {"rendered_sequence", identity.sequence}, {"rendered_correlation_id", identity.correlation_id}, - {"generated_time_unix_ms", - static_cast(created_time_unix_ns) / 1'000'000.0}, + { + "generated_time_unix_ms", + static_cast(created_time_unix_ns) / 1'000'000.0 + }, {"delivery", pacing.video_enabled ? "gallery-video" : "diagnostics"}, {"frame_rate_fps", frame_rate}, {"dropped_sequence_count", dropped_sequences}, {"window_capacity", diagnostic_window_capacity}, - {"pixel", {{"width", pixel_width}, {"height", pixel_height}, - {"format", format}, {"native_format", native_format}, - {"supported_formats", std::move(supported_formats)}, - {"byte_length", byte_length}}}, + { + "pixel", { + {"width", pixel_width}, {"height", pixel_height}, + {"format", format}, {"native_format", native_format}, + {"supported_formats", std::move(supported_formats)}, + {"byte_length", byte_length} + } + }, {"frame_policy", frame_policy_state_json(pacing)}, {"render_admission", admission_name}, {"frame_statistics", std::move(frame_statistics)}, - {"input_statistics", std::move(input_statistics)}}; + {"input_statistics", std::move(input_statistics)} + }; if (is_3d) { const auto& pipeline = d->frame_policy_3d->read_state< Frame_Policy_3D::Base_Tag>(); output["frame_policy"]["three_dimensional_pipeline"] = { {"capacity", Frame_Policy_3D::pipeline_capacity}, {"overlapped_release_count", pipeline.overlapped_release_count}, - {"completion_gated_release_count", - pipeline.completion_gated_release_count}, + { + "completion_gated_release_count", + pipeline.completion_gated_release_count + }, {"gpu_completion_count", pipeline.gpu_completion_count}, {"last_completed_sequence", pipeline.last_completed_sequence} }; const auto& gpu = render_3d::detail::Gpu_Completion_Service::instance(). - read_state(); + read_state(); const auto milliseconds = [](std::uint64_t nanoseconds) { return static_cast(nanoseconds) / 1'000'000.0; }; @@ -1852,13 +1863,12 @@ nlohmann::json Plot::diagnostics() const { {"callback_failure_count", gpu.callback_failure_count}, {"backpressure_count", gpu.backpressure_count}, {"fault_count", gpu.fault_count}, - {"abandoned_count", gpu.abandoned_count}}; + {"abandoned_count", gpu.abandoned_count} + }; } - if (const auto failure = d->terminal_failure.load(std::memory_order_acquire)) - output["terminal_failure"] = *failure; + if (const auto failure = d->terminal_failure.load(std::memory_order_acquire)) output["terminal_failure"] = *failure; return output; } - void Plot::request_taskflow_trace(std::size_t frame_count) { if (frame_count == 0 || frame_count > Private::maximum_taskflow_trace_frames) @@ -1868,25 +1878,21 @@ void Plot::request_taskflow_trace(std::size_t frame_count) { for (;;) { const auto requested = static_cast(control >> 32U); const auto captured = static_cast(control); - if (requested != captured) - throw std::logic_error("A Taskflow frame trace request is already active"); + if (requested != captured) throw std::logic_error("A Taskflow frame trace request is already active"); const auto next = static_cast(frame_count) << 32U; if (d->taskflow_trace_control.compare_exchange_weak( - control, next, std::memory_order_release, - std::memory_order_acquire)) + control, next, std::memory_order_release, + std::memory_order_acquire)) break; } - for (auto& slot : d->taskflow_trace_slots) - slot.store({}, std::memory_order_release); + for (auto& slot : d->taskflow_trace_slots) slot.store({}, std::memory_order_release); d->taskflow_trace_remaining.store(frame_count, std::memory_order_release); } - nlohmann::json Plot::taskflow_trace() const { return d->trace_response(d->taskflow_trace_control, d->taskflow_trace_remaining, d->taskflow_trace_slots); } - void Plot::request_post_publish_taskflow_trace(std::size_t frame_count) { if (frame_count == 0 || frame_count > Private::maximum_taskflow_trace_frames) @@ -1902,28 +1908,27 @@ void Plot::request_post_publish_taskflow_trace(std::size_t frame_count) { "A post-publish Taskflow trace request is already active"); const auto next = static_cast(frame_count) << 32U; if (d->post_publish_trace_control.compare_exchange_weak( - control, next, std::memory_order_release, - std::memory_order_acquire)) + control, next, std::memory_order_release, + std::memory_order_acquire)) break; } - for (auto& slot : d->post_publish_trace_slots) - slot.store({}, std::memory_order_release); + for (auto& slot : d->post_publish_trace_slots) slot.store({}, std::memory_order_release); d->post_publish_trace_remaining.store(frame_count, std::memory_order_release); } - nlohmann::json Plot::post_publish_taskflow_trace() const { return d->trace_response(d->post_publish_trace_control, d->post_publish_trace_remaining, d->post_publish_trace_slots); } - void Plot::reset_diagnostics() { std::visit([](auto& scene) { scene->template update_state<&Scene::State::event_statistics>( Event_Statistics_State{}); }, d->scene); d->statistics_generation.fetch_add(1, std::memory_order_acq_rel); - d->with_frame_policy([](auto& policy) { policy.reset_statistics(); }); + d->with_frame_policy([](auto& policy) { + policy.reset_statistics(); + }); d->arm_tick_consumer(weak_from_this()); } } diff --git a/render_3D/render_3D/detail/Gpu_Completion_Service.cpp b/render_3D/render_3D/detail/Gpu_Completion_Service.cpp index 0ced1a6..d6b2201 100644 --- a/render_3D/render_3D/detail/Gpu_Completion_Service.cpp +++ b/render_3D/render_3D/detail/Gpu_Completion_Service.cpp @@ -5,7 +5,6 @@ #include #include #include - namespace aethera::render_3d::detail { namespace { std::uint64_t elapsed_nanoseconds( @@ -14,20 +13,15 @@ std::uint64_t elapsed_nanoseconds( std::chrono::steady_clock::now() - started).count(); return elapsed > 0 ? static_cast(elapsed) : 0ULL; } - template void update_peak(Value& peak, Value value) noexcept { peak = std::max(peak, value); } } - Gpu_Completion_Service::Private::Private() = default; - Gpu_Completion_Service::Private::~Private() = default; - Gpu_Completion_Service::Gpu_Completion_Service() = default; Gpu_Completion_Service::~Gpu_Completion_Service() = default; - Gpu_Completion_Service& Gpu_Completion_Service::instance() { /* * GPU completion 域与进程同寿命。主动泄放所有权可避免静态析构阶段 @@ -43,26 +37,20 @@ Gpu_Completion_Service& Gpu_Completion_Service::instance() { }(); return *service; } - Gpu_Completion_Service::Reservation::Reservation() = default; - Gpu_Completion_Service::Reservation::Reservation( - std::shared_ptr pending) noexcept - : pending_(std::move(pending)) {} - + std::shared_ptr pending) noexcept : pending_(std::move(pending)) {} Gpu_Completion_Service::Reservation::~Reservation() noexcept { - try { cancel(); } + try { + cancel(); + } catch (...) {} } - Gpu_Completion_Service::Reservation::Reservation( - Reservation&& other) noexcept - : pending_(std::exchange(other.pending_, {})) {} - + Reservation&& other) noexcept : pending_(std::exchange(other.pending_, {})) {} Gpu_Completion_Service::Prepare_Result::operator bool() const noexcept { return result == Admission_Result::none; } - void Gpu_Completion_Service::Reservation::watch(std::uintptr_t device, std::uintptr_t fence) { if (!pending_ || device == 0 || fence == 0) @@ -71,41 +59,32 @@ void Gpu_Completion_Service::Reservation::watch(std::uintptr_t device, pending_->service->watch(pending_, device, fence); pending_.reset(); } - void Gpu_Completion_Service::Reservation::cancel() { if (!pending_) return; pending_->service->cancel(pending_); pending_.reset(); } - Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare( Completion completion, Exception_Handler on_exception, bool observe) { return static_cast(*d).prepare( std::move(completion), std::move(on_exception), observe); } - void Gpu_Completion_Service::watch( const std::shared_ptr& pending, std::uintptr_t device, std::uintptr_t fence) { static_cast(*d).watch(pending, device, fence); } - void Gpu_Completion_Service::cancel( const std::shared_ptr& pending) { static_cast(*d).cancel(pending); } - -Gpu_Completion_Service::Prepare_Result -Gpu_Completion_Service::Private::prepare( +Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::Private::prepare( Completion completion, Exception_Handler on_exception, bool observe) { - if (!completion) - throw std::invalid_argument("GPU completion callback is empty"); + if (!completion) throw std::invalid_argument("GPU completion callback is empty"); if (!on_exception) throw std::invalid_argument( "GPU completion exception handler is empty"); - if (stopping.load(std::memory_order_acquire)) - return {{}, Admission_Result::stopping}; - + if (stopping.load(std::memory_order_acquire)) return {{}, Admission_Result::stopping}; auto pending = std::make_shared(); pending->completion = std::move(completion); pending->on_exception = std::move(on_exception); @@ -113,11 +92,11 @@ Gpu_Completion_Service::Private::prepare( pending->service = object; Admission_Result admission{Admission_Result::none}; double_buffer::detail::Internal_Access::update_state< - &Gpu_Completion_State::State::in_flight, &Gpu_Completion_State::State::peak_in_flight, - &Gpu_Completion_State::State::reservation_count, &Gpu_Completion_State::State::backpressure_count>( + &State::in_flight, &State::peak_in_flight, + &State::reservation_count, &State::backpressure_count>( object, [&](State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); if (state.in_flight >= state.capacity) { ++state.backpressure_count; admission = Admission_Result::capacity_exhausted; @@ -127,23 +106,21 @@ Gpu_Completion_Service::Private::prepare( update_peak(state.peak_in_flight, state.in_flight); ++state.reservation_count; }); - if (admission != Admission_Result::none) - return {{}, admission}; + if (admission != Admission_Result::none) return {{}, admission}; try { double_buffer::detail::Internal_Access::submit_stream(object, pending); } catch (...) { - double_buffer::detail::Internal_Access::update_state<&Gpu_Completion_State::State::in_flight>(object, - [](State_Access states) { - auto& state = states.template get(); - if (state.in_flight != 0) --state.in_flight; - }); + double_buffer::detail::Internal_Access::update_state<&State::in_flight>(object, + [](State_Access states) { + auto& state = states.template get(); + if (state.in_flight != 0) --state.in_flight; + }); throw; } request_poll(std::chrono::nanoseconds{1}); return {Reservation(std::move(pending)), Admission_Result::none}; } - void Gpu_Completion_Service::Private::watch( const std::shared_ptr& pending, std::uintptr_t device, std::uintptr_t fence) { @@ -158,42 +135,39 @@ void Gpu_Completion_Service::Private::watch( pending->watched_at = std::chrono::steady_clock::now(); pending->status = Gpu_Completion_Pending_Fence::Status::watched; } - double_buffer::detail::Internal_Access::update_state<&Gpu_Completion_State::State::watched, &Gpu_Completion_State::State::peak_watched>(object, - [](State_Access states) { - auto& state = states.template get(); - ++state.watched; - update_peak(state.peak_watched, state.watched); - }); + double_buffer::detail::Internal_Access::update_state<&State::watched, &State::peak_watched>(object, + [](State_Access states) { + auto& state = states.template get(); + ++state.watched; + update_peak(state.peak_watched, state.watched); + }); request_poll(std::chrono::nanoseconds{1}); } - void Gpu_Completion_Service::Private::cancel( const std::shared_ptr& pending) { { std::lock_guard lock(service_mutex); if (!pending || pending->service != object) return; - if (pending->status == Gpu_Completion_Pending_Fence::Status::reserved) - pending->status = Gpu_Completion_Pending_Fence::Status::canceled; + if (pending->status == Gpu_Completion_Pending_Fence::Status::reserved) pending->status = Gpu_Completion_Pending_Fence::Status::canceled; } request_poll(std::chrono::nanoseconds{1}); } - void Gpu_Completion_Service::Private::request_poll( std::chrono::nanoseconds delay) noexcept { if (stopping.load(std::memory_order_acquire)) return; - try { poll_timer.start_once(delay); } + try { + poll_timer.start_once(delay); + } catch (...) {} } - void Gpu_Completion_Service::Private::poll() noexcept { if (stopping.load(std::memory_order_acquire)) return; try { double_buffer::detail::Internal_Access::exchange_stream(object); double_buffer::detail::Internal_Access::access_rendering_stream(object, - [&](std::span> pending) { - active.insert(active.end(), pending.begin(), pending.end()); - }); - + [&](std::span> pending) { + active.insert(active.end(), pending.begin(), pending.end()); + }); const auto finish = [this]( const std::shared_ptr& pending, VkResult vulkan_result, Completion_Error error) noexcept { @@ -202,41 +176,41 @@ void Gpu_Completion_Service::Private::poll() noexcept { Result result{}; { std::lock_guard lock(service_mutex); - if (pending->status != Gpu_Completion_Pending_Fence::Status::watched) - return; + if (pending->status != Gpu_Completion_Pending_Fence::Status::watched) return; completion = std::move(pending->completion); on_exception = std::move(pending->on_exception); result.error = error; result.vulkan_result = static_cast(vulkan_result); - if (pending->observe) - result.wait_duration_ns = elapsed_nanoseconds(pending->watched_at); + if (pending->observe) result.wait_duration_ns = elapsed_nanoseconds(pending->watched_at); pending->status = Gpu_Completion_Pending_Fence::Status::canceled; } double_buffer::detail::Internal_Access::update_state< - &Gpu_Completion_State::State::watched, &Gpu_Completion_State::State::fault_count, &Gpu_Completion_State::State::abandoned_count>( + &State::watched, &State::fault_count, &State::abandoned_count>( object, [error](State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); if (state.watched != 0) --state.watched; if (error != Completion_Error::none) { ++state.fault_count; - if (error == Completion_Error::fence_abandoned) - ++state.abandoned_count; + if (error == Completion_Error::fence_abandoned) ++state.abandoned_count; } }); - const auto callback_started = std::chrono::steady_clock::now(); std::exception_ptr callback_failure; - try { completion(std::move(result)); } - catch (...) { callback_failure = std::current_exception(); } + try { + completion(std::move(result)); + } + catch (...) { + callback_failure = std::current_exception(); + } const auto callback_ns = elapsed_nanoseconds(callback_started); double_buffer::detail::Internal_Access::update_state< - &Gpu_Completion_State::State::callback_total_ns, &Gpu_Completion_State::State::callback_max_ns, - &Gpu_Completion_State::State::callback_failure_count, &Gpu_Completion_State::State::completion_count, - &Gpu_Completion_State::State::in_flight>( + &State::callback_total_ns, &State::callback_max_ns, + &State::callback_failure_count, &State::completion_count, + &State::in_flight>( object, [callback_ns, &callback_failure](State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); state.callback_total_ns += callback_ns; update_peak(state.callback_max_ns, callback_ns); if (callback_failure) ++state.callback_failure_count; @@ -250,7 +224,6 @@ void Gpu_Completion_Service::Private::poll() noexcept { } catch (...) {} }; - std::size_t reserved_count{}; bool needs_more{}; for (auto iterator = active.begin(); iterator != active.end();) { @@ -268,10 +241,10 @@ void Gpu_Completion_Service::Private::poll() noexcept { if (status == Gpu_Completion_Pending_Fence::Status::canceled) { iterator = active.erase(iterator); double_buffer::detail::Internal_Access::update_state< - &Gpu_Completion_State::State::cancellation_count, &Gpu_Completion_State::State::in_flight>( + &State::cancellation_count, &State::in_flight>( object, [](State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); ++state.cancellation_count; if (state.in_flight != 0) --state.in_flight; }); @@ -288,10 +261,10 @@ void Gpu_Completion_Service::Private::poll() noexcept { finish(pending, VK_TIMEOUT, Completion_Error::fence_abandoned); continue; } - double_buffer::detail::Internal_Access::update_state<&Gpu_Completion_State::State::fence_probe_count>(object, - [](State_Access states) { - ++states.template get().fence_probe_count; - }); + double_buffer::detail::Internal_Access::update_state<&State::fence_probe_count>(object, + [](State_Access states) { + ++states.template get().fence_probe_count; + }); const VkResult result = vkGetFenceStatus( reinterpret_cast(device), reinterpret_cast(fence)); @@ -303,26 +276,25 @@ void Gpu_Completion_Service::Private::poll() noexcept { auto pending = *iterator; iterator = active.erase(iterator); finish(pending, result, - result == VK_SUCCESS ? Completion_Error::none - : Completion_Error::vulkan_failure); + result == VK_SUCCESS + ? Completion_Error::none + : Completion_Error::vulkan_failure); } - const auto now = std::chrono::steady_clock::now(); if (last_state_publication == std::chrono::steady_clock::time_point{} || now - last_state_publication >= state_publication_interval) { double_buffer::detail::Internal_Access::update_state< - &Gpu_Completion_State::State::active_fences, &Gpu_Completion_State::State::pending_fences>( + &State::active_fences, &State::pending_fences>( object, [active_count = active.size(), reserved_count]( State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); state.active_fences = active_count; state.pending_fences = reserved_count; }); - double_buffer::detail::Internal_Access::publish_state(object); + double_buffer::detail::Internal_Access::publish_state(object); last_state_publication = now; } - if (needs_more) request_poll(probe_interval); } catch (...) { @@ -332,13 +304,14 @@ void Gpu_Completion_Service::Private::poll() noexcept { std::lock_guard lock(service_mutex); for (auto& pending : active) { pending->status = Gpu_Completion_Pending_Fence::Status::canceled; - if (pending->on_exception) - handlers.push_back(std::move(pending->on_exception)); + if (pending->on_exception) handlers.push_back(std::move(pending->on_exception)); } active.clear(); } for (auto& handler : handlers) { - try { handler(failure); } + try { + handler(failure); + } catch (...) {} } } diff --git a/render_3D/render_3D/detail/Gpu_Completion_Service.hpp b/render_3D/render_3D/detail/Gpu_Completion_Service.hpp index f036f88..cd33b84 100644 --- a/render_3D/render_3D/detail/Gpu_Completion_Service.hpp +++ b/render_3D/render_3D/detail/Gpu_Completion_Service.hpp @@ -7,10 +7,12 @@ #include namespace aethera::render_3d::detail { struct GPU_Task_Tag {}; - -/* GPU completion 统计是独立状态领域;Service 只负责行为与任务流。 */ -struct Gpu_Completion_State : Def { +struct Gpu_Completion_Pending_Fence; +struct Gpu_Completion_Service : Def>> { struct Prop : Prev_Prop {}; + /* GPU completion 统计是独立状态领域;Service 只负责行为与任务流。 */ struct State : Prev_State { std::size_t capacity{}; /* 可同时监视的最大 fence 数量。 */ std::size_t in_flight{}; /* 已准入且尚未完成的 reservation 数量。 */ @@ -35,15 +37,6 @@ struct Gpu_Completion_State : Def { std::uint64_t abandoned_count{}; bool operator==(const State&) const = default; }; - struct Private : Prev_Private {}; -}; - -struct Gpu_Completion_Pending_Fence; -struct Gpu_Completion_Service : Def>> { - struct Prop : Prev_Prop {}; - struct State : Prev_State {}; struct Private; enum struct Admission_Result : std::uint8_t { none, diff --git a/render_3D/render_3D/detail/Gpu_Completion_Service.ipp b/render_3D/render_3D/detail/Gpu_Completion_Service.ipp index 276d2f2..970b7cf 100644 --- a/render_3D/render_3D/detail/Gpu_Completion_Service.ipp +++ b/render_3D/render_3D/detail/Gpu_Completion_Service.ipp @@ -35,12 +35,14 @@ struct Gpu_Completion_Service::Private : Prev_Private { void bind_private_crtp(Attached_Object* attached) { Prev_Private::bind_private_crtp(attached); object = not_null{static_cast(attached)}; - double_buffer::detail::Internal_Access::update_state<&Gpu_Completion_State::State::capacity>(object, - static_cast(default_capacity)); - double_buffer::detail::Internal_Access::publish_state(object); + double_buffer::detail::Internal_Access::update_state<&State::capacity>(object, + static_cast(default_capacity)); + double_buffer::detail::Internal_Access::publish_state(object); active.reserve(static_cast(default_capacity)); poll_timer = Frame_Scheduler::instance().make_timer( - [this](Frame_Scheduler::Tick) { poll(); }); + [this](Frame_Scheduler::Tick) { + poll(); + }); } [[nodiscard]] Prepare_Result prepare(Completion completion, Exception_Handler on_exception, @@ -54,6 +56,6 @@ struct Gpu_Completion_Service::Private : Prev_Private { static constexpr auto probe_interval = std::chrono::milliseconds(1); static constexpr std::uint64_t maximum_fence_age_ns = 30'000'000'000ULL; static constexpr auto state_publication_interval = - std::chrono::milliseconds(100); + std::chrono::milliseconds(100); }; }