功能比较完善的一版
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "Graph_WebSocket.hpp" /* 图像素流 WebSocket。 */
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
@@ -21,26 +22,52 @@ void Graph_WebSocket::start() { if (d->attached) return; const auto weak = weak_
|
||||
void Graph_WebSocket::receive(std::string_view message) {
|
||||
const auto json = nlohmann::json::parse(message, nullptr, false);
|
||||
if (json.is_discarded() || !json.is_object()) return;
|
||||
const auto kind = json.find("kind");
|
||||
if (kind == json.end() || !kind->is_string()
|
||||
|| (*kind != "frame" && *kind != "input")) return;
|
||||
try {
|
||||
Plot_Event event;
|
||||
if (const auto value = json.find("time"); value != json.end() && value->is_number())
|
||||
event.time_milliseconds = value->get<double>();
|
||||
if (const auto value = json.find("width"); value != json.end() && value->is_number_unsigned())
|
||||
event.width = std::clamp(value->get<std::uint32_t>(), 160U, 1920U);
|
||||
if (const auto value = json.find("height"); value != json.end() && value->is_number_unsigned())
|
||||
event.height = std::clamp(value->get<std::uint32_t>(), 120U, 1080U);
|
||||
if (const auto value = json.find("wheel"); value != json.end() && value->is_object()) {
|
||||
const auto x = value->find("x");
|
||||
const auto y = value->find("y");
|
||||
const auto delta_y = value->find("delta_y");
|
||||
if (x != value->end() && x->is_number() && y != value->end() && y->is_number()
|
||||
&& delta_y != value->end() && delta_y->is_number()) {
|
||||
event.wheel = Plot_Wheel_Event{
|
||||
std::clamp(x->get<double>(), 0.0, static_cast<double>(event.width)),
|
||||
std::clamp(y->get<double>(), 0.0, static_cast<double>(event.height)),
|
||||
std::clamp(delta_y->get<double>(), -120.0, 120.0)};
|
||||
}
|
||||
if (const auto viewport = json.find("viewport"); viewport != json.end() && viewport->is_object()) {
|
||||
if (const auto value = viewport->find("width"); value != viewport->end() && value->is_number_unsigned())
|
||||
event.width = std::clamp(value->get<std::uint32_t>(), 160U, 1920U);
|
||||
if (const auto value = viewport->find("height"); value != viewport->end() && value->is_number_unsigned())
|
||||
event.height = std::clamp(value->get<std::uint32_t>(), 120U, 1080U);
|
||||
}
|
||||
if (*kind == "input") {
|
||||
const auto input = json.find("event");
|
||||
if (input == json.end() || !input->is_object()) return;
|
||||
const auto type = magic_enum::enum_cast<Event_Type>(input->value("type", std::string{}));
|
||||
if (!type) return;
|
||||
Plot_Input_Event decoded;
|
||||
decoded.type = *type;
|
||||
const auto read_point = [&](std::string_view key, render_2d::Point_F& point, bool viewport_relative) {
|
||||
const auto value = input->find(key);
|
||||
if (value == input->end() || !value->is_object()) return;
|
||||
const auto x = value->value("x", 0.0);
|
||||
const auto y = value->value("y", 0.0);
|
||||
point.x = viewport_relative ? std::clamp(x, 0.0, static_cast<double>(event.width)) : x;
|
||||
point.y = viewport_relative ? std::clamp(y, 0.0, static_cast<double>(event.height)) : y;
|
||||
};
|
||||
read_point("position", decoded.position, true);
|
||||
read_point("global_position", decoded.global_position, false);
|
||||
decoded.button = magic_enum::enum_cast<Mouse_Button>(input->value("button", std::string{"none"})).value_or(Mouse_Button::none);
|
||||
decoded.buttons = static_cast<Mouse_Button_Mask>(std::clamp(input->value("buttons", 0), 0, 255));
|
||||
decoded.modifiers = static_cast<Keyboard_Modifier>(std::clamp(input->value("modifiers", 0), 0, 15));
|
||||
decoded.pixel_delta_x = std::clamp(input->value("pixel_delta_x", 0.0), -4096.0, 4096.0);
|
||||
decoded.pixel_delta_y = std::clamp(input->value("pixel_delta_y", 0.0), -4096.0, 4096.0);
|
||||
decoded.angle_delta_x = std::clamp(input->value("angle_delta_x", 0.0), -120.0, 120.0);
|
||||
decoded.angle_delta_y = std::clamp(input->value("angle_delta_y", 0.0), -120.0, 120.0);
|
||||
decoded.key = magic_enum::enum_cast<Key>(input->value("key", std::string{"unknown"})).value_or(Key::unknown);
|
||||
decoded.native_key = input->value("native_key", 0U);
|
||||
decoded.auto_repeat = input->value("auto_repeat", false);
|
||||
event.input = decoded;
|
||||
}
|
||||
d->plot->submit(event);
|
||||
} catch (const nlohmann::json::exception&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
void Graph_WebSocket::close() { if (!d->attached) return; d->plot->detach(d->owner); d->attached = false; }
|
||||
|
||||
|
||||
+246
-47
@@ -77,6 +77,7 @@ std::string encode_frame(const Pixel_Frame& frame, std::uint64_t sequence) {
|
||||
|
||||
struct Schema_Query { Plot::Json_Handler handler; };
|
||||
struct Prop_Write {
|
||||
std::string component;
|
||||
std::string key;
|
||||
nlohmann::json value;
|
||||
Plot::Json_Handler handler;
|
||||
@@ -86,21 +87,29 @@ using Plot_Input = std::variant<Plot_Event, Schema_Query, Prop_Write>;
|
||||
template <typename... Owned_Objects>
|
||||
class Scene_View_Model final : public Plot::Scene_View {
|
||||
public:
|
||||
Scene_View_Model(std::unique_ptr<detail::Renderable_Descriptor> value_descriptor,
|
||||
Scene_View_Model(std::vector<std::unique_ptr<detail::Renderable_Descriptor>> value_descriptors,
|
||||
std::function<void(const Plot_Event&)> value_update,
|
||||
Owned_Objects... owned_objects)
|
||||
: descriptor(std::move(value_descriptor)),
|
||||
: descriptors(std::move(value_descriptors)),
|
||||
update_scene(std::move(value_update)),
|
||||
objects(std::move(owned_objects)...) {}
|
||||
|
||||
nlohmann::json schema() const override { return descriptor->schema(); }
|
||||
nlohmann::json write_prop(std::string_view key, const nlohmann::json& value) override {
|
||||
return descriptor->write_prop(key, value);
|
||||
nlohmann::json schema() const override {
|
||||
nlohmann::json components = nlohmann::json::array();
|
||||
for (const auto& descriptor : descriptors) components.push_back(descriptor->schema());
|
||||
return {{"protocol", "aethera.plot.inspector"}, {"version", 1}, {"components", std::move(components)}};
|
||||
}
|
||||
nlohmann::json write_prop(std::string_view component, std::string_view key, const nlohmann::json& value) override {
|
||||
const auto found = std::ranges::find_if(descriptors, [&](const auto& item) { return item->id() == component; });
|
||||
if (found == descriptors.end()) return {{"success", false}, {"error", "unknown component"}};
|
||||
auto result = (*found)->write_prop(key, value);
|
||||
result["component"] = component;
|
||||
return result;
|
||||
}
|
||||
void update(const Plot_Event& event) override { update_scene(event); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<detail::Renderable_Descriptor> descriptor;
|
||||
std::vector<std::unique_ptr<detail::Renderable_Descriptor>> descriptors;
|
||||
std::function<void(const Plot_Event&)> update_scene;
|
||||
std::tuple<Owned_Objects...> objects;
|
||||
};
|
||||
@@ -111,11 +120,9 @@ using Prop_Field = detail::Prop_Field<Member, Key, Description>;
|
||||
template <typename Definition, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
using State_Field = detail::State_Field<typename Definition::Base_Tag, Member, Key, Description>;
|
||||
|
||||
template <typename... Fields, typename Object, typename... Owned_Objects>
|
||||
std::unique_ptr<Plot::Scene_View> make_scene_view(
|
||||
Object& object,
|
||||
std::function<void(const Plot_Event&)> update,
|
||||
Owned_Objects&&... owned_objects) {
|
||||
template <typename Object, typename... Fields>
|
||||
std::unique_ptr<detail::Renderable_Descriptor> make_renderable_component(
|
||||
std::string id, std::string label, std::string kind, Object& object) {
|
||||
using Definition = typename Object::Attached_Object;
|
||||
using Tag = typename Definition::Base_Tag;
|
||||
using State = typename Definition::State;
|
||||
@@ -130,8 +137,134 @@ std::unique_ptr<Plot::Scene_View> make_scene_view(
|
||||
detail::State_Field<Tag, &State::paint_task_count, "paint_task_count", "Number of tasks in the current paint execution graph.">,
|
||||
detail::State_Field<Tag, &State::prepare_execution_time_ns, "prepare_execution_time_ns", "Measured prepare-stage execution time in nanoseconds.">,
|
||||
detail::State_Field<Tag, &State::paint_execution_time_ns, "paint_execution_time_ns", "Measured paint-stage execution time in nanoseconds.">>;
|
||||
return detail::make_renderable_descriptor(std::move(id), std::move(label), std::move(kind), Adapter{object});
|
||||
}
|
||||
|
||||
template <typename Scene_Object>
|
||||
std::unique_ptr<detail::Renderable_Descriptor> make_scene_component(Scene_Object& scene) {
|
||||
using Definition = typename Scene_Object::Attached_Object;
|
||||
using Prop = typename Definition::Prop;
|
||||
using Adapter = detail::Renderable_Adapter<Scene_Object,
|
||||
detail::Prop_Field<&Prop::background, "background", "Scene clear color.">,
|
||||
detail::Prop_Field<&Prop::view_active, "view_active", "Whether the scene publishes rendered frames.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_rebuilt, "taskflow_rebuilt", "Whether the scene task graph was rebuilt.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::renderable_count, "renderable_count", "Number of renderables attached to the scene.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_task_count, "taskflow_task_count", "Number of tasks in the scene graph.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_dependency_count, "taskflow_dependency_count", "Number of graph dependencies.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_max_predecessors, "taskflow_max_predecessors", "Maximum direct predecessors of a task.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_max_successors, "taskflow_max_successors", "Maximum direct successors of a task.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_execution_time_ns, "taskflow_execution_time_ns", "Scene graph execution time in nanoseconds.">>;
|
||||
return detail::make_renderable_descriptor("scene", "Scene", "scene", Adapter{scene});
|
||||
}
|
||||
|
||||
template <>
|
||||
std::unique_ptr<detail::Renderable_Descriptor> make_scene_component<Scene_3D>(Scene_3D& scene) {
|
||||
using Adapter = detail::Renderable_Adapter<Scene_3D,
|
||||
detail::Prop_Field<&Render_Scene_3D::Prop::clear_color, "clear_color", "Linear scene clear color.">,
|
||||
detail::Prop_Field<&Render_Scene_3D::Prop::view_active, "view_active", "Whether the scene publishes rendered frames.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_rebuilt, "taskflow_rebuilt", "Whether the scene task graph was rebuilt.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::renderable_count, "renderable_count", "Number of renderables attached to the scene.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_task_count, "taskflow_task_count", "Number of tasks in the scene graph.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_dependency_count, "taskflow_dependency_count", "Number of graph dependencies.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_max_predecessors, "taskflow_max_predecessors", "Maximum direct predecessors of a task.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_max_successors, "taskflow_max_successors", "Maximum direct successors of a task.">,
|
||||
detail::State_Field<Scene::Base_Tag, &Scene::State::taskflow_execution_time_ns, "taskflow_execution_time_ns", "Scene graph execution time in nanoseconds.">>;
|
||||
return detail::make_renderable_descriptor("scene", "Scene", "scene", Adapter{scene});
|
||||
}
|
||||
|
||||
template <typename Axis_Object>
|
||||
std::unique_ptr<detail::Renderable_Descriptor> make_axis_component(
|
||||
std::string id, std::string label, Axis_Object& axis) {
|
||||
using Adapter = detail::Renderable_Adapter<Axis_Object,
|
||||
Prop_Field<&Abs_Axis::Prop::position, "position", "Axis origin in viewport pixels.">,
|
||||
Prop_Field<&Abs_Axis::Prop::canvas_size, "canvas_size", "Axis canvas extent.">,
|
||||
Prop_Field<&Abs_Axis::Prop::pixel_length, "pixel_length", "Signed axis length in pixels.">,
|
||||
Prop_Field<&Abs_Axis::Prop::orientation, "orientation", "Axis orientation.">,
|
||||
Prop_Field<&Abs_Axis::Prop::tick_length, "tick_length", "Major tick length.">,
|
||||
Prop_Field<&Abs_Axis::Prop::sub_tick_length, "sub_tick_length", "Minor tick length.">,
|
||||
Prop_Field<&Abs_Axis::Prop::axis_pen, "axis_pen", "Axis line and tick style.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text, "unit_text", "Axis unit label.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text_font, "unit_text_font", "Axis label font.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text_pen, "unit_text_pen", "Axis label foreground.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text_background_brush, "unit_text_background_brush", "Axis label background.">,
|
||||
Prop_Field<&Abs_Axis::Prop::label_rotation_degrees, "label_rotation_degrees", "Tick label rotation.">,
|
||||
Prop_Field<&Numeric_Axis::Prop::coordinate_range, "coordinate_range", "Visible coordinate range.">,
|
||||
Prop_Field<&Numeric_Axis::Prop::precision, "precision", "Maximum decimal precision.">,
|
||||
Prop_Field<&Numeric_Axis::Prop::locale, "locale", "Numeric label locale.">,
|
||||
Prop_Field<&Numeric_Axis::Prop::wheel_enabled, "wheel_enabled", "Allows wheel zoom.">,
|
||||
Prop_Field<&Numeric_Axis::Prop::drag_enabled, "drag_enabled", "Allows pointer drag panning.">>;
|
||||
return make_renderable_component<Axis_Object,
|
||||
Prop_Field<&Abs_Axis::Prop::position, "position", "Axis origin in viewport pixels.">,
|
||||
Prop_Field<&Abs_Axis::Prop::canvas_size, "canvas_size", "Axis canvas extent.">,
|
||||
Prop_Field<&Abs_Axis::Prop::pixel_length, "pixel_length", "Signed axis length in pixels.">,
|
||||
Prop_Field<&Abs_Axis::Prop::orientation, "orientation", "Axis orientation.">,
|
||||
Prop_Field<&Abs_Axis::Prop::tick_length, "tick_length", "Major tick length.">,
|
||||
Prop_Field<&Abs_Axis::Prop::sub_tick_length, "sub_tick_length", "Minor tick length.">,
|
||||
Prop_Field<&Abs_Axis::Prop::axis_pen, "axis_pen", "Axis line and tick style.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text, "unit_text", "Axis unit label.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text_font, "unit_text_font", "Axis label font.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text_pen, "unit_text_pen", "Axis label foreground.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text_background_brush, "unit_text_background_brush", "Axis label background.">,
|
||||
Prop_Field<&Abs_Axis::Prop::label_rotation_degrees, "label_rotation_degrees", "Tick label rotation.">,
|
||||
Prop_Field<&Numeric_Axis::Prop::coordinate_range, "coordinate_range", "Visible coordinate range.">,
|
||||
Prop_Field<&Numeric_Axis::Prop::precision, "precision", "Maximum decimal precision.">,
|
||||
Prop_Field<&Numeric_Axis::Prop::locale, "locale", "Numeric label locale.">,
|
||||
Prop_Field<&Numeric_Axis::Prop::wheel_enabled, "wheel_enabled", "Allows wheel zoom.">,
|
||||
Prop_Field<&Numeric_Axis::Prop::drag_enabled, "drag_enabled", "Allows pointer drag panning.">>(
|
||||
std::move(id), std::move(label), "axis", axis);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::unique_ptr<detail::Renderable_Descriptor> make_axis_component<Time_Axis_Object>(
|
||||
std::string id, std::string label, Time_Axis_Object& axis) {
|
||||
return make_renderable_component<Time_Axis_Object,
|
||||
Prop_Field<&Abs_Axis::Prop::position, "position", "Axis origin in viewport pixels.">,
|
||||
Prop_Field<&Abs_Axis::Prop::canvas_size, "canvas_size", "Axis canvas extent.">,
|
||||
Prop_Field<&Abs_Axis::Prop::pixel_length, "pixel_length", "Signed axis length in pixels.">,
|
||||
Prop_Field<&Abs_Axis::Prop::orientation, "orientation", "Axis orientation.">,
|
||||
Prop_Field<&Abs_Axis::Prop::tick_length, "tick_length", "Major tick length.">,
|
||||
Prop_Field<&Abs_Axis::Prop::sub_tick_length, "sub_tick_length", "Minor tick length.">,
|
||||
Prop_Field<&Abs_Axis::Prop::axis_pen, "axis_pen", "Axis line and tick style.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text, "unit_text", "Axis unit label.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text_font, "unit_text_font", "Axis label font.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text_pen, "unit_text_pen", "Axis label foreground.">,
|
||||
Prop_Field<&Abs_Axis::Prop::unit_text_background_brush, "unit_text_background_brush", "Axis label background.">,
|
||||
Prop_Field<&Abs_Axis::Prop::label_rotation_degrees, "label_rotation_degrees", "Tick label rotation.">,
|
||||
Prop_Field<&Time_Axis::Prop::visible_count, "visible_count", "Maximum visible time samples.">,
|
||||
Prop_Field<&Time_Axis::Prop::tick_label_spacing_px, "tick_label_spacing_px", "Spacing between time labels.">,
|
||||
Prop_Field<&Time_Axis::Prop::estimated_label_width_px, "estimated_label_width_px", "Estimated time label width.">,
|
||||
Prop_Field<&Time_Axis::Prop::format, "format", "Time label format.">,
|
||||
Prop_Field<&Time_Axis::Prop::newest_at_start, "newest_at_start", "Places the newest time at the range origin.">,
|
||||
State_Field<Time_Axis, &Time_Axis::State::next_tick, "next_tick", "Next allocated time tick.">,
|
||||
State_Field<Time_Axis, &Time_Axis::State::samples, "samples", "Published time sample window.">>(
|
||||
std::move(id), std::move(label), "axis", axis);
|
||||
}
|
||||
|
||||
template <typename... Fields, typename Object, typename... Owned_Objects>
|
||||
std::unique_ptr<Plot::Scene_View> make_scene_view(
|
||||
Object& object,
|
||||
Scene_2D& scene,
|
||||
std::function<void(const Plot_Event&)> update,
|
||||
Owned_Objects&&... owned_objects) {
|
||||
using Definition = typename Object::Attached_Object;
|
||||
using Tag = typename Definition::Base_Tag;
|
||||
using State = typename Definition::State;
|
||||
std::vector<std::unique_ptr<detail::Renderable_Descriptor>> components;
|
||||
components.push_back(make_scene_component(scene));
|
||||
components.push_back(make_renderable_component<Object, Fields...>("plot", "Plot Renderable", "renderable", object));
|
||||
std::size_t axis_index{};
|
||||
const auto append_owned = [&](const auto& owned) {
|
||||
using Owned = std::remove_cvref_t<decltype(owned)>;
|
||||
if constexpr (std::same_as<typename Owned::element_type, Frequency_Axis_Object>
|
||||
|| std::same_as<typename Owned::element_type, Numeric_Axis_Object>
|
||||
|| std::same_as<typename Owned::element_type, Time_Axis_Object>) {
|
||||
const auto id = axis_index++ == 0 ? "axis-x" : "axis-y";
|
||||
components.push_back(make_axis_component(id, id == std::string_view{"axis-x"} ? "Horizontal Axis" : "Vertical Axis", *owned));
|
||||
}
|
||||
};
|
||||
(append_owned(owned_objects), ...);
|
||||
return std::make_unique<Scene_View_Model<std::remove_cvref_t<Owned_Objects>...>>(
|
||||
detail::make_renderable_descriptor(Adapter{object}),
|
||||
std::move(components),
|
||||
std::move(update),
|
||||
std::forward<Owned_Objects>(owned_objects)...);
|
||||
}
|
||||
@@ -173,23 +306,69 @@ Time_Axis_Object::Builder time_axis_builder(
|
||||
|
||||
template <typename... Axes>
|
||||
void resize_axes(Size viewport, Axes*... axes) {
|
||||
const double left = std::clamp(static_cast<double>(viewport.width) * 0.10, 42.0, 68.0);
|
||||
const double right = std::clamp(static_cast<double>(viewport.width) * 0.05, 18.0, 34.0);
|
||||
const double top = std::clamp(static_cast<double>(viewport.height) * 0.07, 16.0, 30.0);
|
||||
const double bottom = std::clamp(static_cast<double>(viewport.height) * 0.13, 36.0, 52.0);
|
||||
const Point_F origin{left, static_cast<double>(viewport.height) - bottom};
|
||||
const Axis_Pixel_Length horizontal_length = std::max(1.0, static_cast<double>(viewport.width) - left - right);
|
||||
const Axis_Pixel_Length vertical_length = -std::max(1.0, static_cast<double>(viewport.height) - top - bottom);
|
||||
const auto resize_axis = [&](auto* axis) {
|
||||
const auto orientation = axis->template read_prop<Abs_Axis::Base_Tag>().orientation;
|
||||
const auto layout = axis->template read_prop<Abs_Axis::Base_Tag>();
|
||||
if (layout.canvas_size == viewport) return;
|
||||
const double horizontal_scale = layout.canvas_size.width > 0
|
||||
? static_cast<double>(viewport.width) / layout.canvas_size.width : 1.0;
|
||||
const double vertical_scale = layout.canvas_size.height > 0
|
||||
? static_cast<double>(viewport.height) / layout.canvas_size.height : 1.0;
|
||||
axis->template set<&Abs_Axis::Prop::canvas_size>(viewport);
|
||||
axis->template set<&Abs_Axis::Prop::position>(origin);
|
||||
axis->template set<&Abs_Axis::Prop::pixel_length>(
|
||||
orientation == Axis_Orientation::horizontal ? horizontal_length : vertical_length);
|
||||
axis->template set<&Abs_Axis::Prop::position>(Point_F{
|
||||
layout.position.x * horizontal_scale,
|
||||
layout.position.y * vertical_scale});
|
||||
axis->template set<&Abs_Axis::Prop::pixel_length>(layout.pixel_length *
|
||||
(layout.orientation == Axis_Orientation::horizontal ? horizontal_scale : vertical_scale));
|
||||
};
|
||||
(resize_axis(axes), ...);
|
||||
}
|
||||
|
||||
template <typename Scene_Object>
|
||||
void dispatch_plot_input(Scene_Object& scene, const Plot_Input_Event& input) {
|
||||
const auto apply_pointer = [&](auto& event) {
|
||||
event.position = input.position;
|
||||
event.global_position = input.global_position;
|
||||
event.button = input.button;
|
||||
event.buttons = input.buttons;
|
||||
event.modifiers = input.modifiers;
|
||||
};
|
||||
switch (input.type) {
|
||||
case Event_Type::pointer_move:
|
||||
case Event_Type::pointer_press:
|
||||
case Event_Type::pointer_release: {
|
||||
Basic_Pointer_Event<Point_F> event(input.type);
|
||||
apply_pointer(event);
|
||||
static_cast<void>(scene.dispatch_event(event));
|
||||
break;
|
||||
}
|
||||
case Event_Type::wheel: {
|
||||
Basic_Wheel_Event<Point_F> event;
|
||||
apply_pointer(event);
|
||||
event.pixel_delta_x = input.pixel_delta_x;
|
||||
event.pixel_delta_y = input.pixel_delta_y;
|
||||
event.angle_delta_x = input.angle_delta_x;
|
||||
event.angle_delta_y = input.angle_delta_y;
|
||||
static_cast<void>(scene.dispatch_event(event));
|
||||
break;
|
||||
}
|
||||
case Event_Type::key_press:
|
||||
case Event_Type::key_release: {
|
||||
Key_Event event(input.type);
|
||||
event.key = input.key;
|
||||
event.native_key = input.native_key;
|
||||
event.modifiers = input.modifiers;
|
||||
event.auto_repeat = input.auto_repeat;
|
||||
static_cast<void>(scene.dispatch_event(event));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Event event(input.type);
|
||||
static_cast<void>(scene.dispatch_event(event));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::shared_ptr<Plot> make_spectrum_plot(asio::any_io_executor executor) {
|
||||
@@ -209,6 +388,7 @@ std::shared_ptr<Plot> make_spectrum_plot(asio::any_io_executor executor) {
|
||||
auto scene = *scene_builder.build();
|
||||
auto update = [raw = spectrum.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
|
||||
if (event.input) return;
|
||||
std::array<double, 256> samples{};
|
||||
for (std::size_t i = 0; i < samples.size(); ++i) {
|
||||
const double x = static_cast<double>(i) / samples.size();
|
||||
@@ -246,7 +426,7 @@ std::shared_ptr<Plot> make_spectrum_plot(asio::any_io_executor executor) {
|
||||
State_Field<Spectrum, &Spectrum::State::sample_count, "sample_count", "Number of input spectrum samples available in the latest update.">,
|
||||
State_Field<Spectrum, &Spectrum::State::rendered_point_count, "rendered_point_count", "Number of curve points emitted by the latest render preparation.">,
|
||||
State_Field<Spectrum, &Spectrum::State::selectable_marker_count, "selectable_marker_count", "Number of markers currently eligible for selection.">>(
|
||||
*spectrum, std::move(update), std::move(frequency), std::move(vertical), std::move(spectrum));
|
||||
*spectrum, *scene, std::move(update), std::move(frequency), std::move(vertical), std::move(spectrum));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
@@ -263,9 +443,13 @@ std::shared_ptr<Plot> make_frequency_trace_plot(asio::any_io_executor executor)
|
||||
.set(&Render_Scene_2D::Prop::view_active, true);
|
||||
scene_builder.add_renderable(trace.get());
|
||||
auto scene = *scene_builder.build();
|
||||
auto update = [raw = trace.get(), time = time.get(), vertical = vertical.get(), tick = std::uint64_t{}](const Plot_Event& event) mutable {
|
||||
auto update = [raw = trace.get(), time = time.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, time, vertical);
|
||||
raw->append_sample(tick++, std::sin(event.time_milliseconds * 0.0025) * 0.8
|
||||
if (event.input) return;
|
||||
constexpr double day_milliseconds = 86'400'000.0;
|
||||
raw->append_sample(Time_Of_Day{static_cast<std::int64_t>(
|
||||
std::fmod(std::max(0.0, event.time_milliseconds), day_milliseconds))},
|
||||
std::sin(event.time_milliseconds * 0.0025) * 0.8
|
||||
+ std::sin(event.time_milliseconds * 0.0007) * 0.2);
|
||||
};
|
||||
auto view = make_scene_view<
|
||||
@@ -275,7 +459,7 @@ std::shared_ptr<Plot> make_frequency_trace_plot(asio::any_io_executor executor)
|
||||
Prop_Field<&Frequency_Trace::Prop::samples, "samples", "Complete time-ordered collection of frequency trace samples.">,
|
||||
State_Field<Frequency_Trace, &Frequency_Trace::State::sample_count, "sample_count", "Number of samples retained by the current trace.">,
|
||||
State_Field<Frequency_Trace, &Frequency_Trace::State::rendered_point_count, "rendered_point_count", "Number of points emitted for the latest trace frame.">>(
|
||||
*trace, std::move(update), std::move(time), std::move(vertical), std::move(trace));
|
||||
*trace, *scene, std::move(update), std::move(time), std::move(vertical), std::move(trace));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
@@ -285,7 +469,9 @@ std::shared_ptr<Plot> make_sweep_spectrum_plot(asio::any_io_executor executor) {
|
||||
auto vertical = *numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}, canvas).build();
|
||||
Impl<Sweep_Spectrum>::Builder sweep_builder(frequency.get(), vertical.get());
|
||||
sweep_builder.set(&Sweep_Spectrum::Prop::frequency_range, Axis_Range{0.0, 100.0});
|
||||
sweep_builder.set(&Sweep_Spectrum::Prop::frequency_range, Axis_Range{0.0, 100.0})
|
||||
.set(&Sweep_Spectrum::Prop::bins_per_block, std::size_t{8})
|
||||
.set(&Sweep_Spectrum::Prop::block_count, std::size_t{64});
|
||||
auto sweep = *sweep_builder.build();
|
||||
Scene_2D::Builder scene_builder;
|
||||
scene_builder.set(&Render_Scene_2D::Prop::viewport, canvas)
|
||||
@@ -293,12 +479,23 @@ std::shared_ptr<Plot> make_sweep_spectrum_plot(asio::any_io_executor executor) {
|
||||
.set(&Render_Scene_2D::Prop::view_active, true);
|
||||
scene_builder.add_renderable(sweep.get());
|
||||
auto scene = *scene_builder.build();
|
||||
auto update = [raw = sweep.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
auto update = [raw = sweep.get(), frequency = frequency.get(), vertical = vertical.get(), block_index = std::size_t{}](const Plot_Event& event) mutable {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
|
||||
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 + event.time_milliseconds * 0.002);
|
||||
if (event.input) return;
|
||||
const auto& state = raw->template read_prop<Sweep_Spectrum::Base_Tag>();
|
||||
const std::size_t block_count = std::max<std::size_t>(1, state.block_count);
|
||||
const std::size_t bins_per_block = std::max<std::size_t>(1, state.bins_per_block);
|
||||
if (block_index >= block_count) {
|
||||
raw->template set<&Sweep_Spectrum::Prop::blocks>(std::vector<std::vector<Plot_Value>>{});
|
||||
block_index = 0;
|
||||
}
|
||||
std::vector<double> values(bins_per_block);
|
||||
for (std::size_t i = 0; i < values.size(); ++i) {
|
||||
const auto sweep_index = block_index * values.size() + i;
|
||||
values[i] = -90.0 + 35.0 * std::sin(sweep_index * 0.08 + event.time_milliseconds * 0.002);
|
||||
}
|
||||
raw->append_block(values);
|
||||
++block_index;
|
||||
};
|
||||
auto view = make_scene_view<
|
||||
Prop_Field<&Sweep_Spectrum::Prop::bins_per_block, "bins_per_block", "Number of frequency bins stored in each incoming sweep block.">,
|
||||
@@ -314,7 +511,7 @@ std::shared_ptr<Plot> make_sweep_spectrum_plot(asio::any_io_executor executor) {
|
||||
State_Field<Sweep_Spectrum, &Sweep_Spectrum::State::stored_block_count, "stored_block_count", "Number of sweep blocks currently retained.">,
|
||||
State_Field<Sweep_Spectrum, &Sweep_Spectrum::State::stored_point_count, "stored_point_count", "Total number of frequency points retained across all blocks.">,
|
||||
State_Field<Sweep_Spectrum, &Sweep_Spectrum::State::rendered_point_count, "rendered_point_count", "Number of points emitted for the latest sweep frame.">>(
|
||||
*sweep, std::move(update), std::move(frequency), std::move(vertical), std::move(sweep));
|
||||
*sweep, *scene, std::move(update), std::move(frequency), std::move(vertical), std::move(sweep));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
@@ -336,6 +533,7 @@ std::shared_ptr<Plot> make_afterglow_plot(asio::any_io_executor executor) {
|
||||
auto scene = *scene_builder.build();
|
||||
auto update = [raw = afterglow.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
|
||||
if (event.input) return;
|
||||
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(
|
||||
@@ -357,7 +555,7 @@ std::shared_ptr<Plot> make_afterglow_plot(asio::any_io_executor executor) {
|
||||
State_Field<Afterglow, &Afterglow::State::history_count, "history_count", "Number of spectrum frames retained in afterglow history.">,
|
||||
State_Field<Afterglow, &Afterglow::State::latest_spectrum_point_count, "latest_spectrum_point_count", "Number of samples in the most recently appended spectrum.">,
|
||||
State_Field<Afterglow, &Afterglow::State::rendered_cell_count, "rendered_cell_count", "Number of colored cells emitted for the latest frame.">>(
|
||||
*afterglow, std::move(update), std::move(frequency), std::move(vertical), std::move(afterglow));
|
||||
*afterglow, *scene, std::move(update), std::move(frequency), std::move(vertical), std::move(afterglow));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
@@ -378,6 +576,7 @@ std::shared_ptr<Plot> make_waterfall_plot(asio::any_io_executor executor) {
|
||||
auto scene = *scene_builder.build();
|
||||
auto update = [raw = waterfall.get(), frequency = frequency.get(), time = time.get()](const Plot_Event& event) {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, time);
|
||||
if (event.input) return;
|
||||
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(
|
||||
@@ -404,7 +603,7 @@ std::shared_ptr<Plot> make_waterfall_plot(asio::any_io_executor executor) {
|
||||
State_Field<Waterfall, &Waterfall::State::row_count, "row_count", "Number of waterfall rows currently retained.">,
|
||||
State_Field<Waterfall, &Waterfall::State::stored_point_count, "stored_point_count", "Total number of spectrum points retained across all rows.">,
|
||||
State_Field<Waterfall, &Waterfall::State::rendered_cell_count, "rendered_cell_count", "Number of raster cells emitted for the latest frame.">>(
|
||||
*waterfall, std::move(update), std::move(frequency), std::move(time), std::move(waterfall));
|
||||
*waterfall, *scene, std::move(update), std::move(frequency), std::move(time), std::move(waterfall));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
@@ -426,6 +625,7 @@ std::shared_ptr<Plot> make_constellation_plot(asio::any_io_executor executor) {
|
||||
auto scene = *scene_builder.build();
|
||||
auto update = [raw = constellation.get(), horizontal = horizontal.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, horizontal, vertical);
|
||||
if (event.input) return;
|
||||
const auto& state = raw->template read_prop<Constellation_Diagram::Base_Tag>();
|
||||
const int anchor_count = static_cast<int>(state.type);
|
||||
const double radius = std::min(state.i_range.size(), state.q_range.size()) * 0.4;
|
||||
@@ -451,7 +651,7 @@ std::shared_ptr<Plot> make_constellation_plot(asio::any_io_executor executor) {
|
||||
Prop_Field<&Constellation_Diagram::Prop::anchor_color, "anchor_color", "Color used to render ideal modulation anchors.">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::points, "points", "Current time-stamped collection of received I/Q samples.">,
|
||||
State_Field<Constellation_Diagram, &Constellation_Diagram::State::point_count, "point_count", "Number of constellation samples currently retained.">>(
|
||||
*constellation, std::move(update), std::move(horizontal), std::move(vertical), std::move(constellation));
|
||||
*constellation, *scene, std::move(update), std::move(horizontal), std::move(vertical), std::move(constellation));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
@@ -481,7 +681,7 @@ std::shared_ptr<Plot> make_selection_overlay_plot(asio::any_io_executor executor
|
||||
&Selection_Rectangle_Overlay::State::selected_region_count,
|
||||
"selected_region_count",
|
||||
"Number of rectangular regions currently selected.">>(
|
||||
*selection, std::move(update), std::move(horizontal), std::move(vertical), std::move(selection));
|
||||
*selection, *scene, std::move(update), std::move(horizontal), std::move(vertical), std::move(selection));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
@@ -556,7 +756,7 @@ void Plot::ensure_started() {
|
||||
continue;
|
||||
}
|
||||
if (auto* write = std::get_if<Prop_Write>(&input)) {
|
||||
write->handler(self->d->view->write_prop(write->key, write->value));
|
||||
write->handler(self->d->view->write_prop(write->component, write->key, write->value));
|
||||
continue;
|
||||
}
|
||||
auto event = std::get<Plot_Event>(input);
|
||||
@@ -566,16 +766,12 @@ void Plot::ensure_started() {
|
||||
if (auto* scene = std::get_if<std::unique_ptr<Scene_2D>>(&self->d->scene)) {
|
||||
(*scene)->set<&Render_Scene_2D::Prop::viewport>(
|
||||
Size{static_cast<int>(event.width), static_cast<int>(event.height)});
|
||||
if (event.wheel) {
|
||||
Wheel_Event wheel;
|
||||
wheel.position = {event.wheel->position_x, event.wheel->position_y};
|
||||
wheel.angle_delta_y = event.wheel->delta_y;
|
||||
(*scene)->dispatch_event(wheel);
|
||||
}
|
||||
if (event.input) dispatch_plot_input(**scene, *event.input);
|
||||
(*scene)->render();
|
||||
} else {
|
||||
auto& scene_3d = std::get<std::unique_ptr<Scene_3D>>(self->d->scene);
|
||||
scene_3d->set<&Render_Scene_3D::Prop::viewport>(Extent{event.width, event.height});
|
||||
if (event.input) dispatch_plot_input(*scene_3d, *event.input);
|
||||
scene_3d->render();
|
||||
}
|
||||
}
|
||||
@@ -610,10 +806,10 @@ void Plot::async_schema(Json_Handler handler) {
|
||||
throw std::runtime_error("plot input queue is unavailable");
|
||||
}
|
||||
|
||||
void Plot::async_write_prop(std::string key, nlohmann::json value, Json_Handler handler) {
|
||||
void Plot::async_write_prop(std::string component, std::string key, nlohmann::json value, Json_Handler handler) {
|
||||
ensure_started();
|
||||
if (!d->inputs.try_send(asio::error_code{}, Plot_Input{
|
||||
Prop_Write{std::move(key), std::move(value), std::move(handler)}}))
|
||||
Prop_Write{std::move(component), std::move(key), std::move(value), std::move(handler)}}))
|
||||
throw std::runtime_error("plot input queue is unavailable");
|
||||
}
|
||||
|
||||
@@ -637,8 +833,11 @@ std::shared_ptr<Plot> make_datoviz_point_plot(asio::any_io_executor executor) {
|
||||
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::item_count, "item_count", "Number of point items currently published by the visual.">,
|
||||
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::prepared_item_count, "prepared_item_count", "Number of point items prepared for the latest GPU submission.">,
|
||||
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::prepared_revision, "prepared_revision", "Property revision represented by the currently prepared GPU data.">>;
|
||||
std::vector<std::unique_ptr<detail::Renderable_Descriptor>> components;
|
||||
components.push_back(make_scene_component(*scene));
|
||||
components.push_back(detail::make_renderable_descriptor("plot", "Point Visual", "visual", Adapter{*visual}));
|
||||
auto view = std::make_unique<Scene_View_Model<decltype(visual)>>(
|
||||
detail::make_renderable_descriptor(Adapter{*visual}),
|
||||
std::move(components),
|
||||
[](const Plot_Event&) {}, std::move(visual));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
+17
-7
@@ -10,16 +10,26 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
namespace aethera::web {
|
||||
struct Plot_Wheel_Event {
|
||||
double position_x{}; /* Pointer position in the rendered viewport, in physical pixels. */
|
||||
double position_y{}; /* Pointer position in the rendered viewport, in physical pixels. */
|
||||
double delta_y{}; /* Normalized vertical wheel angle delta. */
|
||||
struct Plot_Input_Event {
|
||||
Event_Type type{Event_Type::pointer_move};
|
||||
render_2d::Point_F position{};
|
||||
render_2d::Point_F global_position{};
|
||||
Mouse_Button button{Mouse_Button::none};
|
||||
Mouse_Button_Mask buttons{};
|
||||
Keyboard_Modifier modifiers{Keyboard_Modifier::none};
|
||||
double pixel_delta_x{};
|
||||
double pixel_delta_y{};
|
||||
double angle_delta_x{};
|
||||
double angle_delta_y{};
|
||||
Key key{Key::unknown};
|
||||
std::uint32_t native_key{};
|
||||
bool auto_repeat{};
|
||||
};
|
||||
struct Plot_Event {
|
||||
double time_milliseconds{};
|
||||
std::uint32_t width{720};
|
||||
std::uint32_t height{420};
|
||||
std::optional<Plot_Wheel_Event> wheel{}; /* Canvas interaction attached to this render request. */
|
||||
std::optional<Plot_Input_Event> input{};
|
||||
};
|
||||
class Plot final : public std::enable_shared_from_this<Plot> {
|
||||
public:
|
||||
@@ -29,7 +39,7 @@ public:
|
||||
public:
|
||||
virtual ~Scene_View() = default;
|
||||
[[nodiscard]] virtual nlohmann::json schema() const = 0;
|
||||
[[nodiscard]] virtual nlohmann::json write_prop(std::string_view key,
|
||||
[[nodiscard]] virtual nlohmann::json write_prop(std::string_view component, std::string_view key,
|
||||
const nlohmann::json& value) = 0;
|
||||
virtual void update(const Plot_Event& event) = 0;
|
||||
};
|
||||
@@ -46,7 +56,7 @@ public:
|
||||
void detach(const void* owner);
|
||||
void submit(Plot_Event event);
|
||||
void async_schema(Json_Handler handler);
|
||||
void async_write_prop(std::string key, nlohmann::json value, Json_Handler handler);
|
||||
void async_write_prop(std::string component, std::string key, nlohmann::json value, Json_Handler handler);
|
||||
private:
|
||||
struct Private;
|
||||
void ensure_started();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <structive/property/property.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace aethera::web::detail {
|
||||
@@ -25,6 +26,7 @@ class Renderable_Adapter;
|
||||
class Renderable_Descriptor {
|
||||
public:
|
||||
virtual ~Renderable_Descriptor() = default;
|
||||
[[nodiscard]] virtual std::string_view id() const noexcept = 0;
|
||||
[[nodiscard]] virtual nlohmann::json schema() const = 0;
|
||||
[[nodiscard]] virtual nlohmann::json write_prop(std::string_view key, const nlohmann::json& value) = 0;
|
||||
};
|
||||
@@ -32,15 +34,20 @@ public:
|
||||
template <typename Adapter>
|
||||
class Renderable_Descriptor_Model final : public Renderable_Descriptor {
|
||||
public:
|
||||
explicit Renderable_Descriptor_Model(Adapter adapter);
|
||||
Renderable_Descriptor_Model(std::string id, std::string label, std::string kind, Adapter adapter);
|
||||
[[nodiscard]] std::string_view id() const noexcept override;
|
||||
[[nodiscard]] nlohmann::json schema() const override;
|
||||
[[nodiscard]] nlohmann::json write_prop(std::string_view key, const nlohmann::json& value) override;
|
||||
private:
|
||||
std::string component_id;
|
||||
std::string component_label;
|
||||
std::string component_kind;
|
||||
Adapter adapter; /* 与 Plot 内真实 Renderable 同生共死的无所有权协议视图。 */
|
||||
};
|
||||
|
||||
template <typename Adapter>
|
||||
[[nodiscard]] std::unique_ptr<Renderable_Descriptor> make_renderable_descriptor(Adapter adapter);
|
||||
[[nodiscard]] std::unique_ptr<Renderable_Descriptor> make_renderable_descriptor(
|
||||
std::string id, std::string label, std::string kind, Adapter adapter);
|
||||
}
|
||||
|
||||
#include "Renderable_Adapter.ipp"
|
||||
|
||||
@@ -1,80 +1,20 @@
|
||||
#pragma once
|
||||
#include <adminive/adapters/boost_pfr.hpp>
|
||||
#include <adminive/adapters/magic_enum.hpp>
|
||||
#include <adminive/adapters/nlohmann_json.hpp>
|
||||
#include <adminive/json.hpp>
|
||||
#include <boost/pfr.hpp>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <render_2D/plottable/Plottables.hpp>
|
||||
#include <render_3D/Render_3D.hpp>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <ranges>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace aethera::web::detail {
|
||||
template <typename Object, typename Members>
|
||||
struct Json_Member_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;
|
||||
};
|
||||
|
||||
struct Color_Map_Json_Members {
|
||||
decltype(&render_2d::Color_Map::stops) stops{&render_2d::Color_Map::stops}; /* 唯一需要显式适配的非聚合值字段。 */
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
template <typename Value, std::size_t Size, Json_Type Json>
|
||||
struct Value_Adapter<std::array<Value, Size>, Json> {
|
||||
using value_type = std::array<Value, Size>;
|
||||
static constexpr std::string_view type_name{"array"};
|
||||
static Json encode(const value_type& values);
|
||||
static void decode(value_type& target, const Json& value);
|
||||
};
|
||||
|
||||
template <> struct Reflection_Adapter<aethera::Color> : Boost_Pfr_Reflection_Adapter<aethera::Color> {};
|
||||
template <> struct Type_Descriptor<aethera::Color> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_2d::Point_F> : Boost_Pfr_Reflection_Adapter<aethera::render_2d::Point_F> {};
|
||||
template <> struct Type_Descriptor<aethera::render_2d::Point_F> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_2d::Rect_F> : Boost_Pfr_Reflection_Adapter<aethera::render_2d::Rect_F> {};
|
||||
template <> struct Type_Descriptor<aethera::render_2d::Rect_F> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_2d::Axis_Range> : Boost_Pfr_Reflection_Adapter<aethera::render_2d::Axis_Range> {};
|
||||
template <> struct Type_Descriptor<aethera::render_2d::Axis_Range> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_2d::Pen> : Boost_Pfr_Reflection_Adapter<aethera::render_2d::Pen> {};
|
||||
template <> struct Type_Descriptor<aethera::render_2d::Pen> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_2d::Brush> : Boost_Pfr_Reflection_Adapter<aethera::render_2d::Brush> {};
|
||||
template <> struct Type_Descriptor<aethera::render_2d::Brush> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_2d::Font> : Boost_Pfr_Reflection_Adapter<aethera::render_2d::Font> {};
|
||||
template <> struct Type_Descriptor<aethera::render_2d::Font> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_2d::Frequency_Trace_Sample> : Boost_Pfr_Reflection_Adapter<aethera::render_2d::Frequency_Trace_Sample> {};
|
||||
template <> struct Type_Descriptor<aethera::render_2d::Frequency_Trace_Sample> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_2d::Waterfall_Row> : Boost_Pfr_Reflection_Adapter<aethera::render_2d::Waterfall_Row> {};
|
||||
template <> struct Type_Descriptor<aethera::render_2d::Waterfall_Row> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_2d::Constellation_Point> : Boost_Pfr_Reflection_Adapter<aethera::render_2d::Constellation_Point> {};
|
||||
template <> struct Type_Descriptor<aethera::render_2d::Constellation_Point> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_3d::Vec3> : Boost_Pfr_Reflection_Adapter<aethera::render_3d::Vec3> {};
|
||||
template <> struct Type_Descriptor<aethera::render_3d::Vec3> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_3d::Matrix4> : Boost_Pfr_Reflection_Adapter<aethera::render_3d::Matrix4> {};
|
||||
template <> struct Type_Descriptor<aethera::render_3d::Matrix4> { static auto get(); };
|
||||
template <> struct Reflection_Adapter<aethera::render_3d::Point> : Boost_Pfr_Reflection_Adapter<aethera::render_3d::Point> {};
|
||||
template <> struct Type_Descriptor<aethera::render_3d::Point> { static auto get(); };
|
||||
|
||||
template <>
|
||||
struct Reflection_Adapter<aethera::render_2d::Color_Map>
|
||||
: aethera::web::detail::Json_Member_Reflection_Adapter<aethera::render_2d::Color_Map, aethera::web::detail::Color_Map_Json_Members> {};
|
||||
template <>
|
||||
struct Type_Descriptor<aethera::render_2d::Color_Map> { static auto get(); };
|
||||
}
|
||||
|
||||
namespace aethera::web::detail {
|
||||
struct Renderable_Field_Role_Category {};
|
||||
|
||||
@@ -86,9 +26,6 @@ struct Renderable_Field_Role_Attribute {
|
||||
static constexpr auto value = Role;
|
||||
};
|
||||
|
||||
template <Renderable_Field_Role Role>
|
||||
inline constexpr Renderable_Field_Role_Attribute<Role> renderable_field_role{};
|
||||
|
||||
template <auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
struct Prop_Field {
|
||||
static constexpr auto member = Member;
|
||||
@@ -106,25 +43,13 @@ struct State_Field {
|
||||
static constexpr auto role = Renderable_Field_Role::state;
|
||||
};
|
||||
|
||||
template <typename Field>
|
||||
concept Prop_Field_Type = requires {
|
||||
requires Field::role == Renderable_Field_Role::prop;
|
||||
};
|
||||
|
||||
template <typename Field>
|
||||
concept State_Field_Type = requires {
|
||||
typename Field::tag_type;
|
||||
requires Field::role == Renderable_Field_Role::state;
|
||||
};
|
||||
|
||||
template <typename Object, typename... Fields>
|
||||
class Renderable_Adapter final : public structive::Property_Object<Renderable_Adapter<Object, Fields...>, structive::No_Lock_Policy> {
|
||||
public:
|
||||
explicit Renderable_Adapter(Object& object);
|
||||
explicit Renderable_Adapter(Object& value) : object(&value) {}
|
||||
private:
|
||||
template <typename Adapter, typename Field>
|
||||
friend struct Renderable_Field_Accessor;
|
||||
Object* object; /* Plot 拥有且析构晚于本 adapter 的真实引擎对象。 */
|
||||
template <typename Adapter, typename Field> friend struct Renderable_Field_Accessor;
|
||||
Object* object;
|
||||
};
|
||||
|
||||
template <typename Adapter, typename Field>
|
||||
@@ -141,8 +66,8 @@ struct Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, Prop_Fie
|
||||
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;
|
||||
void write(object_type& adapter, value_type value) const;
|
||||
[[nodiscard]] value_type read(const object_type& adapter) const { return adapter.object->template get<Member>(); }
|
||||
void write(object_type& adapter, value_type value) const { adapter.object->template set<Member>(std::move(value)); }
|
||||
};
|
||||
|
||||
template <typename Object, typename... Fields, typename Tag, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
@@ -156,41 +81,13 @@ struct Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, State_Fi
|
||||
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;
|
||||
[[nodiscard]] value_type read(const object_type& adapter) const {
|
||||
value_type result{};
|
||||
adapter.object->template access_state<Tag>([&](const auto& state) { result = state.*Member; });
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Adapter, typename Field>
|
||||
constexpr auto renderable_field_descriptor();
|
||||
}
|
||||
|
||||
namespace structive {
|
||||
template <typename Object, typename... Fields>
|
||||
struct Type_Descriptor<aethera::web::detail::Renderable_Adapter<Object, Fields...>> {
|
||||
static auto get();
|
||||
};
|
||||
}
|
||||
|
||||
namespace aethera::web::detail {
|
||||
template <typename Object, typename... Fields>
|
||||
Renderable_Adapter<Object, Fields...>::Renderable_Adapter(Object& value) : object(&value) {}
|
||||
|
||||
template <typename Object, typename... Fields, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
auto Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, Prop_Field<Member, Key, Description>>::read(const object_type& adapter) const -> value_type {
|
||||
return adapter.object->template get<Member>();
|
||||
}
|
||||
|
||||
template <typename Object, typename... Fields, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
void Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, Prop_Field<Member, Key, Description>>::write(object_type& adapter, value_type value) const {
|
||||
adapter.object->template set<Member>(std::move(value));
|
||||
}
|
||||
|
||||
template <typename Object, typename... Fields, typename Tag, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
auto Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, State_Field<Tag, Member, Key, Description>>::read(const object_type& adapter) const -> value_type {
|
||||
value_type result{};
|
||||
adapter.object->template access_state<Tag>([&](const auto& state) { result = state.*Member; });
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Adapter, typename Field>
|
||||
constexpr auto renderable_field_descriptor() {
|
||||
using Accessor = Renderable_Field_Accessor<Adapter, Field>;
|
||||
@@ -202,54 +99,163 @@ constexpr auto renderable_field_descriptor() {
|
||||
|
||||
namespace structive {
|
||||
template <typename Object, typename... Fields>
|
||||
auto Type_Descriptor<aethera::web::detail::Renderable_Adapter<Object, Fields...>>::get() {
|
||||
using Adapter = aethera::web::detail::Renderable_Adapter<Object, Fields...>;
|
||||
return object<Adapter>(synchronization(sync_all_unsynchronized), aethera::web::detail::renderable_field_descriptor<Adapter, Fields>()...);
|
||||
}
|
||||
struct Type_Descriptor<aethera::web::detail::Renderable_Adapter<Object, Fields...>> {
|
||||
static auto get() {
|
||||
using Adapter = aethera::web::detail::Renderable_Adapter<Object, Fields...>;
|
||||
return object<Adapter>(synchronization(sync_all_unsynchronized),
|
||||
aethera::web::detail::renderable_field_descriptor<Adapter, Fields>()...);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace aethera::web::detail {
|
||||
template <typename T> struct is_std_array : std::false_type {};
|
||||
template <typename T, std::size_t Size> struct is_std_array<std::array<T, Size>> : std::true_type {};
|
||||
template <typename T> inline constexpr bool is_std_array_v = is_std_array<std::remove_cvref_t<T>>::value;
|
||||
template <typename T> struct is_pair : std::false_type {};
|
||||
template <typename A, typename B> struct is_pair<std::pair<A, B>> : std::true_type {};
|
||||
template <typename T> inline constexpr bool is_pair_v = is_pair<std::remove_cvref_t<T>>::value;
|
||||
|
||||
template <typename T>
|
||||
concept Json_Sequence = std::ranges::range<T> && requires(T value, typename T::value_type item) {
|
||||
value.clear();
|
||||
value.emplace_back(std::move(item));
|
||||
} && !std::same_as<std::remove_cvref_t<T>, std::string>;
|
||||
|
||||
template <typename Value> nlohmann::json encode_protocol_value(const Value& value);
|
||||
template <typename Value> void decode_protocol_value(Value& target, const nlohmann::json& input);
|
||||
|
||||
template <typename Value, std::size_t... Index>
|
||||
nlohmann::json encode_aggregate(const Value& value, std::index_sequence<Index...>) {
|
||||
nlohmann::json result = nlohmann::json::object();
|
||||
((result[std::string(boost::pfr::get_name<Index, Value>())] =
|
||||
encode_protocol_value(boost::pfr::get<Index>(value))), ...);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Value, std::size_t... Index>
|
||||
void decode_aggregate(Value& target, const nlohmann::json& input, std::index_sequence<Index...>) {
|
||||
(decode_protocol_value(boost::pfr::get<Index>(target),
|
||||
input.at(std::string(boost::pfr::get_name<Index, Value>()))), ...);
|
||||
}
|
||||
|
||||
template <typename Value>
|
||||
[[nodiscard]] std::string_view json_value_type() {
|
||||
nlohmann::json encode_protocol_value(const Value& value) {
|
||||
using Type = std::remove_cvref_t<Value>;
|
||||
if constexpr (std::same_as<Type, bool> || std::integral<Type> || std::floating_point<Type> || std::same_as<Type, std::string>) {
|
||||
return value;
|
||||
} else if constexpr (std::same_as<Type, char>) {
|
||||
return std::string(1, value);
|
||||
} else if constexpr (std::is_enum_v<Type>) {
|
||||
return magic_enum::enum_name(value);
|
||||
} else if constexpr (std::same_as<Type, render_2d::Color_Map>) {
|
||||
return {{"stops", encode_protocol_value(value.stops)}};
|
||||
} else if constexpr (is_pair_v<Type>) {
|
||||
return nlohmann::json::array({encode_protocol_value(value.first), encode_protocol_value(value.second)});
|
||||
} else if constexpr (is_std_array_v<Type> || Json_Sequence<Type>) {
|
||||
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<Type>) {
|
||||
return encode_aggregate(value, std::make_index_sequence<boost::pfr::tuple_size_v<Type>>{});
|
||||
} else {
|
||||
static_assert(std::is_aggregate_v<Type>, "Aethera protocol needs an explicit codec for this value type");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Value>
|
||||
void decode_protocol_value(Value& target, const nlohmann::json& input) {
|
||||
using Type = std::remove_cvref_t<Value>;
|
||||
if constexpr (std::same_as<Type, bool> || std::integral<Type> || std::floating_point<Type> || std::same_as<Type, std::string>) {
|
||||
target = input.template get<Type>();
|
||||
} else if constexpr (std::same_as<Type, char>) {
|
||||
const auto text = input.template get<std::string>();
|
||||
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<Type>) {
|
||||
const auto value = magic_enum::enum_cast<Type>(input.template get<std::string>());
|
||||
if (!value) throw std::invalid_argument("unknown enum value");
|
||||
target = *value;
|
||||
} else if constexpr (std::same_as<Type, render_2d::Color_Map>) {
|
||||
decode_protocol_value(target.stops, input.at("stops"));
|
||||
} else if constexpr (is_pair_v<Type>) {
|
||||
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<Type>) {
|
||||
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<Type>) {
|
||||
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<Type>) {
|
||||
if (!input.is_object()) throw std::invalid_argument("value must be an object");
|
||||
decode_aggregate(target, input, std::make_index_sequence<boost::pfr::tuple_size_v<Type>>{});
|
||||
} else {
|
||||
static_assert(std::is_aggregate_v<Type>, "Aethera protocol needs an explicit codec for this value type");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Value>
|
||||
constexpr std::string_view protocol_editor() {
|
||||
using Type = std::remove_cvref_t<Value>;
|
||||
if constexpr (std::same_as<Type, bool>) return "boolean";
|
||||
if constexpr (std::integral<Type>) return "integer";
|
||||
if constexpr (std::floating_point<Type>) return "number";
|
||||
if constexpr (std::same_as<Type, std::string> || std::same_as<Type, char>) return "text";
|
||||
if constexpr (std::is_enum_v<Type>) return "select";
|
||||
if constexpr (requires { typename Type::value_type; }) return "array";
|
||||
return "object";
|
||||
if constexpr (std::same_as<Type, Color> || std::same_as<Type, render_3d::Linear_Color>) return "color";
|
||||
if constexpr (std::same_as<Type, render_2d::Point_F>) return "point2";
|
||||
if constexpr (std::same_as<Type, render_2d::Size> || std::same_as<Type, render_3d::Extent>) return "size";
|
||||
if constexpr (std::same_as<Type, render_2d::Rect_F>) return "rect";
|
||||
if constexpr (std::same_as<Type, render_2d::Axis_Range>) return "range";
|
||||
if constexpr (std::same_as<Type, render_2d::Pen>) return "pen";
|
||||
if constexpr (std::same_as<Type, render_2d::Brush>) return "brush";
|
||||
if constexpr (std::same_as<Type, render_2d::Font>) return "font";
|
||||
if constexpr (std::same_as<Type, render_2d::Color_Map>) return "color-map";
|
||||
if constexpr (std::same_as<Type, render_3d::Vec3>) return "vector3";
|
||||
if constexpr (std::same_as<Type, render_3d::Matrix4>) return "matrix4";
|
||||
return "json";
|
||||
}
|
||||
|
||||
template <typename Adapter>
|
||||
[[nodiscard]] nlohmann::json adapter_schema(const Adapter& adapter) {
|
||||
const auto& schema = structive::type_descriptor<Adapter>();
|
||||
nlohmann::json prop = nlohmann::json::array();
|
||||
nlohmann::json state = nlohmann::json::array();
|
||||
schema.for_each_property([&](auto, const auto& field) {
|
||||
nlohmann::json adapter_schema(const Adapter& adapter, std::string_view id, std::string_view label, std::string_view kind) {
|
||||
nlohmann::json properties = nlohmann::json::array();
|
||||
nlohmann::json state = nlohmann::json::object();
|
||||
structive::type_descriptor<Adapter>().for_each_property([&](auto, const auto& field) {
|
||||
using Field = std::remove_cvref_t<decltype(field)>;
|
||||
using Value = typename Field::value_type;
|
||||
const auto role = field.template attribute<Renderable_Field_Role_Category>().value;
|
||||
nlohmann::json item{{"key", field.key()}, {"type", json_value_type<Value>()},
|
||||
{"description", std::string(Field::accessor_type::description.view())}};
|
||||
const auto value = field.accessor.read(adapter);
|
||||
item["value"] = adminive::encode_json_value<nlohmann::json>(value);
|
||||
if constexpr (std::is_enum_v<Value>) {
|
||||
item["options"] = nlohmann::json::array();
|
||||
for (const auto enum_value : adminive::enum_values<Value>()) {
|
||||
const auto name = adminive::enum_name(enum_value);
|
||||
item["options"].push_back({{"value", name}, {"label", name}});
|
||||
if (field.template attribute<Renderable_Field_Role_Category>().value == Renderable_Field_Role::prop) {
|
||||
nlohmann::json item{{"key", field.key()}, {"editor", protocol_editor<Value>()},
|
||||
{"description", std::string(Field::accessor_type::description.view())},
|
||||
{"value", encode_protocol_value(value)}};
|
||||
if constexpr (std::is_enum_v<Value>) {
|
||||
item["options"] = nlohmann::json::array();
|
||||
for (const auto enum_value : magic_enum::enum_values<Value>()) {
|
||||
const auto name = magic_enum::enum_name(enum_value);
|
||||
item["options"].push_back({{"value", name}, {"label", name}});
|
||||
}
|
||||
}
|
||||
properties.push_back(std::move(item));
|
||||
} else {
|
||||
state[field.key()] = encode_protocol_value(value);
|
||||
}
|
||||
(role == Renderable_Field_Role::prop ? prop : state).push_back(std::move(item));
|
||||
});
|
||||
return {{"prop", std::move(prop)}, {"state", std::move(state)}};
|
||||
return {{"id", id}, {"label", label}, {"kind", kind},
|
||||
{"properties", std::move(properties)}, {"state", std::move(state)}};
|
||||
}
|
||||
|
||||
template <typename Adapter>
|
||||
[[nodiscard]] nlohmann::json write_adapter_prop(Adapter& adapter, std::string_view key, const nlohmann::json& input) {
|
||||
const auto& schema = structive::type_descriptor<Adapter>();
|
||||
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(schema, key, [&](auto, const auto& field) {
|
||||
const bool found = structive::visit_schema_property(structive::type_descriptor<Adapter>(), key, [&](auto, const auto& field) {
|
||||
using Field = std::remove_cvref_t<decltype(field)>;
|
||||
using Value = typename Field::value_type;
|
||||
if constexpr (!Field::writable) {
|
||||
@@ -257,14 +263,14 @@ template <typename Adapter>
|
||||
} else {
|
||||
try {
|
||||
Value value{};
|
||||
adminive::assign_json_value<nlohmann::json>(value, input);
|
||||
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"] = adminive::encode_json_value<nlohmann::json>(field.accessor.read(adapter));
|
||||
result["value"] = encode_protocol_value(field.accessor.read(adapter));
|
||||
} catch (const std::exception& error) {
|
||||
result["error"] = error.what();
|
||||
}
|
||||
@@ -275,105 +281,20 @@ template <typename Adapter>
|
||||
}
|
||||
|
||||
template <typename Adapter>
|
||||
Renderable_Descriptor_Model<Adapter>::Renderable_Descriptor_Model(Adapter value) : adapter(std::move(value)) {}
|
||||
|
||||
Renderable_Descriptor_Model<Adapter>::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 <typename Adapter> std::string_view Renderable_Descriptor_Model<Adapter>::id() const noexcept { return component_id; }
|
||||
template <typename Adapter> nlohmann::json Renderable_Descriptor_Model<Adapter>::schema() const {
|
||||
return adapter_schema(adapter, component_id, component_label, component_kind);
|
||||
}
|
||||
template <typename Adapter> nlohmann::json Renderable_Descriptor_Model<Adapter>::write_prop(std::string_view key, const nlohmann::json& value) {
|
||||
return write_adapter_prop(adapter, key, value);
|
||||
}
|
||||
template <typename Adapter>
|
||||
nlohmann::json Renderable_Descriptor_Model<Adapter>::schema() const { return adapter_schema(adapter); }
|
||||
|
||||
template <typename Adapter>
|
||||
nlohmann::json Renderable_Descriptor_Model<Adapter>::write_prop(std::string_view key, const nlohmann::json& value) { return write_adapter_prop(adapter, key, value); }
|
||||
|
||||
template <typename Adapter>
|
||||
std::unique_ptr<Renderable_Descriptor> make_renderable_descriptor(Adapter adapter) {
|
||||
return std::make_unique<Renderable_Descriptor_Model<Adapter>>(std::move(adapter));
|
||||
}
|
||||
}
|
||||
|
||||
namespace aethera::web::detail {
|
||||
template <typename Object, typename Members>
|
||||
template <std::size_t Index>
|
||||
decltype(auto) Json_Member_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) Json_Member_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 Json_Member_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);
|
||||
}
|
||||
template <typename Value, std::size_t Size, Json_Type Json>
|
||||
Json Value_Adapter<std::array<Value, Size>, 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, std::size_t Size, Json_Type Json>
|
||||
void Value_Adapter<std::array<Value, Size>, Json>::decode(value_type& target, const Json& value) {
|
||||
if (!Json_Adapter<Json>::is_array(value) || Json_Adapter<Json>::size(value) != Size) throw std::invalid_argument("array has the wrong size");
|
||||
value_type updated{};
|
||||
for (std::size_t index = 0; index < Size; ++index) assign_json_value<Json>(updated[index], Json_Adapter<Json>::at(value, index));
|
||||
target = std::move(updated);
|
||||
}
|
||||
|
||||
inline auto Type_Descriptor<aethera::Color>::get() {
|
||||
return reflected_object_with<aethera::Color>("color", "Color", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_2d::Point_F>::get() {
|
||||
return reflected_object_with<aethera::render_2d::Point_F>("point", "Point", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_2d::Rect_F>::get() {
|
||||
return reflected_object_with<aethera::render_2d::Rect_F>("rectangle", "Rectangle", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_2d::Axis_Range>::get() {
|
||||
return reflected_object_with<aethera::render_2d::Axis_Range>("axis_range", "Axis range", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_2d::Pen>::get() {
|
||||
return reflected_object_with<aethera::render_2d::Pen>("pen", "Pen", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_2d::Brush>::get() {
|
||||
return reflected_object_with<aethera::render_2d::Brush>("brush", "Brush", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_2d::Font>::get() {
|
||||
return reflected_object_with<aethera::render_2d::Font>("font", "Font", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_2d::Frequency_Trace_Sample>::get() {
|
||||
return reflected_object_with<aethera::render_2d::Frequency_Trace_Sample>("frequency_trace_sample", "Frequency trace sample", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_2d::Waterfall_Row>::get() {
|
||||
return reflected_object_with<aethera::render_2d::Waterfall_Row>("waterfall_row", "Waterfall row", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_2d::Constellation_Point>::get() {
|
||||
return reflected_object_with<aethera::render_2d::Constellation_Point>("constellation_point", "Constellation point", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_2d::Color_Map>::get() {
|
||||
return reflected_object_with<aethera::render_2d::Color_Map>("color_map", "Color map", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_3d::Vec3>::get() {
|
||||
return reflected_object_with<aethera::render_3d::Vec3>("vec3", "3D vector", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_3d::Matrix4>::get() {
|
||||
return reflected_object_with<aethera::render_3d::Matrix4>("matrix4", "Transform", Identity_Field_Customizer{});
|
||||
}
|
||||
inline auto Type_Descriptor<aethera::render_3d::Point>::get() {
|
||||
return reflected_object_with<aethera::render_3d::Point>("point_3d", "3D point", Identity_Field_Customizer{});
|
||||
std::unique_ptr<Renderable_Descriptor> make_renderable_descriptor(
|
||||
std::string id, std::string label, std::string kind, Adapter adapter) {
|
||||
return std::make_unique<Renderable_Descriptor_Model<Adapter>>(
|
||||
std::move(id), std::move(label), std::move(kind), std::move(adapter));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,10 +82,11 @@ int run_web_server(std::uint16_t port, const std::filesystem::path& asset_root)
|
||||
});
|
||||
}, {drogon::Get});
|
||||
|
||||
app.registerHandler("/plot/{1}/prop/{2}", [plots](
|
||||
app.registerHandler("/plot/{1}/component/{2}/prop/{3}", [plots](
|
||||
const drogon::HttpRequestPtr& request,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& callback,
|
||||
std::string plot_id,
|
||||
std::string component,
|
||||
std::string key) {
|
||||
auto plot = find_plot(*plots, plot_id);
|
||||
if (!plot) {
|
||||
@@ -101,7 +102,7 @@ int run_web_server(std::uint16_t port, const std::filesystem::path& asset_root)
|
||||
}
|
||||
auto output = std::make_shared<std::function<void(const drogon::HttpResponsePtr&)>>(
|
||||
std::move(callback));
|
||||
plot->async_write_prop(std::move(key), std::move(value), [output](nlohmann::json result) {
|
||||
plot->async_write_prop(std::move(component), std::move(key), std::move(value), [output](nlohmann::json result) {
|
||||
(*output)(json_response(std::move(result)));
|
||||
});
|
||||
}, {drogon::Put});
|
||||
|
||||
Reference in New Issue
Block a user