2D 绘图缓存节点

This commit is contained in:
2026-08-21 10:16:16 +08:00
parent 26a89335b4
commit 855e25981d
55 changed files with 772 additions and 488 deletions
+19 -21
View File
@@ -1,17 +1,5 @@
#include "Graph_Metadata.hpp"
#include "Graph_Metadata.hpp" /* PFR 属性与状态描述。 */
#include <algorithm>
namespace {
struct State_Field_Metadata {
template <std::size_t Index, typename Field> auto operator()(Field field) const {
if constexpr (Index == 0) return field.label("Frame sequence").description("Latest completed frame sent by this graph.");
if constexpr (Index == 1) return field.label("Input elements").description("Primary published element count read from the graph State.");
return field.label("Rendered elements").description("Prepared render element count read from the graph State.");
}
};
}
namespace adminive {
auto Type_Descriptor<aethera::web::Graph_State_Document>::get() { return reflected_object_with<aethera::web::Graph_State_Document>("graph_state", "Published graph state", State_Field_Metadata{}); }
}
namespace aethera::web {
const std::vector<Graph_Descriptor>& graph_catalog() {
static const std::vector<Graph_Descriptor> value{
@@ -28,16 +16,26 @@ const std::vector<Graph_Descriptor>& graph_catalog() {
}
const Graph_Descriptor* find_graph(std::string_view graph_id) { const auto& catalog = graph_catalog(); const auto found = std::ranges::find(catalog, graph_id, &Graph_Descriptor::id); return found == catalog.end() ? nullptr : &*found; }
nlohmann::json graph_catalog_json() { nlohmann::json result = nlohmann::json::array(); for (const auto& graph : graph_catalog()) result.push_back({{"id", graph.id}, {"title", graph.title}, {"category", graph.category}, {"description", graph.description}, {"dimension", graph.three_dimensional ? "3D" : "2D"}, {"websocket", "/ws/graphs/" + graph.id}, {"state", "/api/graphs/" + graph.id + "/state"}, {"descriptor", "/api/graphs/" + graph.id + "/descriptor"}}); return result; }
nlohmann::json graph_state_descriptor_json() { return adminive::to_descriptor_json<nlohmann::json, Graph_State_Document>(); }
nlohmann::json graph_state_json(const Graph_State_Document& state) { return adminive::model_to_json<nlohmann::json>(state, true); }
nlohmann::json graph_state_descriptor_json(std::string_view graph_id) {
using namespace render_2d;
if (graph_id == "spectrum") return adminive::to_descriptor_json<nlohmann::json, Spectrum::State>();
if (graph_id == "frequency_trace") return adminive::to_descriptor_json<nlohmann::json, Frequency_Trace::State>();
if (graph_id == "sweep_spectrum") return adminive::to_descriptor_json<nlohmann::json, Sweep_Spectrum::State>();
if (graph_id == "afterglow") return adminive::to_descriptor_json<nlohmann::json, Afterglow::State>();
if (graph_id == "waterfall") return adminive::to_descriptor_json<nlohmann::json, Waterfall::State>();
if (graph_id == "constellation") return adminive::to_descriptor_json<nlohmann::json, Constellation_Diagram::State>();
if (graph_id == "selection_overlay") return adminive::to_descriptor_json<nlohmann::json, Selection_Rectangle_Overlay::State>();
return {{"fields", nlohmann::json::array()}};
}
nlohmann::json graph_prop_descriptor_json(std::string_view graph_id) {
using namespace render_2d;
if (graph_id == "spectrum") return adminive::to_descriptor_json<nlohmann::json, Spectrum_Editable_Prop_Data>();
if (graph_id == "frequency_trace") return adminive::to_descriptor_json<nlohmann::json, Frequency_Trace_Editable_Prop_Data>();
if (graph_id == "sweep_spectrum") return adminive::to_descriptor_json<nlohmann::json, Sweep_Spectrum_Editable_Prop_Data>();
if (graph_id == "afterglow") return adminive::to_descriptor_json<nlohmann::json, Afterglow_Editable_Prop_Data>();
if (graph_id == "waterfall") return adminive::to_descriptor_json<nlohmann::json, Waterfall_Editable_Prop_Data>();
if (graph_id == "constellation") return adminive::to_descriptor_json<nlohmann::json, Constellation_Diagram_Editable_Prop_Data>();
if (graph_id == "spectrum") return adminive::to_descriptor_json<nlohmann::json, Spectrum::Prop>();
if (graph_id == "frequency_trace") return adminive::to_descriptor_json<nlohmann::json, Frequency_Trace::Prop>();
if (graph_id == "sweep_spectrum") return adminive::to_descriptor_json<nlohmann::json, Sweep_Spectrum::Prop>();
if (graph_id == "afterglow") return adminive::to_descriptor_json<nlohmann::json, Afterglow::Prop>();
if (graph_id == "waterfall") return adminive::to_descriptor_json<nlohmann::json, Waterfall::Prop>();
if (graph_id == "constellation") return adminive::to_descriptor_json<nlohmann::json, Constellation_Diagram::Prop>();
if (graph_id == "selection_overlay") return adminive::to_descriptor_json<nlohmann::json, Selection_Rectangle_Overlay::Prop>();
return {{"fields", nlohmann::json::array()}};
}
}
+3 -28
View File
@@ -1,20 +1,14 @@
#pragma once
#include <adminive/adapters/boost_pfr.hpp>
#include <adminive/adapters/nlohmann_json.hpp>
#include <adminive/adapters/magic_enum.hpp>
#include <adminive/adapters/nlohmann_json.hpp>
#include <adminive/adminive.hpp>
#include <render_2D/plottable/Plottables.hpp>
#include <cstdint>
#include <nlohmann/json.hpp>
#include <string>
#include <string_view>
#include <vector>
namespace aethera::web {
namespace detail {
struct Editable_Prop_Field_Metadata {
template <std::size_t Index, typename Field> auto operator()(Field field) const { return field.editable(); }
};
}
struct Graph_Descriptor {
std::string id{}; /* HTTP 与 WebSocket 使用的稳定图标识。 */
std::string title{}; /* Gallery 卡片显示名称。 */
@@ -22,29 +16,10 @@ struct Graph_Descriptor {
std::string description{}; /* 图用途和数据语义说明。 */
bool three_dimensional{}; /* 是否使用 Datoviz 3D 后端。 */
};
struct Graph_State_Document {
std::uint64_t frame_sequence{}; /* 最近完成并发送的帧序号。 */
std::uint64_t primary_count{}; /* 图自身发布的主要输入元素数量。 */
std::uint64_t rendered_count{}; /* 最近 Prepare 发布的绘制元素数量。 */
};
[[nodiscard]] const std::vector<Graph_Descriptor>& graph_catalog();
[[nodiscard]] const Graph_Descriptor* find_graph(std::string_view graph_id);
[[nodiscard]] nlohmann::json graph_catalog_json();
[[nodiscard]] nlohmann::json graph_state_descriptor_json();
[[nodiscard]] nlohmann::json graph_state_json(const Graph_State_Document& state);
[[nodiscard]] nlohmann::json graph_state_descriptor_json(std::string_view graph_id);
[[nodiscard]] nlohmann::json graph_prop_descriptor_json(std::string_view graph_id);
}
namespace adminive {
template <> struct Reflection_Adapter<aethera::web::Graph_State_Document> : Boost_Pfr_Reflection_Adapter<aethera::web::Graph_State_Document> {};
template <> struct Type_Descriptor<aethera::web::Graph_State_Document> { static auto get(); };
#define AETHERA_DECLARE_PROP_REFLECTION(Type, Id, Label) \
template <> struct Reflection_Adapter<aethera::render_2d::Type> : Boost_Pfr_Reflection_Adapter<aethera::render_2d::Type> {}; \
template <> struct Type_Descriptor<aethera::render_2d::Type> { static auto get() { return reflected_object_with<aethera::render_2d::Type>(Id, Label, aethera::web::detail::Editable_Prop_Field_Metadata{}); } };
AETHERA_DECLARE_PROP_REFLECTION(Spectrum_Editable_Prop_Data, "spectrum_prop", "Spectrum properties")
AETHERA_DECLARE_PROP_REFLECTION(Frequency_Trace_Editable_Prop_Data, "frequency_trace_prop", "Frequency trace properties")
AETHERA_DECLARE_PROP_REFLECTION(Sweep_Spectrum_Editable_Prop_Data, "sweep_spectrum_prop", "Sweep spectrum properties")
AETHERA_DECLARE_PROP_REFLECTION(Afterglow_Editable_Prop_Data, "afterglow_prop", "Afterglow properties")
AETHERA_DECLARE_PROP_REFLECTION(Waterfall_Editable_Prop_Data, "waterfall_prop", "Waterfall properties")
AETHERA_DECLARE_PROP_REFLECTION(Constellation_Diagram_Editable_Prop_Data, "constellation_prop", "Constellation properties")
#undef AETHERA_DECLARE_PROP_REFLECTION
}
#include "Graph_Metadata.ipp" /* 模板和反射特化实现。 */
+113
View File
@@ -0,0 +1,113 @@
#pragma once
#include <boost/pfr.hpp>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace aethera::web::detail {
struct Editable_Field_Metadata {
template <std::size_t Index, typename Field> auto operator()(Field field) const;
};
template <typename Object, typename Members>
struct Member_Pfr_Reflection_Adapter {
static constexpr std::size_t field_count = boost::pfr::tuple_size_v<Members>;
template <std::size_t Index> static decltype(auto) get(Object& value) noexcept;
template <std::size_t Index> static decltype(auto) get(const Object& value) noexcept;
template <std::size_t Index> static constexpr std::string_view name() noexcept;
};
#define AETHERA_MEMBER(Type, Name) decltype(&Type::Name) Name{&Type::Name}
using namespace render_2d;
struct Spectrum_Prop_Members { AETHERA_MEMBER(Spectrum::Prop, center_frequency); AETHERA_MEMBER(Spectrum::Prop, partition_count); AETHERA_MEMBER(Spectrum::Prop, max_hold_visible); AETHERA_MEMBER(Spectrum::Prop, min_hold_visible); AETHERA_MEMBER(Spectrum::Prop, max_marker_visible); AETHERA_MEMBER(Spectrum::Prop, min_marker_visible); AETHERA_MEMBER(Spectrum::Prop, sweep_region_visible); AETHERA_MEMBER(Spectrum::Prop, visible_range_only); AETHERA_MEMBER(Spectrum::Prop, frequency_range); AETHERA_MEMBER(Spectrum::Prop, sweep_frequency_range); AETHERA_MEMBER(Spectrum::Prop, partition_mode); AETHERA_MEMBER(Spectrum::Prop, interpolation_mode); AETHERA_MEMBER(Spectrum::Prop, max_brush); AETHERA_MEMBER(Spectrum::Prop, current_brush); AETHERA_MEMBER(Spectrum::Prop, min_brush); AETHERA_MEMBER(Spectrum::Prop, max_pen); AETHERA_MEMBER(Spectrum::Prop, current_pen); AETHERA_MEMBER(Spectrum::Prop, min_pen); AETHERA_MEMBER(Spectrum::Prop, selected_marker_pen); AETHERA_MEMBER(Spectrum::Prop, marker_pen); AETHERA_MEMBER(Spectrum::Prop, middle_frequency_pen); AETHERA_MEMBER(Spectrum::Prop, sweep_region_brush); AETHERA_MEMBER(Spectrum::Prop, custom_markers); AETHERA_MEMBER(Spectrum::Prop, selected_marker); };
struct Frequency_Trace_Prop_Members { AETHERA_MEMBER(Frequency_Trace::Prop, partition_count); AETHERA_MEMBER(Frequency_Trace::Prop, pen); AETHERA_MEMBER(Frequency_Trace::Prop, partition_mode); AETHERA_MEMBER(Frequency_Trace::Prop, samples); };
struct Sweep_Spectrum_Prop_Members { AETHERA_MEMBER(Sweep_Spectrum::Prop, bins_per_block); AETHERA_MEMBER(Sweep_Spectrum::Prop, block_count); AETHERA_MEMBER(Sweep_Spectrum::Prop, partition_count); AETHERA_MEMBER(Sweep_Spectrum::Prop, visible_range_only); AETHERA_MEMBER(Sweep_Spectrum::Prop, frequency_range); AETHERA_MEMBER(Sweep_Spectrum::Prop, partition_mode); AETHERA_MEMBER(Sweep_Spectrum::Prop, pen); AETHERA_MEMBER(Sweep_Spectrum::Prop, current_frequency_pen); AETHERA_MEMBER(Sweep_Spectrum::Prop, interpolation_mode); AETHERA_MEMBER(Sweep_Spectrum::Prop, blocks); };
struct Afterglow_Prop_Members { AETHERA_MEMBER(Afterglow::Prop, frequency_point_size); AETHERA_MEMBER(Afterglow::Prop, power_point_size); AETHERA_MEMBER(Afterglow::Prop, partition_count); AETHERA_MEMBER(Afterglow::Prop, interpolate); AETHERA_MEMBER(Afterglow::Prop, attenuation_rate); AETHERA_MEMBER(Afterglow::Prop, frequency_range); AETHERA_MEMBER(Afterglow::Prop, power_range); AETHERA_MEMBER(Afterglow::Prop, partition_mode); AETHERA_MEMBER(Afterglow::Prop, color_map); AETHERA_MEMBER(Afterglow::Prop, spectra); };
struct Waterfall_Prop_Members { AETHERA_MEMBER(Waterfall::Prop, tooltip_enabled); AETHERA_MEMBER(Waterfall::Prop, tooltip_font); AETHERA_MEMBER(Waterfall::Prop, tooltip_text_pen); AETHERA_MEMBER(Waterfall::Prop, tooltip_background_brush); AETHERA_MEMBER(Waterfall::Prop, frequency_bin_count); AETHERA_MEMBER(Waterfall::Prop, partition_count); AETHERA_MEMBER(Waterfall::Prop, visible_range_only); AETHERA_MEMBER(Waterfall::Prop, frequency_range); AETHERA_MEMBER(Waterfall::Prop, power_range); AETHERA_MEMBER(Waterfall::Prop, partition_mode); AETHERA_MEMBER(Waterfall::Prop, interpolation_mode); AETHERA_MEMBER(Waterfall::Prop, color_map); AETHERA_MEMBER(Waterfall::Prop, rows); };
struct Constellation_Prop_Members { AETHERA_MEMBER(Constellation_Diagram::Prop, point_lifetime_ms); AETHERA_MEMBER(Constellation_Diagram::Prop, type); AETHERA_MEMBER(Constellation_Diagram::Prop, phase_offset_radians); AETHERA_MEMBER(Constellation_Diagram::Prop, i_range); AETHERA_MEMBER(Constellation_Diagram::Prop, q_range); AETHERA_MEMBER(Constellation_Diagram::Prop, point_color); AETHERA_MEMBER(Constellation_Diagram::Prop, anchor_color); AETHERA_MEMBER(Constellation_Diagram::Prop, points); };
struct Selection_Prop_Members { AETHERA_MEMBER(Selection_Rectangle_Overlay::Prop, label_font); AETHERA_MEMBER(Selection_Rectangle_Overlay::Prop, label_pen); AETHERA_MEMBER(Selection_Rectangle_Overlay::Prop, selection_brush); AETHERA_MEMBER(Selection_Rectangle_Overlay::Prop, selection_border_pen); AETHERA_MEMBER(Selection_Rectangle_Overlay::Prop, selected_regions); };
struct Color_Map_Members { AETHERA_MEMBER(Color_Map, stops); };
#define AETHERA_RENDER_STATE_MEMBERS(Type) AETHERA_MEMBER(Type, prepare_dirty); AETHERA_MEMBER(Type, paint_dirty); AETHERA_MEMBER(Type, prepare_executed); AETHERA_MEMBER(Type, paint_executed); AETHERA_MEMBER(Type, prepare_graph_rebuilt); AETHERA_MEMBER(Type, paint_graph_rebuilt); AETHERA_MEMBER(Type, prepare_task_count); AETHERA_MEMBER(Type, paint_task_count); AETHERA_MEMBER(Type, prepare_execution_time_ns); AETHERA_MEMBER(Type, paint_execution_time_ns)
struct Spectrum_State_Members { AETHERA_RENDER_STATE_MEMBERS(Spectrum::State); AETHERA_MEMBER(Spectrum::State, sample_count); AETHERA_MEMBER(Spectrum::State, rendered_point_count); AETHERA_MEMBER(Spectrum::State, selectable_marker_count); };
struct Frequency_Trace_State_Members { AETHERA_RENDER_STATE_MEMBERS(Frequency_Trace::State); AETHERA_MEMBER(Frequency_Trace::State, sample_count); AETHERA_MEMBER(Frequency_Trace::State, rendered_point_count); };
struct Sweep_Spectrum_State_Members { AETHERA_RENDER_STATE_MEMBERS(Sweep_Spectrum::State); AETHERA_MEMBER(Sweep_Spectrum::State, stored_block_count); AETHERA_MEMBER(Sweep_Spectrum::State, stored_point_count); AETHERA_MEMBER(Sweep_Spectrum::State, rendered_point_count); };
struct Afterglow_State_Members { AETHERA_RENDER_STATE_MEMBERS(Afterglow::State); AETHERA_MEMBER(Afterglow::State, history_count); AETHERA_MEMBER(Afterglow::State, latest_spectrum_point_count); AETHERA_MEMBER(Afterglow::State, rendered_cell_count); };
struct Waterfall_State_Members { AETHERA_RENDER_STATE_MEMBERS(Waterfall::State); AETHERA_MEMBER(Waterfall::State, row_count); AETHERA_MEMBER(Waterfall::State, stored_point_count); AETHERA_MEMBER(Waterfall::State, rendered_cell_count); };
struct Constellation_State_Members { AETHERA_RENDER_STATE_MEMBERS(Constellation_Diagram::State); AETHERA_MEMBER(Constellation_Diagram::State, point_count); };
struct Selection_State_Members { AETHERA_RENDER_STATE_MEMBERS(Selection_Rectangle_Overlay::State); AETHERA_MEMBER(Selection_Rectangle_Overlay::State, selected_region_count); };
#undef AETHERA_RENDER_STATE_MEMBERS
#undef AETHERA_MEMBER
}
namespace adminive {
template <typename Value, Json_Type Json> struct Value_Adapter<std::vector<Value>, Json> {
using value_type = std::vector<Value>;
static constexpr std::string_view type_name{"array"};
static Json encode(const value_type& values);
static void decode(value_type& target, const Json& value);
};
#define AETHERA_PLAIN_REFLECTION(Type, Id, Label) template <> struct Reflection_Adapter<Type> : Boost_Pfr_Reflection_Adapter<Type> {}; template <> struct Type_Descriptor<Type> { static auto get(); };
AETHERA_PLAIN_REFLECTION(aethera::Color, "color", "Color")
AETHERA_PLAIN_REFLECTION(aethera::render_2d::Point_F, "point", "Point")
AETHERA_PLAIN_REFLECTION(aethera::render_2d::Rect_F, "rectangle", "Rectangle")
AETHERA_PLAIN_REFLECTION(aethera::render_2d::Axis_Range, "axis_range", "Axis range")
AETHERA_PLAIN_REFLECTION(aethera::render_2d::Pen, "pen", "Pen")
AETHERA_PLAIN_REFLECTION(aethera::render_2d::Brush, "brush", "Brush")
AETHERA_PLAIN_REFLECTION(aethera::render_2d::Font, "font", "Font")
AETHERA_PLAIN_REFLECTION(aethera::render_2d::Frequency_Trace_Sample, "frequency_trace_sample", "Frequency trace sample")
AETHERA_PLAIN_REFLECTION(aethera::render_2d::Waterfall_Row, "waterfall_row", "Waterfall row")
AETHERA_PLAIN_REFLECTION(aethera::render_2d::Constellation_Point, "constellation_point", "Constellation point")
#undef AETHERA_PLAIN_REFLECTION
#define AETHERA_MEMBER_REFLECTION(Type, Members, Id, Label, Customizer) template <> struct Reflection_Adapter<Type> : aethera::web::detail::Member_Pfr_Reflection_Adapter<Type, aethera::web::detail::Members> {}; template <> struct Type_Descriptor<Type> { static auto get(); };
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Color_Map, Color_Map_Members, "color_map", "Color map", aethera::web::detail::Editable_Field_Metadata)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Spectrum::Prop, Spectrum_Prop_Members, "spectrum_prop", "Spectrum properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Frequency_Trace::Prop, Frequency_Trace_Prop_Members, "frequency_trace_prop", "Frequency trace properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Sweep_Spectrum::Prop, Sweep_Spectrum_Prop_Members, "sweep_spectrum_prop", "Sweep spectrum properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Afterglow::Prop, Afterglow_Prop_Members, "afterglow_prop", "Afterglow properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Waterfall::Prop, Waterfall_Prop_Members, "waterfall_prop", "Waterfall properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Constellation_Diagram::Prop, Constellation_Prop_Members, "constellation_prop", "Constellation properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Selection_Rectangle_Overlay::Prop, Selection_Prop_Members, "selection_prop", "Selection properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Spectrum::State, Spectrum_State_Members, "spectrum_state", "Spectrum state", Identity_Field_Customizer)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Frequency_Trace::State, Frequency_Trace_State_Members, "frequency_trace_state", "Frequency trace state", Identity_Field_Customizer)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Sweep_Spectrum::State, Sweep_Spectrum_State_Members, "sweep_spectrum_state", "Sweep spectrum state", Identity_Field_Customizer)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Afterglow::State, Afterglow_State_Members, "afterglow_state", "Afterglow state", Identity_Field_Customizer)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Waterfall::State, Waterfall_State_Members, "waterfall_state", "Waterfall state", Identity_Field_Customizer)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Constellation_Diagram::State, Constellation_State_Members, "constellation_state", "Constellation state", Identity_Field_Customizer)
AETHERA_MEMBER_REFLECTION(aethera::render_2d::Selection_Rectangle_Overlay::State, Selection_State_Members, "selection_state", "Selection state", Identity_Field_Customizer)
#undef AETHERA_MEMBER_REFLECTION
}
namespace aethera::web::detail {
template <std::size_t Index, typename Field> auto Editable_Field_Metadata::operator()(Field field) const { return field.editable(); }
template <typename Object, typename Members> template <std::size_t Index> decltype(auto) Member_Pfr_Reflection_Adapter<Object, Members>::get(Object& value) noexcept { return value.*boost::pfr::get<Index>(Members{}); }
template <typename Object, typename Members> template <std::size_t Index> decltype(auto) Member_Pfr_Reflection_Adapter<Object, Members>::get(const Object& value) noexcept { return value.*boost::pfr::get<Index>(Members{}); }
template <typename Object, typename Members> template <std::size_t Index> constexpr std::string_view Member_Pfr_Reflection_Adapter<Object, Members>::name() noexcept { return boost::pfr::get_name<Index, Members>(); }
}
namespace adminive {
template <typename Value, Json_Type Json> Json Value_Adapter<std::vector<Value>, Json>::encode(const value_type& values) { Json result = json_array<Json>(); for (const auto& value : values) json_append(result, encode_json_value<Json>(value)); return result; }
template <typename Value, Json_Type Json> void Value_Adapter<std::vector<Value>, Json>::decode(value_type& target, const Json& value) { if (!Json_Adapter<Json>::is_array(value)) throw std::invalid_argument("value must be an array"); value_type updated; updated.reserve(Json_Adapter<Json>::size(value)); for (std::size_t index = 0; index < Json_Adapter<Json>::size(value); ++index) { Value item{}; assign_json_value<Json>(item, Json_Adapter<Json>::at(value, index)); updated.push_back(std::move(item)); } target = std::move(updated); }
#define AETHERA_DEFINE_PLAIN_DESCRIPTOR(Type, Id, Label) inline auto Type_Descriptor<Type>::get() { return reflected_object_with<Type>(Id, Label, aethera::web::detail::Editable_Field_Metadata{}); }
AETHERA_DEFINE_PLAIN_DESCRIPTOR(aethera::Color, "color", "Color")
AETHERA_DEFINE_PLAIN_DESCRIPTOR(aethera::render_2d::Point_F, "point", "Point")
AETHERA_DEFINE_PLAIN_DESCRIPTOR(aethera::render_2d::Rect_F, "rectangle", "Rectangle")
AETHERA_DEFINE_PLAIN_DESCRIPTOR(aethera::render_2d::Axis_Range, "axis_range", "Axis range")
AETHERA_DEFINE_PLAIN_DESCRIPTOR(aethera::render_2d::Pen, "pen", "Pen")
AETHERA_DEFINE_PLAIN_DESCRIPTOR(aethera::render_2d::Brush, "brush", "Brush")
AETHERA_DEFINE_PLAIN_DESCRIPTOR(aethera::render_2d::Font, "font", "Font")
AETHERA_DEFINE_PLAIN_DESCRIPTOR(aethera::render_2d::Frequency_Trace_Sample, "frequency_trace_sample", "Frequency trace sample")
AETHERA_DEFINE_PLAIN_DESCRIPTOR(aethera::render_2d::Waterfall_Row, "waterfall_row", "Waterfall row")
AETHERA_DEFINE_PLAIN_DESCRIPTOR(aethera::render_2d::Constellation_Point, "constellation_point", "Constellation point")
#undef AETHERA_DEFINE_PLAIN_DESCRIPTOR
#define AETHERA_DEFINE_MEMBER_DESCRIPTOR(Type, Id, Label, Customizer) inline auto Type_Descriptor<Type>::get() { return reflected_object_with<Type>(Id, Label, Customizer{}); }
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Color_Map, "color_map", "Color map", aethera::web::detail::Editable_Field_Metadata)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Spectrum::Prop, "spectrum_prop", "Spectrum properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Frequency_Trace::Prop, "frequency_trace_prop", "Frequency trace properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Sweep_Spectrum::Prop, "sweep_spectrum_prop", "Sweep spectrum properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Afterglow::Prop, "afterglow_prop", "Afterglow properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Waterfall::Prop, "waterfall_prop", "Waterfall properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Constellation_Diagram::Prop, "constellation_prop", "Constellation properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Selection_Rectangle_Overlay::Prop, "selection_prop", "Selection properties", aethera::web::detail::Editable_Field_Metadata)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Spectrum::State, "spectrum_state", "Spectrum state", Identity_Field_Customizer)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Frequency_Trace::State, "frequency_trace_state", "Frequency trace state", Identity_Field_Customizer)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Sweep_Spectrum::State, "sweep_spectrum_state", "Sweep spectrum state", Identity_Field_Customizer)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Afterglow::State, "afterglow_state", "Afterglow state", Identity_Field_Customizer)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Waterfall::State, "waterfall_state", "Waterfall state", Identity_Field_Customizer)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Constellation_Diagram::State, "constellation_state", "Constellation state", Identity_Field_Customizer)
AETHERA_DEFINE_MEMBER_DESCRIPTOR(aethera::render_2d::Selection_Rectangle_Overlay::State, "selection_state", "Selection state", Identity_Field_Customizer)
#undef AETHERA_DEFINE_MEMBER_DESCRIPTOR
}
+20 -15
View File
@@ -1,4 +1,4 @@
#include "Graph_Session.hpp"
#include "Graph_Session.hpp" /* 图对象异步会话。 */
#include <asio/co_spawn.hpp>
#include <asio/error_code.hpp>
#include <asio/experimental/concurrent_channel.hpp>
@@ -48,14 +48,16 @@ struct Plot_2D {
std::unique_ptr<Time_Axis_Object> time{}; /* 时间轴;不用时为空。 */
std::unique_ptr<Root> plot{}; /* 具体 Plottable 的唯一所有权。 */
std::function<void(double)> update{}; /* 根据浏览器时钟更新权威 Prop。 */
std::function<Graph_State_Document(std::uint64_t)> state{}; /* 从具体 State 即时计算 HTTP 文档。 */
std::function<nlohmann::json()> state{}; /* 直接遍历具体图完整 State 即时 HTTP 文档。 */
std::function<nlohmann::json()> prop{};
std::function<nlohmann::json(const nlohmann::json&)> patch_prop{};
};
template <typename Data, auto... Members, typename Object>
template <typename Definition, auto... Members, typename Object>
void bind_prop_api(Plot_2D& plot, Object* object) {
plot.prop = [object] { const auto& prop = object->template read_prop<typename Data::Prop_Tag>(); return adminive::model_to_json<nlohmann::json>(static_cast<const Data&>(prop), true); };
plot.patch_prop = [object](const nlohmann::json& patch) { Data updated = static_cast<const Data&>(object->template read_prop<typename Data::Prop_Tag>()); const auto result = adminive::apply_frontend_patch<nlohmann::json>(updated, patch); if (!result.success) return result.to_json<nlohmann::json>(); ([&] { if (object->template get<Members>() != updated.*Members) object->template set<Members>(updated.*Members); }(), ...); auto output = result.to_json<nlohmann::json>(); output["prop"] = adminive::model_to_json<nlohmann::json>(updated, true); return output; };
using Prop = typename Definition::Prop;
plot.prop = [object] { return adminive::model_to_json<nlohmann::json>(static_cast<const Prop&>(object->template read_prop<typename Definition::Base_Tag>()), true); };
plot.state = [object] { return adminive::model_to_json<nlohmann::json>(static_cast<const typename Definition::State&>(object->template read_state<typename Definition::Base_Tag>()), true); };
plot.patch_prop = [object](const nlohmann::json& patch) { Prop updated = static_cast<const Prop&>(object->template read_prop<typename Definition::Base_Tag>()); const auto result = adminive::apply_frontend_patch<nlohmann::json>(updated, patch); if (!result.success) return result.to_json<nlohmann::json>(); ([&] { if (object->template get<Members>() != updated.*Members) object->template set<Members>(updated.*Members); }(), ...); auto output = result.to_json<nlohmann::json>(); output["prop"] = adminive::model_to_json<nlohmann::json>(static_cast<const Prop&>(object->template read_prop<typename Definition::Base_Tag>()), true); return output; };
}
Plot_2D make_plot_2d(std::string_view id) {
Plot_2D result;
@@ -65,21 +67,24 @@ Plot_2D make_plot_2d(std::string_view id) {
result.scene->set<&Render_Scene_2D::Prop::background>(Color{7, 13, 24, 255});
result.scene->activate_view();
auto make_frequency = [&] { result.frequency = build<Frequency_Axis_Object>(); configure_axis(result.frequency.get(), Axis_Orientation::horizontal, Point_F{64.0, 370.0}, 620.0, canvas); result.frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0}); };
auto make_vertical = [&](Axis_Range range) { result.vertical = build<Numeric_Axis_Object>(); configure_axis(result.vertical.get(), Axis_Orientation::vertical, {64.0, 370.0}, -320.0, canvas); result.vertical->set<&Numeric_Axis::Prop::coordinate_range>(range); };
auto make_vertical = [&](Axis_Range range) {
result.vertical = build<Numeric_Axis_Object>();
configure_axis(result.vertical.get(), Axis_Orientation::vertical, {64.0, 370.0}, -320.0, canvas); result.vertical->set<&Numeric_Axis::Prop::coordinate_range>(range);
};
if (id == "spectrum") {
make_frequency(); make_vertical({-110.0, 0.0}); auto object = build<Impl<Spectrum>>(result.scene.get(), result.frequency.get(), result.vertical.get()); object->set<&Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0}); object->set<&Spectrum::Prop::max_hold_visible>(true); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Spectrum_Editable_Prop_Data, &Spectrum_Editable_Prop_Data::center_frequency, &Spectrum_Editable_Prop_Data::partition_count, &Spectrum_Editable_Prop_Data::max_hold_visible, &Spectrum_Editable_Prop_Data::min_hold_visible, &Spectrum_Editable_Prop_Data::max_marker_visible, &Spectrum_Editable_Prop_Data::min_marker_visible, &Spectrum_Editable_Prop_Data::sweep_region_visible, &Spectrum_Editable_Prop_Data::visible_range_only>(result, raw); result.update = [raw](double time) { std::array<double, 256> samples{}; for (std::size_t i = 0; i < samples.size(); ++i) { const double x = static_cast<double>(i) / samples.size(); samples[i] = -92.0 + 54.0 * std::exp(-180.0 * std::pow(x - 0.28 - 0.03 * std::sin(time * 0.001), 2.0)) + 42.0 * std::exp(-260.0 * std::pow(x - 0.68, 2.0)) + 2.5 * std::sin(i * 0.31 + time * 0.004); } raw->update_samples(samples); }; result.state = [raw](std::uint64_t sequence) { const auto& state = raw->read_state<Spectrum::Base_Tag>(); return Graph_State_Document{sequence, state.sample_count, state.rendered_point_count}; }; result.plot = std::move(object);
make_frequency(); make_vertical({-110.0, 0.0}); auto object = build<Impl<Spectrum>>(result.scene.get(), result.frequency.get(), result.vertical.get()); object->set<&Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0}); object->set<&Spectrum::Prop::max_hold_visible>(true); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Spectrum, &Spectrum::Prop::center_frequency, &Spectrum::Prop::partition_count, &Spectrum::Prop::max_hold_visible, &Spectrum::Prop::min_hold_visible, &Spectrum::Prop::max_marker_visible, &Spectrum::Prop::min_marker_visible, &Spectrum::Prop::sweep_region_visible, &Spectrum::Prop::visible_range_only, &Spectrum::Prop::frequency_range, &Spectrum::Prop::sweep_frequency_range, &Spectrum::Prop::partition_mode, &Spectrum::Prop::interpolation_mode, &Spectrum::Prop::max_brush, &Spectrum::Prop::current_brush, &Spectrum::Prop::min_brush, &Spectrum::Prop::max_pen, &Spectrum::Prop::current_pen, &Spectrum::Prop::min_pen, &Spectrum::Prop::selected_marker_pen, &Spectrum::Prop::marker_pen, &Spectrum::Prop::middle_frequency_pen, &Spectrum::Prop::sweep_region_brush, &Spectrum::Prop::custom_markers, &Spectrum::Prop::selected_marker>(result, raw); result.update = [raw](double time) { std::array<double, 256> samples{}; for (std::size_t i = 0; i < samples.size(); ++i) { const double x = static_cast<double>(i) / samples.size(); samples[i] = -92.0 + 54.0 * std::exp(-180.0 * std::pow(x - 0.28 - 0.03 * std::sin(time * 0.001), 2.0)) + 42.0 * std::exp(-260.0 * std::pow(x - 0.68, 2.0)) + 2.5 * std::sin(i * 0.31 + time * 0.004); } raw->update_samples(samples); }; result.plot = std::move(object);
} else if (id == "frequency_trace") {
result.time = build<Time_Axis_Object>(); configure_axis(result.time.get(), Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas); make_vertical({-1.2, 1.2}); auto object = build<Impl<Frequency_Trace>>(result.scene.get(), result.time.get(), result.vertical.get()); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Frequency_Trace_Editable_Prop_Data, &Frequency_Trace_Editable_Prop_Data::partition_count>(result, raw); auto tick = std::make_shared<std::uint64_t>(); result.update = [raw, tick](double time) { raw->append_sample((*tick)++, std::sin(time * 0.0025) * 0.8 + std::sin(time * 0.0007) * 0.2); }; result.state = [raw](std::uint64_t sequence) { const auto& state = raw->read_state<Frequency_Trace::Base_Tag>(); return Graph_State_Document{sequence, state.sample_count, state.rendered_point_count}; }; result.plot = std::move(object);
result.time = build<Time_Axis_Object>(); configure_axis(result.time.get(), Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas); make_vertical({-1.2, 1.2}); auto object = build<Impl<Frequency_Trace>>(result.scene.get(), result.time.get(), result.vertical.get()); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Frequency_Trace, &Frequency_Trace::Prop::partition_count, &Frequency_Trace::Prop::pen, &Frequency_Trace::Prop::partition_mode, &Frequency_Trace::Prop::samples>(result, raw); auto tick = std::make_shared<std::uint64_t>(); result.update = [raw, tick](double time) { raw->append_sample((*tick)++, std::sin(time * 0.0025) * 0.8 + std::sin(time * 0.0007) * 0.2); }; result.plot = std::move(object);
} else if (id == "sweep_spectrum") {
make_frequency(); make_vertical({-110.0, 0.0}); auto object = build<Impl<Sweep_Spectrum>>(result.scene.get(), result.frequency.get(), result.vertical.get()); object->set<&Sweep_Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0}); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Sweep_Spectrum_Editable_Prop_Data, &Sweep_Spectrum_Editable_Prop_Data::bins_per_block, &Sweep_Spectrum_Editable_Prop_Data::block_count, &Sweep_Spectrum_Editable_Prop_Data::partition_count, &Sweep_Spectrum_Editable_Prop_Data::visible_range_only>(result, raw); result.update = [raw](double time) { std::array<double, 64> values{}; for (std::size_t i = 0; i < values.size(); ++i) values[i] = -90.0 + 35.0 * std::sin(i * 0.08 + time * 0.002); raw->append_block(values); }; result.state = [raw](std::uint64_t sequence) { const auto& state = raw->read_state<Sweep_Spectrum::Base_Tag>(); return Graph_State_Document{sequence, state.stored_block_count, state.rendered_point_count}; }; result.plot = std::move(object);
make_frequency(); make_vertical({-110.0, 0.0}); auto object = build<Impl<Sweep_Spectrum>>(result.scene.get(), result.frequency.get(), result.vertical.get()); object->set<&Sweep_Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0}); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Sweep_Spectrum, &Sweep_Spectrum::Prop::bins_per_block, &Sweep_Spectrum::Prop::block_count, &Sweep_Spectrum::Prop::partition_count, &Sweep_Spectrum::Prop::visible_range_only, &Sweep_Spectrum::Prop::frequency_range, &Sweep_Spectrum::Prop::partition_mode, &Sweep_Spectrum::Prop::pen, &Sweep_Spectrum::Prop::current_frequency_pen, &Sweep_Spectrum::Prop::interpolation_mode, &Sweep_Spectrum::Prop::blocks>(result, raw); result.update = [raw](double time) { std::array<double, 64> values{}; for (std::size_t i = 0; i < values.size(); ++i) values[i] = -90.0 + 35.0 * std::sin(i * 0.08 + time * 0.002); raw->append_block(values); }; result.plot = std::move(object);
} else if (id == "afterglow") {
make_frequency(); make_vertical({-110.0, 0.0}); auto object = build<Impl<Afterglow>>(result.scene.get(), result.frequency.get(), result.vertical.get()); object->set<&Afterglow::Prop::frequency_range>(Axis_Range{0.0, 100.0}); object->set<&Afterglow::Prop::power_range>(Axis_Range{-110.0, 0.0}); object->set<&Afterglow::Prop::power_point_size>(96); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Afterglow_Editable_Prop_Data, &Afterglow_Editable_Prop_Data::frequency_point_size, &Afterglow_Editable_Prop_Data::power_point_size, &Afterglow_Editable_Prop_Data::partition_count, &Afterglow_Editable_Prop_Data::interpolate, &Afterglow_Editable_Prop_Data::attenuation_rate>(result, raw); result.update = [raw](double time) { std::array<double, 192> values{}; for (std::size_t i = 0; i < values.size(); ++i) values[i] = -95.0 + 62.0 * std::exp(-220.0 * std::pow(static_cast<double>(i) / values.size() - 0.5 - 0.18 * std::sin(time * 0.0008), 2.0)); raw->append_spectrum(values); }; result.state = [raw](std::uint64_t sequence) { const auto& state = raw->read_state<Afterglow::Base_Tag>(); return Graph_State_Document{sequence, state.history_count, state.rendered_cell_count}; }; result.plot = std::move(object);
make_frequency(); make_vertical({-110.0, 0.0}); auto object = build<Impl<Afterglow>>(result.scene.get(), result.frequency.get(), result.vertical.get()); object->set<&Afterglow::Prop::frequency_range>(Axis_Range{0.0, 100.0}); object->set<&Afterglow::Prop::power_range>(Axis_Range{-110.0, 0.0}); object->set<&Afterglow::Prop::power_point_size>(96); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Afterglow, &Afterglow::Prop::frequency_point_size, &Afterglow::Prop::power_point_size, &Afterglow::Prop::partition_count, &Afterglow::Prop::interpolate, &Afterglow::Prop::attenuation_rate, &Afterglow::Prop::frequency_range, &Afterglow::Prop::power_range, &Afterglow::Prop::partition_mode, &Afterglow::Prop::color_map, &Afterglow::Prop::spectra>(result, raw); result.update = [raw](double time) { std::array<double, 192> values{}; for (std::size_t i = 0; i < values.size(); ++i) values[i] = -95.0 + 62.0 * std::exp(-220.0 * std::pow(static_cast<double>(i) / values.size() - 0.5 - 0.18 * std::sin(time * 0.0008), 2.0)); raw->append_spectrum(values); }; result.plot = std::move(object);
} else if (id == "waterfall") {
make_frequency(); result.time = build<Time_Axis_Object>(); configure_axis(result.time.get(), Axis_Orientation::vertical, {64.0, 370.0}, -320.0, canvas); auto object = build<Impl<Waterfall>>(result.scene.get(), result.frequency.get(), result.time.get()); object->set<&Waterfall::Prop::frequency_range>(Axis_Range{0.0, 100.0}); object->set<&Waterfall::Prop::power_range>(Axis_Range{-110.0, 0.0}); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Waterfall_Editable_Prop_Data, &Waterfall_Editable_Prop_Data::frequency_bin_count, &Waterfall_Editable_Prop_Data::partition_count, &Waterfall_Editable_Prop_Data::visible_range_only>(result, raw); auto tick = std::make_shared<std::uint64_t>(); result.update = [raw, tick](double time) { std::array<double, 192> values{}; for (std::size_t i = 0; i < values.size(); ++i) values[i] = -100.0 + 70.0 * std::exp(-240.0 * std::pow(static_cast<double>(i) / values.size() - 0.5 - 0.22 * std::sin(time * 0.0006), 2.0)); raw->append_row((*tick)++, values); }; result.state = [raw](std::uint64_t sequence) { const auto& state = raw->read_state<Waterfall::Base_Tag>(); return Graph_State_Document{sequence, state.row_count, state.rendered_cell_count}; }; result.plot = std::move(object);
make_frequency(); result.time = build<Time_Axis_Object>(); configure_axis(result.time.get(), Axis_Orientation::vertical, {64.0, 370.0}, -320.0, canvas); auto object = build<Impl<Waterfall>>(result.scene.get(), result.frequency.get(), result.time.get()); object->set<&Waterfall::Prop::frequency_range>(Axis_Range{0.0, 100.0}); object->set<&Waterfall::Prop::power_range>(Axis_Range{-110.0, 0.0}); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Waterfall, &Waterfall::Prop::tooltip_enabled, &Waterfall::Prop::tooltip_font, &Waterfall::Prop::tooltip_text_pen, &Waterfall::Prop::tooltip_background_brush, &Waterfall::Prop::frequency_bin_count, &Waterfall::Prop::partition_count, &Waterfall::Prop::visible_range_only, &Waterfall::Prop::frequency_range, &Waterfall::Prop::power_range, &Waterfall::Prop::partition_mode, &Waterfall::Prop::interpolation_mode, &Waterfall::Prop::color_map, &Waterfall::Prop::rows>(result, raw); auto tick = std::make_shared<std::uint64_t>(); result.update = [raw, tick](double time) { std::array<double, 192> values{}; for (std::size_t i = 0; i < values.size(); ++i) values[i] = -100.0 + 70.0 * std::exp(-240.0 * std::pow(static_cast<double>(i) / values.size() - 0.5 - 0.22 * std::sin(time * 0.0006), 2.0)); raw->append_row((*tick)++, values); }; result.plot = std::move(object);
} else if (id == "constellation") {
result.horizontal = build<Numeric_Axis_Object>(); configure_axis(result.horizontal.get(), Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas); result.horizontal->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-1.2, 1.2}); make_vertical({-1.2, 1.2}); auto object = build<Impl<Constellation_Diagram>>(result.scene.get(), result.horizontal.get(), result.vertical.get()); object->set<&Constellation_Diagram::Prop::i_range>(Axis_Range{-1.2, 1.2}); object->set<&Constellation_Diagram::Prop::q_range>(Axis_Range{-1.2, 1.2}); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Constellation_Diagram_Editable_Prop_Data, &Constellation_Diagram_Editable_Prop_Data::point_lifetime_ms, &Constellation_Diagram_Editable_Prop_Data::type, &Constellation_Diagram_Editable_Prop_Data::phase_offset_radians>(result, raw); result.update = [raw](double time) { const double phase = time * 0.003; raw->append_point({std::cos(phase) * 0.82 + 0.04 * std::sin(phase * 7.0), std::sin(phase) * 0.82 + 0.04 * std::cos(phase * 5.0)}); }; result.state = [raw](std::uint64_t sequence) { const auto& state = raw->read_state<Constellation_Diagram::Base_Tag>(); return Graph_State_Document{sequence, state.point_count, state.point_count}; }; result.plot = std::move(object);
result.horizontal = build<Numeric_Axis_Object>(); configure_axis(result.horizontal.get(), Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas); result.horizontal->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-1.2, 1.2}); make_vertical({-1.2, 1.2}); auto object = build<Impl<Constellation_Diagram>>(result.scene.get(), result.horizontal.get(), result.vertical.get()); object->set<&Constellation_Diagram::Prop::i_range>(Axis_Range{-1.2, 1.2}); object->set<&Constellation_Diagram::Prop::q_range>(Axis_Range{-1.2, 1.2}); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Constellation_Diagram, &Constellation_Diagram::Prop::point_lifetime_ms, &Constellation_Diagram::Prop::type, &Constellation_Diagram::Prop::phase_offset_radians, &Constellation_Diagram::Prop::i_range, &Constellation_Diagram::Prop::q_range, &Constellation_Diagram::Prop::point_color, &Constellation_Diagram::Prop::anchor_color, &Constellation_Diagram::Prop::points>(result, raw); result.update = [raw](double time) { const double phase = time * 0.003; raw->append_point({std::cos(phase) * 0.82 + 0.04 * std::sin(phase * 7.0), std::sin(phase) * 0.82 + 0.04 * std::cos(phase * 5.0)}); }; result.plot = std::move(object);
} else {
result.horizontal = build<Numeric_Axis_Object>(); configure_axis(result.horizontal.get(), Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas); result.horizontal->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0}); make_vertical({0.0, 100.0}); auto object = build<Impl<Selection_Rectangle_Overlay>>(result.scene.get(), result.horizontal.get(), result.vertical.get()); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); result.prop = [] { return nlohmann::json::object(); }; result.patch_prop = [](const nlohmann::json&) { return nlohmann::json{{"success", true}, {"prop", nlohmann::json::object()}}; }; result.update = [](double) {}; result.state = [raw](std::uint64_t sequence) { const auto& state = raw->read_state<Renderable::Base_Tag>(); return Graph_State_Document{sequence, state.paint_task_count, state.paint_executed ? 1U : 0U}; }; result.plot = std::move(object);
result.horizontal = build<Numeric_Axis_Object>(); configure_axis(result.horizontal.get(), Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas); result.horizontal->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0}); make_vertical({0.0, 100.0}); auto object = build<Impl<Selection_Rectangle_Overlay>>(result.scene.get(), result.horizontal.get(), result.vertical.get()); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_prop_api<Selection_Rectangle_Overlay, &Selection_Rectangle_Overlay::Prop::label_font, &Selection_Rectangle_Overlay::Prop::label_pen, &Selection_Rectangle_Overlay::Prop::selection_brush, &Selection_Rectangle_Overlay::Prop::selection_border_pen, &Selection_Rectangle_Overlay::Prop::selected_regions>(result, raw); result.update = [](double) {}; result.plot = std::move(object);
}
return result;
}
@@ -100,14 +105,14 @@ struct Graph_Session::Private {
std::atomic_uint64_t frame_sequence{}; /* 传输协议的完成帧序号。 */
explicit Private(asio::any_io_executor executor, const Graph_Descriptor& graph) : strand(asio::make_strand(std::move(executor))), events(strand, 32), descriptor(graph), plot(make_plot(graph)) {}
void publish(std::string pixels) { std::vector<Frame_Handler> outputs; { std::lock_guard lock(handlers_mutex); outputs.reserve(handlers.size()); for (const auto& [owner, handler] : handlers) outputs.push_back(handler); } for (auto& output : outputs) output(pixels); }
Graph_State_Document state_document() const { const auto sequence = frame_sequence.load(std::memory_order_acquire); if (const auto* value = std::get_if<Plot_2D>(&plot)) return value->state(sequence); const auto& value = std::get<Plot_3D>(plot); const auto& state = value.visual->read_state<Point_Visual::Base_Tag>(); return {sequence, state.item_count, state.prepared_item_count}; }
nlohmann::json state_document() const { if (const auto* value = std::get_if<Plot_2D>(&plot)) return value->state(); const auto& state = std::get<Plot_3D>(plot).visual->read_state<Point_Visual::Base_Tag>(); return {{"prepare_dirty", state.prepare_dirty}, {"paint_dirty", state.paint_dirty}, {"prepare_executed", state.prepare_executed}, {"paint_executed", state.paint_executed}, {"prepare_graph_rebuilt", state.prepare_graph_rebuilt}, {"paint_graph_rebuilt", state.paint_graph_rebuilt}, {"prepare_task_count", state.prepare_task_count}, {"paint_task_count", state.paint_task_count}, {"prepare_execution_time_ns", state.prepare_execution_time_ns}, {"paint_execution_time_ns", state.paint_execution_time_ns}, {"item_count", state.item_count}, {"prepared_item_count", state.prepared_item_count}, {"prepared_revision", state.prepared_revision}}; }
nlohmann::json prop_document() const { if (const auto* value = std::get_if<Plot_2D>(&plot)) return value->prop(); return nlohmann::json::object(); }
nlohmann::json patch_prop(const nlohmann::json& patch) { if (auto* value = std::get_if<Plot_2D>(&plot)) return value->patch_prop(patch); return {{"success", true}, {"prop", nlohmann::json::object()}}; }
};
Graph_Session::Graph_Session(std::unique_ptr<Private> private_data) : d(std::move(private_data)) {}
std::shared_ptr<Graph_Session> Graph_Session::create(asio::any_io_executor executor, const Graph_Descriptor& descriptor) { auto result = std::shared_ptr<Graph_Session>(new Graph_Session(std::make_unique<Private>(std::move(executor), descriptor))); result->start(); return result; }
Graph_Session::~Graph_Session() { d->events.close(); }
void Graph_Session::start() { auto self = shared_from_this(); if (auto* plot = std::get_if<Plot_2D>(&d->plot)) plot->scene->set_frame_callback([weak = weak_from_this()](Image_View image) { if (auto owner = weak.lock()) { const auto sequence = owner->d->frame_sequence.fetch_add(1, std::memory_order_acq_rel) + 1; owner->d->publish(encode_frame(image, sequence)); } }); else std::get<Plot_3D>(d->plot).scene->set_frame_callback([weak = weak_from_this()](std::shared_ptr<const render_3d::Pixel_Frame> frame) { if (auto owner = weak.lock()) { const auto sequence = owner->d->frame_sequence.fetch_add(1, std::memory_order_acq_rel) + 1; owner->d->publish(encode_frame(*frame, sequence)); } }); asio::co_spawn(d->strand, [self]() -> asio::awaitable<void> { for (;;) { asio::error_code error; auto event = co_await self->d->events.async_receive(asio::redirect_error(asio::use_awaitable, error)); if (error) co_return; if (auto* state = std::get_if<State_Query>(&event)) { state->handler(graph_state_json(self->d->state_document())); continue; } if (auto* prop = std::get_if<Prop_Query>(&event)) { prop->handler(self->d->prop_document()); continue; } if (auto* patch = std::get_if<Prop_Patch>(&event)) { patch->handler(self->d->patch_prop(patch->patch)); continue; } const auto value = std::get<Graph_Event>(event); if (auto* plot = std::get_if<Plot_2D>(&self->d->plot)) { const Size viewport{static_cast<int>(std::clamp(value.width, 160U, 1920U)), static_cast<int>(std::clamp(value.height, 120U, 1080U))}; plot->scene->set<&Render_Scene_2D::Prop::viewport>(viewport); if (plot->frequency) plot->frequency->set<&Abs_Axis::Prop::canvas_size>(viewport); if (plot->horizontal) plot->horizontal->set<&Abs_Axis::Prop::canvas_size>(viewport); if (plot->vertical) plot->vertical->set<&Abs_Axis::Prop::canvas_size>(viewport); if (plot->time) plot->time->set<&Abs_Axis::Prop::canvas_size>(viewport); plot->update(value.time_milliseconds); plot->scene->render(); } else { auto& plot_3d = std::get<Plot_3D>(self->d->plot); plot_3d.scene->set<&Render_Scene_3D::Prop::viewport>(render_3d::Extent{std::clamp(value.width, 160U, 1920U), std::clamp(value.height, 120U, 1080U)}); plot_3d.scene->render(); } } }, [](std::exception_ptr exception) { if (exception) std::rethrow_exception(exception); }); }
void Graph_Session::start() { auto self = shared_from_this(); if (auto* plot = std::get_if<Plot_2D>(&d->plot)) plot->scene->set_frame_callback([weak = weak_from_this()](Image_View image) { if (auto owner = weak.lock()) { const auto sequence = owner->d->frame_sequence.fetch_add(1, std::memory_order_acq_rel) + 1; owner->d->publish(encode_frame(image, sequence)); } }); else std::get<Plot_3D>(d->plot).scene->set_frame_callback([weak = weak_from_this()](std::shared_ptr<const render_3d::Pixel_Frame> frame) { if (auto owner = weak.lock()) { const auto sequence = owner->d->frame_sequence.fetch_add(1, std::memory_order_acq_rel) + 1; owner->d->publish(encode_frame(*frame, sequence)); } }); asio::co_spawn(d->strand, [self]() -> asio::awaitable<void> { for (;;) { asio::error_code error; auto event = co_await self->d->events.async_receive(asio::redirect_error(asio::use_awaitable, error)); if (error) co_return; if (auto* state = std::get_if<State_Query>(&event)) { state->handler(self->d->state_document()); continue; } if (auto* prop = std::get_if<Prop_Query>(&event)) { prop->handler(self->d->prop_document()); continue; } if (auto* patch = std::get_if<Prop_Patch>(&event)) { patch->handler(self->d->patch_prop(patch->patch)); continue; } const auto value = std::get<Graph_Event>(event); if (auto* plot = std::get_if<Plot_2D>(&self->d->plot)) { const Size viewport{static_cast<int>(std::clamp(value.width, 160U, 1920U)), static_cast<int>(std::clamp(value.height, 120U, 1080U))}; plot->scene->set<&Render_Scene_2D::Prop::viewport>(viewport); if (plot->frequency) plot->frequency->set<&Abs_Axis::Prop::canvas_size>(viewport); if (plot->horizontal) plot->horizontal->set<&Abs_Axis::Prop::canvas_size>(viewport); if (plot->vertical) plot->vertical->set<&Abs_Axis::Prop::canvas_size>(viewport); if (plot->time) plot->time->set<&Abs_Axis::Prop::canvas_size>(viewport); plot->update(value.time_milliseconds); plot->scene->render(); } else { auto& plot_3d = std::get<Plot_3D>(self->d->plot); plot_3d.scene->set<&Render_Scene_3D::Prop::viewport>(render_3d::Extent{std::clamp(value.width, 160U, 1920U), std::clamp(value.height, 120U, 1080U)}); plot_3d.scene->render(); } } }, [](std::exception_ptr exception) { if (exception) std::rethrow_exception(exception); }); }
void Graph_Session::attach(const void* owner, Frame_Handler handler) { { std::lock_guard lock(d->handlers_mutex); d->handlers.insert_or_assign(owner, std::move(handler)); } submit({}); }
void Graph_Session::detach(const void* owner) { std::lock_guard lock(d->handlers_mutex); d->handlers.erase(owner); }
void Graph_Session::submit(Graph_Event event) { static_cast<void>(d->events.try_send(asio::error_code{}, Session_Event{event})); }
+1 -1
View File
@@ -1,4 +1,4 @@
#include "Graph_WebSocket.hpp"
#include "Graph_WebSocket.hpp" /* 图像素流 WebSocket。 */
#include <nlohmann/json.hpp>
#include <algorithm>
#include <chrono>
+2 -2
View File
@@ -1,5 +1,5 @@
#include "Web_Server.hpp"
#include "Graph_Metadata.hpp"
#include "Graph_Metadata.hpp" /* 按图请求描述数据。 */
#include "Graph_Session.hpp"
#include "Graph_WebSocket.hpp"
#include <asio/thread_pool.hpp>
@@ -20,7 +20,7 @@ int run_web_server(std::uint16_t port, const std::filesystem::path& asset_root)
auto websocket = std::make_shared<Graph_WebSocket_Controller>(registry);
auto& app = drogon::app();
app.registerHandler("/api/graphs", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) { callback(json_response(graph_catalog_json())); }, {drogon::Get});
app.registerHandler("/api/graphs/{1}/descriptor", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::string graph_id) { if (!find_graph(graph_id)) { callback(error_response(drogon::k404NotFound, "unknown graph")); return; } callback(json_response({{"prop", graph_prop_descriptor_json(graph_id)}, {"state", graph_state_descriptor_json()}, {"state_api", "/api/graphs/" + graph_id + "/state"}, {"prop_api", "/api/graphs/" + graph_id + "/prop"}})); }, {drogon::Get});
app.registerHandler("/api/graphs/{1}/descriptor", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::string graph_id) { if (!find_graph(graph_id)) { callback(error_response(drogon::k404NotFound, "unknown graph")); return; } callback(json_response({{"prop", graph_prop_descriptor_json(graph_id)}, {"state", graph_state_descriptor_json(graph_id)}, {"state_api", "/api/graphs/" + graph_id + "/state"}, {"prop_api", "/api/graphs/" + graph_id + "/prop"}})); }, {drogon::Get});
app.registerHandler("/api/graphs/{1}/state", [registry](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::string graph_id) { auto session = registry->acquire(graph_id); if (!session) { callback(error_response(drogon::k404NotFound, "unknown graph")); return; } auto output = std::make_shared<std::function<void(const drogon::HttpResponsePtr&)>>(std::move(callback)); session->async_state([output](nlohmann::json state) { (*output)(json_response(std::move(state))); }); }, {drogon::Get});
app.registerHandler("/api/graphs/{1}/prop", [registry](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::string graph_id) { auto session = registry->acquire(graph_id); if (!session) { callback(error_response(drogon::k404NotFound, "unknown graph")); return; } auto output = std::make_shared<std::function<void(const drogon::HttpResponsePtr&)>>(std::move(callback)); session->async_prop([output](nlohmann::json prop) { (*output)(json_response(std::move(prop))); }); }, {drogon::Get});
app.registerHandler("/api/graphs/{1}/prop", [registry](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::string graph_id) { auto session = registry->acquire(graph_id); if (!session) { callback(error_response(drogon::k404NotFound, "unknown graph")); return; } nlohmann::json patch; try { patch = nlohmann::json::parse(request->body()); } catch (const nlohmann::json::exception&) { callback(error_response(drogon::k400BadRequest, "invalid JSON patch")); return; } auto output = std::make_shared<std::function<void(const drogon::HttpResponsePtr&)>>(std::move(callback)); session->async_patch_prop(std::move(patch), [output](nlohmann::json result) { (*output)(json_response(std::move(result))); }); }, {drogon::Patch});