优化了 还是卡
This commit is contained in:
+217
-176
@@ -16,9 +16,7 @@
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
@@ -64,16 +62,15 @@ struct Frame_Pacing_Properties {
|
||||
|
||||
class Frame_Policy final {
|
||||
public:
|
||||
[[nodiscard]] Frame_Pacing_Properties snapshot() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return pacing;
|
||||
[[nodiscard]] Frame_Pacing_Properties read() const {
|
||||
return *pacing.load(std::memory_order_acquire);
|
||||
}
|
||||
[[nodiscard]] nlohmann::json schema() const;
|
||||
[[nodiscard]] nlohmann::json write_prop(std::string_view key,
|
||||
const nlohmann::json& value);
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
Frame_Pacing_Properties pacing{};
|
||||
std::atomic<std::shared_ptr<const Frame_Pacing_Properties>> pacing{
|
||||
std::make_shared<const Frame_Pacing_Properties>()};
|
||||
};
|
||||
|
||||
std::string_view pacing_mode_name(Frame_Pacing_Mode mode) {
|
||||
@@ -101,22 +98,22 @@ std::string_view pixel_format_name(render_3d::Pixel_Format format) {
|
||||
}
|
||||
|
||||
nlohmann::json Frame_Policy::schema() const {
|
||||
std::lock_guard lock(mutex);
|
||||
const auto current = read();
|
||||
return {
|
||||
{"id", "frame-analysis"}, {"label", "渲染与媒体流水线"}, {"kind", "analysis"},
|
||||
{"fields", nlohmann::json::array({
|
||||
{{"key", "render_enabled"}, {"label", "持续渲染与采样"}, {"editor", "boolean"},
|
||||
{"editable", true}, {"description", "控制共享帧时钟是否继续调用当前 Scene;画面隐藏不会修改此项。"},
|
||||
{"technical_description", "Authoritative server-side render and sampling switch."},
|
||||
{"value", pacing.render_enabled}},
|
||||
{"value", current.render_enabled}},
|
||||
{{"key", "video_enabled"}, {"label", "图集视频传输"}, {"editor", "boolean"},
|
||||
{"editable", true}, {"description", "控制完成帧是否进入页面级 RGBA 图集;默认开启,用于完整链路压测。"},
|
||||
{"technical_description", "Authoritative tile publication switch for the shared gallery video."},
|
||||
{"value", pacing.video_enabled}},
|
||||
{"value", current.video_enabled}},
|
||||
{{"key", "pacing_mode"}, {"label", "服务端帧策略"}, {"editor", "select"},
|
||||
{"editable", true}, {"description", "只控制 Scene::render 的调用节奏;完成回调只负责归还帧并发布结果。"},
|
||||
{"technical_description", "Render policy driven by the common 100 Hz gallery clock."},
|
||||
{"value", pacing_mode_name(pacing.mode)},
|
||||
{"value", pacing_mode_name(current.mode)},
|
||||
{"options", nlohmann::json::array({
|
||||
{{"value", "manual"}, {"label", "手动渲染"}},
|
||||
{{"value", "fixed_rate"}, {"label", "固定频率"}},
|
||||
@@ -126,19 +123,33 @@ nlohmann::json Frame_Policy::schema() const {
|
||||
{"editable", true}, {"minimum", 0.1}, {"maximum", 100.0}, {"step", 0.1},
|
||||
{"description", "固定频率模式下从页面级 100 Hz 时钟采样当前 Scene 的次数。"},
|
||||
{"technical_description", "Per-plot rate selected from the common gallery render timeline."},
|
||||
{"value", pacing.fixed_rate_fps}}
|
||||
{"value", current.fixed_rate_fps}}
|
||||
})}
|
||||
};
|
||||
}
|
||||
|
||||
nlohmann::json Frame_Policy::write_prop(std::string_view key,
|
||||
const nlohmann::json& value) {
|
||||
std::lock_guard lock(mutex);
|
||||
const auto update = [this](auto&& edit) {
|
||||
auto current = pacing.load(std::memory_order_acquire);
|
||||
for (;;) {
|
||||
auto next = std::make_shared<Frame_Pacing_Properties>(*current);
|
||||
edit(*next);
|
||||
std::shared_ptr<const Frame_Pacing_Properties> desired = next;
|
||||
if (pacing.compare_exchange_weak(
|
||||
current, desired, std::memory_order_release,
|
||||
std::memory_order_acquire))
|
||||
return desired;
|
||||
}
|
||||
};
|
||||
if (key == "render_enabled" || key == "video_enabled") {
|
||||
if (!value.is_boolean())
|
||||
return {{"success", false}, {"error", "frame policy switch requires a boolean"}};
|
||||
bool& target = key == "render_enabled" ? pacing.render_enabled : pacing.video_enabled;
|
||||
target = value.get<bool>();
|
||||
const bool target = value.get<bool>();
|
||||
update([&](Frame_Pacing_Properties& next) {
|
||||
(key == "render_enabled" ? next.render_enabled : next.video_enabled) =
|
||||
target;
|
||||
});
|
||||
return {{"success", true}, {"component", "frame-analysis"}, {"key", key},
|
||||
{"value", target}};
|
||||
}
|
||||
@@ -148,9 +159,9 @@ nlohmann::json Frame_Policy::write_prop(std::string_view key,
|
||||
const auto parsed = parse_pacing_mode(value.get_ref<const std::string&>());
|
||||
if (!parsed)
|
||||
return {{"success", false}, {"error", "unknown frame pacing mode"}};
|
||||
pacing.mode = *parsed;
|
||||
update([&](Frame_Pacing_Properties& next) { next.mode = *parsed; });
|
||||
return {{"success", true}, {"component", "frame-analysis"}, {"key", key},
|
||||
{"value", pacing_mode_name(pacing.mode)}};
|
||||
{"value", pacing_mode_name(*parsed)}};
|
||||
}
|
||||
if (key == "fixed_rate_fps") {
|
||||
if (!value.is_number())
|
||||
@@ -158,9 +169,11 @@ nlohmann::json Frame_Policy::write_prop(std::string_view key,
|
||||
const double next = value.get<double>();
|
||||
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.fixed_rate_fps = next;
|
||||
update([&](Frame_Pacing_Properties& properties) {
|
||||
properties.fixed_rate_fps = next;
|
||||
});
|
||||
return {{"success", true}, {"component", "frame-analysis"}, {"key", key},
|
||||
{"value", pacing.fixed_rate_fps}};
|
||||
{"value", next}};
|
||||
}
|
||||
return {{"success", false}, {"error", "unknown frame runtime property"}};
|
||||
}
|
||||
@@ -332,7 +345,7 @@ struct Plot::Private {
|
||||
struct Managed_Frame {
|
||||
std::chrono::microseconds presentation_time{}; /* 共享页面时钟产生的媒体时间戳。 */
|
||||
Frame frame{}; /* 三缓冲物理槽拥有且反复承载逻辑帧。 */
|
||||
Frame_State state{Frame_State::available}; /* 本槽唯一生命周期状态。 */
|
||||
std::atomic<Frame_State> state{Frame_State::available}; /* 本槽唯一生命周期状态。 */
|
||||
};
|
||||
|
||||
struct Consumer {
|
||||
@@ -340,29 +353,28 @@ struct Plot::Private {
|
||||
std::uint32_t width{};
|
||||
std::uint32_t height{};
|
||||
};
|
||||
using Consumer_Map = std::unordered_map<Stream_Id, Consumer>;
|
||||
|
||||
struct Stream_Snapshot {
|
||||
std::vector<std::pair<Stream_Id, Consumer>> consumers;
|
||||
std::shared_ptr<const Consumer_Map> consumers;
|
||||
std::uint32_t width{};
|
||||
std::uint32_t height{};
|
||||
};
|
||||
|
||||
std::unique_ptr<Scene_View> view;
|
||||
std::once_flag start_once;
|
||||
mutable std::mutex consumers_mutex;
|
||||
std::unordered_map<Stream_Id, Consumer> consumers; /* 媒体图集和诊断连接的唯一订阅表。 */
|
||||
std::atomic<std::shared_ptr<const Consumer_Map>> consumers{
|
||||
std::make_shared<const Consumer_Map>()}; /* 低频订阅修改发布不可变版本。 */
|
||||
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};
|
||||
Frame_Policy frame_policy{};
|
||||
mutable std::mutex frame_mutex;
|
||||
static constexpr std::size_t scene_frame_capacity{3};
|
||||
std::array<Managed_Frame, scene_frame_capacity> frame_slots{}; /* Scene 借用的稳定三缓冲物理帧。 */
|
||||
std::optional<std::size_t> callback_retired_slot{}; /* 上一个回调返回后才可重新开始的槽。 */
|
||||
std::atomic_size_t callback_retired_slot{scene_frame_capacity};
|
||||
Scene scene; /* 析构顺序保证 Scene 先停止,再释放物理帧。 */
|
||||
mutable std::mutex tick_mutex;
|
||||
std::optional<Plot_Render_Tick> pending_tick{}; /* 时钟拥塞时只保留尚未处理的最新时间点。 */
|
||||
bool tick_task_scheduled{}; /* Taskflow 中是否已有唯一 tick 消费任务。 */
|
||||
std::atomic<std::shared_ptr<const Plot_Render_Tick>> pending_tick{};
|
||||
std::atomic_bool tick_task_scheduled{}; /* 唯一短任务准入;不占用 Worker 等待。 */
|
||||
double last_clock_render_time_ms{-std::numeric_limits<double>::infinity()};
|
||||
std::chrono::steady_clock::time_point clock_origin{std::chrono::steady_clock::now()};
|
||||
std::atomic_uint64_t received_tick_count{}; /* 页面时钟交付给本 Plot 的 tick 总数。 */
|
||||
@@ -373,9 +385,12 @@ struct Plot::Private {
|
||||
std::atomic_uint64_t scene_rejection_count{}; /* Scene 单帧准入拒绝的提交次数。 */
|
||||
std::atomic_uint64_t submitted_frame_count{}; /* 成功提交给 Scene 的帧总数。 */
|
||||
std::atomic_size_t taskflow_trace_remaining{}; /* 尚待标记的实际渲染帧数。 */
|
||||
mutable std::mutex taskflow_trace_mutex{}; /* 只保护低频请求结果的交换。 */
|
||||
std::size_t taskflow_trace_requested_count{}; /* 当前批次请求总帧数。 */
|
||||
std::vector<Taskflow_Frame_Trace> taskflow_traces{}; /* 已完成帧直接发布的 DAG 与 Observer 结果。 */
|
||||
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<std::atomic<std::shared_ptr<const Taskflow_Frame_Trace>>,
|
||||
maximum_taskflow_trace_frames> taskflow_trace_slots{};
|
||||
Task_Node completion_tail{}; /* Scene 完成图中当前最后一个业务阶段。 */
|
||||
std::vector<std::unique_ptr<Task_Graph>> completion_extensions{}; /* 生命周期覆盖 Scene 对子图的借用。 */
|
||||
|
||||
@@ -444,10 +459,9 @@ nlohmann::json Plot::Private::schema() const {
|
||||
|
||||
Plot::Private::Stream_Snapshot Plot::Private::stream_snapshot() const {
|
||||
Stream_Snapshot result;
|
||||
std::lock_guard lock(consumers_mutex);
|
||||
result.consumers.reserve(consumers.size());
|
||||
for (const auto& [id, consumer] : consumers) {
|
||||
result.consumers.emplace_back(id, consumer);
|
||||
result.consumers = consumers.load(std::memory_order_acquire);
|
||||
for (const auto& [id, consumer] : *result.consumers) {
|
||||
static_cast<void>(id);
|
||||
if (consumer.width == 0 || consumer.height == 0) continue;
|
||||
result.width = std::max(result.width, consumer.width);
|
||||
result.height = std::max(result.height, consumer.height);
|
||||
@@ -463,7 +477,7 @@ void Plot::Private::publish(
|
||||
try {
|
||||
const auto snapshot = stream_snapshot();
|
||||
std::vector<Stream_Id> failed_consumers;
|
||||
for (const auto& [id, consumer] : snapshot.consumers) {
|
||||
for (const auto& [id, consumer] : *snapshot.consumers) {
|
||||
if (!consumer.handler) continue;
|
||||
try {
|
||||
consumer.handler(frame);
|
||||
@@ -473,27 +487,27 @@ void Plot::Private::publish(
|
||||
}
|
||||
}
|
||||
if (failed_consumers.empty()) return;
|
||||
std::lock_guard lock(consumers_mutex);
|
||||
for (const auto id : failed_consumers) consumers.erase(id);
|
||||
auto current = consumers.load(std::memory_order_acquire);
|
||||
for (;;) {
|
||||
auto next = std::make_shared<Consumer_Map>(*current);
|
||||
for (const auto id : failed_consumers) next->erase(id);
|
||||
std::shared_ptr<const Consumer_Map> desired = next;
|
||||
if (consumers.compare_exchange_weak(
|
||||
current, desired, std::memory_order_release,
|
||||
std::memory_order_acquire))
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
void Plot::Private::consume_tick(std::weak_ptr<Plot> lifetime) {
|
||||
std::optional<Plot_Render_Tick> tick;
|
||||
{
|
||||
std::lock_guard lock(tick_mutex);
|
||||
tick = std::exchange(pending_tick, {});
|
||||
}
|
||||
const auto tick = pending_tick.exchange({}, std::memory_order_acq_rel);
|
||||
if (tick) clock_tick(*tick);
|
||||
|
||||
bool schedule_again{};
|
||||
{
|
||||
std::lock_guard lock(tick_mutex);
|
||||
schedule_again = pending_tick.has_value();
|
||||
if (!schedule_again) tick_task_scheduled = false;
|
||||
}
|
||||
if (schedule_again) {
|
||||
tick_task_scheduled.store(false, std::memory_order_release);
|
||||
if (pending_tick.load(std::memory_order_acquire) &&
|
||||
!tick_task_scheduled.exchange(true, std::memory_order_acq_rel)) {
|
||||
aethera::schedule_task("web.plot.tick.consume", [lifetime] {
|
||||
const auto plot = lifetime.lock();
|
||||
if (!plot) return;
|
||||
@@ -505,7 +519,7 @@ void Plot::Private::consume_tick(std::weak_ptr<Plot> 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.snapshot();
|
||||
const auto pacing = frame_policy.read();
|
||||
if (!pacing.render_enabled || pacing.mode == Frame_Pacing_Mode::manual) {
|
||||
policy_skip_count.fetch_add(1, std::memory_order_relaxed);
|
||||
return;
|
||||
@@ -539,46 +553,43 @@ bool Plot::Private::mark_taskflow_trace(Render_Frame& frame) {
|
||||
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.snapshot();
|
||||
if (!pacing.render_enabled || streams.consumers.empty()) return;
|
||||
const auto pacing = frame_policy.read();
|
||||
if (!pacing.render_enabled || streams.consumers->empty()) return;
|
||||
|
||||
std::size_t slot_index{};
|
||||
Managed_Frame* managed{};
|
||||
{
|
||||
std::lock_guard lock(frame_mutex);
|
||||
/*
|
||||
* Frame_State 是 Plot 数据准备生命周期的唯一权威来源。必须在修改
|
||||
* Scene/Visual 输入之前取得准入;Scene::render() 内部再拒绝已经太晚,
|
||||
* 因为上一帧的异步 prepare 可能正在读取同一份业务数据。
|
||||
*/
|
||||
if (std::ranges::any_of(frame_slots, [](const Managed_Frame& slot) {
|
||||
return slot.state == Frame_State::in_flight;
|
||||
})) {
|
||||
preparation_busy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
const auto available = std::ranges::find_if(
|
||||
frame_slots, [](const Managed_Frame& slot) {
|
||||
return slot.state == Frame_State::available;
|
||||
});
|
||||
if (available == frame_slots.end()) {
|
||||
frame_slot_busy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
slot_index = static_cast<std::size_t>(
|
||||
std::distance(frame_slots.begin(), available));
|
||||
managed = &*available;
|
||||
managed->state = Frame_State::in_flight;
|
||||
managed->presentation_time =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::duration<double, std::milli>(
|
||||
tick.time_milliseconds));
|
||||
/* Frame_State 是 Plot 数据准备生命周期的唯一权威来源。槽位通过 CAS
|
||||
* 准入,完成图和时钟任务不再共享一把外围锁。 */
|
||||
if (std::ranges::any_of(frame_slots, [](const Managed_Frame& slot) {
|
||||
return slot.state.load(std::memory_order_acquire) ==
|
||||
Frame_State::in_flight;
|
||||
})) {
|
||||
preparation_busy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
for (std::size_t index = 0; index < frame_slots.size(); ++index) {
|
||||
auto expected = Frame_State::available;
|
||||
if (!frame_slots[index].state.compare_exchange_strong(
|
||||
expected, Frame_State::in_flight,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire))
|
||||
continue;
|
||||
slot_index = index;
|
||||
managed = &frame_slots[index];
|
||||
break;
|
||||
}
|
||||
if (!managed) {
|
||||
frame_slot_busy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
managed->presentation_time =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::duration<double, std::milli>(tick.time_milliseconds));
|
||||
const auto rollback_unsubmitted = [this, slot_index] {
|
||||
std::lock_guard lock(frame_mutex);
|
||||
auto& slot = frame_slots[slot_index];
|
||||
if (slot.state == Frame_State::in_flight)
|
||||
slot.state = Frame_State::available;
|
||||
auto expected = Frame_State::in_flight;
|
||||
static_cast<void>(slot.state.compare_exchange_strong(
|
||||
expected, Frame_State::available, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire));
|
||||
};
|
||||
bool taskflow_trace_claimed{};
|
||||
const auto restore_taskflow_trace_claim = [this, &taskflow_trace_claimed] {
|
||||
@@ -662,36 +673,32 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
void Plot::Private::publish_completed_frame() {
|
||||
Render_Frame* frame{};
|
||||
Managed_Frame* managed{};
|
||||
{
|
||||
std::lock_guard lock(frame_mutex);
|
||||
/*
|
||||
* Scene 在用户回调返回之后才标记 callback_finished,因此当前回调
|
||||
* 不能立即重启自己的物理帧。下一个串行完成回调开始时,上一个
|
||||
* callback_retired 槽已经确定离开 Scene,可安全归还。三槽由此在
|
||||
* 不改变 Scene 回调契约的前提下形成稳定的循环生命周期。
|
||||
*/
|
||||
if (callback_retired_slot) {
|
||||
auto& retired = frame_slots[*callback_retired_slot];
|
||||
if (retired.state != Frame_State::callback_retired)
|
||||
throw std::logic_error("Plot retired frame state is inconsistent");
|
||||
retired.state = Frame_State::available;
|
||||
callback_retired_slot.reset();
|
||||
}
|
||||
for (std::size_t index = 0; index < frame_slots.size(); ++index) {
|
||||
if (frame_slots[index].state != Frame_State::in_flight) continue;
|
||||
if (managed)
|
||||
throw std::logic_error("Plot has multiple active Scene frames");
|
||||
frame = std::visit(
|
||||
[](const auto& value) -> Render_Frame* { return value.get(); },
|
||||
frame_slots[index].frame);
|
||||
managed = &frame_slots[index];
|
||||
}
|
||||
if (!managed || managed->state != Frame_State::in_flight)
|
||||
throw std::logic_error("Scene completion graph has no active Plot frame");
|
||||
/* Scene 在用户回调返回之后才标记 callback_finished。下一次串行完成图
|
||||
* 开始时,上一 callback_retired 槽才可归还。 */
|
||||
const auto retired_index = callback_retired_slot.exchange(
|
||||
scene_frame_capacity, std::memory_order_acq_rel);
|
||||
if (retired_index != scene_frame_capacity) {
|
||||
auto expected = Frame_State::callback_retired;
|
||||
if (!frame_slots[retired_index].state.compare_exchange_strong(
|
||||
expected, Frame_State::available, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
throw std::logic_error("Plot retired frame state is inconsistent");
|
||||
}
|
||||
for (std::size_t index = 0; index < frame_slots.size(); ++index) {
|
||||
if (frame_slots[index].state.load(std::memory_order_acquire) !=
|
||||
Frame_State::in_flight) continue;
|
||||
if (managed)
|
||||
throw std::logic_error("Plot has multiple active Scene frames");
|
||||
frame = std::visit(
|
||||
[](const auto& value) -> Render_Frame* { return value.get(); },
|
||||
frame_slots[index].frame);
|
||||
managed = &frame_slots[index];
|
||||
}
|
||||
if (!managed)
|
||||
throw std::logic_error("Scene completion graph has no active Plot frame");
|
||||
|
||||
try {
|
||||
const auto pacing = frame_policy.snapshot();
|
||||
const auto pacing = frame_policy.read();
|
||||
const auto identity = frame->identity();
|
||||
Frame_Identity rendered_identity = identity;
|
||||
std::shared_ptr<const std::vector<std::byte>> pixel_storage;
|
||||
@@ -748,30 +755,44 @@ void Plot::Private::retire_completed_frame(Render_Frame* frame) {
|
||||
if (!frame)
|
||||
throw std::invalid_argument("Plot received a null completed frame");
|
||||
std::size_t slot_index{scene_frame_capacity};
|
||||
{
|
||||
std::lock_guard lock(frame_mutex);
|
||||
for (std::size_t index = 0; index < frame_slots.size(); ++index) {
|
||||
auto* address = std::visit(
|
||||
[](const auto& value) -> Render_Frame* { return value.get(); },
|
||||
frame_slots[index].frame);
|
||||
if (address != frame) continue;
|
||||
if (frame_slots[index].state != Frame_State::in_flight)
|
||||
throw std::logic_error("completed Plot frame is not in flight");
|
||||
slot_index = index;
|
||||
break;
|
||||
}
|
||||
if (slot_index == scene_frame_capacity)
|
||||
throw std::logic_error(
|
||||
"frame callback has no externally owned active frame");
|
||||
if (callback_retired_slot)
|
||||
throw std::logic_error("Plot has more than one callback-retired frame");
|
||||
frame_slots[slot_index].state = Frame_State::callback_retired;
|
||||
callback_retired_slot = slot_index;
|
||||
for (std::size_t index = 0; index < frame_slots.size(); ++index) {
|
||||
auto* address = std::visit(
|
||||
[](const auto& value) -> Render_Frame* { return value.get(); },
|
||||
frame_slots[index].frame);
|
||||
if (address != frame) continue;
|
||||
auto expected = Frame_State::in_flight;
|
||||
if (!frame_slots[index].state.compare_exchange_strong(
|
||||
expected, Frame_State::callback_retired,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire))
|
||||
throw std::logic_error("completed Plot frame is not in flight");
|
||||
slot_index = index;
|
||||
break;
|
||||
}
|
||||
if (slot_index == scene_frame_capacity)
|
||||
throw std::logic_error(
|
||||
"frame callback has no externally owned active frame");
|
||||
auto no_retired = scene_frame_capacity;
|
||||
if (!callback_retired_slot.compare_exchange_strong(
|
||||
no_retired, slot_index, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
throw std::logic_error("Plot has more than one callback-retired frame");
|
||||
if (frame->taskflow_trace_requested()) {
|
||||
auto trace = frame->taskflow_trace();
|
||||
std::lock_guard lock(taskflow_trace_mutex);
|
||||
taskflow_traces.push_back(std::move(trace));
|
||||
auto trace = std::make_shared<const Taskflow_Frame_Trace>(
|
||||
frame->taskflow_trace());
|
||||
auto control = taskflow_trace_control.load(std::memory_order_acquire);
|
||||
for (;;) {
|
||||
const auto requested = static_cast<std::uint32_t>(control >> 32U);
|
||||
const auto captured = static_cast<std::uint32_t>(control);
|
||||
if (captured >= requested) break;
|
||||
taskflow_trace_slots[captured].store(trace,
|
||||
std::memory_order_release);
|
||||
const auto next = (static_cast<std::uint64_t>(requested) << 32U) |
|
||||
static_cast<std::uint64_t>(captured + 1U);
|
||||
if (taskflow_trace_control.compare_exchange_weak(
|
||||
control, next, std::memory_order_release,
|
||||
std::memory_order_acquire))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,14 +858,18 @@ Plot::Stream_Id Plot::subscribe(Stream_Handler 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);
|
||||
Stream_Handler notification;
|
||||
{
|
||||
std::lock_guard lock(d->consumers_mutex);
|
||||
d->consumers.emplace(id, Private::Consumer{std::move(handler)});
|
||||
if (d->terminal_failure.load(std::memory_order_acquire))
|
||||
notification = d->consumers.at(id).handler;
|
||||
const auto notification = handler;
|
||||
auto current = d->consumers.load(std::memory_order_acquire);
|
||||
for (;;) {
|
||||
auto next = std::make_shared<Private::Consumer_Map>(*current);
|
||||
next->emplace(id, Private::Consumer{handler});
|
||||
std::shared_ptr<const Private::Consumer_Map> desired = next;
|
||||
if (d->consumers.compare_exchange_weak(
|
||||
current, desired, std::memory_order_release,
|
||||
std::memory_order_acquire))
|
||||
break;
|
||||
}
|
||||
if (notification) {
|
||||
if (d->terminal_failure.load(std::memory_order_acquire)) {
|
||||
const auto failure = d->terminal_failure.load(std::memory_order_acquire);
|
||||
try {
|
||||
notification(std::make_shared<const Plot_Stream_Frame>(
|
||||
@@ -863,35 +888,44 @@ Plot::Stream_Id Plot::subscribe(Stream_Handler handler) {
|
||||
}
|
||||
|
||||
void Plot::unsubscribe(Stream_Id stream) {
|
||||
std::lock_guard lock(d->consumers_mutex);
|
||||
d->consumers.erase(stream);
|
||||
auto current = d->consumers.load(std::memory_order_acquire);
|
||||
while (current->contains(stream)) {
|
||||
auto next = std::make_shared<Private::Consumer_Map>(*current);
|
||||
next->erase(stream);
|
||||
std::shared_ptr<const Private::Consumer_Map> desired = next;
|
||||
if (d->consumers.compare_exchange_weak(
|
||||
current, desired, std::memory_order_release,
|
||||
std::memory_order_acquire))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Plot::configure_stream(Stream_Id stream, std::uint32_t width,
|
||||
std::uint32_t height) {
|
||||
std::lock_guard lock(d->consumers_mutex);
|
||||
const auto found = d->consumers.find(stream);
|
||||
if (found == d->consumers.end()) return;
|
||||
found->second.width = width;
|
||||
found->second.height = height;
|
||||
auto current = d->consumers.load(std::memory_order_acquire);
|
||||
for (;;) {
|
||||
const auto found = current->find(stream);
|
||||
if (found == current->end()) return;
|
||||
auto next = std::make_shared<Private::Consumer_Map>(*current);
|
||||
auto& consumer = next->at(stream);
|
||||
consumer.width = width;
|
||||
consumer.height = height;
|
||||
std::shared_ptr<const Private::Consumer_Map> desired = next;
|
||||
if (d->consumers.compare_exchange_weak(
|
||||
current, desired, std::memory_order_release,
|
||||
std::memory_order_acquire))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Plot::schedule_render(Plot_Render_Tick tick) {
|
||||
ensure_started();
|
||||
if (d->terminal_failure.load(std::memory_order_acquire)) return;
|
||||
d->received_tick_count.fetch_add(1, std::memory_order_relaxed);
|
||||
bool schedule{};
|
||||
{
|
||||
std::lock_guard lock(d->tick_mutex);
|
||||
if (d->pending_tick)
|
||||
d->coalesced_tick_count.fetch_add(1, std::memory_order_relaxed);
|
||||
d->pending_tick = tick;
|
||||
if (!d->tick_task_scheduled) {
|
||||
d->tick_task_scheduled = true;
|
||||
schedule = true;
|
||||
}
|
||||
}
|
||||
if (!schedule) return;
|
||||
const auto next = std::make_shared<const Plot_Render_Tick>(std::move(tick));
|
||||
if (d->pending_tick.exchange(next, std::memory_order_acq_rel))
|
||||
d->coalesced_tick_count.fetch_add(1, std::memory_order_relaxed);
|
||||
if (d->tick_task_scheduled.exchange(true, std::memory_order_acq_rel)) return;
|
||||
const auto weak = weak_from_this();
|
||||
aethera::schedule_task("web.plot.tick.consume", [weak] {
|
||||
const auto owner = weak.lock();
|
||||
@@ -995,7 +1029,7 @@ nlohmann::json Plot::diagnostics() const {
|
||||
}
|
||||
}, d->scene);
|
||||
|
||||
const auto pacing = d->frame_policy.snapshot();
|
||||
const auto pacing = d->frame_policy.read();
|
||||
const auto stream = d->stream_snapshot();
|
||||
nlohmann::json supported_formats = nlohmann::json::array();
|
||||
if (is_3d) {
|
||||
@@ -1068,7 +1102,6 @@ nlohmann::json Plot::diagnostics() const {
|
||||
{"callback_max_ms", milliseconds(gpu.callback_max_ns)},
|
||||
{"callback_failure_count", gpu.callback_failure_count},
|
||||
{"backpressure_count", gpu.backpressure_count},
|
||||
{"backpressure_wait_ms", milliseconds(gpu.backpressure_wait_ns)},
|
||||
{"fault_count", gpu.fault_count},
|
||||
{"abandoned_count", gpu.abandoned_count},
|
||||
{"stopping", gpu.stopping}};
|
||||
@@ -1079,29 +1112,37 @@ nlohmann::json Plot::diagnostics() const {
|
||||
}
|
||||
|
||||
void Plot::request_taskflow_trace(std::size_t frame_count) {
|
||||
if (frame_count == 0 || frame_count > 120)
|
||||
if (frame_count == 0 ||
|
||||
frame_count > Private::maximum_taskflow_trace_frames)
|
||||
throw std::invalid_argument("Taskflow trace frame_count must be between 1 and 120");
|
||||
ensure_started();
|
||||
{
|
||||
std::lock_guard lock(d->taskflow_trace_mutex);
|
||||
if (d->taskflow_trace_requested_count != d->taskflow_traces.size())
|
||||
auto control = d->taskflow_trace_control.load(std::memory_order_acquire);
|
||||
for (;;) {
|
||||
const auto requested = static_cast<std::uint32_t>(control >> 32U);
|
||||
const auto captured = static_cast<std::uint32_t>(control);
|
||||
if (requested != captured)
|
||||
throw std::logic_error("A Taskflow frame trace request is already active");
|
||||
d->taskflow_traces.clear();
|
||||
d->taskflow_traces.reserve(frame_count);
|
||||
d->taskflow_trace_requested_count = frame_count;
|
||||
const auto next = static_cast<std::uint64_t>(frame_count) << 32U;
|
||||
if (d->taskflow_trace_control.compare_exchange_weak(
|
||||
control, next, std::memory_order_release,
|
||||
std::memory_order_acquire))
|
||||
break;
|
||||
}
|
||||
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 {
|
||||
nlohmann::json frames = nlohmann::json::array();
|
||||
std::size_t requested{};
|
||||
{
|
||||
std::lock_guard lock(d->taskflow_trace_mutex);
|
||||
requested = d->taskflow_trace_requested_count;
|
||||
for (const auto& trace : d->taskflow_traces)
|
||||
frames.push_back(taskflow_trace_json(trace));
|
||||
}
|
||||
const auto control = d->taskflow_trace_control.load(
|
||||
std::memory_order_acquire);
|
||||
const auto requested = static_cast<std::uint32_t>(control >> 32U);
|
||||
const auto captured = static_cast<std::uint32_t>(control);
|
||||
for (std::uint32_t index = 0; index < captured; ++index)
|
||||
if (const auto trace = d->taskflow_trace_slots[index].load(
|
||||
std::memory_order_acquire))
|
||||
frames.push_back(taskflow_trace_json(*trace));
|
||||
const auto remaining = d->taskflow_trace_remaining.load(
|
||||
std::memory_order_acquire);
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user