From 144602ef458ef7f954c6a758f4fc9004bce7f186 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Fri, 28 Aug 2026 01:36:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/kernel/Frame_Policy/Frame_Policy.hpp | 2 +- .../kernel/Frame_Policy/Frame_Policy_3D.cpp | 234 +++++++++++++++++- .../kernel/Frame_Policy/Frame_Policy_3D.hpp | 28 ++- kernel/src/test/Frame_Policy_3D_Test.cpp | 43 +++- web_server/src/Plot.cpp | 142 +++++++---- 5 files changed, 400 insertions(+), 49 deletions(-) diff --git a/kernel/src/kernel/Frame_Policy/Frame_Policy.hpp b/kernel/src/kernel/Frame_Policy/Frame_Policy.hpp index 69ce88e..5e52ae1 100644 --- a/kernel/src/kernel/Frame_Policy/Frame_Policy.hpp +++ b/kernel/src/kernel/Frame_Policy/Frame_Policy.hpp @@ -52,7 +52,7 @@ struct Frame_Policy_Event { * 任意线程只向 MPMC 事件流提交事实;Plot tick consumer 是唯一归并写者。 * 查询方只读取 current State,不参与事件归并,也不获取 Plot/Scene 锁。 */ -struct Frame_Policy : double_buffer::Def< +struct Frame_Policy final : double_buffer::Def< Frame_Policy, double_buffer::Root, double_buffer::Mpmc_Triple_Buffer< detail::Frame_Policy_Event_Stream, detail::Frame_Policy_Event>> { diff --git a/kernel/src/kernel/Frame_Policy/Frame_Policy_3D.cpp b/kernel/src/kernel/Frame_Policy/Frame_Policy_3D.cpp index 72929f6..b99b76a 100644 --- a/kernel/src/kernel/Frame_Policy/Frame_Policy_3D.cpp +++ b/kernel/src/kernel/Frame_Policy/Frame_Policy_3D.cpp @@ -1,9 +1,235 @@ #include "Frame_Policy_3D.hpp" #include +#include #include +#include #include namespace aethera { +namespace { + +std::uint64_t monotonic_nanoseconds_3d( + std::chrono::steady_clock::time_point value = + std::chrono::steady_clock::now()) noexcept { + return static_cast( + std::chrono::duration_cast( + value.time_since_epoch()).count()); +} + +std::uint64_t duration_nanoseconds_3d( + std::chrono::steady_clock::duration value) noexcept { + return static_cast(std::max( + 0, std::chrono::duration_cast(value).count())); +} + +void observe_common_event(Frame_Policy::State& state, + const detail::Frame_Policy_Event& event) noexcept { + if (event.occurred_ns == 0) return; + if (state.observation_started_ns == 0) + state.observation_started_ns = event.occurred_ns; + state.observed_until_ns = std::max(state.observed_until_ns, + event.occurred_ns); +} + +void reset_common_counters(Frame_Policy::State& state, + std::uint64_t occurred_ns) noexcept { + const auto generation = state.generation + 1; + const auto render_enabled = state.render_enabled; + const auto video_enabled = state.video_enabled; + const auto mode = state.mode; + const auto fixed_rate_fps = state.fixed_rate_fps; + state = {}; + state.generation = generation; + state.render_enabled = render_enabled; + state.video_enabled = video_enabled; + state.mode = mode; + state.fixed_rate_fps = fixed_rate_fps; + state.observation_started_ns = occurred_ns; + state.observed_until_ns = occurred_ns; +} + +} + +void Frame_Policy_3D::submit(detail::Frame_Policy_Event event) { + if (event.occurred_ns == 0) + event.occurred_ns = monotonic_nanoseconds_3d(); + submit_stream(std::move(event)); +} + +void Frame_Policy_3D::set_render_enabled(bool enabled) { + submit({.type = detail::Frame_Policy_Event_Type::render_enabled, + .enabled = enabled}); +} + +void Frame_Policy_3D::set_video_enabled(bool enabled) { + submit({.type = detail::Frame_Policy_Event_Type::video_enabled, + .enabled = enabled}); +} + +void Frame_Policy_3D::set_mode(Frame_Pacing_Mode mode) { + submit({.type = detail::Frame_Policy_Event_Type::mode, .mode = mode}); +} + +void Frame_Policy_3D::set_fixed_rate(double fps) { + if (!std::isfinite(fps) || fps <= 0.0) + throw std::invalid_argument("frame pacing fps must be positive"); + submit({.type = detail::Frame_Policy_Event_Type::fixed_rate, + .number = fps}); +} + +void Frame_Policy_3D::reset_statistics() { + submit({.type = detail::Frame_Policy_Event_Type::reset}); +} + +void Frame_Policy_3D::record_request( + Frame_Request_Source source, + std::chrono::steady_clock::time_point occurred_at) { + submit({.type = detail::Frame_Policy_Event_Type::request_received, + .source = source, + .occurred_ns = monotonic_nanoseconds_3d(occurred_at)}); +} + +void Frame_Policy_3D::record_request_accepted(Frame_Request_Source source) { + submit({.type = detail::Frame_Policy_Event_Type::request_accepted, + .source = source}); +} + +void Frame_Policy_3D::record_request_coalesced() { + submit({.type = detail::Frame_Policy_Event_Type::request_coalesced}); +} + +void Frame_Policy_3D::record_policy_rejection() { + submit({.type = detail::Frame_Policy_Event_Type::policy_rejected}); +} + +void Frame_Policy_3D::record_frame_slot_backpressure() { + submit({.type = detail::Frame_Policy_Event_Type::frame_slot_backpressure}); +} + +void Frame_Policy_3D::record_scene_rejection() { + submit({.type = detail::Frame_Policy_Event_Type::scene_rejected}); +} + +void Frame_Policy_3D::record_frame_submitted( + std::uint64_t sequence, Frame_Request_Source source, + std::chrono::steady_clock::duration tick_queue_duration) { + submit({.type = detail::Frame_Policy_Event_Type::frame_submitted, + .source = source, + .sequence = sequence, + .duration_ns = duration_nanoseconds_3d(tick_queue_duration)}); +} + +void Frame_Policy_3D::record_frame_completed( + std::uint64_t sequence, + std::chrono::steady_clock::duration completion_latency) { + submit({.type = detail::Frame_Policy_Event_Type::frame_completed, + .sequence = sequence, + .duration_ns = duration_nanoseconds_3d(completion_latency)}); +} + +bool Frame_Policy_3D::consume_events() { + exchange_stream(); + bool consumed{}; + bool configuration_changed{}; + auto& state = static_cast( + double_buffer::detail::Internal_Access::pending_state(this)); + auto& common = state.common; + access_rendering_stream( + [&](std::span events) { + for (const auto& event : events) { + consumed = true; + observe_common_event(common, event); + switch (event.type) { + case detail::Frame_Policy_Event_Type::render_enabled: + configuration_changed |= + common.render_enabled != event.enabled; + common.render_enabled = event.enabled; + ++common.generation; + break; + case detail::Frame_Policy_Event_Type::video_enabled: + common.video_enabled = event.enabled; + ++common.generation; + break; + case detail::Frame_Policy_Event_Type::mode: + configuration_changed |= common.mode != event.mode; + common.mode = event.mode; + ++common.generation; + break; + case detail::Frame_Policy_Event_Type::fixed_rate: + configuration_changed |= + common.fixed_rate_fps != event.number; + common.fixed_rate_fps = event.number; + ++common.generation; + break; + case detail::Frame_Policy_Event_Type::request_received: + ++common.request_count; + if (event.source == Frame_Request_Source::periodic) + ++common.periodic_request_count; + else if (event.source == Frame_Request_Source::immediate) + ++common.immediate_request_count; + else if (event.source == Frame_Request_Source::maximum_rate) + ++common.maximum_rate_request_count; + break; + case detail::Frame_Policy_Event_Type::request_accepted: + ++common.accepted_request_count; + break; + case detail::Frame_Policy_Event_Type::request_coalesced: + ++common.coalesced_request_count; + break; + case detail::Frame_Policy_Event_Type::policy_rejected: + ++common.policy_rejection_count; + break; + case detail::Frame_Policy_Event_Type::frame_slot_backpressure: + ++common.frame_slot_backpressure_count; + break; + case detail::Frame_Policy_Event_Type::scene_rejected: + ++common.scene_rejection_count; + break; + case detail::Frame_Policy_Event_Type::frame_submitted: + ++common.submitted_frame_count; + ++common.active_frame_count; + common.last_frame_sequence = event.sequence; + common.last_request_source = event.source; + common.latest_tick_queue_ns = event.duration_ns; + common.tick_queue_total_ns += event.duration_ns; + common.maximum_tick_queue_ns = std::max( + common.maximum_tick_queue_ns, event.duration_ns); + break; + case detail::Frame_Policy_Event_Type::frame_completed: { + ++common.completed_frame_count; + if (common.active_frame_count != 0) + --common.active_frame_count; + common.last_frame_sequence = event.sequence; + common.latest_completion_latency_ns = event.duration_ns; + common.completion_latency_total_ns += event.duration_ns; + common.maximum_completion_latency_ns = std::max( + common.maximum_completion_latency_ns, + event.duration_ns); + if (common.first_completion_ns == 0) + common.first_completion_ns = event.occurred_ns; + if (common.last_completion_ns != 0) { + const auto interval = event.occurred_ns - + common.last_completion_ns; + ++common.completion_interval_count; + common.latest_completion_interval_ns = interval; + common.completion_interval_total_ns += interval; + const auto value = static_cast(interval); + common.completion_interval_squared_total_ns2 += + value * value; + } + common.last_completion_ns = event.occurred_ns; + break; + } + case detail::Frame_Policy_Event_Type::reset: + reset_common_counters(common, event.occurred_ns); + break; + } + } + }); + if (consumed) publish_state(); + return configuration_changed; +} + void Frame_Policy_3D::submit_3d(detail::Frame_Policy_3D_Event event) { submit_stream(std::move(event)); } @@ -50,10 +276,14 @@ bool Frame_Policy_3D::consume_3d_events() { break; case detail::Frame_Policy_3D_Event_Type::reset: { const auto in_flight = state.gpu_in_flight; - state = {}; - state.pipeline_capacity = 3; + state.gpu_submission_count = 0; + state.overlapped_release_count = 0; + state.completion_gated_release_count = 0; + state.gpu_completion_count = 0; state.gpu_in_flight = in_flight; state.peak_gpu_in_flight = in_flight; + state.last_submitted_sequence = 0; + state.last_completed_sequence = 0; break; } } diff --git a/kernel/src/kernel/Frame_Policy/Frame_Policy_3D.hpp b/kernel/src/kernel/Frame_Policy/Frame_Policy_3D.hpp index 69c52f4..31a1ea4 100644 --- a/kernel/src/kernel/Frame_Policy/Frame_Policy_3D.hpp +++ b/kernel/src/kernel/Frame_Policy/Frame_Policy_3D.hpp @@ -23,12 +23,16 @@ struct Frame_Policy_3D_Event { * 事件流进入,Plot 的唯一 tick consumer 归并并通过 double-buffer 发布。 */ struct Frame_Policy_3D final : double_buffer::Def< - Frame_Policy_3D, Frame_Policy, + Frame_Policy_3D, double_buffer::Root, + double_buffer::Mpmc_Triple_Buffer< + detail::Frame_Policy_Event_Stream, + detail::Frame_Policy_Event>, double_buffer::Mpmc_Triple_Buffer< detail::Frame_Policy_3D_Event_Stream, detail::Frame_Policy_3D_Event>> { struct Prop : Prev_Prop {}; struct State : Prev_State { + Frame_Policy::State common{}; /* 3D 策略拥有的唯一公共节奏状态,不别名 2D 存储。 */ std::size_t pipeline_capacity{3}; /* Scene 可绑定的物理 3D 帧槽数量。 */ std::uint64_t gpu_submission_count{}; /* Vulkan 实际提交的累计帧数。 */ std::uint64_t overlapped_release_count{}; /* 提交后即开放下一次 Prepare 的累计帧数。 */ @@ -42,12 +46,34 @@ struct Frame_Policy_3D final : double_buffer::Def< }; struct Private : Prev_Private {}; + void set_render_enabled(bool enabled); + void set_video_enabled(bool enabled); + void set_mode(Frame_Pacing_Mode mode); + void set_fixed_rate(double fps); + void reset_statistics(); + + void record_request(Frame_Request_Source source, + std::chrono::steady_clock::time_point occurred_at); + void record_request_accepted(Frame_Request_Source source); + void record_request_coalesced(); + void record_policy_rejection(); + void record_frame_slot_backpressure(); + void record_scene_rejection(); + void record_frame_submitted( + std::uint64_t sequence, Frame_Request_Source source, + std::chrono::steady_clock::duration tick_queue_duration); + void record_frame_completed( + std::uint64_t sequence, + std::chrono::steady_clock::duration completion_latency); + [[nodiscard]] bool consume_events(); + void record_gpu_submitted(std::uint64_t sequence, bool overlaps_gpu); void record_gpu_completed(std::uint64_t sequence); void reset_3d_statistics(); [[nodiscard]] bool consume_3d_events(); private: + void submit(detail::Frame_Policy_Event event); void submit_3d(detail::Frame_Policy_3D_Event event); }; } diff --git a/kernel/src/test/Frame_Policy_3D_Test.cpp b/kernel/src/test/Frame_Policy_3D_Test.cpp index fd2d320..7a08903 100644 --- a/kernel/src/test/Frame_Policy_3D_Test.cpp +++ b/kernel/src/test/Frame_Policy_3D_Test.cpp @@ -1,4 +1,5 @@ #include "Frame_Policy/Frame_Policy_3D.hpp" +#include #include #include @@ -27,12 +28,52 @@ TEST(frame_policy_3d, publishes_gpu_pipeline_facts_without_changing_common_polic EXPECT_EQ(pipeline.last_submitted_sequence, 12u); EXPECT_EQ(pipeline.last_completed_sequence, 11u); - const auto& common = policy->read_state(); + const auto& common = pipeline.common; EXPECT_EQ(common.submitted_frame_count, 0u); EXPECT_EQ(common.completed_frame_count, 0u); EXPECT_EQ(common.mode, aethera::Frame_Pacing_Mode::fixed_rate); } +TEST(frame_policy_3d, owns_common_event_stream_without_base_storage_aliasing) { + auto built = aethera::Frame_Policy_3D::Builder< + aethera::Frame_Policy_3D>{}.build(); + ASSERT_TRUE(built.has_value()); + auto policy = std::move(*built); + + const auto now = std::chrono::steady_clock::now(); + policy->set_mode(aethera::Frame_Pacing_Mode::maximum_rate); + policy->record_request(aethera::Frame_Request_Source::maximum_rate, now); + policy->record_request_accepted( + aethera::Frame_Request_Source::maximum_rate); + policy->record_frame_submitted( + 31, aethera::Frame_Request_Source::maximum_rate, + std::chrono::microseconds{40}); + policy->record_frame_completed(31, std::chrono::microseconds{90}); + policy->record_gpu_submitted(31, true); + EXPECT_TRUE(policy->consume_events()); + EXPECT_TRUE(policy->consume_3d_events()); + + const auto& state = policy->read_state< + aethera::Frame_Policy_3D::Base_Tag>(); + EXPECT_EQ(state.common.mode, aethera::Frame_Pacing_Mode::maximum_rate); + EXPECT_EQ(state.common.request_count, 1u); + EXPECT_EQ(state.common.accepted_request_count, 1u); + EXPECT_EQ(state.common.submitted_frame_count, 1u); + EXPECT_EQ(state.common.completed_frame_count, 1u); + EXPECT_EQ(state.common.active_frame_count, 0u); + EXPECT_EQ(state.common.last_frame_sequence, 31u); + EXPECT_EQ(state.gpu_submission_count, 1u); + EXPECT_EQ(state.gpu_in_flight, 1u); + + policy->reset_statistics(); + static_cast(policy->consume_events()); + const auto& reset = policy->read_state< + aethera::Frame_Policy_3D::Base_Tag>(); + EXPECT_EQ(reset.common.request_count, 0u); + EXPECT_EQ(reset.gpu_submission_count, 1u); + EXPECT_EQ(reset.gpu_in_flight, 1u); +} + TEST(frame_policy_3d, reset_preserves_only_current_in_flight_ownership) { auto built = aethera::Frame_Policy_3D::Builder< aethera::Frame_Policy_3D>{}.build(); diff --git a/web_server/src/Plot.cpp b/web_server/src/Plot.cpp index 94b21b4..21d60c4 100644 --- a/web_server/src/Plot.cpp +++ b/web_server/src/Plot.cpp @@ -77,8 +77,19 @@ std::string_view pixel_format_name(render_3d::Pixel_Format format) { throw std::logic_error("unknown 3D pixel format"); } -nlohmann::json frame_policy_schema(const Frame_Policy& policy) { - const auto& current = policy.read_state(); +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({ @@ -108,7 +119,8 @@ nlohmann::json frame_policy_schema(const Frame_Policy& policy) { }; } -nlohmann::json write_frame_policy_prop(Frame_Policy& pacing, +template +nlohmann::json write_frame_policy_prop(Policy& pacing, std::string_view key, const nlohmann::json& value) { if (key == "render_enabled" || key == "video_enabled") { @@ -462,7 +474,6 @@ struct Plot::Private { std::uint64_t next_frame_sequence{1}; std::unique_ptr frame_policy_2d{}; std::unique_ptr frame_policy_3d{}; - Frame_Policy* frame_policy{}; /* 指向当前维度策略的公共事实层。 */ Frame_Scheduler::Timer frame_timer{}; /* 每 Plot/Scene 只有轻量时间轮节点,不持有线程。 */ static constexpr std::size_t scene_frame_capacity{3}; std::array frame_slots{}; /* Scene 与外接消费者共享生命周期的稳定三缓冲。 */ @@ -506,7 +517,6 @@ struct Plot::Private { if (!built_policy) throw std::logic_error("2D Frame Policy dependency graph is invalid"); frame_policy_2d = std::move(*built_policy); - frame_policy = frame_policy_2d.get(); } else { auto built_policy = @@ -514,9 +524,8 @@ struct Plot::Private { if (!built_policy) throw std::logic_error("3D Frame Policy dependency graph is invalid"); frame_policy_3d = std::move(*built_policy); - frame_policy = frame_policy_3d.get(); - frame_policy->set_mode(Frame_Pacing_Mode::maximum_rate); - static_cast(frame_policy->consume_events()); + 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) @@ -535,6 +544,34 @@ struct Plot::Private { } } + template + decltype(auto) with_frame_policy(Callback&& callback) { + 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); + 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(); + return frame_policy_3d->read_state().common; + } + + [[nodiscard]] bool consume_frame_policy_events() { + const bool configuration_changed = with_frame_policy( + [](auto& policy) { return policy.consume_events(); }); + if (frame_policy_3d) + static_cast(frame_policy_3d->consume_3d_events()); + return configuration_changed; + } + [[nodiscard]] nlohmann::json schema() const; [[nodiscard]] Stream_Snapshot stream_snapshot() const; void publish(std::shared_ptr frame) noexcept; @@ -590,7 +627,8 @@ void Plot::Private::fail(std::exception_ptr failure) noexcept { nlohmann::json Plot::Private::schema() const { auto result = view->schema(); - auto analysis = frame_policy_schema(*frame_policy); + auto analysis = with_frame_policy( + [](const auto& policy) { return frame_policy_schema(policy); }); if (frame_policy_3d) { analysis["fields"].push_back({ {"key", "pipeline_capacity"}, {"label", "3D 帧槽容量"}, @@ -652,7 +690,7 @@ void Plot::Private::publish( void Plot::Private::refresh_schedule() { if (!frame_timer.valid()) return; const auto current_consumers = consumers.load(std::memory_order_acquire); - const auto& pacing = frame_policy->read_state(); + const auto& pacing = pacing_state(); if (!pacing.render_enabled || current_consumers->empty()) { frame_timer.cancel(); return; @@ -677,7 +715,9 @@ void Plot::Private::submit_tick_request(Plot_Render_Tick tick) { const auto issued_at = tick.issued_at; if (!tick_requests.enqueue(std::move(tick))) throw std::bad_alloc{}; - frame_policy->record_request(source, issued_at); + with_frame_policy([&](auto& policy) { + policy.record_request(source, issued_at); + }); tick_request_generation.fetch_add(1, std::memory_order_release); } @@ -694,7 +734,9 @@ void Plot::Private::keep_latest_tick(const Plot_Render_Tick& tick) { if (deferred_tick) { const auto current_priority = priority(deferred_tick->source); const auto next_priority = priority(tick.source); - frame_policy->record_request_coalesced(); + with_frame_policy([](auto& policy) { + policy.record_request_coalesced(); + }); if (current_priority > next_priority || (current_priority == next_priority && deferred_tick->issued_at >= tick.issued_at)) @@ -720,7 +762,7 @@ void Plot::Private::release_render_admission(std::weak_ptr lifetime) { expected, Render_Admission_State::ready, std::memory_order_acq_rel, std::memory_order_acquire)) return; - const auto& pacing = frame_policy->read_state(); + const auto& pacing = pacing_state(); if (pacing.render_enabled && pacing.mode == Frame_Pacing_Mode::maximum_rate) { const auto current_consumers = consumers.load(std::memory_order_acquire); if (!current_consumers->empty()) { @@ -736,9 +778,7 @@ void Plot::Private::release_render_admission(std::weak_ptr lifetime) { } void Plot::Private::consume_tick(std::weak_ptr lifetime) { - if (frame_policy_3d) - static_cast(frame_policy_3d->consume_3d_events()); - if (frame_policy->consume_events()) refresh_schedule(); + if (consume_frame_policy_events()) refresh_schedule(); const auto observed_generation = tick_request_generation.load(std::memory_order_acquire); Plot_Render_Tick requested; @@ -749,9 +789,7 @@ void Plot::Private::consume_tick(std::weak_ptr lifetime) { auto tick = std::exchange(deferred_tick, {}); if (tick) clock_tick(*tick); } - if (frame_policy_3d) - static_cast(frame_policy_3d->consume_3d_events()); - if (frame_policy->consume_events()) refresh_schedule(); + if (consume_frame_policy_events()) refresh_schedule(); tick_task_scheduled.store(false, std::memory_order_release); if (tick_request_generation.load(std::memory_order_acquire) != observed_generation || @@ -762,7 +800,7 @@ void Plot::Private::consume_tick(std::weak_ptr lifetime) { void Plot::Private::clock_tick(const Plot_Render_Tick& tick) { if (terminal_failure.load(std::memory_order_acquire)) return; - const auto& pacing = frame_policy->read_state(); + const auto& pacing = pacing_state(); const bool accepted = pacing.render_enabled && (tick.source == Frame_Request_Source::immediate || (tick.source == Frame_Request_Source::periodic && @@ -770,10 +808,14 @@ void Plot::Private::clock_tick(const Plot_Render_Tick& tick) { (tick.source == Frame_Request_Source::maximum_rate && pacing.mode == Frame_Pacing_Mode::maximum_rate)); if (!accepted) { - frame_policy->record_policy_rejection(); + with_frame_policy([](auto& policy) { + policy.record_policy_rejection(); + }); return; } - frame_policy->record_request_accepted(tick.source); + with_frame_policy([&](auto& policy) { + policy.record_request_accepted(tick.source); + }); render_frame(tick); } @@ -848,7 +890,7 @@ nlohmann::json Plot::Private::trace_response( 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 = frame_policy->read_state(); + const auto& pacing = pacing_state(); if (!pacing.render_enabled || streams.consumers->empty()) return; auto admission_expected = Render_Admission_State::ready; @@ -899,7 +941,9 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { break; } if (!managed) { - frame_policy->record_frame_slot_backpressure(); + with_frame_policy([](auto& policy) { + policy.record_frame_slot_backpressure(); + }); keep_latest_tick(tick); /* * 三个槽都仍被外接消费者持有时,只保留 latest pending。这里绝不能 @@ -937,8 +981,7 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { const Frame_Identity identity{ sequence, tick.sequence == 0 ? sequence : tick.sequence}; const auto request_source = tick.source; - const auto policy_state = - frame_policy->read_state(); + 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, @@ -985,16 +1028,20 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { Size{static_cast(tick.width), static_cast(tick.height)}); const auto result = (*scene_2d)->render(&output); if (!result) { - frame_policy->record_scene_rejection(); + with_frame_policy([](auto& policy) { + policy.record_scene_rejection(); + }); rollback_unsubmitted(); restore_taskflow_trace_claim(); release_render_admission(lifetime); } else { taskflow_trace_claimed = false; managed->submitted_at = std::chrono::steady_clock::now(); - frame_policy->record_frame_submitted( - sequence, request_source, - managed->submitted_at - managed->tick_issued_at); + with_frame_policy([&](auto& policy) { + policy.record_frame_submitted( + sequence, request_source, + managed->submitted_at - managed->tick_issued_at); + }); } return; } @@ -1006,12 +1053,16 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { if (result == Render_Scene_3D::Render_Result::submitted) { taskflow_trace_claimed = false; managed->submitted_at = std::chrono::steady_clock::now(); - frame_policy->record_frame_submitted( - sequence, request_source, - managed->submitted_at - managed->tick_issued_at); + with_frame_policy([&](auto& policy) { + policy.record_frame_submitted( + sequence, request_source, + managed->submitted_at - managed->tick_issued_at); + }); return; } - frame_policy->record_scene_rejection(); + with_frame_policy([](auto& policy) { + policy.record_scene_rejection(); + }); rollback_unsubmitted(); restore_taskflow_trace_claim(); release_render_admission(lifetime); @@ -1046,7 +1097,7 @@ void Plot::Private::publish_completed_frame(Render_Frame* completed) { if (!managed) throw std::logic_error("Scene completion graph has no rendering Plot frame"); - const auto &pacing = frame_policy->read_state(); + const auto& pacing = pacing_state(); const auto identity = frame->identity(); Frame_Identity rendered_identity = identity; std::shared_ptr> pixel_storage; @@ -1254,11 +1305,13 @@ void Plot::Private::finalize_retired_frame(Render_Frame* frame) { if (!managed) throw std::logic_error("retired frame has no owned Plot slot"); - frame_policy->record_frame_completed( - frame->identity().sequence, - managed->submitted_at.time_since_epoch().count() == 0 - ? std::chrono::steady_clock::duration::zero() - : std::chrono::steady_clock::now() - managed->submitted_at); + with_frame_policy([&](auto& policy) { + policy.record_frame_completed( + frame->identity().sequence, + managed->submitted_at.time_since_epoch().count() == 0 + ? std::chrono::steady_clock::duration::zero() + : std::chrono::steady_clock::now() - managed->submitted_at); + }); const auto statistics_generation_value = statistics_generation.load(std::memory_order_acquire); @@ -1526,7 +1579,9 @@ nlohmann::json Plot::write_prop(std::string_view component, ensure_started(); if (component != "frame-analysis") return d->view->write_prop(component, key, value); - auto result = write_frame_policy_prop(*d->frame_policy, 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()); return result; @@ -1592,8 +1647,7 @@ nlohmann::json Plot::diagnostics() const { ? 1'000.0 / interval.trimmed_average : 0.0; } - const auto& pacing = - d->frame_policy->read_state(); + const auto& pacing = d->pacing_state(); const auto stream = d->stream_snapshot(); const auto admission = d->render_admission.load(std::memory_order_acquire); const auto admission_name = [&] { @@ -1748,7 +1802,7 @@ nlohmann::json Plot::post_publish_taskflow_trace() const { void Plot::reset_diagnostics() { std::visit([](auto& scene) { scene->reset_diagnostics(); }, d->scene); d->statistics_generation.fetch_add(1, std::memory_order_acq_rel); - d->frame_policy->reset_statistics(); + d->with_frame_policy([](auto& policy) { policy.reset_statistics(); }); if (d->frame_policy_3d) d->frame_policy_3d->reset_3d_statistics(); d->arm_tick_consumer(weak_from_this()); }