#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace aethera::web::detail { struct Renderable_Field_Role_Category {}; template struct Renderable_Field_Role_Attribute { using attribute_category = Renderable_Field_Role_Category; static constexpr bool single_valued = true; static constexpr bool inheritable = false; static constexpr auto value = Role; }; template struct Prop_Field { static constexpr auto member = Member; static constexpr auto key = Key; static constexpr auto description = Description; static constexpr auto role = Renderable_Field_Role::prop; }; template struct State_Field { using tag_type = Tag; static constexpr auto member = Member; static constexpr auto key = Key; static constexpr auto description = Description; static constexpr auto role = Renderable_Field_Role::state; }; template class Renderable_Adapter final : public structive::Property_Object, structive::No_Lock_Policy> { public: explicit Renderable_Adapter(Object& value) : object(&value) {} private: template friend struct Renderable_Field_Accessor; Object* object; }; template struct Renderable_Field_Accessor; template struct Renderable_Field_Accessor, Prop_Field> { using object_type = Renderable_Adapter; using value_type = typename structive::Member_Pointer_Traits::value_type; using storage_identity = void; using dependency_spec = structive::No_Property_Dependencies; static constexpr auto description = Description; static constexpr bool readable = true; static constexpr bool writable = true; static constexpr bool synchronized_view_read = false; static constexpr bool trusted_object_access = true; [[nodiscard]] value_type read(const object_type& adapter) const { return adapter.object->template get(); } void write(object_type& adapter, value_type value) const { adapter.object->template set(std::move(value)); } }; template struct Renderable_Field_Accessor, State_Field> { using object_type = Renderable_Adapter; using value_type = typename structive::Member_Pointer_Traits::value_type; using storage_identity = void; using dependency_spec = structive::No_Property_Dependencies; static constexpr auto description = Description; static constexpr bool readable = true; static constexpr bool writable = false; static constexpr bool synchronized_view_read = false; static constexpr bool trusted_object_access = true; [[nodiscard]] value_type read(const object_type& adapter) const { value_type result{}; adapter.object->template access_state([&](const auto& state) { result = state.*Member; }); return result; } }; template constexpr auto renderable_field_descriptor() { using Accessor = Renderable_Field_Accessor; using Role = Renderable_Field_Role_Attribute; using Key = structive::Key_Attribute; return structive::Property_Descriptor{{}, {Key{}, Role{}}}; } } namespace structive { template struct Type_Descriptor> { static auto get() { using Adapter = aethera::web::detail::Renderable_Adapter; return object(synchronization(sync_all_unsynchronized), aethera::web::detail::renderable_field_descriptor()...); } }; } namespace aethera::web::detail { template struct is_std_array : std::false_type {}; template struct is_std_array> : std::true_type {}; template inline constexpr bool is_std_array_v = is_std_array>::value; template struct is_pair : std::false_type {}; template struct is_pair> : std::true_type {}; template inline constexpr bool is_pair_v = is_pair>::value; template concept Json_Sequence = std::ranges::range && requires(T value, typename T::value_type item) { value.clear(); value.emplace_back(std::move(item)); } && !std::same_as, std::string>; template nlohmann::json encode_protocol_value(const Value& value); template void decode_protocol_value(Value& target, const nlohmann::json& input); template nlohmann::json encode_aggregate(const Value& value, std::index_sequence) { nlohmann::json result = nlohmann::json::object(); ((result[std::string(boost::pfr::get_name())] = encode_protocol_value(boost::pfr::get(value))), ...); return result; } template void decode_aggregate(Value& target, const nlohmann::json& input, std::index_sequence) { (decode_protocol_value(boost::pfr::get(target), input.at(std::string(boost::pfr::get_name()))), ...); } template nlohmann::json encode_protocol_value(const Value& value) { using Type = std::remove_cvref_t; if constexpr (std::same_as || std::integral || std::floating_point || std::same_as) { return value; } else if constexpr (std::same_as) { return std::string(1, value); } else if constexpr (std::is_enum_v) { return magic_enum::enum_name(value); } else if constexpr (std::same_as) { return {{"stops", encode_protocol_value(value.stops)}}; } else if constexpr (is_pair_v) { return nlohmann::json::array({encode_protocol_value(value.first), encode_protocol_value(value.second)}); } else if constexpr (is_std_array_v || Json_Sequence) { nlohmann::json result = nlohmann::json::array(); for (const auto& item : value) result.push_back(encode_protocol_value(item)); return result; } else if constexpr (std::is_aggregate_v) { return encode_aggregate(value, std::make_index_sequence>{}); } else { static_assert(std::is_aggregate_v, "Aethera protocol needs an explicit codec for this value type"); } } template void decode_protocol_value(Value& target, const nlohmann::json& input) { using Type = std::remove_cvref_t; if constexpr (std::same_as || std::integral || std::floating_point || std::same_as) { target = input.template get(); } else if constexpr (std::same_as) { const auto text = input.template get(); if (text.size() != 1) throw std::invalid_argument("character value must contain exactly one character"); target = text.front(); } else if constexpr (std::is_enum_v) { const auto value = magic_enum::enum_cast(input.template get()); if (!value) throw std::invalid_argument("unknown enum value"); target = *value; } else if constexpr (std::same_as) { decode_protocol_value(target.stops, input.at("stops")); } else if constexpr (is_pair_v) { if (!input.is_array() || input.size() != 2) throw std::invalid_argument("pair value must be a two-item array"); decode_protocol_value(target.first, input[0]); decode_protocol_value(target.second, input[1]); } else if constexpr (is_std_array_v) { if (!input.is_array() || input.size() != target.size()) throw std::invalid_argument("array has the wrong size"); for (std::size_t index = 0; index < target.size(); ++index) decode_protocol_value(target[index], input[index]); } else if constexpr (Json_Sequence) { if (!input.is_array()) throw std::invalid_argument("value must be an array"); Type updated; for (const auto& encoded : input) { typename Type::value_type item{}; decode_protocol_value(item, encoded); updated.emplace_back(std::move(item)); } target = std::move(updated); } else if constexpr (std::is_aggregate_v) { if (!input.is_object()) throw std::invalid_argument("value must be an object"); decode_aggregate(target, input, std::make_index_sequence>{}); } else { static_assert(std::is_aggregate_v, "Aethera protocol needs an explicit codec for this value type"); } } template constexpr std::string_view protocol_editor() { using Type = std::remove_cvref_t; if constexpr (std::same_as) return "boolean"; if constexpr (std::integral) return "integer"; if constexpr (std::floating_point) return "number"; if constexpr (std::same_as || std::same_as) return "text"; if constexpr (std::is_enum_v) return "select"; if constexpr (std::same_as || std::same_as) return "color"; if constexpr (std::same_as) return "point2"; if constexpr (std::same_as || std::same_as) return "size"; if constexpr (std::same_as) return "rect"; if constexpr (std::same_as) return "range"; if constexpr (std::same_as) return "pen"; if constexpr (std::same_as) return "brush"; if constexpr (std::same_as) return "font"; if constexpr (std::same_as) return "color-map"; if constexpr (std::same_as) return "vector3"; if constexpr (std::same_as) return "matrix4"; return "json"; } inline std::string_view protocol_field_label(std::string_view key) { static constexpr std::pair labels[] = { {"position", "位置"}, {"viewport", "视口尺寸"}, {"background", "背景颜色"}, {"clear_color", "清屏颜色"}, {"view_active", "启用视图"}, {"pixel_length", "轴线长度"}, {"orientation", "轴线方向"}, {"tick_length", "主刻度长度"}, {"sub_tick_length", "次刻度长度"}, {"axis_pen", "轴线画笔"}, {"unit_text", "单位文字"}, {"unit_text_font", "单位字体"}, {"unit_text_pen", "单位文字画笔"}, {"unit_text_background_brush", "单位文字背景"}, {"label_rotation_degrees", "标签旋转角度"}, {"coordinate_range", "坐标范围"}, {"precision", "小数精度"}, {"locale", "数字区域格式"}, {"wheel_enabled", "允许滚轮缩放"}, {"drag_enabled", "允许拖动平移"}, {"visible_count", "可见数量"}, {"tick_label_spacing_px", "刻度文字间距"}, {"estimated_label_width_px", "预计标签宽度"}, {"format", "时间格式"}, {"newest_at_start", "最新数据置于起点"}, {"center_frequency", "中心频率"}, {"partition_count", "分区数量"}, {"partition_mode", "分区模式"}, {"interpolation_mode", "插值模式"}, {"visible_range_only", "仅处理可见范围"}, {"frequency_range", "频率范围"}, {"power_range", "功率范围"}, {"i_range", "同相范围"}, {"q_range", "正交范围"}, {"phase_offset_radians", "相位偏移"}, {"pen", "画笔"}, {"samples", "样本数据"}, {"rows", "瀑布图行数据"}, {"color_map", "颜色映射"}, {"tooltip_enabled", "启用提示框"}, {"tooltip_font", "提示框字体"}, {"tooltip_text_pen", "提示文字画笔"}, {"tooltip_background_brush", "提示框背景"}, {"frequency_bin_count", "频率箱数量"}, {"frequency_point_size", "频率网格数量"}, {"power_point_size", "功率网格数量"}, {"attenuation_rate", "衰减速率"}, {"interpolate", "启用插值"}, {"spectra", "频谱历史"}, {"blocks", "扫频块数据"}, {"bins_per_block", "每块频点数"}, {"block_count", "扫频块数量"}, {"max_hold_visible", "显示最大保持"}, {"min_hold_visible", "显示最小保持"}, {"max_marker_visible", "显示最大标记"}, {"min_marker_visible", "显示最小标记"}, {"sweep_region_visible", "显示扫频区域"}, {"sweep_frequency_range", "扫频范围"}, {"max_brush", "最大保持画刷"}, {"current_brush", "当前曲线画刷"}, {"min_brush", "最小保持画刷"}, {"max_pen", "最大保持画笔"}, {"current_pen", "当前曲线画笔"}, {"min_pen", "最小保持画笔"}, {"selected_marker_pen", "选中标记画笔"}, {"marker_pen", "标记画笔"}, {"middle_frequency_pen", "中心频率画笔"}, {"sweep_region_brush", "扫频区域画刷"}, {"current_frequency_pen", "当前频率指示线"}, {"custom_markers", "自定义标记"}, {"selected_marker", "当前选中标记"}, {"point_lifetime_ms", "点保留时间"}, {"type", "星座类型"}, {"point_color", "数据点颜色"}, {"anchor_color", "参考点颜色"}, {"points", "星座点数据"}, {"label_font", "标签字体"}, {"label_pen", "标签画笔"}, {"selection_brush", "选区画刷"}, {"selection_border_pen", "选区边框画笔"}, {"selected_regions", "已选区域"}, {"transform", "空间变换"}, {"visible", "是否可见"}, {"depth_test", "深度测试"}, {"items", "项目数据"}, {"prepare_dirty", "准备阶段待更新"}, {"paint_dirty", "绘制阶段待更新"}, {"prepare_executed", "准备阶段已执行"}, {"paint_executed", "绘制阶段已执行"}, {"prepare_graph_rebuilt", "准备任务图已重建"}, {"paint_graph_rebuilt", "绘制任务图已重建"}, {"prepare_task_count", "准备任务数量"}, {"paint_task_count", "绘制任务数量"}, {"prepare_execution_time_ns", "准备耗时"}, {"paint_execution_time_ns", "绘制耗时"}, {"taskflow_rebuilt", "场景任务图已重建"}, {"renderable_count", "可渲染对象数量"}, {"taskflow_task_count", "场景任务数量"}, {"taskflow_dependency_count", "任务依赖数量"}, {"taskflow_max_predecessors", "最大前驱数量"}, {"taskflow_max_successors", "最大后继数量"}, {"taskflow_execution_time_ns", "场景执行耗时"}, {"next_tick", "下一时间刻度"}, {"sample_count", "样本数量"}, {"rendered_point_count", "已绘制点数量"}, {"selectable_marker_count", "可选标记数量"}, {"stored_block_count", "已保存数据块数量"}, {"stored_point_count", "已保存数据点数量"}, {"history_count", "历史帧数量"}, {"latest_spectrum_point_count", "最新频谱点数量"}, {"rendered_cell_count", "已绘制单元数量"}, {"row_count", "瀑布图行数量"}, {"point_count", "数据点数量"}, {"selected_region_count", "已选区域数量"}, {"item_count", "项目数量"}, {"prepared_item_count", "已准备项目数量"}, {"prepared_revision", "已准备修订号"} }; const auto found = std::ranges::find_if(labels, [key](const auto& entry) { return entry.first == key; }); return found == std::end(labels) ? key : found->second; } template nlohmann::json adapter_schema(const Adapter& adapter, std::string_view id, std::string_view label, std::string_view kind) { nlohmann::json fields = nlohmann::json::array(); nlohmann::json state = nlohmann::json::object(); structive::type_descriptor().for_each_property([&](auto, const auto& field) { using Field = std::remove_cvref_t; using Value = typename Field::value_type; const auto value = field.accessor.read(adapter); const bool editable = field.template attribute().value == Renderable_Field_Role::prop; const auto field_label = protocol_field_label(field.key()); nlohmann::json item{{"key", field.key()}, {"label", field_label}, {"editor", protocol_editor()}, {"editable", editable}, {"description", std::string(field_label) + "。协议字段:" + std::string(field.key()) + "。"}, {"technical_description", std::string(Field::accessor_type::description.view())}, {"value", encode_protocol_value(value)}}; if constexpr (std::same_as) item["color_channel_scale"] = "normalized"; else if constexpr (std::same_as) item["color_channel_scale"] = "byte"; if (editable) { if constexpr (std::is_enum_v) { item["options"] = nlohmann::json::array(); for (const auto enum_value : magic_enum::enum_values()) { const auto name = magic_enum::enum_name(enum_value); item["options"].push_back({{"value", name}, {"label", name}}); } } } else { state[field.key()] = encode_protocol_value(value); } fields.push_back(std::move(item)); }); return {{"id", id}, {"label", label}, {"kind", kind}, {"fields", std::move(fields)}, {"state", std::move(state)}}; } template nlohmann::json write_adapter_prop(Adapter& adapter, std::string_view key, const nlohmann::json& input) { nlohmann::json result{{"success", false}, {"key", key}}; const bool found = structive::visit_schema_property(structive::type_descriptor(), key, [&](auto, const auto& field) { using Field = std::remove_cvref_t; using Value = typename Field::value_type; if constexpr (!Field::writable) { result["error"] = "property is read-only"; } else { try { Value value{}; decode_protocol_value(value, input); const auto status = adapter.runtime_write(key, typeid(Value), &value); if (status != structive::Runtime_Access_Result::ok) { result["error"] = "property write was rejected"; return; } result["success"] = true; result["value"] = encode_protocol_value(field.accessor.read(adapter)); } catch (const std::exception& error) { result["error"] = error.what(); } } }); if (!found) result["error"] = "unknown property"; return result; } template Renderable_Descriptor_Model::Renderable_Descriptor_Model( std::string id, std::string label, std::string kind, Adapter value) : component_id(std::move(id)), component_label(std::move(label)), component_kind(std::move(kind)), adapter(std::move(value)) {} template std::string_view Renderable_Descriptor_Model::id() const noexcept { return component_id; } template nlohmann::json Renderable_Descriptor_Model::schema() const { return adapter_schema(adapter, component_id, component_label, component_kind); } template nlohmann::json Renderable_Descriptor_Model::write_prop(std::string_view key, const nlohmann::json& value) { return write_adapter_prop(adapter, key, value); } template std::unique_ptr make_renderable_descriptor( std::string id, std::string label, std::string kind, Adapter adapter) { return std::make_unique>( std::move(id), std::move(label), std::move(kind), std::move(adapter)); } }