帧策略职责明确

This commit is contained in:
2026-08-29 22:04:25 +08:00
parent 20be348a6a
commit bb6bcec901
29 changed files with 1743 additions and 333 deletions
+14 -14
View File
@@ -27,13 +27,13 @@ public:
struct Data_Generator {
nlohmann::json schema;
std::function<nlohmann::json(const nlohmann::json &)> generate;
std::function<void(const nlohmann::json &, const Plot_Render_Tick &)> advance;
std::function<void(const nlohmann::json &, const Frame_Request &)> advance;
explicit operator bool() const noexcept {
return static_cast<bool>(generate);
}
};
Scene_View_Model(std::vector<std::unique_ptr<detail::Renderable_Descriptor>> value_descriptors,
std::function<void(const Plot_Render_Tick &, bool)> value_update,
std::function<void(const Frame_Request &, bool)> value_update,
Data_Generator value_data_generator,
Owned_Objects... owned_objects) : descriptors(std::move(value_descriptors)),
update_scene(std::move(value_update)),
@@ -85,14 +85,14 @@ public:
std::memory_order_release);
return result;
}
void update(const Plot_Render_Tick& request) override {
void update(const Frame_Request& request) override {
const auto input = generated_data.load(std::memory_order_acquire);
if (input && data_generator.advance) data_generator.advance(*input, request);
update_scene(request, !input);
}
private:
std::vector<std::unique_ptr<detail::Renderable_Descriptor>> descriptors;
std::function<void(const Plot_Render_Tick &, bool)> update_scene;
std::function<void(const Frame_Request &, bool)> update_scene;
Data_Generator data_generator;
std::atomic<std::shared_ptr<const nlohmann::json>> generated_data{}; /* 成功生成后发布不可变参数;渲染任务按同一配置持续产生压力数据。 */
std::tuple<Owned_Objects...> objects;
@@ -420,7 +420,7 @@ template <typename... Fields, typename Object, typename... Owned_Objects>
std::unique_ptr<Plot::Scene_View> make_scene_view(
Object & object,
Scene_2D & scene,
std::function < void(const Plot_Render_Tick &, bool) > update,
std::function < void(const Frame_Request &, bool) > update,
Owned_Objects &&... owned_objects) {
using Definition = typename Object::Attached_Object;
using Tag = typename Definition::Base_Tag;
@@ -456,7 +456,7 @@ std::unique_ptr<Plot::Scene_View> make_scene_view(
return generate_2d_data(object, input);
},
[&object](const nlohmann::json& input,
const Plot_Render_Tick& tick) {
const Frame_Request& tick) {
const auto interval = generator_count(
input, "update_every_n_frames", 100'000);
if (tick.sequence % interval != 0) return;
@@ -555,7 +555,7 @@ std::shared_ptr<Plot> make_axes_plot() {
auto update = [scene = not_null{scene.get()},
frequency = not_null{frequency.get()},
numeric = not_null{numeric.get()},
time = not_null{time.get()}](const Plot_Render_Tick& event, bool) {
time = not_null{time.get()}](const Frame_Request& event, bool) {
resize_axes(scene, {static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, numeric, time);
constexpr double day_milliseconds = 86'400'000.0;
time->append_time(Time_Of_Day{
@@ -630,7 +630,7 @@ std::shared_ptr<Plot> make_spectrum_plot() {
auto update = [scene = not_null{scene.get()},
raw = not_null{spectrum.get()},
frequency = not_null{frequency.get()},
vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool demo_data) {
vertical = not_null{vertical.get()}](const Frame_Request& event, bool demo_data) {
resize_axes(scene, {static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
if (!demo_data) return;
std::array < double, 256 > samples{};
@@ -695,7 +695,7 @@ std::shared_ptr<Plot> make_frequency_trace_plot() {
auto update = [scene = not_null{scene.get()},
raw = not_null{trace.get()},
time = not_null{time.get()},
vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool demo_data) {
vertical = not_null{vertical.get()}](const Frame_Request& event, bool demo_data) {
resize_axes(scene, {static_cast<int>(event.width), static_cast<int>(event.height)}, time, vertical);
if (!demo_data) return;
constexpr double day_milliseconds = 86'400'000.0;
@@ -739,7 +739,7 @@ std::shared_ptr<Plot> make_sweep_spectrum_plot() {
auto update = [scene = not_null{scene.get()},
raw = not_null{sweep.get()},
frequency = not_null{frequency.get()},
vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool demo_data) {
vertical = not_null{vertical.get()}](const Frame_Request& event, bool demo_data) {
resize_axes(scene, {static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
if (!demo_data) return;
const auto& state = raw->template read_prop<Sweep_Spectrum::Base_Tag>();
@@ -793,7 +793,7 @@ std::shared_ptr<Plot> make_afterglow_plot() {
auto update = [scene = not_null{scene.get()},
raw = not_null{afterglow.get()},
frequency = not_null{frequency.get()},
vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool demo_data) {
vertical = not_null{vertical.get()}](const Frame_Request& event, bool demo_data) {
resize_axes(scene, {static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
if (!demo_data) return;
std::array < double, 192 > values{};
@@ -841,7 +841,7 @@ std::shared_ptr<Plot> make_waterfall_plot() {
auto update = [scene = not_null{scene.get()},
raw = not_null{waterfall.get()},
frequency = not_null{frequency.get()},
time = not_null{time.get()}](const Plot_Render_Tick& event, bool demo_data) {
time = not_null{time.get()}](const Frame_Request& event, bool demo_data) {
resize_axes(scene, {static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, time);
if (!demo_data) return;
constexpr double day_milliseconds = 86'400'000.0;
@@ -900,7 +900,7 @@ std::shared_ptr<Plot> make_constellation_plot() {
auto update = [scene = not_null{scene.get()},
raw = not_null{constellation.get()},
horizontal = not_null{horizontal.get()},
vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool demo_data) {
vertical = not_null{vertical.get()}](const Frame_Request& event, bool demo_data) {
resize_axes(scene, {static_cast<int>(event.width), static_cast<int>(event.height)}, horizontal, vertical);
if (!demo_data) return;
const auto& state = raw->template read_prop<Constellation_Diagram::Base_Tag>();
@@ -952,7 +952,7 @@ std::shared_ptr<Plot> make_selection_overlay_plot() {
.build();
auto update = [scene = not_null{scene.get()},
horizontal = not_null{horizontal.get()},
vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool) {
vertical = not_null{vertical.get()}](const Frame_Request& event, bool) {
resize_axes(scene, {static_cast<int>(event.width), static_cast<int>(event.height)}, horizontal, vertical);
};
auto view = make_scene_view<
+4 -4
View File
@@ -233,7 +233,7 @@ struct Random_Data_Generator {
}
template <typename Visual_Object>
void update(Visual_Object& visual, Axes_3D&, Marker_Visual*,
const Plot_Render_Tick& request) {
const Frame_Request& request) {
using Definition = typename Visual_Object::Attached_Object;
using Prop = typename Definition::Prop;
const auto sequence = request.sequence != 0
@@ -436,11 +436,11 @@ public:
}
}
}
void update(const Plot_Render_Tick& request) override {
void update(const Frame_Request& request) override {
if constexpr (requires(Data_Generator& generator, Visual_Object& visual,
Axes_Object& axes,
Marker_Object* markers,
const Plot_Render_Tick& frame) {
const Frame_Request& frame) {
generator.update(visual, axes, markers, frame);
}) {
data_generator_.update(
@@ -757,7 +757,7 @@ struct Spectrogram_Data_Generator {
}
void update(Mesh_Visual& visual, Axes_3D&,
Marker_Visual* markers,
const Plot_Render_Tick& request) {
const Frame_Request& request) {
if (!parameters.animation_enabled ||
request.sequence % parameters.update_every_n_frames != 0)
return;
+132 -140
View File
@@ -211,6 +211,39 @@ nlohmann::json frame_policy_state_json(const Frame_Policy::State& state) {
{"active_frame_count", state.active_frame_count}
}
},
{
"lifecycle", {
{"pending_request", state.pending_request.has_value()},
{"in_flight_window", 1},
{"in_flight_frame_sequence", state.in_flight_frame_sequence},
{"publication_feedback_expected",
state.publication_feedback_expected},
{"publication_feedback_received",
state.publication_feedback_received},
{"publication_feedback_remaining",
state.publication_feedback_expected >
state.publication_feedback_received
? state.publication_feedback_expected -
state.publication_feedback_received
: 0},
{"latest_publication_dispatch_ms",
static_cast<double>(
state.latest_publication_dispatch_latency_ns) /
1'000'000.0},
{"maximum_publication_dispatch_ms",
static_cast<double>(
state.maximum_publication_dispatch_latency_ns) /
1'000'000.0},
{"published_frame_count", state.published_frame_count},
{"publication_failed_count", state.publication_failed_count},
{"latest_publication_ms",
static_cast<double>(state.latest_publication_latency_ns) /
1'000'000.0},
{"maximum_publication_ms",
static_cast<double>(state.maximum_publication_latency_ns) /
1'000'000.0}
}
},
{
"throughput", {
{"request_rate_fps", rate(state.request_count)},
@@ -613,11 +646,6 @@ struct Plot::Private {
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::steady_clock::time_point tick_issued_at{}; /* 本逻辑帧请求进入 Plot 的时刻。 */
@@ -647,9 +675,12 @@ struct Plot::Private {
std::atomic<std::shared_ptr<const Consumer_Map>> consumers{
std::make_shared<const Consumer_Map>()
}; /* 低频订阅修改发布不可变版本。 */
struct Publication_Dispatch {
std::size_t asynchronous_feedback_count{};
bool succeeded{};
};
std::atomic_uint64_t next_stream_id{1};
std::atomic<std::shared_ptr<const std::string>> terminal_failure{}; /* 首次 Plot Unknown Failure 的唯一终止状态。 */
std::uint64_t next_frame_sequence{1};
std::unique_ptr<Frame_Policy> frame_policy_2d{};
std::unique_ptr<Frame_Policy_3D> frame_policy_3d{};
Frame_Scheduler::Timer frame_timer{}; /* 每 Plot/Scene 只有轻量时间轮节点,不持有线程。 */
@@ -658,11 +689,8 @@ struct Plot::Private {
std::atomic<Managed_Frame*> latest_statistics_frame{}; /* 只定位权威帧槽,不保存统计副本。 */
std::atomic<Managed_Frame*> retired_frames{}; /* 完成回调返回、唯一帧策略写者消费的物理帧。 */
Scene scene; /* 析构顺序保证 Scene 先停止,再释放物理帧。 */
moodycamel::ConcurrentQueue<Plot_Render_Tick> tick_requests{}; /* 多生产者提交、唯一短任务消费的帧请求流。 */
std::optional<Plot_Render_Tick> 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_State> 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{}; /* 尚待标记的实际渲染帧数。 */
static constexpr std::size_t maximum_taskflow_trace_frames{120};
@@ -717,17 +745,16 @@ struct Plot::Private {
}
[[nodiscard]] nlohmann::json schema() const;
[[nodiscard]] Stream_Snapshot stream_snapshot() const;
void publish(std::shared_ptr<const Plot_Stream_Frame> frame) noexcept;
void submit_tick_request(Plot_Render_Tick tick);
void keep_latest_tick(const Plot_Render_Tick& tick);
[[nodiscard]] Publication_Dispatch publish(
std::shared_ptr<const Plot_Stream_Frame> frame) noexcept;
void submit_frame_request(Frame_Request request);
void arm_tick_consumer(std::weak_ptr<Plot> lifetime);
void release_render_admission(std::weak_ptr<Plot> lifetime);
void request_next_maximum_rate_frame();
void retain_frame_policy_lifetime();
void release_frame_policy_lifetime_if_idle();
void consume_tick(std::weak_ptr<Plot> lifetime);
void refresh_schedule();
void clock_tick(const Plot_Render_Tick& tick);
void render_frame(Plot_Render_Tick tick);
void render_frame(Frame_Production production);
void publish_completed_frame(not_null<Render_Frame*> completed);
void consume_completed_frame(not_null<Render_Frame*> frame);
void retire_completed_frame(not_null<Render_Frame*> frame);
@@ -766,7 +793,7 @@ void Plot::Private::fail(std::exception_ptr failure) noexcept {
}.dump(),
{}
});
publish(std::move(output));
static_cast<void>(publish(std::move(output)));
}
catch (...) {}
}
@@ -802,22 +829,32 @@ 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(
Plot::Private::Publication_Dispatch Plot::Private::publish(
std::shared_ptr<const Plot_Stream_Frame> frame) noexcept {
if (!frame) return;
Publication_Dispatch dispatch;
if (!frame) return dispatch;
try {
const auto snapshot = stream_snapshot();
std::vector<Stream_Id> failed_consumers;
for (const auto& [id, consumer] : *snapshot.consumers) {
if (!consumer.handler) continue;
try {
consumer.handler(frame);
switch (consumer.handler(frame)) {
case Plot_Frame_Publication::ignored:
break;
case Plot_Frame_Publication::completed:
dispatch.succeeded = true;
break;
case Plot_Frame_Publication::asynchronous:
++dispatch.asynchronous_feedback_count;
break;
}
}
catch (...) {
failed_consumers.push_back(id);
}
}
if (failed_consumers.empty()) return;
if (failed_consumers.empty()) return dispatch;
auto current = consumers.load(std::memory_order_acquire);
for (;;) {
auto next = std::make_shared<Consumer_Map>(*current);
@@ -830,6 +867,7 @@ void Plot::Private::publish(
}
}
catch (...) {}
return dispatch;
}
void Plot::Private::refresh_schedule() {
if (!frame_timer.valid()) return;
@@ -846,50 +884,24 @@ void Plot::Private::refresh_schedule() {
frame_timer.cancel();
if (pacing.mode != Frame_Pacing_Mode::maximum_rate) return;
const auto now = std::chrono::steady_clock::now();
submit_tick_request(Plot_Render_Tick{
submit_frame_request(Frame_Request{
.issued_at = now,
.time_milliseconds = std::chrono::duration<double, std::milli>(
now - clock_origin).count(),
.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{};
void Plot::Private::submit_frame_request(Frame_Request request) {
with_frame_policy([&](auto& policy) {
policy.record_request(source, issued_at);
policy.submit_request(std::move(request));
});
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) {
case Frame_Request_Source::unspecified: return 0;
case Frame_Request_Source::periodic: return 0;
case Frame_Request_Source::maximum_rate: return 1;
case Frame_Request_Source::immediate: return 2;
}
return 0;
};
if (deferred_tick) {
const auto current_priority = priority(deferred_tick->source);
const auto next_priority = priority(tick.source);
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))
return;
}
deferred_tick = tick;
arm_tick_consumer(lifetime);
}
void Plot::Private::arm_tick_consumer(std::weak_ptr<Plot> 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] {
aethera::schedule_task("frame.policy.consume", [lifetime] {
const auto plot = lifetime.lock();
if (!plot) return;
try {
@@ -900,18 +912,13 @@ void Plot::Private::arm_tick_consumer(std::weak_ptr<Plot> lifetime) {
}
});
}
void Plot::Private::release_render_admission(std::weak_ptr<Plot> 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))
return;
void Plot::Private::request_next_maximum_rate_frame() {
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()) {
const auto now = std::chrono::steady_clock::now();
submit_tick_request(Plot_Render_Tick{
submit_frame_request(Frame_Request{
.issued_at = now,
.time_milliseconds = std::chrono::duration<double, std::milli>(
now - clock_origin).count(),
@@ -919,7 +926,6 @@ void Plot::Private::release_render_admission(std::weak_ptr<Plot> lifetime) {
});
}
}
arm_tick_consumer(std::move(lifetime));
}
void Plot::Private::retain_frame_policy_lifetime() {
if (frame_policy_lifetime.load(std::memory_order_acquire)) return;
@@ -944,43 +950,18 @@ void Plot::Private::consume_tick(std::weak_ptr<Plot> lifetime) {
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);
if (render_admission.load(std::memory_order_acquire) ==
Render_Admission_State::ready) {
auto tick = std::exchange(deferred_tick, {});
if (tick) clock_tick(*tick);
}
auto production = with_frame_policy([](auto& policy) {
return policy.begin_production();
});
if (production) render_frame(std::move(*production));
consume_retired_frames();
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 ||
retired_frames.load(std::memory_order_acquire) ||
(render_admission.load(std::memory_order_acquire) ==
Render_Admission_State::ready && deferred_tick))
retired_frames.load(std::memory_order_acquire))
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));
if (!accepted) {
with_frame_policy([](auto& policy) {
policy.record_policy_rejection();
});
return;
}
with_frame_policy([&](auto& policy) {
policy.record_request_accepted(tick.source);
});
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) {
@@ -1035,22 +1016,24 @@ nlohmann::json Plot::Private::trace_response(
{"frames", std::move(frames)}
};
}
void Plot::Private::render_frame(Plot_Render_Tick tick) {
if (terminal_failure.load(std::memory_order_acquire)) return;
void Plot::Private::render_frame(Frame_Production production) {
const auto sequence = production.sequence;
auto tick = std::move(production.request);
if (terminal_failure.load(std::memory_order_acquire)) {
with_frame_policy([&](auto& policy) {
policy.reject_production(sequence);
});
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;
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)) {
keep_latest_tick(tick);
if (streams.consumers->empty() && !direct_diagnostics_frame) {
with_frame_policy([&](auto& policy) {
policy.reject_production(sequence);
});
return;
}
std::size_t slot_index{};
@@ -1092,19 +1075,15 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
break;
}
if (!managed) {
with_frame_policy([](auto& policy) {
policy.record_frame_slot_backpressure();
with_frame_policy([&](auto& policy) {
policy.defer_production(std::move(production));
});
keep_latest_tick(tick);
/*
* latest pending
* arm tick consumer
* consume -> no slot -> consume Taskflow
* retire_completed_frame consuming available
*/
render_admission.store(
Render_Admission_State::frame_slots_exhausted,
std::memory_order_release);
return;
}
retain_frame_policy_lifetime();
@@ -1135,7 +1114,6 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
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
};
@@ -1201,12 +1179,12 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
}
});
if (!result) {
with_frame_policy([](auto& policy) {
policy.record_scene_rejection();
with_frame_policy([&](auto& policy) {
policy.reject_production(sequence);
});
rollback_unsubmitted();
restore_taskflow_trace_claim();
release_render_admission(lifetime);
request_next_maximum_rate_frame();
}
else {
taskflow_trace_claimed = false;
@@ -1230,7 +1208,7 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
.submitted = [weak](not_null<Frame_3D*>, bool) {
if (auto owner = weak.lock()) {
try {
owner->d->release_render_admission(weak);
owner->d->request_next_maximum_rate_frame();
}
catch (...) {
owner->d->fail(std::current_exception());
@@ -1260,18 +1238,22 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
});
return;
}
with_frame_policy([](auto& policy) {
policy.record_scene_rejection();
with_frame_policy([&](auto& policy) {
policy.reject_production(sequence);
});
rollback_unsubmitted();
restore_taskflow_trace_claim();
release_render_admission(lifetime);
request_next_maximum_rate_frame();
if (result == Render_Scene_3D::Render_Result::backend_unavailable) throw std::runtime_error("3D render backend became unavailable before submission");
}
catch (...) {
rollback_unsubmitted();
restore_taskflow_trace_claim();
release_render_admission(lifetime);
const auto active_sequence = pacing_state().in_flight_frame_sequence;
if (active_sequence == sequence)
with_frame_policy([&](auto& policy) {
policy.reject_production(sequence);
});
throw;
}
}
@@ -1330,11 +1312,21 @@ void Plot::Private::publish_completed_frame(
const auto published = std::make_shared<const Plot_Stream_Frame>(
Plot_Stream_Frame{{}, std::move(pixels)});
const auto publish_started = std::chrono::steady_clock::now();
publish(std::move(published));
const auto dispatch = publish(std::move(published));
const auto publish_completed = std::chrono::steady_clock::now();
with_frame_policy([&](auto& policy) {
policy.submit_publication_dispatch({
.started_at = publish_started,
.completed_at = publish_completed,
.sequence = identity.sequence,
.asynchronous_feedback_count =
dispatch.asynchronous_feedback_count,
.succeeded = dispatch.succeeded});
});
frame->record(Frame_Trace_Measurement::plot_publish_ns,
static_cast<std::uint64_t>(std::max<std::int64_t>(
0, std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - publish_started)
publish_completed - publish_started)
.count())));
auto expected = Frame_State::rendering;
if (!managed->state.compare_exchange_strong(
@@ -1359,7 +1351,7 @@ void Plot::Private::consume_completed_frame(not_null<Render_Frame*> frame) {
/* Scene 完成即释放 Plot admission。媒体采样属于 Gallery 自己的独立时钟和
* DAG Plot post-publish 线 */
if (std::holds_alternative<std::unique_ptr<Frame_2D>>(managed->frame))
release_render_admission(lifetime);
request_next_maximum_rate_frame();
}
void Plot::Private::consume_retired_frames() {
for (;;) {
@@ -1482,11 +1474,6 @@ void Plot::Private::finalize_retired_frame(not_null<Render_Frame*> frame) {
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<void>(render_admission.compare_exchange_strong(
admission, Render_Admission_State::ready,
std::memory_order_acq_rel, std::memory_order_acquire));
arm_tick_consumer(lifetime);
release_frame_policy_lifetime_if_idle();
if (captured_trace) {
@@ -1515,7 +1502,7 @@ void Plot::ensure_started() {
d->frame_timer = Frame_Scheduler::instance().make_timer(
[weak](Frame_Scheduler::Tick tick) {
if (const auto owner = weak.lock()) {
owner->schedule_render(Plot_Render_Tick{
owner->schedule_render(Frame_Request{
.issued_at = tick.issued_at,
.sequence = tick.sequence,
.time_milliseconds = tick.time_milliseconds,
@@ -1592,22 +1579,31 @@ void Plot::configure_stream(Stream_Id stream, std::uint32_t width,
return;
}
}
void Plot::schedule_render(Plot_Render_Tick tick) {
if (!std::isfinite(tick.time_milliseconds) ||
tick.time_milliseconds < 0.0)
void Plot::schedule_render(Frame_Request request) {
if (!std::isfinite(request.time_milliseconds) ||
request.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 (request.width == 0 || request.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->submit_frame_request(std::move(request));
}
void Plot::submit_publication_feedback(
Frame_Publication_Feedback feedback) {
ensure_started();
if (d->terminal_failure.load(std::memory_order_acquire)) return;
d->with_frame_policy([&](auto& policy) {
policy.submit_publication_feedback(std::move(feedback));
});
d->consumer_work_generation.fetch_add(1, std::memory_order_release);
d->arm_tick_consumer(weak_from_this());
}
void Plot::render_once() {
ensure_started();
const auto now = std::chrono::steady_clock::now();
const auto elapsed = now - d->clock_origin;
schedule_render(Plot_Render_Tick{
schedule_render(Frame_Request{
.issued_at = now,
.time_milliseconds =
std::chrono::duration<double, std::milli>(elapsed).count(),
@@ -1728,15 +1724,11 @@ nlohmann::json Plot::diagnostics() const {
}
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 = [&] {
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";
}
return "unknown";
}();
const auto lifecycle_name = pacing.in_flight_frame_sequence != 0
? "publication_in_flight"
: pacing.pending_request
? "request_pending"
: "ready";
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));
@@ -1783,7 +1775,7 @@ nlohmann::json Plot::diagnostics() const {
}
},
{"frame_policy", frame_policy_state_json(pacing)},
{"render_admission", admission_name},
{"frame_lifecycle", lifecycle_name},
{"frame_statistics", std::move(frame_statistics)},
{"input_statistics", std::move(input_statistics)}
};
+10 -11
View File
@@ -27,14 +27,6 @@ struct Plot_Input_Event {
std::uint32_t native_key{}; /* 浏览器原生按键码。 */
bool auto_repeat{}; /* 是否为系统重复按键。 */
};
struct Plot_Render_Tick {
std::chrono::steady_clock::time_point issued_at{}; /* 页面帧时钟发布本 tick 的单调时刻;手动帧在提交时填写。 */
std::uint64_t sequence{}; /* Kernel 全局 Frame_Scheduler 时间轴上的关联序号。 */
double time_milliseconds{}; /* 页面级单调时间线,所有图共享同一个动画时刻。 */
std::uint32_t width{320}; /* 当前图在媒体图集中的固定像素宽度。 */
std::uint32_t height{192}; /* 当前图在媒体图集中的固定像素高度。 */
Frame_Request_Source source{Frame_Request_Source::immediate}; /* 本次请求来自周期时钟、手动操作或最大吞吐自驱动。 */
};
enum struct Plot_Pixel_Layout : std::uint8_t {
bgra8,
rgba8
@@ -54,10 +46,16 @@ struct Plot_Stream_Frame {
std::string notification{}; /* 仅用于终止错误等低频控制通知;正常帧为空。 */
std::shared_ptr<const Plot_Pixel_Frame> pixels{}; /* 每帧完成进度及其可选图集像素。 */
};
enum struct Plot_Frame_Publication : std::uint8_t {
ignored, /* 订阅者不消费本像素帧,不参与发布反馈。 */
completed, /* 订阅回调返回时已同步完成业务消费。 */
asynchronous /* 订阅者已接管发布,并会执行帧策略反馈任务。 */
};
struct Plot final : public std::enable_shared_from_this<Plot> {
public:
using Stream_Id = std::uint64_t;
using Stream_Handler = std::function<void(std::shared_ptr<const Plot_Stream_Frame>)>;
using Stream_Handler = std::function<Plot_Frame_Publication(
std::shared_ptr<const Plot_Stream_Frame>)>;
using Json_Handler = std::function<void(nlohmann::json)>;
struct Scene_View {
public:
@@ -72,7 +70,7 @@ public:
const std::vector<std::string>& components) const = 0;
[[nodiscard]] virtual nlohmann::json data_generator_schema() const = 0;
[[nodiscard]] virtual nlohmann::json generate_data(const nlohmann::json& input) = 0;
virtual void update(const Plot_Render_Tick& tick) = 0;
virtual void update(const Frame_Request& request) = 0;
};
Plot(std::unique_ptr<render_2d::Render_Scene_2D> scene,
std::unique_ptr<Scene_View> view);
@@ -84,7 +82,8 @@ public:
[[nodiscard]] Stream_Id subscribe(Stream_Handler handler);
void unsubscribe(Stream_Id stream);
void configure_stream(Stream_Id stream, std::uint32_t width, std::uint32_t height);
void schedule_render(Plot_Render_Tick tick);
void schedule_render(Frame_Request request);
void submit_publication_feedback(Frame_Publication_Feedback feedback);
void render_once();
void submit_input(Plot_Input_Event event);
[[nodiscard]] nlohmann::json schema();