Files
Renderive/web_server/app/Gallery_Protocol.cpp
2026-08-13 00:35:00 +08:00

695 lines
33 KiB
C++

#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 <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;
};
using Action_Model = Gallery_Action_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;
}
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;
}
void append_session_actions(Gallery_Frame_Mode frame_mode,
std::vector<Action_Model>& result) {
switch (frame_mode) {
case Gallery_Frame_Mode::Manual:
append_gallery_actions<Gallery_Session_Control<Manual_Scene2D>>(result);
return;
case Gallery_Frame_Mode::Low_Latency:
append_gallery_actions<Gallery_Session_Control<Scene2D>>(result);
return;
case Gallery_Frame_Mode::Playback:
append_gallery_actions<Gallery_Session_Control<Playback_Scene2D>>(result);
return;
}
throw std::invalid_argument("unknown gallery frame mode");
}
std::size_t session_control_count(Gallery_Frame_Mode frame_mode) {
switch (frame_mode) {
case Gallery_Frame_Mode::Manual:
return gallery_control_count<Gallery_Session_Control<Manual_Scene2D>>();
case Gallery_Frame_Mode::Low_Latency:
return gallery_control_count<Gallery_Session_Control<Scene2D>>();
case Gallery_Frame_Mode::Playback:
return gallery_control_count<Gallery_Session_Control<Playback_Scene2D>>();
}
throw std::invalid_argument("unknown gallery frame mode");
}
std::vector<Action_Model> registered_actions(std::string_view case_id,
Gallery_Frame_Mode frame_mode) {
std::vector<Action_Model> result;
append_session_actions(frame_mode, 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<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();
}
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 <class Object>
const Json& described_field(std::string_view name) {
static const Json descriptor = adminive::to_descriptor_json<Json, Object>();
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<std::string>() == name;
});
if (found == fields.end())
throw std::logic_error("missing Adminive dashboard field: " + std::string(name));
return *found;
}
template <class Object>
std::string described_source(std::string_view prefix, std::string_view name) {
static_cast<void>(described_field<Object>(name));
return std::string(prefix) + "." + std::string(name);
}
template <class Object>
Json described_dashboard_field(std::string_view prefix, std::string_view name) {
const Json& field = described_field<Object>(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<std::string>(),
described_source<Object>(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 <class Predicate>
Json observer_fields(Predicate&& predicate) {
const Json descriptor =
adminive::to_descriptor_json<Json, renderive::Frame_Observer_Snapshot>();
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<renderive::Frame_Observer_Snapshot>(
"kernel_observer", name);
if (name == "frequency_hz") {
field["enabled_source"] =
described_source<renderive::Frame_Observer_Snapshot>(
"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, Gallery_Consumer_Feedback_Snapshot>();
Json result = Json::array();
for (const Json& field : descriptor.at("fields")) {
const std::string name = field.at("name");
result.push_back(described_dashboard_field<Gallery_Consumer_Feedback_Snapshot>(
"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<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "limit_state");
bottleneck["format"] = "duration_enum";
bottleneck["duration_sources"] = {
{"frequency_limited", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "target_interval_ns")},
{"paint_limited", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "paint_duration_ns")},
{"render_limited", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "render_duration_ns")},
{"consumer_limited", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "consumer_interval_ns")}
};
Json dashboard{
{"value_maps", {
{"enabled", {{"true", "启用"}, {"false", "关闭"}}},
{"consumer_feedback_source", {
{"disabled", "总开关关闭"}, {"none", ""}, {"pixel", "像素响应"},
{"presentation", "浏览器呈现"}, {"manual", "手动"}
}},
{"limit_state", {
{"frequency_limited", "内核频率受限"}, {"paint_limited", "绘制事件受限"},
{"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("Core 滑动平均 ms", "performance.average_render_ms", "fixed", 2),
dashboard_field("Core P95 ms", "performance.render_p95_ms", "fixed", 2),
dashboard_field("Core P99 ms", "performance.render_p99_ms", "fixed", 2),
dashboard_field("像素编码 ms", "performance.last_pixel_encode_ms", "fixed", 2),
dashboard_field("编码 P95 ms", "performance.pixel_encode_p95_ms", "fixed", 2),
dashboard_field("编码 P99 ms", "performance.pixel_encode_p99_ms", "fixed", 2),
dashboard_field("WS 平均 ms", "client_performance.frame_round_trip_average_ms", "fixed", 2),
dashboard_field("WS P95 ms", "client_performance.frame_round_trip_p95_ms", "fixed", 2),
dashboard_field("WS P99 ms", "client_performance.frame_round_trip_p99_ms", "fixed", 2),
dashboard_field("呈现平均 ms", "client_performance.display_interval_average_ms", "fixed", 2),
dashboard_field("呈现 P95 ms", "client_performance.display_interval_p95_ms", "fixed", 2),
dashboard_field("呈现 P99 ms", "client_performance.display_interval_p99_ms", "fixed", 2),
dashboard_field("响应负载 MB/s", "performance.pixel_payload_megabytes_per_second", "fixed", 1),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "pending_frame_count"),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "dropped_frame_count"),
std::move(point_pair), std::move(bottleneck),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"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", {
{"kernel", {
{"title", "内核帧观察器"},
{"source", "kernel_observer"},
{"descriptor", adminive::to_descriptor_json<
Json, renderive::Frame_Observer_Snapshot>()}
}},
{"renderables_source", "renderable_observers"},
{"renderable_fields", Json::array({
"event", "event_time_ns", "cache_update_count", "publish_count"
})}
}},
{"performance", {
{"renderables_source", "renderable_observers"},
{"renderable_fields", Json::array({
"event", "event_time_ns", "cache_update_count", "publish_count"
})},
{"resources", Json::array({
{
{"title", "渲染性能"},
{"source", "performance"},
{"descriptor", adminive::to_descriptor_json<
Json, Gallery_Render_Performance>()}
},
{
{"title", "浏览器性能"},
{"source", "client_performance"},
{"descriptor", adminive::to_descriptor_json<
Json, Gallery_Client_Performance>()}
}
})}
}}
}},
{"limits", {
{"aria_label", "低延迟限速来源"}, {"title", "限速来源"},
{"current_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "limit_state")},
{"active_label", "当前瓶颈"}, {"inactive_label", "未受限"},
{"disabled_label", "已关闭"},
{"fields", Json::array({
{{"label", "内核用户频率"}, {"active_value", "frequency_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "target_interval_ns")},
{"enabled_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "frequency_limit_enabled")}},
{{"label", "内核绘制事件"}, {"active_value", "paint_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "paint_duration_ns")}},
{{"label", "内核后台渲染"}, {"active_value", "render_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "render_duration_ns")}},
{{"label", "消费者反馈"}, {"active_value", "consumer_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "consumer_interval_ns")},
{"enabled_source", described_source<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "consumer_feedback_enabled")}}
})}
}},
{"observer", {
{"aria_label", "内核低延迟全量统计"},
{"header", {
{"prefix", "内核"}, {"suffix", "观察器"}, {"event_label", "事件"},
{"mode", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "mode")},
{"limit", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "limit_state")},
{"event", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"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 平均周期", "client_performance.display_interval_average_ms", "milliseconds", 3),
dashboard_field("RAF P95 周期", "client_performance.display_interval_p95_ms", "milliseconds", 3),
dashboard_field("RAF P99 周期", "client_performance.display_interval_p99_ms", "milliseconds", 3),
dashboard_field("RAF 周期标准差", "client_performance.display_interval_deviation_ms", "milliseconds", 3),
dashboard_field("WS 往返", "client_performance.frame_round_trip_ms", "milliseconds", 3),
dashboard_field("WS 往返平均", "client_performance.frame_round_trip_average_ms", "milliseconds", 3),
dashboard_field("WS 往返 P95", "client_performance.frame_round_trip_p95_ms", "milliseconds", 3),
dashboard_field("WS 往返 P99", "client_performance.frame_round_trip_p99_ms", "milliseconds", 3),
dashboard_field("WS 往返标准差", "client_performance.frame_round_trip_deviation_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", "内核各阶段等待耗时"},
{"fields", observer_fields(observer_detail_field)}}
})}
}}
};
dashboard["observer"]["descriptor"] =
adminive::to_descriptor_json<Json, renderive::Frame_Observer_Snapshot>();
dashboard["observer"]["consumer_feedback_descriptor"] =
adminive::to_descriptor_json<Json, Gallery_Consumer_Feedback_Snapshot>();
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<Json, gallery_detail::Case_Model>();
result["frame_modes"] = Json::array();
constexpr auto frame_modes = magic_enum::enum_values<Gallery_Frame_Mode>();
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();
const std::size_t renderable_controls = gallery_renderable_control_count(item.id);
for (const auto mode : frame_modes) {
const std::string key = gallery_enum_id(mode);
const std::size_t controls =
gallery_detail::session_control_count(mode) + renderable_controls;
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"] =
gallery_detail::session_control_count(Gallery_Frame_Mode::Low_Latency) +
renderable_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<Image_Interpolation_Mode>()}};
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,
bool manual_refresh) {
Json result = protocol_base();
result["type"] = manual_refresh ? "refresh_state" : "case_state";
result["case"] = case_contract(case_id);
result["frame_mode"] = frame_mode_contract(frame_mode);
try {
result["controls"] = Json::parse(controls_json.begin(), controls_json.end());
} catch (const std::exception&) {
result["controls"] = {
{"resources", Json::array()},
{"observers", Json::array()},
{"render_plan", Json::object()}
};
}
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& action : gallery_detail::registered_actions(case_id, frame_mode))
result["actions"]["data"].push_back(
adminive::to_frontend_json<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_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 = gallery_enum_cast<Gallery_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::optional<Gallery_Control_Patch_Request> 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<std::string>(), 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