#include "Gallery_Protocol.h" #include "Gallery_Renderables.h" #include "Gallery_Session_Control_Adminive.h" #include "adminive/adminive.hpp" #include "adminive/adapters/nlohmann_json.hpp" #include #include #include #include #include #include #include #include #include #include namespace renderive::web::gallery_detail { using Json = nlohmann::json; struct Case_Model { std::string id; std::string title; std::string component; std::string category; std::string description; int order{}; int preferred_width = 560; int preferred_height = 320; }; struct Action_Model { std::string id; std::string label; std::string api; std::string description; std::string group; std::string argument_input; std::string argument_label; double argument_default{}; bool request_frame{}; }; struct Action_Definition { Action_Model model; }; const std::vector& cases() { static const std::vector value{ {"axis_lab", "坐标轴实验室", "Axis / Frequency_Axis / Time_Axis", "基础控件", "普通轴、频率轴和时间轴的布局、格式化、滚轮与拖拽 API。", 10}, {"spectrum", "实时频谱", "Spectrum", "三种频率图", "频率控制模式 1:曲线、保持线、扫频区、峰值和自定义 Marker。", 20}, {"afterglow", "余辉频谱", "Afterglow", "三种频率图", "频率控制模式 2:二维功率密度、衰减和功率 bin 插值。", 30}, {"sweep_spectrum", "扫频频谱", "Sweep_Spectrum", "三种频率图", "频率控制模式 3:分块扫频、当前频率游标和六种线插值。", 40}, {"waterfall", "瀑布图", "Waterfall", "热力图控件", "逐行时频热力图;Nearest、Bilinear、Bicubic 三种图像模式均可切换。", 50}, {"frequency_trace", "时间轨迹", "Frequency_Trace", "曲线控件", "Time_Axis 驱动的连续轨迹,覆盖时间格式和属性化笔刷 API。", 60}, {"selection_overlay", "框选叠层", "Selection_Rectangle_Overlay", "交互控件", "鼠标框选、多区域保留、标注样式和清空。", 70}, {"constellation", "星座图", "Constellation_Diagram", "点图控件", "PSK4/PSK8/PSK16 模式、相位、寿命、坐标范围和方形拟合。", 80} }; return value; } Action_Definition action(std::string id, std::string label, std::string api, std::string group, std::string description = {}, std::string argument_input = {}, std::string argument_label = {}, double argument_default = 0) { return {{std::move(id), std::move(label), std::move(api), std::move(description), std::move(group), std::move(argument_input), std::move(argument_label), argument_default}}; } Action_Definition frame_action(std::string id, std::string label, std::string api, std::string group) { auto result = action(std::move(id), std::move(label), std::move(api), std::move(group)); result.model.request_frame = true; return result; } std::vector actions(std::string_view case_id, Gallery_Frame_Mode frame_mode) { std::vector result{ action("reset", "恢复本图默认值", "Gallery_Scene::rebuild", "会话"), action("toggle_view", "切换 View 生命周期", "Plot_Core::activate_view / deactivate_view", "会话"), action("axis_probe", "读取坐标映射与刻度", "coord_to_pixel / pixel_to_coord / tick_step / sub_tick_count / tick_label", "Axis") }; if (frame_mode == Gallery_Frame_Mode::Manual) { result.push_back(action("mode_prepare", "准备帧", "Manual_Refresh_Strategy::acquire_painter / Plot_Core::prepare_frame", "Manual_Refresh_Strategy")); result.push_back(action("mode_refresh", "提交手动刷新", "Manual_Refresh_Strategy::refresh / Plot_Core::refresh_manual_frame", "Manual_Refresh_Strategy")); result.push_back(frame_action("mode_render", "渲染已刷新帧", "Manual_Refresh_Strategy::acquire_renderer / Plot_Core::render_prepared_frame", "Manual_Refresh_Strategy")); result.push_back(action("mode_discard", "丢弃待刷新帧", "Plot_Core::discard_pending_frame", "Manual_Refresh_Strategy")); result.push_back(frame_action("mode_cycle", "执行完整手动帧周期", "Plot_Core::render_frame", "Manual_Refresh_Strategy")); } else if (frame_mode == Gallery_Frame_Mode::Low_Latency) { result.push_back(action("mode_prepare", "发布低延迟帧", "Low_Latency_Strategy::acquire_painter / Plot_Core::prepare_frame", "Low_Latency_Strategy")); result.push_back(frame_action("mode_render", "消费最新帧", "Low_Latency_Strategy::acquire_renderer / Plot_Core::render_prepared_frame", "Low_Latency_Strategy")); result.push_back(action("mode_discard", "丢弃陈旧帧", "Plot_Core::discard_pending_frame", "Low_Latency_Strategy")); result.push_back(frame_action("mode_cycle", "执行低延迟帧周期", "Plot_Core::render_frame", "Low_Latency_Strategy")); } else { result.push_back(action("mode_enqueue", "压入一帧", "Flow_Refresh_Strategy::acquire_painter / Plot_Core::prepare_frame", "Flow_Refresh_Strategy")); result.push_back(action("mode_enqueue_burst", "批量压入回放队列", "Flow_Refresh_Strategy::acquire_painter / Plot_Core::prepare_frame × N", "Flow_Refresh_Strategy", {}, "number", "帧数", 8)); result.push_back(frame_action("mode_dequeue", "消费队首帧", "Flow_Refresh_Strategy::acquire_renderer / Plot_Core::render_prepared_frame", "Flow_Refresh_Strategy")); result.push_back(frame_action("mode_cycle", "执行回放帧周期", "Plot_Core::render_frame", "Flow_Refresh_Strategy")); } if (case_id == "axis_lab") { result.push_back(action("append_time", "追加时间点", "Time_Axis::append_time / tick_to_time", "Time_Axis")); result.push_back(action("read_data_shape", "读取轴采样点状态", "Abs_Axis::transform / Time_Axis::time_point_count", "观察者")); } else if (case_id == "spectrum" || case_id == "selection_overlay") { result.push_back(action("push_samples", "推送一帧样本", "Spectrum::update_samples", "Spectrum")); result.push_back(action("power_at", "查询指定频率功率", "Spectrum::power_at", "Spectrum", {}, "number", "频率", 98e6)); result.push_back(action("add_marker", "添加点 Marker", "Spectrum::add_custom_marker", "Marker", {}, "number", "频率", 96e6)); result.push_back(action("add_line_marker", "添加线 Marker", "Spectrum::add_custom_line_marker", "Marker", {}, "number", "频率", 100e6)); result.push_back(action("remove_marker", "按频率删除 Marker", "Spectrum::remove_custom_marker", "Marker", {}, "number", "频率", 96e6)); result.push_back(action("remove_selected_marker", "删除选中 Marker", "Spectrum::remove_selected_marker", "Marker")); result.push_back(action("clear_markers", "清空 Marker", "Spectrum::clear_custom_markers", "Marker")); result.push_back(action("select_marker", "选择 Marker 索引", "Spectrum::set_selected_marker_index", "Marker", {}, "number", "索引", 0)); result.push_back(action("select_next_marker", "选择下一个 Marker", "Spectrum::select_next_marker", "Marker")); result.push_back(action("select_previous_marker", "选择上一个 Marker", "Spectrum::select_previous_marker", "Marker")); result.push_back(action("clear_marker_selection", "清除 Marker 选择", "Spectrum::clear_marker_selection", "Marker")); result.push_back(action("set_marker_frequency", "修改选中 Marker 频率", "Spectrum::set_marker_frequency / set_current_marker_frequency", "Marker", {}, "number", "频率", 99e6)); result.push_back(action("read_data_shape", "读取输入与绘制点数", "Spectrum::sample_count / rendered_point_count", "观察者")); if (case_id == "selection_overlay") { result.push_back(action("clear_selection", "清空框选区域", "Selection_Rectangle_Overlay::clear_selected_regions", "Selection Overlay")); } } else if (case_id == "waterfall") { result.push_back(action("append_row", "追加一行", "Waterfall::append_row", "Waterfall")); result.push_back(action("append_tick_row", "按 tick 追加一行", "Time_Axis::append_time / Waterfall::append_row(int, span)", "Waterfall")); result.push_back(action("read_data_shape", "读取行、点与渲染单元", "Waterfall::row_count / stored_point_count / rendered_cell_count", "观察者")); } else if (case_id == "afterglow") { result.push_back(action("append_spectrum", "追加一帧频谱", "Afterglow::append_spectrum", "Afterglow")); result.push_back(action("read_data_shape", "读取历史帧与渲染网格", "Afterglow::history_count / latest_spectrum_point_count / rendered_cell_count", "观察者")); } else if (case_id == "sweep_spectrum") { result.push_back(action("append_block", "追加一个扫频块", "Sweep_Spectrum::append_block", "Sweep Spectrum")); result.push_back(action("read_data_shape", "读取块与绘制点数", "Sweep_Spectrum::stored_block_count / stored_point_count / rendered_point_count", "观察者")); } else if (case_id == "frequency_trace") { result.push_back(action("append_sample", "追加一个样本", "Frequency_Trace::append_sample", "Frequency Trace")); result.push_back(action("append_tick_sample", "按 tick 追加样本", "Time_Axis::append_time / Frequency_Trace::append_sample(int, double)", "Frequency Trace")); result.push_back(action("read_data_shape", "读取轨迹点数", "Frequency_Trace::sample_count / rendered_point_count / Time_Axis::time_point_count", "观察者")); } else if (case_id == "constellation") { result.push_back(action("append_points", "追加一组 IQ 点", "Constellation_Diagram::append_point", "Constellation")); result.push_back(action("fit_square", "按轴拟合正方形", "Constellation_Diagram::fit_square_to_axes", "Constellation")); result.push_back(action("read_data_shape", "读取星座点与锚点数", "Constellation_Diagram::point_count", "观察者")); } return result; } const Case_Model* find_case(std::string_view id) { const auto& all = cases(); const auto found = std::find_if(all.begin(), all.end(), [id](const Case_Model& item) { return item.id == id; }); return found == all.end() ? nullptr : &*found; } Json parse_request(std::string_view message) { return Json::parse(message.begin(), message.end()); } adminive::Table_View action_view(); } // namespace renderive::web::gallery_detail namespace adminive { template <> struct Type_Descriptor { static auto get() { using T = renderive::web::gallery_detail::Case_Model; return object("renderive_gallery_case", ADMINIVE_FIELD_LABEL(T, id, "标识"), ADMINIVE_FIELD_LABEL(T, title, "标题"), ADMINIVE_FIELD_LABEL(T, component, "Core2 控件"), ADMINIVE_FIELD_LABEL(T, category, "分类"), ADMINIVE_FIELD_LABEL(T, description, "说明"), ADMINIVE_FIELD_LABEL(T, order, "顺序"), ADMINIVE_FIELD_LABEL(T, preferred_width, "建议宽度"), ADMINIVE_FIELD_LABEL(T, preferred_height, "建议高度")) .label("Renderive 控件用例"); } }; template <> struct Type_Descriptor { static auto get() { using T = renderive::web::gallery_detail::Action_Model; return object("renderive_gallery_action", ADMINIVE_FIELD_LABEL(T, id, "动作"), ADMINIVE_FIELD_LABEL(T, label, "名称"), ADMINIVE_FIELD_LABEL(T, api, "Core2 API"), ADMINIVE_FIELD_LABEL(T, description, "说明"), ADMINIVE_FIELD_LABEL(T, group, "分组"), ADMINIVE_FIELD_LABEL(T, argument_input, "参数类型"), ADMINIVE_FIELD_LABEL(T, argument_label, "参数名称"), ADMINIVE_FIELD_LABEL(T, argument_default, "参数默认值"), ADMINIVE_FIELD_LABEL(T, request_frame, "执行后请求像素帧")) .label("后端动作菜单"); } }; } // namespace adminive namespace renderive::web::gallery_detail { adminive::Table_View action_view() { using T = Action_Model; return adminive::table_view( adminive::column<&T::label>("动作"), adminive::column<&T::group>("分组"), adminive::column<&T::api>("Core2 API")) .titled("保留 API 动作"); } } // namespace renderive::web::gallery_detail namespace renderive::web { namespace { using gallery_detail::Json; Json protocol_base() { return {{"category", "gallery"}, {"protocol", "renderive.control-gallery"}, {"protocol_version", 4}}; } Json case_contract(std::string_view case_id) { const auto* item = gallery_detail::find_case(case_id); return item ? adminive::to_frontend_json(*item) : Json::object(); } std::string_view frame_mode_id(Gallery_Frame_Mode mode) { switch (mode) { case Gallery_Frame_Mode::Manual: return "manual"; case Gallery_Frame_Mode::Low_Latency: return "low_latency"; case Gallery_Frame_Mode::Playback: return "playback"; } return "low_latency"; } std::optional parse_frame_mode(std::string_view value) { if (value == "manual") return Gallery_Frame_Mode::Manual; if (value == "low_latency") return Gallery_Frame_Mode::Low_Latency; if (value == "playback") return Gallery_Frame_Mode::Playback; return std::nullopt; } Json dashboard_field(std::string_view label, std::string_view source, std::string_view format = "integer", int digits = -1) { Json result{{"label", label}, {"source", source}, {"format", format}}; if (digits >= 0) result["digits"] = digits; return result; } Json mapped_dashboard_field(std::string_view label, std::string_view source, std::string_view format, std::string_view value_map) { Json result = dashboard_field(label, source, format); result["value_map"] = value_map; return result; } Json dashboard_contract() { Json point_pair{{"label", "输入→绘制"}, {"format", "pair"}, {"sources", Json::array({"data_shape.input_points", "data_shape.rendered_elements"})}, {"separator", "→"}}; Json bottleneck = mapped_dashboard_field("当前瓶颈", "low_latency_limit.current", "duration_enum", "limit_state"); bottleneck["duration_sources"] = { {"frequency_limited", "kernel_observer.target_interval_ns"}, {"paint_limited", "kernel_observer.paint_duration_ns"}, {"render_limited", "kernel_observer.render_duration_ns"}, {"consumer_limited", "kernel_observer.consumer_interval_ns"} }; Json configured_frequency = dashboard_field("配置频率", "kernel_observer.configured_frequency_hz", "frequency"); configured_frequency["enabled_source"] = "kernel_observer.frequency_limit_enabled"; configured_frequency["disabled_label"] = "已关闭"; Json dashboard{ {"value_maps", { {"enabled", {{"true", "启用"}, {"false", "关闭"}}}, {"consumer_feedback_source", { {"disabled", "总开关关闭"}, {"none", "无"}, {"pixel", "像素响应"}, {"presentation", "浏览器呈现"}, {"manual", "手动"} }}, {"limit_state", { {"frequency_limited", "Kernel 频率受限"}, {"paint_limited", "PaintEvent 受限"}, {"render_limited", "后台渲染受限"}, {"consumer_limited", "消费者反馈受限"}, {"unlimited", "无限制"}, {"not_applicable", "N/A"} }} }}, {"performance", { {"fields", Json::array({ dashboard_field("后端渲染 FPS", "performance.measured_fps", "fixed", 1), dashboard_field("像素响应 FPS", "performance.pixel_response_fps", "fixed", 1), dashboard_field("浏览器呈现 FPS", "client_performance.presentation_fps", "fixed", 1), dashboard_field("WS 往返 ms", "client_performance.frame_round_trip_ms", "fixed", 2), dashboard_field("未呈现覆盖", "client_performance.overwritten_pixel_frames"), dashboard_field("Core 渲染 ms", "performance.last_render_ms", "fixed", 2), dashboard_field("像素编码 ms", "performance.last_pixel_encode_ms", "fixed", 2), dashboard_field("响应负载 MB/s", "performance.pixel_payload_megabytes_per_second", "fixed", 1), dashboard_field("Kernel 待处理", "kernel_observer.pending_frame_count"), dashboard_field("Kernel 丢弃", "kernel_observer.dropped_frame_count"), std::move(point_pair), std::move(bottleneck), dashboard_field("观察事件", "kernel_observer.last_event", "text"), dashboard_field("像素超时", "client_performance.frame_request_timeout_count"), dashboard_field("最近像素龄", "client_performance.last_pixel_receive_age_ms", "milliseconds", 0) })} }}, {"menu_views", { {"observer", {{"source", "kernel_observer"}}}, {"performance", Json::object()} }}, {"limits", { {"aria_label", "低延迟限速来源"}, {"title", "限速来源"}, {"current_source", "low_latency_limit.current"}, {"active_label", "当前瓶颈"}, {"inactive_label", "未受限"}, {"disabled_label", "已关闭"}, {"fields", Json::array({ {{"label", "Kernel 用户频率"}, {"active_value", "frequency_limited"}, {"duration_source", "kernel_observer.target_interval_ns"}, {"enabled_source", "kernel_observer.frequency_limit_enabled"}}, {{"label", "Kernel PaintEvent"}, {"active_value", "paint_limited"}, {"duration_source", "kernel_observer.paint_duration_ns"}}, {{"label", "Kernel 后台渲染"}, {"active_value", "render_limited"}, {"duration_source", "kernel_observer.render_duration_ns"}}, {{"label", "消费者反馈"}, {"active_value", "consumer_limited"}, {"duration_source", "kernel_observer.consumer_interval_ns"}, {"enabled_source", "kernel_observer.consumer_feedback_enabled"}} })} }}, {"observer", { {"aria_label", "Kernel 低延迟全量统计"}, {"header", { {"prefix", "KERNEL"}, {"suffix", "OBSERVER"}, {"event_label", "事件"}, {"mode", dashboard_field("", "kernel_observer.mode", "text")}, {"limit", mapped_dashboard_field("", "kernel_observer.limit_state", "enum", "limit_state")}, {"event", dashboard_field("", "kernel_observer.last_event", "text")} }}, {"sections", Json::array({ {{"class_name", "observer-counters"}, {"fields", Json::array({ std::move(configured_frequency), dashboard_field("观察次数", "kernel_observer.observation_count"), dashboard_field("最新序号", "kernel_observer.latest_sequence"), dashboard_field("发布", "kernel_observer.produced_frame_count"), dashboard_field("完成", "kernel_observer.consumed_frame_count"), dashboard_field("待处理", "kernel_observer.pending_frame_count"), dashboard_field("丢弃", "kernel_observer.dropped_frame_count"), dashboard_field("失败", "kernel_observer.failed_operation_count") })}}, {{"class_name", "latency-summary"}, {"fields", Json::array({ dashboard_field("目标间隔", "kernel_observer.target_interval_ns", "nanoseconds"), dashboard_field("PaintEvent", "kernel_observer.paint_duration_ns", "nanoseconds"), dashboard_field("后台渲染", "kernel_observer.render_duration_ns", "nanoseconds"), dashboard_field("内部瓶颈", "kernel_observer.bottleneck_duration_ns", "nanoseconds"), mapped_dashboard_field("消费者总开关", "kernel_observer.consumer_feedback_master_enabled", "enum", "enabled"), mapped_dashboard_field("Kernel 反馈有效", "kernel_observer.consumer_feedback_enabled", "enum", "enabled"), mapped_dashboard_field("像素响应反馈", "kernel_observer.consumer_pixel_feedback_enabled", "enum", "enabled"), mapped_dashboard_field("浏览器呈现反馈", "kernel_observer.consumer_presentation_feedback_enabled", "enum", "enabled"), mapped_dashboard_field("手动消费者反馈", "kernel_observer.consumer_manual_feedback_enabled", "enum", "enabled"), dashboard_field("手动消费者 FPS", "kernel_observer.consumer_manual_fps", "fps", 2), mapped_dashboard_field("生效反馈来源", "kernel_observer.consumer_feedback_source", "flags", "consumer_feedback_source"), dashboard_field("像素响应周期", "kernel_observer.consumer_pixel_interval_ns", "nanoseconds"), dashboard_field("浏览器呈现周期", "kernel_observer.consumer_presentation_interval_ns", "nanoseconds"), dashboard_field("手动消费者周期", "kernel_observer.consumer_manual_interval_ns", "nanoseconds"), dashboard_field("消费者原始采样", "kernel_observer.consumer_sample_interval_ns", "nanoseconds"), dashboard_field("消费者平滑周期", "kernel_observer.consumer_smoothed_interval_ns", "nanoseconds"), dashboard_field("消费者抖动", "kernel_observer.consumer_variation_ns", "nanoseconds"), dashboard_field("消费者安全期限", "kernel_observer.consumer_safety_interval_ns", "nanoseconds"), dashboard_field("消费者限速周期", "kernel_observer.consumer_interval_ns", "nanoseconds"), dashboard_field("消费者限速 FPS", "kernel_observer.consumer_interval_ns", "inverse_fps", 2), dashboard_field("下次刷新", "kernel_observer.next_refresh_interval_ns", "nanoseconds"), Json{{"label", "端到端延迟"}, {"source", "kernel_observer.end_to_end_ns"}, {"format", "nanoseconds"}, {"cell_class", "critical"}} })}}, {{"class_name", "client-summary"}, {"aria_label", "浏览器消费者反馈全量统计"}, {"fields", Json::array({ dashboard_field("RAF 最新周期", "client_performance.display_interval_latest_ms", "milliseconds", 3), dashboard_field("RAF 中位周期", "client_performance.display_interval_ms", "milliseconds", 3), dashboard_field("RAF P95 周期", "client_performance.display_interval_p95_ms", "milliseconds", 3), dashboard_field("RAF P95-P50 抖动", "client_performance.display_jitter_ms", "milliseconds", 3), dashboard_field("WS 往返", "client_performance.frame_round_trip_ms", "milliseconds", 3), dashboard_field("像素响应 FPS", "client_performance.transport_fps", "fps", 2), dashboard_field("浏览器呈现 FPS", "client_performance.presentation_fps", "fps", 2), dashboard_field("WS 缓冲", "client_performance.websocket_buffered_bytes", "bytes"), dashboard_field("未呈现覆盖", "client_performance.overwritten_pixel_frames"), dashboard_field("变化像素帧", "client_performance.changed_pixel_frames"), dashboard_field("重复像素帧", "client_performance.duplicate_pixel_frames"), dashboard_field("像素请求超时", "client_performance.frame_request_timeout_count"), dashboard_field("最近像素龄", "client_performance.last_pixel_receive_age_ms", "milliseconds", 3), dashboard_field("最近变化龄", "client_performance.last_pixel_change_age_ms", "milliseconds", 3) })}}, {{"class_name", "latency-details"}, {"aria_label", "Kernel 各阶段等待耗时"}, {"fields", Json::array({ dashboard_field("Painter Lease 等待", "kernel_observer.paint_lease_wait_ns", "nanoseconds"), dashboard_field("Paint State 等待", "kernel_observer.paint_state_wait_ns", "nanoseconds"), dashboard_field("Publish State 等待", "kernel_observer.publish_state_wait_ns", "nanoseconds"), dashboard_field("Ready 等待", "kernel_observer.ready_wait_ns", "nanoseconds"), dashboard_field("开始渲染时帧龄", "kernel_observer.frame_age_at_render_ns", "nanoseconds"), dashboard_field("Render Lease 等待", "kernel_observer.render_lease_wait_ns", "nanoseconds"), dashboard_field("Render State 等待", "kernel_observer.render_state_wait_ns", "nanoseconds"), dashboard_field("Render Finish 等待", "kernel_observer.render_finish_state_wait_ns", "nanoseconds"), dashboard_field("回放队列等待", "kernel_observer.queue_wait_ns", "nanoseconds") })}} })} }} }; return dashboard; } Json frame_mode_contract(Gallery_Frame_Mode mode) { switch (mode) { case Gallery_Frame_Mode::Manual: return {{"id", "manual"}, {"title", "手动刷新"}, {"strategy", "Manual_Refresh_Strategy"}, {"description", "显式准备、刷新和渲染;页面不会自动拉取像素。"}, {"automatic", false}, {"request_after_response", false}, {"request_on_animation_frame", false}, {"observer_visible", false}, {"frame_button_label", "手动刷新一帧"}, {"accent", "#ffd166aa"}, {"order", 10}}; case Gallery_Frame_Mode::Low_Latency: return {{"id", "low_latency"}, {"title", "低延迟"}, {"strategy", "Low_Latency_Strategy"}, {"description", "以最大 FPS 自动发布并消费最新帧,可观测丢帧与端到端延迟。"}, {"automatic", true}, {"request_after_response", true}, {"request_on_animation_frame", false}, {"observer_visible", true}, {"frame_button_label", "立即刷新"}, {"accent", "#45ddbeaa"}, {"order", 20}}; case Gallery_Frame_Mode::Playback: return {{"id", "playback"}, {"title", "回放队列"}, {"strategy", "Flow_Refresh_Strategy"}, {"description", "所有帧按队列顺序入队和消费,可观测队深与排队时间。"}, {"automatic", true}, {"request_after_response", true}, {"request_on_animation_frame", true}, {"observer_visible", false}, {"frame_button_label", "消费下一帧"}, {"accent", "#6aa9ffaa"}, {"order", 30}}; } return Json::object(); } } // namespace std::string Gallery_Protocol::catalog_json() { Json result = protocol_base(); result["type"] = "catalog"; result["transport"] = {{"events", "websocket-text"}, {"pixels", "websocket-binary-rvp1"}, {"http_api", false}, {"socket_per_canvas", true}}; result["navigation"] = { {"default_mode", "low_latency"}, {"all_categories_label", "全部"}, {"catalog_loaded_text", "Kernel 策略目录已加载"}, {"hero_eyebrow", "REAL KERNEL STRATEGIES"}, {"hero_title", "对称策略页面,同一批控件,直接比较"} }; result["dashboard"] = dashboard_contract(); result["case_descriptor"] = adminive::to_descriptor_json(); result["frame_modes"] = Json::array(); constexpr Gallery_Frame_Mode frame_modes[]{Gallery_Frame_Mode::Manual, Gallery_Frame_Mode::Low_Latency, Gallery_Frame_Mode::Playback}; for (const auto mode : frame_modes) result["frame_modes"].push_back(frame_mode_contract(mode)); result["cases"] = Json::array(); std::size_t controls_total{}; std::size_t actions_total{}; for (const auto& item : gallery_detail::cases()) { Json entry = adminive::to_frontend_json(item); entry["control_count_by_mode"] = Json::object(); entry["action_count_by_mode"] = Json::object(); const std::size_t controls = gallery_control_count() + gallery_renderable_control_count(item.id); for (const auto mode : frame_modes) { const std::string key(frame_mode_id(mode)); const std::size_t actions = gallery_detail::actions(item.id, mode).size(); entry["control_count_by_mode"][key] = controls; entry["action_count_by_mode"][key] = actions; controls_total += controls; actions_total += actions; } entry["control_count"] = controls; entry["action_count"] = gallery_detail::actions(item.id, Gallery_Frame_Mode::Low_Latency).size(); result["cases"].push_back(std::move(entry)); } result["coverage"] = {{"case_count", gallery_detail::cases().size()}, {"page_count", std::size(frame_modes)}, {"canvas_count", gallery_detail::cases().size() * std::size(frame_modes)}, {"manual_control_count", controls_total}, {"manual_action_count", actions_total}, {"frequency_modes", Json::array({"spectrum", "afterglow", "sweep_spectrum"})}, {"image_interpolation_modes", Json::array({"Nearest", "Bilinear", "Bicubic"})}}; return result.dump(); } bool Gallery_Protocol::is_case(std::string_view case_id) { return gallery_detail::find_case(case_id) != nullptr; } std::string Gallery_Protocol::case_json_from_controls( std::string_view case_id, std::string_view controls_json, std::string_view telemetry_json, std::string_view notice, Gallery_Frame_Mode frame_mode) { Json result = protocol_base(); result["type"] = "case_state"; result["case"] = case_contract(case_id); result["frame_mode"] = frame_mode_contract(frame_mode); try { result["controls"] = { {"resources", Json::parse(controls_json.begin(), controls_json.end())} }; } catch (const std::exception&) { result["controls"] = {{"resources", Json::array()}}; } result["actions"] = { {"descriptor", adminive::to_descriptor_json()}, {"view", adminive::to_view_json( gallery_detail::action_view())}, {"data", Json::array()} }; for (const auto& definition : gallery_detail::actions(case_id, frame_mode)) result["actions"]["data"].push_back( adminive::to_frontend_json(definition.model)); try { result["telemetry"] = Json::parse(telemetry_json.begin(), telemetry_json.end()); } catch (const std::exception&) { result["telemetry"] = Json::object(); } if (!notice.empty()) result["notice"] = notice; return result.dump(); } std::string Gallery_Protocol::error_json(std::string_view message, std::string_view field) { Json result = protocol_base(); result["type"] = "error"; result["message"] = message; result["field_errors"] = Json::object(); if (!field.empty()) result["field_errors"][std::string(field)] = message; return result.dump(); } std::string Gallery_Protocol::observer_json(std::string_view case_id, Gallery_Frame_Mode frame_mode, std::string_view telemetry_json) { Json result = protocol_base(); result["type"] = "observer_state"; result["case_id"] = case_id; result["frame_mode"] = frame_mode_contract(frame_mode); try { result["telemetry"] = Json::parse(telemetry_json.begin(), telemetry_json.end()); } catch (const std::exception&) { result["telemetry"] = Json::object(); } return result.dump(); } std::optional Gallery_Protocol::open_request(std::string_view message) { try { const Json request = gallery_detail::parse_request(message); if (!request.is_object() || request.value("category", "") != "event" || request.value("type", "") != "gallery_open" || !request.contains("case") || !request.at("case").is_string()) return std::nullopt; const std::string value = request.at("case").get(); if (!is_case(value) || !request.contains("frame_mode") || !request.at("frame_mode").is_string()) return std::nullopt; const auto mode = parse_frame_mode(request.at("frame_mode").get()); return mode ? std::optional(Gallery_Open_Request{value, *mode}) : std::nullopt; } catch (const std::exception&) { return std::nullopt; } } std::optional Gallery_Protocol::action_request( std::string_view message) { try { const Json request = gallery_detail::parse_request(message); if (!request.is_object() || request.value("category", "") != "event" || request.value("type", "") != "gallery_action" || !request.contains("action") || !request.at("action").is_string()) return std::nullopt; Gallery_Action_Request result{request.at("action").get()}; if (request.contains("argument")) { const Json& argument = request.at("argument"); if (argument.is_boolean()) result.argument = argument.get(); else if (argument.is_number()) { const double value = argument.get(); if (!std::isfinite(value)) return std::nullopt; result.argument = value; } else if (argument.is_string()) result.argument = argument.get(); else return std::nullopt; } return result; } catch (const std::exception&) { return std::nullopt; } } std::optional Gallery_Protocol::control_patch_request( std::string_view message) { try { const Json request = gallery_detail::parse_request(message); if (!request.is_object() || request.value("category", "") != "event" || request.value("type", "") != "gallery_patch" || !request.contains("target") || !request.at("target").is_string() || !request.contains("patch") || !request.at("patch").is_object()) return std::nullopt; return Gallery_Control_Patch_Request{ request.at("target").get(), request.at("patch").dump()}; } catch (const std::exception&) { return std::nullopt; } } bool Gallery_Protocol::action_available(std::string_view case_id, Gallery_Frame_Mode frame_mode, std::string_view action_id) { const auto available = gallery_detail::actions(case_id, frame_mode); return std::any_of(available.begin(), available.end(), [action_id](const gallery_detail::Action_Definition& item) { return item.model.id == action_id; }); } } // namespace renderive::web