#include "Gallery_Protocol.h" #include "Gallery_Enum.h" #include "Gallery_Observer_Adminive.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; }; using Action_Model = Gallery_Action_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; } 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; } std::vector registered_actions(std::string_view case_id, Gallery_Frame_Mode frame_mode) { std::vector result; append_gallery_actions(result); for (auto& action : gallery_frame_actions(frame_mode)) { result.push_back(std::move(action)); } append_gallery_renderable_actions(case_id, result); return result; } 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(); } 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; } template const Json& described_field(std::string_view name) { static const Json descriptor = adminive::to_descriptor_json(); const auto& fields = descriptor.at("fields"); const auto found = std::find_if(fields.begin(), fields.end(), [name](const Json& field) { return field.at("name").template get() == name; }); if (found == fields.end()) throw std::logic_error("missing Adminive dashboard field: " + std::string(name)); return *found; } template std::string described_source(std::string_view prefix, std::string_view name) { static_cast(described_field(name)); return std::string(prefix) + "." + std::string(name); } template Json described_dashboard_field(std::string_view prefix, std::string_view name) { const Json& field = described_field(name); const std::string value_type = field.at("value_type"); std::string format = "integer"; if (name.ends_with("_ns")) format = "nanoseconds"; else if (name.ends_with("_fps")) format = "fps"; else if (name.ends_with("frequency_hz")) format = "frequency"; else if (name == "source") format = "flags"; else if (value_type == "boolean" || name == "limit_state") format = "enum"; else if (value_type == "string" || value_type == "enum") format = "text"; Json result = dashboard_field( field.at("presentation").at("label").template get(), described_source(prefix, name), format, name.ends_with("_fps") ? 2 : -1); if (value_type == "boolean") result["value_map"] = "enabled"; else if (name == "limit_state") result["value_map"] = "limit_state"; else if (name == "source") result["value_map"] = "consumer_feedback_source"; return result; } bool observer_header_field(std::string_view name) { return name == "mode" || name == "last_event" || name == "limit_state"; } bool observer_counter_field(std::string_view name) { return name == "frequency_hz" || name == "latest_sequence" || name.ends_with("_count"); } bool observer_detail_field(std::string_view name) { return name.ends_with("_wait_ns") || name == "frame_age_at_render_ns"; } template Json observer_fields(Predicate&& predicate) { const Json descriptor = adminive::to_descriptor_json(); Json result = Json::array(); for (const Json& field : descriptor.at("fields")) { const std::string name = field.at("name"); if (predicate(name)) { Json field = described_dashboard_field( "kernel_observer", name); if (name == "frequency_hz") { field["enabled_source"] = described_source( "kernel_observer", "frequency_limit_enabled"); field["disabled_label"] = "已关闭"; } else if (name == "end_to_end_ns") { field["cell_class"] = "critical"; } result.push_back(std::move(field)); } } return result; } Json consumer_feedback_fields() { const Json descriptor = adminive::to_descriptor_json(); Json result = Json::array(); for (const Json& field : descriptor.at("fields")) { const std::string name = field.at("name"); result.push_back(described_dashboard_field( "consumer_feedback", name)); } return result; } Json observer_summary_fields() { Json result = observer_fields([](std::string_view name) { return !observer_header_field(name) && !observer_counter_field(name) && !observer_detail_field(name); }); for (Json& field : consumer_feedback_fields()) result.push_back(std::move(field)); 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 = described_dashboard_field( "kernel_observer", "limit_state"); bottleneck["format"] = "duration_enum"; bottleneck["duration_sources"] = { {"frequency_limited", described_source( "kernel_observer", "target_interval_ns")}, {"paint_limited", described_source( "kernel_observer", "paint_duration_ns")}, {"render_limited", described_source( "kernel_observer", "render_duration_ns")}, {"consumer_limited", described_source( "kernel_observer", "consumer_interval_ns")} }; 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), described_dashboard_field( "kernel_observer", "pending_frame_count"), described_dashboard_field( "kernel_observer", "dropped_frame_count"), std::move(point_pair), std::move(bottleneck), described_dashboard_field( "kernel_observer", "last_event"), 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", described_source( "kernel_observer", "limit_state")}, {"active_label", "当前瓶颈"}, {"inactive_label", "未受限"}, {"disabled_label", "已关闭"}, {"fields", Json::array({ {{"label", "Kernel 用户频率"}, {"active_value", "frequency_limited"}, {"duration_source", described_source( "kernel_observer", "target_interval_ns")}, {"enabled_source", described_source( "kernel_observer", "frequency_limit_enabled")}}, {{"label", "Kernel PaintEvent"}, {"active_value", "paint_limited"}, {"duration_source", described_source( "kernel_observer", "paint_duration_ns")}}, {{"label", "Kernel 后台渲染"}, {"active_value", "render_limited"}, {"duration_source", described_source( "kernel_observer", "render_duration_ns")}}, {{"label", "消费者反馈"}, {"active_value", "consumer_limited"}, {"duration_source", described_source( "kernel_observer", "consumer_interval_ns")}, {"enabled_source", described_source( "kernel_observer", "consumer_feedback_enabled")}} })} }}, {"observer", { {"aria_label", "Kernel 低延迟全量统计"}, {"header", { {"prefix", "KERNEL"}, {"suffix", "OBSERVER"}, {"event_label", "事件"}, {"mode", described_dashboard_field( "kernel_observer", "mode")}, {"limit", described_dashboard_field( "kernel_observer", "limit_state")}, {"event", described_dashboard_field( "kernel_observer", "last_event")} }}, {"sections", Json::array({ {{"class_name", "observer-counters"}, {"fields", observer_fields(observer_counter_field)}}, {{"class_name", "latency-summary"}, {"fields", observer_summary_fields()}}, {{"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", observer_fields(observer_detail_field)}} })} }} }; dashboard["observer"]["descriptor"] = adminive::to_descriptor_json(); dashboard["observer"]["consumer_feedback_descriptor"] = adminive::to_descriptor_json(); return dashboard; } Json frame_mode_contract(Gallery_Frame_Mode mode) { switch (mode) { case Gallery_Frame_Mode::Manual: return {{"id", gallery_enum_id(mode)}, {"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", gallery_enum_id(mode)}, {"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", gallery_enum_id(mode)}, {"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", gallery_enum_id(Gallery_Frame_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 auto frame_modes = magic_enum::enum_values(); 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 = gallery_enum_id(mode); const std::size_t actions = gallery_detail::registered_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::registered_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", magic_enum::enum_names()}}; 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& action : gallery_detail::registered_actions(case_id, frame_mode)) result["actions"]["data"].push_back( adminive::to_frontend_json(action)); 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 = gallery_enum_cast( 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::registered_actions(case_id, frame_mode); return std::any_of(available.begin(), available.end(), [action_id](const gallery_detail::Action_Model& item) { return item.id == action_id; }); } } // namespace renderive::web