#include "Gallery_Scene2D.h" #include "../Gallery_Controls.h" #include "../Gallery_Enum.h" #include "../Gallery_Observer_Adminive.h" #include "../Gallery_Session_Control_Adminive.h" #include "../common/Gallery_Scheduler_Adminive.h" #include "../Pixel_Frame.h" #include "../Web_Performance_Log.h" #include "render_2D/export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace renderive::web { namespace { template void update_axis_state(const renderive_Owner& axis, Update&& update) { if (const auto numeric = renderive_dynamic_owner_cast(axis)) { numeric->update([&](Axis_Properties& state) { std::forward(update)(static_cast(state)); }); } else if (const auto time = renderive_dynamic_owner_cast(axis)) { time->update([&](auto& state) { std::forward(update)(static_cast(state)); }); } } std::uint64_t milliseconds_to_ns(double milliseconds) { return milliseconds > 0.0 ? static_cast(std::llround(milliseconds * 1'000'000.0)) : 0; } std::uint64_t frequency_to_ns(double frequency_hz) { return frequency_hz > 0.0 ? static_cast(std::llround(1'000'000'000.0 / frequency_hz)) : 0; } int hex_digit(char value) { if (value >= '0' && value <= '9') return value - '0'; if (value >= 'a' && value <= 'f') return value - 'a' + 10; return value - 'A' + 10; } Color color_from_hex(std::string_view value, std::uint8_t alpha = 255) { const auto channel = [value](std::size_t offset) { return static_cast(hex_digit(value[offset]) * 16 + hex_digit(value[offset + 1])); }; return {channel(1), channel(3), channel(5), alpha}; } Time_Of_Day current_time_of_day() { constexpr std::int64_t day_ms = 24LL * 60LL * 60LL * 1000LL; const auto now = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); return {now % day_ms}; } double gaussian(double x, double center, double width) { const double normalized = (x - center) / width; return std::exp(-0.5 * normalized * normalized); } Event_Type event_type(Gallery_View_Input_Type type) { switch (type) { case Gallery_View_Input_Type::Show: return Event_Type::Show; case Gallery_View_Input_Type::Hide: return Event_Type::Hide; case Gallery_View_Input_Type::Leave: return Event_Type::Leave; } return Event_Type::Leave; } Event_Type event_type(Gallery_Pointer_Input_Type type) { switch (type) { case Gallery_Pointer_Input_Type::Move: return Event_Type::Pointer_Move; case Gallery_Pointer_Input_Type::Press: return Event_Type::Pointer_Press; case Gallery_Pointer_Input_Type::Release: return Event_Type::Pointer_Release; } return Event_Type::Pointer_Move; } Event_Type event_type(Gallery_Key_Input_Type type) { return type == Gallery_Key_Input_Type::Press ? Event_Type::Key_Press : Event_Type::Key_Release; } Mouse_Button mouse_button(Gallery_Mouse_Button button) { switch (button) { case Gallery_Mouse_Button::None: return Mouse_Button::None; case Gallery_Mouse_Button::Left: return Mouse_Button::Left; case Gallery_Mouse_Button::Right: return Mouse_Button::Right; case Gallery_Mouse_Button::Middle: return Mouse_Button::Middle; } return Mouse_Button::None; } Key key(Gallery_Key value) { switch (value) { case Gallery_Key::Escape: return Key::Escape; case Gallery_Key::Enter: return Key::Enter; case Gallery_Key::Space: return Key::Space; case Gallery_Key::Delete: return Key::Delete; case Gallery_Key::Backspace: return Key::Backspace; case Gallery_Key::Left: return Key::Left; case Gallery_Key::Right: return Key::Right; case Gallery_Key::Up: return Key::Up; case Gallery_Key::Down: return Key::Down; case Gallery_Key::Unknown: return Key::Unknown; } return Key::Unknown; } Keyboard_Modifier modifiers(std::uint8_t value) { return static_cast(value); } std::optional action_number(const Gallery_Action_Request& request) { if (!request.argument || !std::holds_alternative(*request.argument)) return std::nullopt; return std::get(*request.argument); } Plot_Frame_Mode plot_frame_mode(Gallery_Frame_Mode mode) { switch (mode) { case Gallery_Frame_Mode::Manual: return Plot_Frame_Mode::Manual; case Gallery_Frame_Mode::Low_Latency: return Plot_Frame_Mode::Low_Latency; case Gallery_Frame_Mode::Playback: return Plot_Frame_Mode::Playback; } throw std::invalid_argument("unknown gallery frame mode"); } } // namespace class Gallery_Scene final : public Gallery_Scene_Interface { public: Gallery_Scene(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode frame_mode, bool automatic_low_latency) : session_id_(session_id), case_id_(std::move(case_id)), frame_mode_(frame_mode), automatic_low_latency_(automatic_low_latency), plot_(Plot_Scene_Options{plot_frame_mode(frame_mode)}), performance_started_(std::chrono::steady_clock::now()) { plot_.init(); plot_.set_viewport_size({560, 320}); root_ = plot_.root_renderable(); root_->set_object_name("画布根节点"); { auto attach = plot_.attach_builder(); const auto make_group = [&](std::string name) { auto group = detail::make_renderable_group(true); group->set_object_name(std::move(name)); attach.attach(group); attach.add_display_parent(group, root_); attach.add_dependency_parent(group, root_); return group; }; axes_node_ = make_group("坐标轴层"); data_node_ = make_group("数据绘制层"); overlay_node_ = make_group("交互覆盖层"); axes_node_->set_cache_mode(Renderable_Cache_Mode::Local_Pixel); build_axes(attach); build_primary(attach); } attach_performance_overlay(plot_); apply_layout(); session_control_ = std::make_unique>( plot_, *primary_, feedback_policy_); register_controls(); set_performance_plot_name(plot_, case_id_ + "/" + gallery_enum_id(frame_mode_)); if (frame_mode_ == Gallery_Frame_Mode::Low_Latency) static_cast(plot_.set_max_render_fps(30.0)); plot_.activate_view(); update_model(); (void)plot_.render_frame(true); } ~Gallery_Scene() { plot_.deactivate_view(); } Gallery_Scene(const Gallery_Scene&) = delete; Gallery_Scene& operator=(const Gallery_Scene&) = delete; [[nodiscard]] const std::string& case_id() const noexcept { return case_id_; } [[nodiscard]] nlohmann::json controls() const { return { {"resources", controls_.resources()}, {"observers", controls_.observers(plot_.render_scene())}, {"render_plan", controls_.render_plan(plot_.render_scene())}, {"performance_capture", gallery_performance_capture_json(plot_.render_scene())} }; } [[nodiscard]] Gallery_Frame_Mode frame_mode() const noexcept { return frame_mode_; } void reset_monitoring() { performance_started_ = std::chrono::steady_clock::now(); last_performance_log_ = performance_started_; render_attempt_count_ = 0; successful_render_count_ = 0; last_render_ms_ = 0.0; render_duration_statistics_.clear(); render_history_.clear(); pixel_frame_count_ = 0; last_pixel_snapshot_ms_ = 0.0; last_pixel_encode_ms_ = 0.0; last_pixel_request_ms_ = 0.0; last_pixel_bytes_ = 0; pixel_encode_statistics_.clear(); pixel_request_statistics_.clear(); pixel_history_.clear(); client_performance_ = {}; consumer_pixel_interval_ns_ = 0; consumer_presentation_interval_ns_ = 0; apply_consumer_feedback(); } [[nodiscard]] bool can_render_automatically() const noexcept { return automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency && plot_.view_active(); } [[nodiscard]] std::uint64_t kernel_refresh_interval_ns() const { return plot_.refresh_feedback_snapshot().next_refresh_interval_ns; } void set_client_metrics(Gallery_Client_Performance metrics) noexcept { const auto finite = [](double value, double maximum = 100000.0) { return std::isfinite(value) ? std::clamp(value, 0.0, maximum) : 0.0; }; metrics.transport_fps = finite(metrics.transport_fps); metrics.presentation_fps = finite(metrics.presentation_fps); metrics.frame_round_trip_ms = finite(metrics.frame_round_trip_ms); metrics.frame_round_trip_average_ms = finite(metrics.frame_round_trip_average_ms); metrics.frame_round_trip_deviation_ms = finite(metrics.frame_round_trip_deviation_ms); metrics.frame_round_trip_p95_ms = finite(metrics.frame_round_trip_p95_ms); metrics.frame_round_trip_p99_ms = finite(metrics.frame_round_trip_p99_ms); metrics.display_interval_ms = finite(metrics.display_interval_ms); metrics.display_interval_average_ms = finite(metrics.display_interval_average_ms); metrics.display_interval_latest_ms = finite(metrics.display_interval_latest_ms); metrics.display_interval_p95_ms = finite(metrics.display_interval_p95_ms); metrics.display_interval_p99_ms = finite(metrics.display_interval_p99_ms); metrics.display_interval_deviation_ms = finite(metrics.display_interval_deviation_ms); metrics.last_pixel_receive_age_ms = finite(metrics.last_pixel_receive_age_ms); metrics.last_pixel_change_age_ms = finite(metrics.last_pixel_change_age_ms); client_performance_ = metrics; apply_consumer_feedback(); } [[nodiscard]] adminive::Update_Result apply_patch(std::string_view target, const nlohmann::json& patch) { auto result = controls_.apply(target, patch); if (!result.success) return result; if (target == "scene") { apply_consumer_feedback(); render_history_.clear(); } apply_layout(); update_model(); plot_.request_redraw(); return result; } void resize(int width, int height) override { const Size previous = plot_.viewport_size(); const Size current{width, height}; plot_.set_viewport_size(current); apply_layout(); Resize_Event event; event.old_size = previous; event.new_size = current; plot_.dispatch_event(event); } void dispatch(const Gallery_Input_Event& event) override { std::visit([this](const auto& value) { using T = std::decay_t; if constexpr (std::is_same_v) { Event input(event_type(value.type)); if (input.type == Event_Type::Show) plot_.activate_view(); else if (input.type == Event_Type::Hide) plot_.deactivate_view(); plot_.dispatch_event(input); } else if constexpr (std::is_same_v) { Pointer_Event input(event_type(value.type)); input.position = {value.x, value.y}; input.global_position = {value.global_x, value.global_y}; input.button = mouse_button(value.button); input.buttons = value.buttons; input.modifiers = modifiers(value.modifiers); plot_.dispatch_event(input); } else if constexpr (std::is_same_v) { Wheel_Event input; input.position = {value.x, value.y}; input.global_position = {value.global_x, value.global_y}; input.button = mouse_button(value.button); input.buttons = value.buttons; input.modifiers = modifiers(value.modifiers); input.angle_delta_x = value.angle_delta_x; input.angle_delta_y = value.angle_delta_y; input.pixel_delta_x = value.pixel_delta_x; input.pixel_delta_y = value.pixel_delta_y; plot_.dispatch_event(input); } else if constexpr (std::is_same_v) { Key_Event input(event_type(value.type)); input.key = key(value.key); input.native_key = value.native_key; input.modifiers = modifiers(value.modifiers); input.auto_repeat = value.auto_repeat; plot_.dispatch_event(input); } }, event); } bool render_latest_frame() { if (!plot_.view_active()) return false; update_model(); const auto started = std::chrono::steady_clock::now(); const bool rendered = plot_.render_frame(true); record_performance(started, rendered); if (rendered) rendered_since_last_pixel_ = true; return rendered; } [[nodiscard]] std::optional encode_latest_pixels() { if (!plot_.view_active()) return std::nullopt; if (!rendered_since_last_pixel_ && !can_render_automatically() && !render_latest_frame()) return std::nullopt; rendered_since_last_pixel_ = false; std::string pixels; const Color background = plot_.background_color(); const std::uint64_t sequence = plot_.frame_status().latest_sequence; plot_.with_frame([&pixels, background, sequence](Image_View image) { pixels = encode_pixel_frame(image, sequence, background); }); return pixels.empty() ? std::nullopt : std::optional(std::move(pixels)); } void record_pixel_response(std::chrono::steady_clock::time_point request_started, std::chrono::steady_clock::time_point encode_started, std::chrono::steady_clock::time_point encode_finished, std::size_t pixel_bytes) { record_pixel_performance(request_started, encode_started, encode_finished, pixel_bytes); } [[nodiscard]] std::string action(const Gallery_Action_Request& request, bool& recognized) { last_action_result_.clear(); recognized = true; if (request.id == "capture_next_frame") { const auto state = plot_.render_scene().capture_state(); if (state.enabled()) return "A performance capture session is already active"; const auto capture = plot_.render_scene().capture_next_frame(); if (!capture) { if (capture.error == Capture_Error::session_active) return "A performance capture session is already active"; throw std::logic_error("validated capture request rejected"); } last_action_result_ = "capture session=" + std::to_string(capture.session_id); return "Capture next render frame requested"; } if (request.id == "capture_frames") { const auto argument = action_number(request); if (!argument) return invalid_argument(recognized); const std::size_t count = static_cast( std::clamp(std::llround(*argument), 1LL, 1000LL)); const auto state = plot_.render_scene().capture_state(); if (state.enabled()) return "A performance capture session is already active"; const auto capture = plot_.render_scene().capture_frames(count); if (!capture) { if (capture.error == Capture_Error::session_active) return "A performance capture session is already active"; throw std::logic_error("validated capture request rejected"); } last_action_result_ = "capture session=" + std::to_string(capture.session_id) + ", frames=" + std::to_string(count); return "Consecutive render frame capture requested"; } if (request.id == "mode_prepare" || request.id == "mode_enqueue") { update_model(); const bool prepared = plot_.prepare_frame(); last_action_result_ = prepared ? "frame prepared" : "prepare rejected"; return prepared ? "Kernel 帧已准备/入队" : "Kernel 拒绝准备帧"; } if (request.id == "mode_enqueue_burst") { const auto argument = action_number(request); if (!argument) return invalid_argument(recognized); const int count = std::clamp(static_cast(std::lround(*argument)), 1, 256); int prepared{}; for (int index = 0; index < count; ++index) { update_model(); prepared += plot_.prepare_frame() ? 1 : 0; } last_action_result_ = "enqueued=" + std::to_string(prepared); return "回放帧已批量压入 Flow 队列"; } if (request.id == "mode_refresh") { const bool refreshed = plot_.refresh_manual_frame(); last_action_result_ = refreshed ? "manual refresh succeeded" : "manual refresh failed"; return refreshed ? "Manual 待处理帧已提交刷新" : "当前没有可刷新的 Manual 帧"; } if (request.id == "mode_render" || request.id == "mode_dequeue") { const auto started = std::chrono::steady_clock::now(); const bool rendered = plot_.render_prepared_frame(); record_performance(started, rendered); rendered_since_last_pixel_ = rendered; last_action_result_ = rendered ? "prepared frame rendered" : "no prepared frame"; return rendered ? "Kernel 已渲染准备帧" : "当前没有可消费的准备帧"; } if (request.id == "mode_discard") { const bool discarded = plot_.discard_pending_frame(); last_action_result_ = discarded ? "pending frame discarded" : "no pending frame"; return discarded ? "待处理帧已丢弃" : "当前没有可丢弃帧"; } if (request.id == "mode_cycle") { update_model(); const auto started = std::chrono::steady_clock::now(); const bool rendered = plot_.render_frame(true); record_performance(started, rendered); rendered_since_last_pixel_ = rendered; last_action_result_ = rendered ? "full frame cycle rendered" : "frame cycle skipped"; return rendered ? "完整帧控制周期已执行" : "完整帧控制周期未产生图像"; } if (request.id == "read_data_shape") { last_action_result_ = "data shape refreshed in observer telemetry"; return "输入点数、实际绘制点/单元数已刷新到观察者与性能面板"; } if (request.id == "toggle_view") { if (plot_.view_active()) plot_.deactivate_view(); else plot_.activate_view(); return plot_.view_active() ? "Plot view 已激活" : "Plot view 已停用;可再次执行恢复"; } if (request.id == "axis_probe") return "坐标映射、刻度步长、次刻度数与标签结果已刷新到遥测区"; if (request.id == "append_time" && time_axis_) { const int tick = time_axis_->append_time(current_time_of_day()); last_action_result_ = "tick=" + std::to_string(tick) + ", ms=" + std::to_string(time_axis_->tick_to_time(tick).milliseconds); return "Time_Axis 时间点已追加并回读"; } if (spectrum_) { if (request.id == "push_samples") { push_spectrum_samples(); return "Spectrum 样本已手动推送"; } if (request.id == "power_at") { const auto argument = action_number(request); if (!argument) return invalid_argument(recognized); bool ok{}; const double power = spectrum_->power_at(*argument, ok); last_action_result_ = ok ? std::to_string(power) + " dB" : "频率不在样本范围"; return "Spectrum::power_at 查询完成"; } if (request.id == "add_marker" || request.id == "add_line_marker" || request.id == "remove_marker" || request.id == "set_marker_frequency") { const auto argument = action_number(request); if (!argument) return invalid_argument(recognized); if (request.id == "add_marker") spectrum_->add_custom_marker(*argument); else if (request.id == "add_line_marker") spectrum_->add_custom_line_marker(*argument); else if (request.id == "remove_marker") spectrum_->remove_custom_marker(*argument); else { const int selected = spectrum_->selected_marker_index(); if (selected >= 0) spectrum_->set_marker_frequency(selected, *argument); spectrum_->set_current_marker_frequency(*argument); } return "Marker API 已执行"; } if (request.id == "remove_selected_marker") { spectrum_->remove_selected_marker(); return "选中 Marker 已删除"; } if (request.id == "clear_markers") { spectrum_->clear_custom_markers(); return "全部 Marker 已清空"; } if (request.id == "select_marker") { const auto argument = action_number(request); if (!argument) return invalid_argument(recognized); spectrum_->set_selected_marker_index(static_cast(std::lround(*argument))); return "Marker 索引已选择"; } if (request.id == "select_next_marker") { spectrum_->select_next_marker(); return "已选择下一个 Marker"; } if (request.id == "select_previous_marker") { spectrum_->select_previous_marker(); return "已选择上一个 Marker"; } if (request.id == "clear_marker_selection") { spectrum_->clear_marker_selection(); return "Marker 选择已清除"; } } if (selection_) { if (request.id == "clear_selection") { selection_->clear_selected_regions(); return "框选区域已清空"; } } if (waterfall_) { if (request.id == "append_row") { push_waterfall_row(); return "Waterfall 行已手动追加"; } if (request.id == "append_tick_row") { const int tick = time_axis_->append_time(current_time_of_day()); waterfall_->append_row(tick, spectrum_values(waterfall_->get<&Waterfall::Properties::frequency_bin_count>(), waterfall_->get<&Waterfall::Properties::power_range>())); return "Waterfall 已通过 int tick 重载追加一行"; } } if (afterglow_) { if (request.id == "append_spectrum") { push_afterglow_spectrum(); return "Afterglow 频谱已手动追加"; } } if (sweep_) { if (request.id == "append_block") { push_sweep_block(); return "Sweep_Spectrum 扫频块已手动追加"; } } if (trace_) { if (request.id == "append_sample") { push_trace_sample(); return "Frequency_Trace 样本已手动追加"; } if (request.id == "append_tick_sample") { const int tick = time_axis_->append_time(current_time_of_day()); trace_->append_sample(tick, std::sin(frame_index_ * 0.12)); return "Frequency_Trace 已通过 int tick 重载追加样本"; } } if (constellation_) { if (request.id == "append_points") { push_constellation_points(); return "Constellation IQ 点已手动追加"; } if (request.id == "fit_square") { constellation_->fit_square_to_axes(); return "Constellation 已按轴拟合正方形"; } } recognized = false; return {}; } void record_action_notice_if_empty(std::string_view notice) { if (last_action_result_.empty()) last_action_result_ = notice; } [[nodiscard]] std::string telemetry_json() const { const auto observer = plot_.frame_status(); const auto telemetry_now = std::chrono::steady_clock::now(); const double elapsed_seconds = std::max( std::chrono::duration(telemetry_now - performance_started_).count(), 1e-9); const double render_fps = recent_rate(render_history_, telemetry_now); const double pixel_fps = recent_pixel_rate(pixel_history_, telemetry_now); const double pixel_megabytes_per_second = recent_pixel_megabytes_per_second( pixel_history_, telemetry_now); const auto render_window = render_duration_statistics_.snapshot(); const auto pixel_encode_window = pixel_encode_statistics_.snapshot(); const auto pixel_request_window = pixel_request_statistics_.snapshot(); const bool low_latency = frame_mode_ == Gallery_Frame_Mode::Low_Latency; const Gallery_Consumer_Feedback_Snapshot consumer_feedback{ low_latency && feedback_policy_.enabled, low_latency && feedback_policy_.pixel, low_latency && feedback_policy_.presentation, low_latency && feedback_policy_.manual, low_latency ? feedback_policy_.manual_fps : 0.0, consumer_feedback_source_, consumer_pixel_interval_ns_, consumer_presentation_interval_ns_, consumer_manual_interval_ns_}; Gallery_Render_Performance render_performance; render_performance.render_attempt_count = render_attempt_count_; render_performance.successful_render_count = successful_render_count_; render_performance.failed_render_count = render_attempt_count_ - successful_render_count_; render_performance.measured_fps = render_fps; render_performance.lifetime_average_fps = static_cast(successful_render_count_) / elapsed_seconds; render_performance.last_render_ms = last_render_ms_; render_performance.average_render_ms = render_window.average; render_performance.maximum_render_ms = render_window.maximum; render_performance.render_deviation_ms = render_window.deviation; render_performance.render_p50_ms = render_window.p50; render_performance.render_p95_ms = render_window.p95; render_performance.render_p99_ms = render_window.p99; render_performance.render_sample_count = render_window.sample_count; render_performance.pixel_response_fps = pixel_fps; render_performance.last_pixel_snapshot_ms = last_pixel_snapshot_ms_; render_performance.last_pixel_encode_ms = last_pixel_encode_ms_; render_performance.average_pixel_encode_ms = pixel_encode_window.average; render_performance.maximum_pixel_encode_ms = pixel_encode_window.maximum; render_performance.pixel_encode_deviation_ms = pixel_encode_window.deviation; render_performance.pixel_encode_p50_ms = pixel_encode_window.p50; render_performance.pixel_encode_p95_ms = pixel_encode_window.p95; render_performance.pixel_encode_p99_ms = pixel_encode_window.p99; render_performance.pixel_encode_sample_count = pixel_encode_window.sample_count; render_performance.last_pixel_request_ms = last_pixel_request_ms_; render_performance.average_pixel_request_ms = pixel_request_window.average; render_performance.pixel_request_deviation_ms = pixel_request_window.deviation; render_performance.pixel_request_p95_ms = pixel_request_window.p95; render_performance.pixel_request_p99_ms = pixel_request_window.p99; render_performance.last_pixel_bytes = last_pixel_bytes_; render_performance.pixel_payload_megabytes_per_second = pixel_megabytes_per_second; render_performance.automatic_low_latency_scheduler = automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency; const Gallery_Client_Performance client_performance = client_performance_; const auto scene_execution = plot_.render_scene().execution_statistics(); nlohmann::json observer_data = adminive::to_frontend_json(observer); nlohmann::json consumer_feedback_data = adminive::to_frontend_json(consumer_feedback); nlohmann::json render_performance_data = adminive::to_frontend_json(render_performance); nlohmann::json client_performance_data = adminive::to_frontend_json(client_performance); nlohmann::json telemetry{ {"case", case_id_}, {"frame_mode", gallery_enum_id(frame_mode_)}, {"frame_index", frame_index_}, { "viewport", { {"width", plot_.viewport_size().width}, {"height", plot_.viewport_size().height} } }, {"view_active", plot_.view_active()}, {"kernel_frame_count", plot_.diagnostics().refresh.frame_count}, {"performance", std::move(render_performance_data)}, {"kernel_observer", std::move(observer_data)}, {"consumer_feedback", std::move(consumer_feedback_data)}, {"client_performance", std::move(client_performance_data)}, {"scheduler", adminive::to_frontend_json( renderive::scheduling::scheduler_statistics())}, {"queue_pressure", {{"scene_execution", {{"capacity", scene_execution.capacity}, {"queued", scene_execution.queued}, {"peak_queued", scene_execution.peak_queued}, {"backpressure_count", scene_execution.backpressure_count}, {"backpressure_wait_ns", scene_execution.backpressure_wait_ns}}}}}, {"renderable_observers", controls_.observers(plot_.render_scene())}, {"performance_capture", gallery_performance_capture_json(plot_.render_scene())}, {"last_action_result", last_action_result_} }; if (primary_) { telemetry["renderable"] = { {"object_name", primary_->object_name()}, {"visible", primary_->is_visible()}, { "cache_mode", magic_enum::enum_name(primary_->get_cache_mode()) } }; } if (const auto overlay = plot_.performance_overlay()) { telemetry["performance_overlay_enabled"] = overlay->enabled(); if (const auto snapshot = overlay->display_snapshot()) telemetry["performance_overlay_lines"] = snapshot->lines.size(); } if (const auto axis = horizontal_axis()) { const Axis_Transform transform = axis->transform(); const Range range = transform.coordinate_range; const double center = range.center(); telemetry["axis"] = { {"origin", range.origin}, {"target", range.target}, {"start_coord", range.origin}, {"end_coord", range.target}, {"center_pixel", transform.coord_to_pixel(center)}, { "center_roundtrip", transform.pixel_to_coord( transform.coord_to_pixel(center)) }, {"pixel_samples", static_cast(transform.pixel_length) + (transform.pixel_length > 0.0 ? 1 : 0)}, {"tick_step", axis->tick_step(range)}, { "sub_tick_count", axis->sub_tick_count(axis->tick_step(range)) }, {"center_label", axis->tick_label(center)} }; } if (spectrum_) { telemetry["spectrum"] = { {"selectable_line_markers", spectrum_->selectable_line_marker_count()}, {"selected_marker_index", spectrum_->selected_marker_index()}, {"configured_frequency_points", spectrum_->get<&Spectrum::Properties::frequency_point_size>()}, {"input_sample_count", spectrum_->sample_count()}, {"rendered_point_count", spectrum_->rendered_point_count()} }; const int selected = spectrum_->selected_marker_index(); if (selected >= 0 && selected < spectrum_->selectable_line_marker_count()) telemetry["spectrum"]["selected_marker_frequency"] = spectrum_->marker_frequency(selected); } if (selection_) telemetry["selection_regions"] = selection_->selected_regions().size(); if (waterfall_) { telemetry["waterfall"] = { {"configured_frequency_bins", waterfall_->get<&Waterfall::Properties::frequency_bin_count>()}, {"row_count", waterfall_->row_count()}, {"stored_point_count", waterfall_->stored_point_count()}, {"rendered_cell_count", waterfall_->rendered_cell_count()}, {"time_axis_point_count", time_axis_ ? time_axis_->time_point_count() : 0} }; } if (afterglow_) { telemetry["afterglow"] = { {"configured_frequency_points", afterglow_->get<&Afterglow::Properties::frequency_point_size>()}, {"configured_power_points", afterglow_->get<&Afterglow::Properties::power_point_size>()}, {"history_frame_count", afterglow_->history_count()}, {"latest_input_point_count", afterglow_->latest_spectrum_point_count()}, {"rendered_cell_count", afterglow_->rendered_cell_count()} }; } if (sweep_) { telemetry["sweep_spectrum"] = { {"configured_bins_per_block", sweep_->get<&Sweep_Spectrum::Properties::bins_per_block>()}, {"configured_block_count", sweep_->get<&Sweep_Spectrum::Properties::block_count>()}, {"stored_block_count", sweep_->stored_block_count()}, {"stored_point_count", sweep_->stored_point_count()}, {"rendered_point_count", sweep_->rendered_point_count()} }; } if (trace_) { telemetry["frequency_trace"] = { {"sample_count", trace_->sample_count()}, {"rendered_point_count", trace_->rendered_point_count()}, {"time_axis_point_count", time_axis_ ? time_axis_->time_point_count() : 0} }; } if (constellation_) { const int anchor_count = static_cast( constellation_->get<&Constellation_Diagram::Properties::type>()); telemetry["constellation"] = { {"point_count", constellation_->point_count()}, {"anchor_count", anchor_count}, { "rendered_point_count", constellation_->point_count() + static_cast(anchor_count) }, {"point_lifetime_ms", constellation_->get<&Constellation_Diagram::Properties::point_lifetime_ms>()} }; } if (case_id_ == "axis_lab") { telemetry["axis_lab"] = { {"domain_axis_pixel_samples", horizontal_axis() ? static_cast(horizontal_axis()->transform().pixel_length) + 1 : 0}, {"time_axis_point_count", time_axis_ ? time_axis_->time_point_count() : 0} }; } if (spectrum_) telemetry["data_shape"] = { {"input_points", spectrum_->sample_count()}, {"rendered_elements", spectrum_->rendered_point_count()}, {"unit", "points"} }; else if (waterfall_) telemetry["data_shape"] = { {"input_points", waterfall_->stored_point_count()}, {"rendered_elements", waterfall_->rendered_cell_count()}, {"unit", "cells"} }; else if (afterglow_) telemetry["data_shape"] = { {"input_points", afterglow_->latest_spectrum_point_count()}, {"rendered_elements", afterglow_->rendered_cell_count()}, {"unit", "cells"} }; else if (sweep_) telemetry["data_shape"] = { {"input_points", sweep_->stored_point_count()}, {"rendered_elements", sweep_->rendered_point_count()}, {"unit", "points"} }; else if (trace_) telemetry["data_shape"] = { {"input_points", trace_->sample_count()}, {"rendered_elements", trace_->rendered_point_count()}, {"unit", "points"} }; else if (constellation_) { const std::size_t anchors = static_cast( constellation_->get<&Constellation_Diagram::Properties::type>()); telemetry["data_shape"] = { {"input_points", constellation_->point_count()}, {"rendered_elements", constellation_->point_count() + anchors}, {"unit", "points"} }; } else telemetry["data_shape"] = { {"input_points", 0}, {"rendered_elements", horizontal_axis() ? static_cast(horizontal_axis()->transform().pixel_length) + 1 : 0}, {"unit", "axis samples"} }; return telemetry.dump(); } private: using Performance_Clock = std::chrono::steady_clock; struct Pixel_Performance_Sample { Performance_Clock::time_point time; std::size_t bytes; }; static void record_timestamp(std::deque& history, Performance_Clock::time_point now) { history.push_back(now); const auto oldest = now - std::chrono::seconds(1); while (history.size() > 2 && history.front() < oldest) history.pop_front(); } static double recent_rate(const std::deque& history, Performance_Clock::time_point now) { if (history.size() < 2 || now - history.back() > std::chrono::seconds(1)) return 0.0; const double seconds = std::chrono::duration(history.back() - history.front()).count(); return seconds > 0.0 ? static_cast(history.size() - 1) / seconds : 0.0; } static void record_pixel_sample(std::deque& history, Performance_Clock::time_point now, std::size_t bytes) { history.push_back({now, bytes}); const auto oldest = now - std::chrono::seconds(1); while (history.size() > 2 && history.front().time < oldest) history.pop_front(); } static double recent_pixel_rate(const std::deque& history, Performance_Clock::time_point now) { if (history.size() < 2 || now - history.back().time > std::chrono::seconds(1)) return 0.0; const double seconds = std::chrono::duration(history.back().time - history.front().time).count(); return seconds > 0.0 ? static_cast(history.size() - 1) / seconds : 0.0; } static double recent_pixel_megabytes_per_second( const std::deque& history, Performance_Clock::time_point now) { if (history.size() < 2 || now - history.back().time > std::chrono::seconds(1)) return 0.0; const double seconds = std::chrono::duration(history.back().time - history.front().time).count(); if (seconds <= 0.0) return 0.0; std::size_t bytes{}; for (std::size_t index = 1; index < history.size(); ++index) bytes += history[index].bytes; return static_cast(bytes) / seconds / 1e6; } void apply_consumer_feedback() { if (frame_mode_ != Gallery_Frame_Mode::Low_Latency) return; consumer_pixel_interval_ns_ = frequency_to_ns(client_performance_.transport_fps); consumer_presentation_interval_ns_ = milliseconds_to_ns(client_performance_.display_interval_ms); consumer_manual_interval_ns_ = frequency_to_ns(feedback_policy_.manual_fps); consumer_feedback_source_ = "none"; if (!feedback_policy_.enabled) { plot_.clear_consumer_feedback(); consumer_feedback_source_ = "disabled"; return; } std::uint64_t interval_ns = 0; const auto select_source = [&interval_ns, this](bool enabled, std::uint64_t candidate, std::string_view source) { if (!enabled || candidate == 0) return; if (candidate > interval_ns) { interval_ns = candidate; consumer_feedback_source_ = source; return; } if (candidate == interval_ns) { if (consumer_feedback_source_ != "none") consumer_feedback_source_ += '+'; consumer_feedback_source_ += source; } }; select_source(feedback_policy_.pixel, consumer_pixel_interval_ns_, "pixel"); select_source(feedback_policy_.presentation, consumer_presentation_interval_ns_, "presentation"); select_source(feedback_policy_.manual, consumer_manual_interval_ns_, "manual"); if (interval_ns == 0) { plot_.clear_consumer_feedback(); return; } static_cast(plot_.set_consumer_feedback({interval_ns})); } void record_performance(std::chrono::steady_clock::time_point started, bool rendered) { const auto finished = std::chrono::steady_clock::now(); ++render_attempt_count_; const double duration_ms = std::chrono::duration(finished - started).count(); last_render_ms_ = duration_ms; if (rendered) { ++successful_render_count_; render_duration_statistics_.add(duration_ms); record_timestamp(render_history_, finished); } maybe_log_performance(finished); } void record_pixel_performance(Performance_Clock::time_point request_started, Performance_Clock::time_point encode_started, Performance_Clock::time_point encode_finished, std::size_t pixel_bytes) { last_pixel_snapshot_ms_ = std::chrono::duration(encode_started - request_started).count(); last_pixel_encode_ms_ = std::chrono::duration(encode_finished - encode_started).count(); last_pixel_request_ms_ = std::chrono::duration(encode_finished - request_started).count(); pixel_encode_statistics_.add(last_pixel_encode_ms_); pixel_request_statistics_.add(last_pixel_request_ms_); last_pixel_bytes_ = pixel_bytes; ++pixel_frame_count_; record_pixel_sample(pixel_history_, encode_finished, pixel_bytes); } void maybe_log_performance(Performance_Clock::time_point now) { if (now - last_performance_log_ < std::chrono::seconds(1)) return; last_performance_log_ = now; const auto observer = plot_.frame_status(); const auto unix_ms = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); const double pixel_fps = recent_pixel_rate(pixel_history_, now); const double pixel_megabytes_per_second = recent_pixel_megabytes_per_second( pixel_history_, now); const bool low_latency = frame_mode_ == Gallery_Frame_Mode::Low_Latency; const auto scheduler_interval_ns = low_latency ? kernel_refresh_interval_ns() : 0; const nlohmann::json line{ {"event", "gallery_performance"}, {"unix_ms", unix_ms}, {"session_id", session_id_}, {"case", case_id_}, {"frame_mode", gallery_enum_id(frame_mode_)}, {"configured_fps", low_latency ? plot_.max_render_fps() : 0.0}, {"automatic_scheduler_interval_ns", scheduler_interval_ns}, {"automatic_scheduler_fps", scheduler_interval_ns == 0 ? 0.0 : 1'000'000'000.0 / static_cast(scheduler_interval_ns)}, {"automatic_scheduler_limit_source", low_latency ? observer.limit_state : "not_applicable"}, {"frequency_limit_enabled", low_latency && observer.frequency_limit_enabled}, {"consumer_feedback_enabled", low_latency && observer.consumer_feedback_enabled}, {"consumer_feedback_sample_interval_ns", low_latency ? observer.consumer_sample_interval_ns : 0}, {"consumer_feedback_smoothed_interval_ns", low_latency ? observer.consumer_smoothed_interval_ns : 0}, {"consumer_feedback_variation_ns", low_latency ? observer.consumer_variation_ns : 0}, {"consumer_feedback_safety_interval_ns", low_latency ? observer.consumer_safety_interval_ns : 0}, {"consumer_feedback_interval_ns", low_latency ? observer.consumer_interval_ns : 0}, {"backend_render_fps", recent_rate(render_history_, now)}, {"pixel_response_fps", pixel_fps}, {"client_transport_fps", client_performance_.transport_fps}, {"client_presentation_fps", client_performance_.presentation_fps}, {"client_changed_pixel_frames", client_performance_.changed_pixel_frames}, {"client_duplicate_pixel_frames", client_performance_.duplicate_pixel_frames}, {"client_frame_request_timeout_count", client_performance_.frame_request_timeout_count}, {"client_frame_round_trip_ms", client_performance_.frame_round_trip_ms}, {"client_display_interval_ms", client_performance_.display_interval_ms}, {"client_display_interval_latest_ms", client_performance_.display_interval_latest_ms}, {"client_display_interval_p95_ms", client_performance_.display_interval_p95_ms}, {"client_display_interval_p99_ms", client_performance_.display_interval_p99_ms}, {"client_display_interval_deviation_ms", client_performance_.display_interval_deviation_ms}, {"client_overwritten_pixel_frames", client_performance_.overwritten_pixel_frames}, {"client_last_pixel_receive_age_ms", client_performance_.last_pixel_receive_age_ms}, {"client_last_pixel_change_age_ms", client_performance_.last_pixel_change_age_ms}, {"last_render_ms", last_render_ms_}, {"last_pixel_snapshot_ms", last_pixel_snapshot_ms_}, {"last_pixel_encode_ms", last_pixel_encode_ms_}, {"last_pixel_request_ms", last_pixel_request_ms_}, {"pixel_payload_bytes", last_pixel_bytes_}, {"pixel_payload_megabytes_per_second", pixel_megabytes_per_second}, {"websocket_buffered_bytes", client_performance_.websocket_buffered_bytes}, { "automatic_low_latency_scheduler", automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency }, {"successful_render_count", successful_render_count_}, {"failed_render_count", render_attempt_count_ - successful_render_count_}, {"pixel_frame_count", pixel_frame_count_}, {"last_event", observer.last_event}, {"limit_state", observer.limit_state}, {"observation_count", observer.observation_count}, {"latest_sequence", observer.latest_sequence}, {"produced_frame_count", observer.produced_frame_count}, {"consumed_frame_count", observer.consumed_frame_count}, {"target_interval_ns", observer.target_interval_ns}, {"paint_duration_ns", observer.paint_duration_ns}, {"render_duration_ns", observer.render_duration_ns}, {"bottleneck_duration_ns", observer.bottleneck_duration_ns}, {"consumer_sample_interval_ns", observer.consumer_sample_interval_ns}, {"consumer_smoothed_interval_ns", observer.consumer_smoothed_interval_ns}, {"consumer_variation_ns", observer.consumer_variation_ns}, {"consumer_safety_interval_ns", observer.consumer_safety_interval_ns}, {"consumer_interval_ns", observer.consumer_interval_ns}, {"next_refresh_interval_ns", observer.next_refresh_interval_ns}, {"paint_lease_wait_ns", observer.paint_lease_wait_ns}, {"paint_state_wait_ns", observer.paint_state_wait_ns}, {"publish_state_wait_ns", observer.publish_state_wait_ns}, {"ready_wait_ns", observer.ready_wait_ns}, {"frame_age_at_render_ns", observer.frame_age_at_render_ns}, {"render_lease_wait_ns", observer.render_lease_wait_ns}, {"render_state_wait_ns", observer.render_state_wait_ns}, {"render_finish_state_wait_ns", observer.render_finish_state_wait_ns}, {"queue_wait_ns", observer.queue_wait_ns}, {"end_to_end_ns", observer.end_to_end_ns}, {"pending_frames", observer.pending_frame_count}, {"dropped_frames", observer.dropped_frame_count}, {"failed_operations", observer.failed_operation_count} }; write_web_performance_log(line.dump()); } void build_axes(::Scene_2D_Base::Attach_Builder& attach) { constexpr Color axis_color{118, 145, 184, 255}; Range coordinate_range{88'000'000.0, 108'000'000.0}; if (case_id_ == "sweep_spectrum") coordinate_range = {0.0, 300.0}; else if (case_id_ == "constellation") coordinate_range = {-1.25, 1.25}; if (case_id_ == "frequency_trace") { time_axis_ = Gallery_Time_Axis::Builder{&attach} .set<&Gallery_Time_Axis::Properties::orientation>(Orientation::Horizontal) .set<&Gallery_Time_Axis::Properties::visible_count>(80) .set<&Gallery_Time_Axis::Properties::tick_label_spacing_px>(28) .set<&Gallery_Time_Axis::Properties::format>("mm:ss.zzz") .set<&Gallery_Time_Axis::Properties::color>(axis_color) .build(axes_node_); } else if (case_id_ == "sweep_spectrum" || case_id_ == "constellation") { numeric_domain_axis_ = Gallery_Axis::Builder{&attach} .set<&Gallery_Axis::Properties::orientation>(Orientation::Horizontal) .set<&Gallery_Axis::Properties::coordinates>(coordinate_range) .set<&Gallery_Axis::Properties::wheel>(true) .set<&Gallery_Axis::Properties::drag>(true) .set<&Gallery_Axis::Properties::color>(axis_color) .build(axes_node_); } else { frequency_domain_axis_ = Gallery_Frequency_Axis::Builder{&attach} .set<&Gallery_Frequency_Axis::Properties::orientation>(Orientation::Horizontal) .set<&Gallery_Frequency_Axis::Properties::coordinates>(coordinate_range) .set<&Gallery_Frequency_Axis::Properties::wheel>(true) .set<&Gallery_Frequency_Axis::Properties::drag>(true) .set<&Gallery_Frequency_Axis::Properties::color>(axis_color) .build(axes_node_); } if (case_id_ == "waterfall") { time_axis_ = Gallery_Time_Axis::Builder{&attach} .set<&Gallery_Time_Axis::Properties::orientation>(Orientation::Vertical) .set<&Gallery_Time_Axis::Properties::visible_count>(80) .set<&Gallery_Time_Axis::Properties::tick_label_spacing_px>(28) .set<&Gallery_Time_Axis::Properties::format>("mm:ss.zzz") .set<&Gallery_Time_Axis::Properties::color>(axis_color) .build(axes_node_); } else { Range value_range{-120.0, -20.0}; if (case_id_ == "frequency_trace") value_range = {-1.0, 1.0}; else if (case_id_ == "constellation") value_range = {-1.25, 1.25}; value_axis_ = Gallery_Axis::Builder{&attach} .set<&Gallery_Axis::Properties::orientation>(Orientation::Vertical) .set<&Gallery_Axis::Properties::coordinates>(value_range) .set<&Gallery_Axis::Properties::precision>(case_id_ == "constellation" ? 2 : 0) .set<&Gallery_Axis::Properties::color>(axis_color) .build(axes_node_); } if (case_id_ == "axis_lab") { time_axis_ = Gallery_Time_Axis::Builder{&attach} .set<&Gallery_Time_Axis::Properties::orientation>(Orientation::Horizontal) .set<&Gallery_Time_Axis::Properties::visible_count>(80) .set<&Gallery_Time_Axis::Properties::tick_label_spacing_px>(28) .set<&Gallery_Time_Axis::Properties::format>("mm:ss.zzz") .set<&Gallery_Time_Axis::Properties::color>(Color{69, 221, 190, 255}) .build(axes_node_); } const auto style = [this](const renderive_Owner& axis) { if (!axis) return; update_axis_state(axis, [this](Axis_Base_Properties& value) { value.tick_length = 8; value.sub_tick_length = 4; value.unit_text = case_id_ == "sweep_spectrum" ? "MHz" : case_id_ == "constellation" ? "I / Q" : case_id_ == "frequency_trace" ? "value" : "Hz"; value.unit_text_font = {12.0, 500, false}; value.unit_text_pen = {Color{220, 233, 255, 255}}; value.unit_text_background_brush = { Color{7, 17, 31, 255}, Brush_Style::Solid}; }); }; style(horizontal_axis()); style(vertical_axis()); if (case_id_ == "axis_lab") style(time_axis_); } void build_primary(::Scene_2D_Base::Attach_Builder& attach) { if (case_id_ == "axis_lab") { primary_ = renderive_static_owner_cast(frequency_domain_axis_); return; } if (case_id_ == "spectrum" || case_id_ == "selection_overlay") { Gallery_Spectrum::Properties properties; properties.frequency_range = {88'000'000.0, 108'000'000.0}; properties.frequency_point_size = 512; properties.center_frequency = 98'000'000.0; properties.sweep_frequency_range = {94'000'000.0, 102'000'000.0}; properties.max_hold_visible = true; properties.max_marker_visible = true; properties.sweep_region_visible = true; properties.max_brush = {color_from_hex("#332810", 76), Brush_Style::Solid}; properties.current_brush = {color_from_hex("#0e3a35", 76), Brush_Style::Solid}; properties.min_brush = {color_from_hex("#19213a", 76), Brush_Style::Solid}; properties.max_pen = {color_from_hex("#ffd166"), 1.2}; properties.current_pen = {color_from_hex("#35e6b2"), 2.0, Line_Style::Solid, Line_Cap::Round, Line_Join::Round}; properties.min_pen = {color_from_hex("#7aa2ff"), 1.2}; properties.selected_marker_pen = {color_from_hex("#ff4d8d"), 2.0}; properties.marker_pen = {color_from_hex("#ff7a59"), 1.2}; properties.middle_frequency_pen = {color_from_hex("#6bc8ff"), 1.0, Line_Style::Dash}; properties.sweep_region_brush = {color_from_hex("#293257", 80), Brush_Style::Solid}; spectrum_ = Gallery_Spectrum::Builder{properties, &attach}.build( data_node_, frequency_domain_axis_, value_axis_); primary_ = spectrum_; if (case_id_ == "selection_overlay") { Gallery_Selection_Rectangle_Overlay::Properties overlay; overlay.label_font = {12.0, 500, false}; overlay.selection_brush = {color_from_hex("#173b66", 70), Brush_Style::Solid}; overlay.selection_border_pen = {color_from_hex("#7dd3fc"), 1.0, Line_Style::Dash}; selection_ = Gallery_Selection_Rectangle_Overlay::Builder{overlay, &attach}.build( overlay_node_, frequency_domain_axis_, value_axis_); primary_ = selection_; } return; } if (case_id_ == "waterfall") { Gallery_Waterfall::Properties properties; properties.frequency_range = {88'000'000.0, 108'000'000.0}; properties.power_range = {-120.0, -20.0}; properties.frequency_bin_count = 256; waterfall_ = Gallery_Waterfall::Builder{properties, &attach}.build( data_node_, frequency_domain_axis_, time_axis_); primary_ = waterfall_; return; } if (case_id_ == "afterglow") { Gallery_Afterglow::Properties properties; properties.frequency_range = {88'000'000.0, 108'000'000.0}; properties.power_range = {-120.0, -20.0}; properties.frequency_point_size = 192; properties.power_point_size = 96; properties.attenuation_rate = 0.18; afterglow_ = Gallery_Afterglow::Builder{properties, &attach}.build( data_node_, frequency_domain_axis_, value_axis_); primary_ = afterglow_; return; } if (case_id_ == "sweep_spectrum") { Gallery_Sweep_Spectrum::Properties properties; properties.frequency_range = {0.0, 300.0}; properties.bins_per_block = 64; properties.block_count = 8; properties.pen = {color_from_hex("#ffd166"), 1.8}; properties.current_frequency_pen = {color_from_hex("#ff5d73"), 2.0}; sweep_ = Gallery_Sweep_Spectrum::Builder{properties, &attach}.build( data_node_, numeric_domain_axis_, value_axis_); primary_ = sweep_; return; } if (case_id_ == "frequency_trace") { Gallery_Frequency_Trace::Properties properties; properties.pen = {color_from_hex("#35e6b2"), 2.0, Line_Style::Solid, Line_Cap::Round, Line_Join::Round}; trace_ = Gallery_Frequency_Trace::Builder{properties, &attach}.build( data_node_, time_axis_, value_axis_); primary_ = trace_; return; } if (case_id_ == "constellation") { Gallery_Constellation_Diagram::Properties properties; properties.i_range = {-1.25, 1.25}; properties.q_range = {-1.25, 1.25}; properties.point_color = color_from_hex("#49e6c3"); properties.anchor_color = color_from_hex("#ffd166"); properties.point_lifetime_ms = 1800; constellation_ = Gallery_Constellation_Diagram::Builder{properties, &attach}.build( data_node_, numeric_domain_axis_, value_axis_); primary_ = constellation_; } } renderive_Owner horizontal_axis() const { if (frequency_domain_axis_) return frequency_domain_axis_; if (numeric_domain_axis_) return numeric_domain_axis_; return time_axis_; } renderive_Owner vertical_axis() const { if (case_id_ == "waterfall") return time_axis_; return value_axis_; } void apply_layout() { const Size viewport = plot_.viewport_size(); const int left = viewport.width < 440 ? 48 : 62; const int right = 24; const int top = case_id_ == "axis_lab" ? 48 : 18; const int bottom = 46; const int width = std::max(1, viewport.width - left - right); const int height = std::max(1, viewport.height - top - bottom); const auto layout_axis = [=](const renderive_Owner& target) { if (!target) return; update_axis_state(target, [=](Axis_Base_Properties& axis) { axis.x = left; axis.y = axis.orientation == Orientation::Horizontal ? top + height : top; axis.pixel_length = static_cast( axis.orientation == Orientation::Horizontal ? width : height); }); }; layout_axis(horizontal_axis()); layout_axis(vertical_axis()); if (case_id_ == "axis_lab" && time_axis_) { time_axis_->update([&](auto& axis) { axis.x = left; axis.y = axis.orientation == Orientation::Horizontal ? 26 : top; axis.pixel_length = static_cast( axis.orientation == Orientation::Horizontal ? width : height); }); } } void register_controls() { controls_.add(*session_control_); if (frequency_domain_axis_) controls_.add(*frequency_domain_axis_); if (numeric_domain_axis_) controls_.add("domain_axis", *numeric_domain_axis_); if (value_axis_) controls_.add("value_axis", *value_axis_); if (time_axis_) controls_.add(*time_axis_); if (spectrum_) controls_.add(*spectrum_); if (waterfall_) controls_.add(*waterfall_); if (afterglow_) controls_.add(*afterglow_); if (sweep_) controls_.add(*sweep_); if (trace_) controls_.add(*trace_); if (selection_) controls_.add(*selection_); if (constellation_) controls_.add(*constellation_); } std::vector spectrum_values(int count, Range power_range = {-120.0, -20.0}) const { count = std::max(count, 2); std::vector values(static_cast(count)); const double time = static_cast(frame_index_) * 0.09; for (int index = 0; index < count; ++index) { const double x = static_cast(index) / (count - 1); const double noise = std::sin(index * 11.71 + time * 2.9) * std::sin(index * 0.19 + time * 0.7) * 4.0; const double signal = 58.0 * gaussian(x, 0.32 + std::sin(time) * 0.04, 0.035) + 45.0 * gaussian(x, 0.57, 0.07) + 32.0 * gaussian(x, 0.78, 0.018); values[static_cast(index)] = std::clamp(-112.0 + noise + signal, std::min(power_range.origin, power_range.target), std::max(power_range.origin, power_range.target)); } return values; } void push_spectrum_samples() { spectrum_->update_samples(spectrum_values(spectrum_->get<&Spectrum::Properties::frequency_point_size>())); } void push_waterfall_row() { waterfall_->append_row(current_time_of_day(), spectrum_values(waterfall_->get<&Waterfall::Properties::frequency_bin_count>(), waterfall_->get<&Waterfall::Properties::power_range>())); } void push_afterglow_spectrum() { afterglow_->append_spectrum(spectrum_values(afterglow_->get<&Afterglow::Properties::frequency_point_size>(), afterglow_->get<&Afterglow::Properties::power_range>())); } void push_sweep_block() { const int count = sweep_->get<&Sweep_Spectrum::Properties::bins_per_block>(); std::vector block(static_cast(std::max(count, 1))); for (int index = 0; index < count; ++index) { const double x = static_cast(index) / std::max(1, count - 1); block[static_cast(index)] = -104.0 + 68.0 * gaussian(x, 0.5, 0.16) + std::sin(frame_index_ * 0.2 + index) * 4.0; } sweep_->append_block(block); } void push_trace_sample() { const Range range = value_axis_->get<&Axis_Properties::coordinates>(); const double lower = range.origin; const double upper = range.target; const double center = (lower + upper) * 0.5; const double amplitude = std::abs(upper - lower) * 0.42; trace_->append_sample(current_time_of_day(), center + amplitude * std::sin(frame_index_ * 0.12)); } void push_constellation_points() { const int count = static_cast( constellation_->get<&Constellation_Diagram::Properties::type>()); const double phase = constellation_->get< &Constellation_Diagram::Properties::phase_offset_radians>(); for (int index = 0; index < 24; ++index) { const int anchor = static_cast((frame_index_ * 7 + index * 5) % count); const double angle = phase + 2.0 * std::numbers::pi * anchor / count; const double noise = std::sin(frame_index_ * 0.31 + index * 1.73) * 0.055; constellation_->append_point({ std::cos(angle) + noise, std::sin(angle) - noise * 0.7 }); } } void update_model() { if (spectrum_) push_spectrum_samples(); if (waterfall_) push_waterfall_row(); if (afterglow_) push_afterglow_spectrum(); if (sweep_) push_sweep_block(); if (trace_) push_trace_sample(); if (constellation_) push_constellation_points(); if (case_id_ == "axis_lab" && time_axis_) time_axis_->append_time(current_time_of_day()); ++frame_index_; } static std::string invalid_argument(bool& recognized) { recognized = false; return {}; } std::uint64_t session_id_{}; std::string case_id_; Gallery_Frame_Mode frame_mode_ = Gallery_Frame_Mode::Low_Latency; bool automatic_low_latency_{}; Plot_Scene plot_; Gallery_Feedback_Policy feedback_policy_; std::unique_ptr> session_control_; Gallery_Controls controls_; std::chrono::steady_clock::time_point performance_started_; std::chrono::steady_clock::time_point last_performance_log_; renderive_Owner root_; renderive_Owner axes_node_; renderive_Owner data_node_; renderive_Owner overlay_node_; renderive_Owner numeric_domain_axis_; renderive_Owner frequency_domain_axis_; renderive_Owner value_axis_; renderive_Owner time_axis_; renderive_Owner primary_; renderive_Owner spectrum_; renderive_Owner waterfall_; renderive_Owner trace_; renderive_Owner afterglow_; renderive_Owner sweep_; renderive_Owner selection_; renderive_Owner constellation_; std::uint64_t frame_index_{}; std::uint64_t render_attempt_count_{}; std::uint64_t successful_render_count_{}; double last_render_ms_{}; Rolling_Statistics render_duration_statistics_{256}; std::deque render_history_; std::uint64_t pixel_frame_count_{}; double last_pixel_snapshot_ms_{}; double last_pixel_encode_ms_{}; double last_pixel_request_ms_{}; Rolling_Statistics pixel_encode_statistics_{256}; Rolling_Statistics pixel_request_statistics_{256}; std::size_t last_pixel_bytes_{}; std::deque pixel_history_; Gallery_Client_Performance client_performance_; std::uint64_t consumer_pixel_interval_ns_{}; std::uint64_t consumer_presentation_interval_ns_{}; std::uint64_t consumer_manual_interval_ns_{}; std::string consumer_feedback_source_{"none"}; bool rendered_since_last_pixel_{}; std::string last_action_result_; }; std::unique_ptr make_gallery_scene_2d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency) { return std::make_unique(session_id, std::move(case_id), mode, automatic_low_latency); } }