Files
Renderive/web_server/app/Gallery_Protocol.cpp
T
2026-08-12 00:06:44 +08:00

788 lines
43 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "Gallery_Protocol.h"
#include "Gallery_Properties_Adminive.h"
#include "adminive/adminive.hpp"
#include "adminive/adapters/nlohmann_json.hpp"
#include <algorithm>
#include <array>
#include <cctype>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
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<Case_Model>& cases() {
static const std::vector<Case_Model> 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<Action_Definition> actions(std::string_view case_id,
Gallery_Frame_Mode frame_mode) {
std::vector<Action_Definition> result{
action("reset", "恢复本图默认值", "Gallery_Scene::rebuild", "会话"),
action("rebuild_renderable", "移除并重建控件", "Plot_Core::remove_renderable / Builder::build", "会话"),
action("toggle_view", "切换 View 生命周期", "Plot_Core::activate_view / deactivate_view", "会话"),
action("axis_set_start", "写入坐标范围起点", "Axis::set<&Axis_Properties::coordinates>", "Axis"),
action("axis_set_length", "写入坐标范围跨度", "Axis::set<&Axis_Properties::coordinates>", "Axis"),
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());
}
template <class Properties>
Gallery_Property_Model make_property_model(Gallery_Frame_Mode frame_mode) {
if(frame_mode == Gallery_Frame_Mode::Low_Latency) {
using T = Gallery_Low_Latency_Case_Properties<Properties>;
return Gallery_Property_Model{std::in_place_type<T>};
}
return Gallery_Property_Model{std::in_place_type<Properties>};
}
Gallery_Property_Model make_property_model(std::string_view case_id, Gallery_Frame_Mode frame_mode) {
if(case_id == "spectrum")
return make_property_model<Gallery_Spectrum_Properties>(frame_mode);
if(case_id == "afterglow")
return make_property_model<Gallery_Afterglow_Properties>(frame_mode);
if(case_id == "sweep_spectrum")
return make_property_model<Gallery_Sweep_Spectrum_Properties>(frame_mode);
if(case_id == "waterfall")
return make_property_model<Gallery_Waterfall_Properties>(frame_mode);
if(case_id == "frequency_trace")
return make_property_model<Gallery_Frequency_Trace_Properties>(frame_mode);
if(case_id == "selection_overlay")
return make_property_model<Gallery_Selection_Overlay_Properties>(frame_mode);
if(case_id == "constellation")
return make_property_model<Gallery_Constellation_Properties>(frame_mode);
return make_property_model<Gallery_Axis_Lab_Properties>(frame_mode);
}
Gallery_Value gallery_value(const Json& value) {
if(value.is_boolean())
return value.get<bool>();
if(value.is_number())
return value.get<double>();
return value.get<std::string>();
}
template <class T>
std::optional<Gallery_Value> property_value(const T& value, std::string_view id) {
std::optional<Gallery_Value> result;
const auto descriptor = adminive::describe<T>();
std::apply([&](const auto&... field) {
([&] {
if(!result && field.name() == id)
result = gallery_value(adminive::encode_json_value<Json>(field.get(value)));
}(), ...);
}, descriptor.fields());
return result;
}
template <class T>
std::size_t property_count(const T&) {
return std::tuple_size_v<adminive::Descriptor_Fields<T>>;
}
template <class T>
Json property_resource(const T& value) {
return {
{"descriptor", adminive::to_descriptor_json<Json, T>()},
{"view", adminive::to_view_json<Json, T>(adminive::describe_edit_view<T>().titled("控件属性").submit("应用"))},
{"data", adminive::to_frontend_json<Json>(value)}
};
}
template <class T>
adminive::Update_Result apply_property_patch(T& value, const Json& patch) {
return adminive::apply_frontend_patch<Json>(value, patch);
}
adminive::Table_View action_view();
} // namespace renderive::web::gallery_detail
namespace adminive {
template <>
struct Type_Descriptor<renderive::web::gallery_detail::Case_Model> {
static auto get() {
using T = renderive::web::gallery_detail::Case_Model;
return object<T>("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<renderive::web::gallery_detail::Action_Model> {
static auto get() {
using T = renderive::web::gallery_detail::Action_Model;
return object<T>("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<T>(
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<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<Gallery_Frame_Mode> 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
Gallery_Value gallery_property_value(const Gallery_State& state, std::string_view id) {
const auto result = std::visit([id](const auto& properties) {
return gallery_detail::property_value(properties, id);
}, state.properties);
if(!result)
throw std::out_of_range("gallery property does not exist: " + std::string(id));
return *result;
}
bool gallery_has_property(const Gallery_State& state, std::string_view id) {
return std::visit([id](const auto& properties) {
return gallery_detail::property_value(properties, id).has_value();
}, state.properties);
}
std::size_t gallery_property_count(const Gallery_State& state) {
return std::visit([](const auto& properties) {
return gallery_detail::property_count(properties);
}, state.properties);
}
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<Json, gallery_detail::Case_Model>();
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<Json>(item);
entry["control_count_by_mode"] = Json::object();
entry["action_count_by_mode"] = Json::object();
for (const auto mode : frame_modes) {
const std::string key(frame_mode_id(mode));
const std::size_t mode_controls = control_count(item.id, mode);
const std::size_t mode_actions = action_count(item.id, mode);
entry["control_count_by_mode"][key] = mode_controls;
entry["action_count_by_mode"][key] = mode_actions;
controls_total += mode_controls;
actions_total += mode_actions;
}
entry["control_count"] = control_count(item.id, Gallery_Frame_Mode::Low_Latency);
entry["action_count"] = action_count(item.id, Gallery_Frame_Mode::Low_Latency);
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;
}
Gallery_State Gallery_Protocol::default_state(std::string_view case_id,
Gallery_Frame_Mode frame_mode) {
return {gallery_detail::make_property_model(case_id, frame_mode)};
}
Gallery_State Gallery_Protocol::default_state(std::string_view case_id) {
return default_state(case_id, Gallery_Frame_Mode::Low_Latency);
}
std::string Gallery_Protocol::case_json(std::string_view case_id,
const Gallery_State& state,
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);
result["controls"] = std::visit([](const auto& properties) {
return gallery_detail::property_resource(properties);
}, state.properties);
result["actions"] = {
{"descriptor", adminive::to_descriptor_json<Json, gallery_detail::Action_Model>()},
{"view", adminive::to_view_json<Json, gallery_detail::Action_Model>(
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<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::case_json(std::string_view case_id,
const Gallery_State& state,
std::string_view telemetry_json,
std::string_view notice) {
return case_json(case_id, state, telemetry_json, notice,
Gallery_Frame_Mode::Low_Latency);
}
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();
}
Gallery_Patch_Result Gallery_Protocol::apply_patch(std::string_view case_id,
const Gallery_State& current,
std::string_view message,
Gallery_Frame_Mode frame_mode) {
try {
const Json request = gallery_detail::parse_request(message);
if (!request.is_object() || request.value("category", "") != "event" ||
request.value("type", "") != "gallery_patch" ||
!request.contains("patch") || !request.at("patch").is_object()) {
return {std::nullopt, error_json("gallery_patch 格式无效")};
}
Gallery_State candidate = current;
const auto update = std::visit([&request](auto& properties) {
return gallery_detail::apply_property_patch(properties, request.at("patch"));
}, candidate.properties);
if(!update.success) {
Json response = protocol_base();
response["type"] = "error";
response["message"] = update.message;
response["field_errors"] = update.field_errors;
return {std::nullopt, response.dump()};
}
return {std::move(candidate), {}};
} catch (const std::exception& error) {
return {std::nullopt, error_json(error.what())};
}
}
Gallery_Patch_Result Gallery_Protocol::apply_patch(std::string_view case_id,
const Gallery_State& current,
std::string_view message) {
return apply_patch(case_id, current, message, Gallery_Frame_Mode::Low_Latency);
}
std::optional<Gallery_Open_Request> 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<std::string>();
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<std::string>());
return mode ? std::optional<Gallery_Open_Request>(Gallery_Open_Request{value, *mode})
: std::nullopt;
} catch (const std::exception&) {
return std::nullopt;
}
}
std::optional<Gallery_Action_Request> 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<std::string>()};
if (request.contains("argument")) {
const Json& argument = request.at("argument");
if (argument.is_boolean())
result.argument = argument.get<bool>();
else if (argument.is_number()) {
const double value = argument.get<double>();
if (!std::isfinite(value))
return std::nullopt;
result.argument = value;
} else if (argument.is_string())
result.argument = argument.get<std::string>();
else
return std::nullopt;
}
return result;
} catch (const std::exception&) {
return std::nullopt;
}
}
std::size_t Gallery_Protocol::control_count(std::string_view case_id,
Gallery_Frame_Mode frame_mode) {
return gallery_property_count(default_state(case_id, frame_mode));
}
std::size_t Gallery_Protocol::control_count(std::string_view case_id) {
return control_count(case_id, Gallery_Frame_Mode::Low_Latency);
}
std::size_t Gallery_Protocol::action_count(std::string_view case_id,
Gallery_Frame_Mode frame_mode) {
return gallery_detail::actions(case_id, frame_mode).size();
}
std::size_t Gallery_Protocol::action_count(std::string_view case_id) {
return action_count(case_id, Gallery_Frame_Mode::Low_Latency);
}
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