From 2fff357dff88242cb979b6bdd4f9d288b60cb026 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Wed, 12 Aug 2026 01:44:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E5=AE=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Kernel/src/renderive/event/Event.hpp | 119 +++++++ Kernel/tests/renderive/event/Event_Test.cpp | 34 ++ render_2D/event/Event.h | 87 +---- render_2D/plottable/Plottable.h | 8 +- web_server/app/Gallery_Plot_Session.cpp | 46 +-- web_server/app/Gallery_Renderables.h | 61 ++-- web_server/app/Gallery_Renderables_Adminive.h | 331 ++++++++++-------- web_server/app/Gallery_Session_Control.cpp | 47 --- web_server/app/Gallery_Session_Control.h | 18 +- .../app/Gallery_Session_Control_Adminive.h | 115 ++++-- web_server/app/Pixel_Frame.cpp | 28 -- web_server/app/Pixel_Frame.h | 12 - web_server/app/Web_Event.h | 29 +- web_server/app/Web_Event_Adapter.cpp | 168 ++++----- web_server/app/Web_Event_Adapter.h | 87 ++++- web_server/app/Web_Plot_Session.cpp | 2 +- web_server/tests/Web_Bridge_Tests.cpp | 30 +- 17 files changed, 717 insertions(+), 505 deletions(-) create mode 100644 Kernel/src/renderive/event/Event.hpp create mode 100644 Kernel/tests/renderive/event/Event_Test.cpp delete mode 100644 web_server/app/Gallery_Session_Control.cpp diff --git a/Kernel/src/renderive/event/Event.hpp b/Kernel/src/renderive/event/Event.hpp new file mode 100644 index 0000000..abd2015 --- /dev/null +++ b/Kernel/src/renderive/event/Event.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include + +namespace renderive { + +enum class Event_Type : std::uint8_t { + Resize, + Show, + Hide, + Leave, + Pointer_Move, + Pointer_Press, + Pointer_Release, + Wheel, + Key_Press, + Key_Release +}; + +struct Event { + explicit Event(Event_Type value) : type(value) {} + virtual ~Event() = default; + + void accept() const noexcept { accepted_ = true; } + [[nodiscard]] bool is_accepted() const noexcept { return accepted_; } + + Event_Type type; + +private: + mutable bool accepted_{}; +}; + +template +concept Event_Object = std::derived_from, Event>; + +template +concept Event_Point = std::default_initializable && requires(T value) { + value.x; + value.y; +}; + +template +concept Event_Size = std::default_initializable && requires(T value) { + value.width; + value.height; +}; + +enum class Mouse_Button : std::uint8_t { None, Left, Right, Middle }; +using Mouse_Button_Mask = std::uint8_t; + +enum class Keyboard_Modifier : std::uint8_t { + None = 0, + Ctrl = 1 << 0, + Shift = 1 << 1, + Alt = 1 << 2, + Meta = 1 << 3 +}; + +constexpr Keyboard_Modifier operator|(Keyboard_Modifier left, + Keyboard_Modifier right) noexcept { + return static_cast(static_cast(left) | + static_cast(right)); +} + +template +struct Basic_Pointer_Event : Event { + explicit Basic_Pointer_Event(Event_Type value = Event_Type::Pointer_Move) + : Event(value) {} + + Point position; + Point global_position; + Mouse_Button button = Mouse_Button::None; + Mouse_Button_Mask buttons{}; + Keyboard_Modifier modifiers = Keyboard_Modifier::None; +}; + +template +struct Basic_Wheel_Event : Basic_Pointer_Event { + Basic_Wheel_Event() : Basic_Pointer_Event(Event_Type::Wheel) {} + + double angle_delta_x{}; + double angle_delta_y{}; + double pixel_delta_x{}; + double pixel_delta_y{}; +}; + +template +struct Basic_Resize_Event : Event { + Basic_Resize_Event() : Event(Event_Type::Resize) {} + + Size old_size; + Size new_size; +}; + +enum class Key : std::uint16_t { + Unknown, + Escape, + Enter, + Space, + Delete, + Backspace, + Left, + Right, + Up, + Down +}; + +struct Key_Event : Event { + explicit Key_Event(Event_Type value) : Event(value) {} + + Key key = Key::Unknown; + std::uint32_t native_key{}; + Keyboard_Modifier modifiers = Keyboard_Modifier::None; + bool auto_repeat{}; +}; + +} // namespace renderive diff --git a/Kernel/tests/renderive/event/Event_Test.cpp b/Kernel/tests/renderive/event/Event_Test.cpp new file mode 100644 index 0000000..6baa747 --- /dev/null +++ b/Kernel/tests/renderive/event/Event_Test.cpp @@ -0,0 +1,34 @@ +#include + +#include + +namespace { + +struct Point3 { + double x{}; + double y{}; + double z{}; +}; + +struct Viewport_Extent { + int width{}; + int height{}; +}; + +static_assert(renderive::Event_Point); +static_assert(renderive::Event_Size); +static_assert(renderive::Event_Object>); + +TEST(RenderiveEvent, SupportsDomainSpecificCoordinateAndSizeTypes) { + renderive::Basic_Pointer_Event pointer(renderive::Event_Type::Pointer_Press); + pointer.position = {1.0, 2.0, 3.0}; + EXPECT_EQ(pointer.type, renderive::Event_Type::Pointer_Press); + EXPECT_DOUBLE_EQ(pointer.position.z, 3.0); + + renderive::Basic_Resize_Event resize; + resize.old_size = {640, 480}; + resize.new_size = {1280, 720}; + EXPECT_EQ(resize.new_size.width, 1280); +} + +} // namespace diff --git a/render_2D/event/Event.h b/render_2D/event/Event.h index fb3475d..5a4c637 100644 --- a/render_2D/event/Event.h +++ b/render_2D/event/Event.h @@ -1,92 +1,13 @@ #pragma once #include "../base/Types.h" +#include namespace renderive { -enum class Event_Type : std::uint8_t { - Resize, - Show, - Hide, - Leave, - Pointer_Move, - Pointer_Press, - Pointer_Release, - Wheel, - Key_Press, - Key_Release -}; - -struct Event { - explicit Event(Event_Type value) : type(value) {} - virtual ~Event() = default; - void accept() const noexcept { accepted = true; } - [[nodiscard]] bool is_accepted() const noexcept { return accepted; } - - Event_Type type; -private: - mutable bool accepted{}; -}; - -enum class Mouse_Button : std::uint8_t { None, Left, Right, Middle }; -using Mouse_Button_Mask = std::uint8_t; - -enum class Keyboard_Modifier : std::uint8_t { - None = 0, - Ctrl = 1 << 0, - Shift = 1 << 1, - Alt = 1 << 2, - Meta = 1 << 3 -}; - -constexpr Keyboard_Modifier operator|(Keyboard_Modifier left, Keyboard_Modifier right) noexcept { - return static_cast(static_cast(left) | - static_cast(right)); -} - -struct Pointer_Event : Event { - explicit Pointer_Event(Event_Type value = Event_Type::Pointer_Move) : Event(value) {} - PointF position; - PointF global_position; - Mouse_Button button = Mouse_Button::None; - Mouse_Button_Mask buttons{}; - Keyboard_Modifier modifiers = Keyboard_Modifier::None; -}; - -struct Wheel_Event : Pointer_Event { - Wheel_Event() : Pointer_Event(Event_Type::Wheel) {} - double angle_delta_x{}; - double angle_delta_y{}; - double pixel_delta_x{}; - double pixel_delta_y{}; -}; - -struct Resize_Event : Event { - Resize_Event() : Event(Event_Type::Resize) {} - Size old_size; - Size new_size; -}; - -enum class Key : std::uint16_t { - Unknown, - Escape, - Enter, - Space, - Delete, - Backspace, - Left, - Right, - Up, - Down -}; - -struct Key_Event : Event { - explicit Key_Event(Event_Type value) : Event(value) {} - Key key = Key::Unknown; - std::uint32_t native_key{}; - Keyboard_Modifier modifiers = Keyboard_Modifier::None; - bool auto_repeat{}; -}; +using Pointer_Event = Basic_Pointer_Event; +using Wheel_Event = Basic_Wheel_Event; +using Resize_Event = Basic_Resize_Event; } // namespace renderive diff --git a/render_2D/plottable/Plottable.h b/render_2D/plottable/Plottable.h index 57e0847..f53d63a 100644 --- a/render_2D/plottable/Plottable.h +++ b/render_2D/plottable/Plottable.h @@ -71,11 +71,9 @@ protected: Properties properties() const { return Base::read([](const Properties& value) { return value; }); } - void replace_properties(Properties value) { - Base::update([&value](Properties& current) { - current = std::move(value); - }); - this->changed(); + template + auto property_value() const { + return Base::read([](const Properties& value) { return value.*Member; }); } const Properties& render_properties(const Render_State_View& state) const noexcept { return state.get(static_cast(*this)); diff --git a/web_server/app/Gallery_Plot_Session.cpp b/web_server/app/Gallery_Plot_Session.cpp index 2dac2e6..0ba85d2 100644 --- a/web_server/app/Gallery_Plot_Session.cpp +++ b/web_server/app/Gallery_Plot_Session.cpp @@ -97,10 +97,6 @@ std::string_view frame_mode_name(Gallery_Frame_Mode mode) { return "low_latency"; } } // namespace -struct Gallery_Pixel_Copy { - Pixel_Frame_Copy image; - Color background; -}; class Gallery_Scene final { public: Gallery_Scene(std::uint64_t session_id, std::string case_id, @@ -220,19 +216,19 @@ public: rendered_since_last_pixel_ = true; return rendered; } - [[nodiscard]] std::optional copy_latest_pixels() { + [[nodiscard]] std::optional encode_latest_pixels() { if (!plot_.view_active()) return std::nullopt; if (!rendered_since_last_pixel_ && !can_render_automatically() && !render_latest_frame()) return std::nullopt; rendered_since_last_pixel_ = false; - Gallery_Pixel_Copy copy; - copy.background = plot_.background_color(); - plot_.with_frame([©](Image_View image) { - copy.image = copy_pixel_frame(image); + std::string pixels; + const Color background = plot_.background_color(); + plot_.with_frame([&pixels, background](Image_View image) { + pixels = encode_pixel_frame(image, background); }); - return copy.image.empty() ? std::nullopt : std::optional(std::move(copy)); + return pixels.empty() ? std::nullopt : std::optional(std::move(pixels)); } void record_pixel_response(std::chrono::steady_clock::time_point request_started, std::chrono::steady_clock::time_point encode_started, @@ -1591,31 +1587,21 @@ struct Gallery_Plot_Session::Impl { } std::optional handle_frame_request() { const auto request_started = std::chrono::steady_clock::now(); - std::optional copy; - std::uint64_t generation{}; + std::optional pixels; { auto lock = acquire_foreground_lock(); if (!scene) return std::nullopt; - generation = scene_generation; - copy = scene->copy_latest_pixels(); - } - if (!copy) - return std::nullopt; - const auto encode_started = std::chrono::steady_clock::now(); - std::string pixels = encode_pixel_frame(copy->image, copy->background); - const auto encode_finished = std::chrono::steady_clock::now(); - { - auto lock = acquire_foreground_lock(); - if (scene && scene_generation == generation) + const auto encode_started = std::chrono::steady_clock::now(); + pixels = scene->encode_latest_pixels(); + const auto encode_finished = std::chrono::steady_clock::now(); + if (pixels) scene->record_pixel_response(request_started, encode_started, encode_finished, - pixels.size()); + pixels->size()); } - return pixels.empty() - ? std::nullopt - : std::optional(Web_Response{ - Web_Response_Type::Pixels, std::move(pixels) - }); + if (!pixels) + return std::nullopt; + return Web_Response{Web_Response_Type::Pixels, std::move(*pixels)}; } std::optional handle(const Web_Event& event) { if (std::holds_alternative(event)) @@ -1641,7 +1627,7 @@ struct Gallery_Plot_Session::Impl { if (scene) scene->dispatch(value); } - else if constexpr (std::is_base_of_v) { + else if constexpr (Event_Object) { if (scene) scene->dispatch(value); } diff --git a/web_server/app/Gallery_Renderables.h b/web_server/app/Gallery_Renderables.h index ad2a60b..2d075f7 100644 --- a/web_server/app/Gallery_Renderables.h +++ b/web_server/app/Gallery_Renderables.h @@ -10,16 +10,33 @@ #include "render_2D/plottable/Spectrum.h" #include "render_2D/plottable/Sweep_Spectrum.h" #include "render_2D/plottable/Waterfall.h" +#include +#include #include -namespace adminive { -template -struct Object_Adapter; -} - namespace renderive::web { +template +struct Gallery_Property_Accessor { + using object_type = Object; + using Properties = typename Object::Properties; + using value_type = std::remove_cvref_t().*Member)>; + using storage_identity = void; + using dependency_spec = structive::No_Property_Dependencies; + static constexpr bool readable = true; + static constexpr bool writable = true; + static constexpr bool synchronized_view_read = false; + static constexpr bool trusted_object_access = true; + + value_type read(const Object& object) const { + return object.template adminive_read(); + } + void write(Object& object, value_type value) const { + object.template adminive_write(std::move(value)); + } +}; + template class Gallery_Plottable : public Base { public: @@ -27,16 +44,16 @@ public: using Base::Base; private: - [[nodiscard]] Properties adminive_snapshot() const { - return this->properties(); + template + auto adminive_read() const { + return this->template property_value(); } - - void adminive_commit(Properties properties) { - this->replace_properties(std::move(properties)); + template + void adminive_write(Value&& value) { + this->template set(std::forward(value)); } - - template - friend struct ::adminive::Object_Adapter; + template + friend struct Gallery_Property_Accessor; }; class Gallery_Spectrum final : public Gallery_Plottable { @@ -90,20 +107,18 @@ public: using Base::Base; private: - [[nodiscard]] Properties adminive_snapshot() const { + template + auto adminive_read() const { return this->read([](const auto& state) { - return Properties(static_cast(state)); + return state.*Member; }); } - - void adminive_commit(Properties properties) { - this->update([&properties](auto& state) { - static_cast(state) = std::move(properties); - }); + template + void adminive_write(Value&& value) { + this->template set(std::forward(value)); } - - template - friend struct ::adminive::Object_Adapter; + template + friend struct Gallery_Property_Accessor; }; class Gallery_Axis final : public Gallery_Axis_Base { diff --git a/web_server/app/Gallery_Renderables_Adminive.h b/web_server/app/Gallery_Renderables_Adminive.h index ab5f8d1..5a0ef68 100644 --- a/web_server/app/Gallery_Renderables_Adminive.h +++ b/web_server/app/Gallery_Renderables_Adminive.h @@ -169,6 +169,57 @@ auto color(std::string name, std::string label) { return editable(std::move(name), std::move(label), adminive::Field_Control::color); } +template +auto property(std::string name, std::string label, adminive::Field_Control control) { + return adminive::field(std::move(name), std::move(label), + Gallery_Property_Accessor{}) + .editable() + .unsynchronized() + .control(control); +} + +template +auto readonly_property(std::string name, std::string label) { + return adminive::field(std::move(name), std::move(label), + Gallery_Property_Accessor{}); +} + +template +auto property_number(std::string name, std::string label) { + return property(std::move(name), std::move(label), + adminive::Field_Control::number); +} + +template +auto property_boolean(std::string name, std::string label) { + return property(std::move(name), std::move(label), + adminive::Field_Control::boolean); +} + +template +auto property_select(std::string name, std::string label) { + return property(std::move(name), std::move(label), + adminive::Field_Control::select); +} + +template +auto property_color(std::string name, std::string label) { + return property(std::move(name), std::move(label), + adminive::Field_Control::color); +} + +template +auto property_object(std::string name, std::string label) { + return property(std::move(name), std::move(label), + adminive::Field_Control::automatic); +} + +template +auto property_text(std::string name, std::string label) { + return property(std::move(name), std::move(label), + adminive::Field_Control::text); +} + } // namespace renderive::web::gallery_adminive namespace adminive { @@ -295,7 +346,7 @@ struct Type_Descriptor { static auto get() { using T = renderive::Range; using namespace renderive::web::gallery_adminive; - return object("range", "Range", + return adminive::object("range", "Range", number<&T::origin>("origin", "Origin"), number<&T::target>("target", "Target")); } @@ -306,7 +357,7 @@ struct Type_Descriptor { static auto get() { using T = renderive::Pen; using namespace renderive::web::gallery_adminive; - return object("pen", "Pen", + return adminive::object("pen", "Pen", color<&T::color>("color", "Color"), number<&T::width>("width", "Width"), select<&T::style>("style", "Style"), @@ -320,7 +371,7 @@ struct Type_Descriptor { static auto get() { using T = renderive::Brush; using namespace renderive::web::gallery_adminive; - return object("brush", "Brush", + return adminive::object("brush", "Brush", color<&T::color>("color", "Color"), select<&T::style>("style", "Style")); } @@ -331,158 +382,158 @@ struct Type_Descriptor { static auto get() { using T = renderive::Font; using namespace renderive::web::gallery_adminive; - return object("font", "Font", + return adminive::object("font", "Font", number<&T::size>("size", "Size"), number<&T::weight>("weight", "Weight"), boolean<&T::italic>("italic", "Italic")); } }; +#define RENDERIVE_AXIS_DESCRIPTOR(GalleryType, Name, Label) \ + template <> struct Type_Descriptor { \ + static auto get() { \ + using T = renderive::web::GalleryType; \ + using B = renderive::Axis_Base_Properties; \ + using P = renderive::Axis_Properties; \ + using namespace renderive::web::gallery_adminive; \ + return adminive::object(Name, Label, \ + readonly_property("x", "X"), \ + readonly_property("y", "Y"), \ + property_select("orientation", "Orientation"), \ + readonly_property("pixel_length", "Pixel length"), \ + property_number("tick_length", "Tick length"), \ + property_number("sub_tick_length", "Sub tick length"), \ + property_color("color", "Color"), \ + property_select("locale", "Decimal separator"), \ + property_text("unit_text", "Unit text"), \ + property_object("unit_text_font", "Unit font"), \ + property_object("unit_text_pen", "Unit pen"), \ + property_object("unit_text_background_brush", "Unit background"), \ + property_number("label_rotation_degrees", "Label rotation"), \ + property_object("coordinates", "Coordinates"), \ + property_number("precision", "Precision"), \ + property_boolean("wheel", "Wheel zoom"), \ + property_boolean("drag", "Drag pan")); \ + } \ + } + +RENDERIVE_AXIS_DESCRIPTOR(Gallery_Axis, "gallery_axis", "Axis"); +RENDERIVE_AXIS_DESCRIPTOR(Gallery_Frequency_Axis, "gallery_frequency_axis", "Frequency axis"); +#undef RENDERIVE_AXIS_DESCRIPTOR + template <> -struct Type_Descriptor { +struct Type_Descriptor { static auto get() { - using T = renderive::Axis_Properties; + using T = renderive::web::Gallery_Time_Axis; using B = renderive::Axis_Base_Properties; + using P = renderive::Time_Axis_Properties; using namespace renderive::web::gallery_adminive; - return object("axis_properties", "Axis", - select<&B::orientation>("orientation", "Orientation"), - number<&B::tick_length>("tick_length", "Tick length"), - number<&B::sub_tick_length>("sub_tick_length", "Sub tick length"), - color<&B::color>("color", "Color"), - select<&B::locale>("locale", "Decimal separator"), - editable<&B::unit_text>("unit_text", "Unit text", Field_Control::text), - editable<&B::unit_text_font>("unit_text_font", "Unit font", Field_Control::automatic), - editable<&B::unit_text_pen>("unit_text_pen", "Unit pen", Field_Control::automatic), - editable<&B::unit_text_background_brush>("unit_text_background_brush", "Unit background", Field_Control::automatic), - number<&B::label_rotation_degrees>("label_rotation_degrees", "Label rotation"), - editable<&T::coordinates>("coordinates", "Coordinates", Field_Control::automatic), - number<&T::precision>("precision", "Precision"), - boolean<&T::wheel>("wheel", "Wheel zoom"), - boolean<&T::drag>("drag", "Drag pan")); + return adminive::object("gallery_time_axis", "Time axis", + readonly_property("x", "X"), + readonly_property("y", "Y"), + property_select("orientation", "Orientation"), + readonly_property("pixel_length", "Pixel length"), + property_number("tick_length", "Tick length"), + property_number("sub_tick_length", "Sub tick length"), + property_color("color", "Color"), + property_select("locale", "Decimal separator"), + property_text("unit_text", "Unit text"), + property_object("unit_text_font", "Unit font"), + property_object("unit_text_pen", "Unit pen"), + property_object("unit_text_background_brush", "Unit background"), + property_number("label_rotation_degrees", "Label rotation"), + property_number("visible_count", "Visible points"), + property_number("tick_label_spacing_px", "Label spacing"), + property_text("format", "Time format"), + property_boolean("newest_at_start", "Newest at start")); } }; -template <> -struct Type_Descriptor { - static auto get() { - using T = renderive::Time_Axis_Properties; - using B = renderive::Axis_Base_Properties; - using namespace renderive::web::gallery_adminive; - return object("time_axis_properties", "Time axis", - select<&B::orientation>("orientation", "Orientation"), - number<&B::tick_length>("tick_length", "Tick length"), - number<&B::sub_tick_length>("sub_tick_length", "Sub tick length"), - color<&B::color>("color", "Color"), - editable<&B::unit_text>("unit_text", "Unit text", Field_Control::text), - editable<&B::unit_text_font>("unit_text_font", "Unit font", Field_Control::automatic), - number<&T::visible_count>("visible_count", "Visible points"), - number<&T::tick_label_spacing_px>("tick_label_spacing_px", "Label spacing"), - editable<&T::format>("format", "Time format", Field_Control::text), - boolean<&T::newest_at_start>("newest_at_start", "Newest at start")); - } -}; - -#define RENDERIVE_PROPERTY_DESCRIPTOR(Type, Label, ...) \ - template <> struct Type_Descriptor { \ - static auto get() { using T = renderive::Type; using namespace renderive::web::gallery_adminive; \ - return object(#Type, Label, __VA_ARGS__); } } - -RENDERIVE_PROPERTY_DESCRIPTOR(Spectrum_Properties, "Spectrum", - number<&T::frequency_point_size>("frequency_point_size", "Frequency points"), - editable<&T::frequency_range>("frequency_range", "Frequency range", Field_Control::automatic), - number<&T::center_frequency>("center_frequency", "Center frequency"), - editable<&T::sweep_frequency_range>("sweep_frequency_range", "Sweep range", Field_Control::automatic), - boolean<&T::max_hold_visible>("max_hold_visible", "Max hold"), - boolean<&T::min_hold_visible>("min_hold_visible", "Min hold"), - boolean<&T::max_marker_visible>("max_marker_visible", "Max marker"), - boolean<&T::use_min_marker>("use_min_marker", "Use min marker"), - boolean<&T::sweep_region_visible>("sweep_region_visible", "Sweep region"), - boolean<&T::visible_range_only>("visible_range_only", "Visible range only"), - select<&T::interpolation_mode>("interpolation_mode", "Interpolation"), - editable<&T::max_brush>("max_brush", "Max brush", Field_Control::automatic), - editable<&T::current_brush>("current_brush", "Current brush", Field_Control::automatic), - editable<&T::min_brush>("min_brush", "Min brush", Field_Control::automatic), - editable<&T::max_pen>("max_pen", "Max pen", Field_Control::automatic), - editable<&T::current_pen>("current_pen", "Current pen", Field_Control::automatic), - editable<&T::min_pen>("min_pen", "Min pen", Field_Control::automatic), - editable<&T::selected_marker_pen>("selected_marker_pen", "Selected marker pen", Field_Control::automatic), - editable<&T::marker_pen>("marker_pen", "Marker pen", Field_Control::automatic), - editable<&T::middle_frequency_pen>("middle_frequency_pen", "Middle frequency pen", Field_Control::automatic), - editable<&T::sweep_region_brush>("sweep_region_brush", "Sweep region brush", Field_Control::automatic), - boolean<&T::tooltip_enabled>("tooltip_enabled", "Tooltip"), - editable<&T::tooltip_font>("tooltip_font", "Tooltip font", Field_Control::automatic), - editable<&T::tooltip_text_pen>("tooltip_text_pen", "Tooltip text pen", Field_Control::automatic), - editable<&T::tooltip_background_brush>("tooltip_background_brush", "Tooltip background", Field_Control::automatic)); - -RENDERIVE_PROPERTY_DESCRIPTOR(Waterfall_Properties, "Waterfall", - editable<&T::frequency_range>("frequency_range", "Frequency range", Field_Control::automatic), - editable<&T::power_range>("power_range", "Power range", Field_Control::automatic), - number<&T::frequency_bin_count>("frequency_bin_count", "Frequency bins"), - boolean<&T::visible_range_only>("visible_range_only", "Visible range only"), - select<&T::interpolation_mode>("interpolation_mode", "Interpolation"), - select<&T::color_map>("color_map", "Color map"), - boolean<&T::tooltip_enabled>("tooltip_enabled", "Tooltip"), - editable<&T::tooltip_font>("tooltip_font", "Tooltip font", Field_Control::automatic), - editable<&T::tooltip_text_pen>("tooltip_text_pen", "Tooltip text pen", Field_Control::automatic), - editable<&T::tooltip_background_brush>("tooltip_background_brush", "Tooltip background", Field_Control::automatic)); - -RENDERIVE_PROPERTY_DESCRIPTOR(Afterglow_Properties, "Afterglow", - editable<&T::frequency_range>("frequency_range", "Frequency range", Field_Control::automatic), - editable<&T::power_range>("power_range", "Power range", Field_Control::automatic), - number<&T::frequency_point_size>("frequency_point_size", "Frequency points"), - number<&T::power_point_size>("power_point_size", "Power points"), - boolean<&T::interpolate>("interpolate", "Interpolate power"), - number<&T::attenuation_rate>("attenuation_rate", "Attenuation"), - select<&T::color_map>("color_map", "Color map")); - -RENDERIVE_PROPERTY_DESCRIPTOR(Sweep_Spectrum_Properties, "Sweep spectrum", - editable<&T::frequency_range>("frequency_range", "Frequency range", Field_Control::automatic), - number<&T::bins_per_block>("bins_per_block", "Bins per block"), - number<&T::block_count>("block_count", "Block count"), - editable<&T::pen>("pen", "Sweep pen", Field_Control::automatic), - editable<&T::current_frequency_pen>("current_frequency_pen", "Current frequency pen", Field_Control::automatic), - boolean<&T::visible_range_only>("visible_range_only", "Visible range only"), - select<&T::interpolation_mode>("interpolation_mode", "Interpolation")); - -RENDERIVE_PROPERTY_DESCRIPTOR(Frequency_Trace_Properties, "Frequency trace", - editable<&T::pen>("pen", "Trace pen", Field_Control::automatic)); - -RENDERIVE_PROPERTY_DESCRIPTOR(Selection_Rectangle_Overlay_Properties, "Selection overlay", - editable<&T::label_font>("label_font", "Label font", Field_Control::automatic), - editable<&T::label_pen>("label_pen", "Label pen", Field_Control::automatic), - editable<&T::selection_brush>("selection_brush", "Selection brush", Field_Control::automatic), - editable<&T::selection_border_pen>("selection_border_pen", "Selection border", Field_Control::automatic)); - -RENDERIVE_PROPERTY_DESCRIPTOR(Constellation_Diagram_Properties, "Constellation diagram", - editable<&T::i_range>("i_range", "I range", Field_Control::automatic), - editable<&T::q_range>("q_range", "Q range", Field_Control::automatic), - color<&T::point_color>("point_color", "Point color"), - color<&T::anchor_color>("anchor_color", "Anchor color"), - number<&T::point_lifetime_ms>("point_lifetime_ms", "Point lifetime"), - select<&T::type>("type", "Constellation"), - number<&T::phase_offset_radians>("phase_offset_radians", "Phase offset")); - -#undef RENDERIVE_PROPERTY_DESCRIPTOR - -#define RENDERIVE_OBJECT_ADAPTER(GalleryType, PropertiesType) \ - template <> struct Object_Adapter { \ - using object_type = renderive::web::GalleryType; \ - using model_type = renderive::PropertiesType; \ - static model_type snapshot(const object_type& value) { return value.adminive_snapshot(); } \ - static void commit(object_type& target, model_type value) { target.adminive_commit(std::move(value)); } \ +#define RENDERIVE_GALLERY_DESCRIPTOR(GalleryType, PropertiesType, Label, ...) \ + template <> struct Type_Descriptor { \ + static auto get() { \ + using T = renderive::web::GalleryType; \ + using P = renderive::PropertiesType; \ + using namespace renderive::web::gallery_adminive; \ + return adminive::object(#GalleryType, Label, __VA_ARGS__); \ + } \ } -RENDERIVE_OBJECT_ADAPTER(Gallery_Spectrum, Spectrum_Properties); -RENDERIVE_OBJECT_ADAPTER(Gallery_Waterfall, Waterfall_Properties); -RENDERIVE_OBJECT_ADAPTER(Gallery_Afterglow, Afterglow_Properties); -RENDERIVE_OBJECT_ADAPTER(Gallery_Sweep_Spectrum, Sweep_Spectrum_Properties); -RENDERIVE_OBJECT_ADAPTER(Gallery_Frequency_Trace, Frequency_Trace_Properties); -RENDERIVE_OBJECT_ADAPTER(Gallery_Selection_Rectangle_Overlay, Selection_Rectangle_Overlay_Properties); -RENDERIVE_OBJECT_ADAPTER(Gallery_Constellation_Diagram, Constellation_Diagram_Properties); -RENDERIVE_OBJECT_ADAPTER(Gallery_Axis, Axis_Properties); -RENDERIVE_OBJECT_ADAPTER(Gallery_Frequency_Axis, Axis_Properties); -RENDERIVE_OBJECT_ADAPTER(Gallery_Time_Axis, Time_Axis_Properties); +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum_Properties, "Spectrum", + property_number("frequency_point_size", "Frequency points"), + property_object("frequency_range", "Frequency range"), + property_number("center_frequency", "Center frequency"), + property_object("sweep_frequency_range", "Sweep range"), + property_boolean("max_hold_visible", "Max hold"), + property_boolean("min_hold_visible", "Min hold"), + property_boolean("max_marker_visible", "Max marker"), + property_boolean("use_min_marker", "Use min marker"), + property_boolean("sweep_region_visible", "Sweep region"), + property_boolean("visible_range_only", "Visible range only"), + property_select("interpolation_mode", "Interpolation"), + property_object("max_brush", "Max brush"), + property_object("current_brush", "Current brush"), + property_object("min_brush", "Min brush"), + property_object("max_pen", "Max pen"), + property_object("current_pen", "Current pen"), + property_object("min_pen", "Min pen"), + property_object("selected_marker_pen", "Selected marker pen"), + property_object("marker_pen", "Marker pen"), + property_object("middle_frequency_pen", "Middle frequency pen"), + property_object("sweep_region_brush", "Sweep region brush"), + property_boolean("tooltip_enabled", "Tooltip"), + property_object("tooltip_font", "Tooltip font"), + property_object("tooltip_text_pen", "Tooltip text pen"), + property_object("tooltip_background_brush", "Tooltip background")); -#undef RENDERIVE_OBJECT_ADAPTER +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall_Properties, "Waterfall", + property_object("frequency_range", "Frequency range"), + property_object("power_range", "Power range"), + property_number("frequency_bin_count", "Frequency bins"), + property_boolean("visible_range_only", "Visible range only"), + property_select("interpolation_mode", "Interpolation"), + property_select("color_map", "Color map"), + property_boolean("tooltip_enabled", "Tooltip"), + property_object("tooltip_font", "Tooltip font"), + property_object("tooltip_text_pen", "Tooltip text pen"), + property_object("tooltip_background_brush", "Tooltip background")); + +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow_Properties, "Afterglow", + property_object("frequency_range", "Frequency range"), + property_object("power_range", "Power range"), + property_number("frequency_point_size", "Frequency points"), + property_number("power_point_size", "Power points"), + property_boolean("interpolate", "Interpolate power"), + property_number("attenuation_rate", "Attenuation"), + property_select("color_map", "Color map")); + +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Sweep_Spectrum, Sweep_Spectrum_Properties, "Sweep spectrum", + property_object("frequency_range", "Frequency range"), + property_number("bins_per_block", "Bins per block"), + property_number("block_count", "Block count"), + property_object("pen", "Sweep pen"), + property_object("current_frequency_pen", "Current frequency pen"), + property_boolean("visible_range_only", "Visible range only"), + property_select("interpolation_mode", "Interpolation")); + +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Frequency_Trace, Frequency_Trace_Properties, "Frequency trace", + property_object("pen", "Trace pen")); + +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Selection_Rectangle_Overlay, Selection_Rectangle_Overlay_Properties, "Selection overlay", + property_object("label_font", "Label font"), + property_object("label_pen", "Label pen"), + property_object("selection_brush", "Selection brush"), + property_object("selection_border_pen", "Selection border")); + +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Constellation_Diagram, Constellation_Diagram_Properties, "Constellation diagram", + property_object("i_range", "I range"), + property_object("q_range", "Q range"), + property_color("point_color", "Point color"), + property_color("anchor_color", "Anchor color"), + property_number("point_lifetime_ms", "Point lifetime"), + property_select("type", "Constellation"), + property_number("phase_offset_radians", "Phase offset")); + +#undef RENDERIVE_GALLERY_DESCRIPTOR } // namespace adminive diff --git a/web_server/app/Gallery_Session_Control.cpp b/web_server/app/Gallery_Session_Control.cpp deleted file mode 100644 index b62ce0c..0000000 --- a/web_server/app/Gallery_Session_Control.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "Gallery_Session_Control.h" - -namespace renderive::web { - -Gallery_Session_Control_Model Gallery_Session_Control::adminive_snapshot() const { - Gallery_Session_Control_Model result; - result.background_color = plot_.background_color(); - if (const auto overlay = plot_.performance_overlay()) - result.performance_overlay = overlay->enabled(); - result.renderable_visible = renderable_.is_visible(); - result.cache_mode = renderable_.get_cache_mode(); - result.object_name = renderable_.object_name(); - result.max_render_fps = plot_.max_render_fps(); - result.frequency_limit_enabled = result.max_render_fps > 0.0; - if (!result.frequency_limit_enabled) - result.max_render_fps = 30.0; - result.feedback = feedback_; - return result; -} - -void Gallery_Session_Control::adminive_commit(Gallery_Session_Control_Model model) { - const auto current = adminive_snapshot(); - if (model.frequency_limit_enabled != current.frequency_limit_enabled) { - if (model.frequency_limit_enabled) - plot_.set_max_render_fps(model.max_render_fps); - else - plot_.clear_max_render_fps(); - } else if (model.frequency_limit_enabled && - model.max_render_fps != current.max_render_fps) { - plot_.set_max_render_fps(model.max_render_fps); - } - if (model.background_color != current.background_color) - plot_.set_background_color(model.background_color); - if (model.performance_overlay != current.performance_overlay) - set_performance_overlay_enabled(plot_, model.performance_overlay); - if (model.renderable_visible != current.renderable_visible) - renderable_.set_visible(model.renderable_visible); - if (model.cache_mode != current.cache_mode) - renderable_.set_cache_mode(model.cache_mode); - if (model.object_name != current.object_name) - renderable_.set_object_name(std::move(model.object_name)); - if (model.feedback != current.feedback) - feedback_ = model.feedback; - plot_.notify_model_dirty(); -} - -} // namespace renderive::web diff --git a/web_server/app/Gallery_Session_Control.h b/web_server/app/Gallery_Session_Control.h index d73e6e0..3227814 100644 --- a/web_server/app/Gallery_Session_Control.h +++ b/web_server/app/Gallery_Session_Control.h @@ -9,7 +9,7 @@ namespace adminive { template -struct Object_Adapter; +struct Type_Descriptor; } namespace renderive::web { @@ -25,17 +25,6 @@ struct Gallery_Feedback_Policy { const Gallery_Feedback_Policy&) = default; }; -struct Gallery_Session_Control_Model { - Color background_color{7, 17, 31, 255}; - bool performance_overlay{}; - bool renderable_visible{true}; - Renderable_Cache_Mode cache_mode{Renderable_Cache_Mode::Local_Pixel}; - std::string object_name{"Gallery_Renderable"}; - bool frequency_limit_enabled{true}; - double max_render_fps{30.0}; - Gallery_Feedback_Policy feedback; -}; - class Gallery_Session_Control final { public: Gallery_Session_Control(Plot_Core& plot, Renderable& renderable, @@ -43,15 +32,12 @@ public: : plot_(plot), renderable_(renderable), feedback_(feedback) {} private: - [[nodiscard]] Gallery_Session_Control_Model adminive_snapshot() const; - void adminive_commit(Gallery_Session_Control_Model model); - Plot_Core& plot_; Renderable& renderable_; Gallery_Feedback_Policy& feedback_; template - friend struct ::adminive::Object_Adapter; + friend struct ::adminive::Type_Descriptor; }; } // namespace renderive::web diff --git a/web_server/app/Gallery_Session_Control_Adminive.h b/web_server/app/Gallery_Session_Control_Adminive.h index 5048711..975e45a 100644 --- a/web_server/app/Gallery_Session_Control_Adminive.h +++ b/web_server/app/Gallery_Session_Control_Adminive.h @@ -3,6 +3,25 @@ #include "Gallery_Session_Control.h" #include "Gallery_Renderables_Adminive.h" +#include +#include + +namespace renderive::web::gallery_adminive { + +template +auto callback_property(std::string name, std::string label, + adminive::Field_Control control, Reader reader, Writer writer) { + auto accessor = structive::trusted_callable_accessor( + std::move(reader), std::move(writer)); + static_assert(std::same_as); + return adminive::field(std::move(name), std::move(label), std::move(accessor)) + .editable() + .unsynchronized() + .control(control); +} + +} // namespace renderive::web::gallery_adminive + namespace adminive { template <> @@ -10,7 +29,7 @@ struct Type_Descriptor { static auto get() { using T = renderive::web::Gallery_Feedback_Policy; using namespace renderive::web::gallery_adminive; - return object("feedback_policy", "Consumer feedback", + return adminive::object("feedback_policy", "Consumer feedback", boolean<&T::enabled>("enabled", "Enabled"), boolean<&T::pixel>("pixel", "Pixel feedback"), boolean<&T::presentation>("presentation", "Presentation feedback"), @@ -20,30 +39,84 @@ struct Type_Descriptor { }; template <> -struct Type_Descriptor { +struct Type_Descriptor { static auto get() { - using T = renderive::web::Gallery_Session_Control_Model; + using T = renderive::web::Gallery_Session_Control; using namespace renderive::web::gallery_adminive; - return object("gallery_session", "Scene", - color<&T::background_color>("background_color", "Background"), - boolean<&T::performance_overlay>("performance_overlay", "Performance overlay"), - boolean<&T::renderable_visible>("renderable_visible", "Renderable visible"), - select<&T::cache_mode>("cache_mode", "Cache mode"), - editable<&T::object_name>("object_name", "Object name", Field_Control::text), - boolean<&T::frequency_limit_enabled>("frequency_limit_enabled", "Frequency limit"), - number<&T::max_render_fps>("max_render_fps", "Maximum render FPS"), - editable<&T::feedback>("feedback", "Consumer feedback", Field_Control::automatic)); + const auto dirty = [](T& value) { value.plot_.notify_model_dirty(); }; + return adminive::object("gallery_session", "Scene", + callback_property( + "background_color", "Background", Field_Control::color, + [](const T& value) { return value.plot_.background_color(); }, + [dirty](T& value, renderive::Color color) { + value.plot_.set_background_color(color); + dirty(value); + }), + callback_property( + "performance_overlay", "Performance overlay", Field_Control::boolean, + [](const T& value) { + const auto overlay = value.plot_.performance_overlay(); + return overlay && overlay->enabled(); + }, + [dirty](T& value, bool enabled) { + renderive::set_performance_overlay_enabled(value.plot_, enabled); + dirty(value); + }), + callback_property( + "renderable_visible", "Renderable visible", Field_Control::boolean, + [](const T& value) { return value.renderable_.is_visible(); }, + [dirty](T& value, bool visible) { + value.renderable_.set_visible(visible); + dirty(value); + }), + callback_property( + "cache_mode", "Cache mode", Field_Control::select, + [](const T& value) { return value.renderable_.get_cache_mode(); }, + [dirty](T& value, renderive::Renderable_Cache_Mode mode) { + value.renderable_.set_cache_mode(mode); + dirty(value); + }), + callback_property( + "object_name", "Object name", Field_Control::text, + [](const T& value) { return value.renderable_.object_name(); }, + [dirty](T& value, std::string name) { + value.renderable_.set_object_name(std::move(name)); + dirty(value); + }), + callback_property( + "frequency_limit_enabled", "Frequency limit", Field_Control::boolean, + [](const T& value) { return value.plot_.max_render_fps() > 0.0; }, + [dirty](T& value, bool enabled) { + if (enabled) { + if (value.plot_.max_render_fps() <= 0.0) + value.plot_.set_max_render_fps(30.0); + } else { + value.plot_.clear_max_render_fps(); + } + dirty(value); + }), + callback_property( + "max_render_fps", "Maximum render FPS", Field_Control::number, + [](const T& value) { + const double fps = value.plot_.max_render_fps(); + return fps > 0.0 ? fps : 30.0; + }, + [dirty](T& value, double fps) { + if (value.plot_.max_render_fps() > 0.0) + value.plot_.set_max_render_fps(fps); + dirty(value); + }), + callback_property( + "feedback", "Consumer feedback", Field_Control::automatic, + [](const T& value) { return value.feedback_; }, + [dirty](T& value, renderive::web::Gallery_Feedback_Policy feedback) { + value.feedback_ = std::move(feedback); + dirty(value); + })); } }; -template <> -struct Object_Adapter { - using object_type = renderive::web::Gallery_Session_Control; - using model_type = renderive::web::Gallery_Session_Control_Model; - static model_type snapshot(const object_type& value) { return value.adminive_snapshot(); } - static void commit(object_type& target, model_type value) { - target.adminive_commit(std::move(value)); - } -}; +static_assert(Described_Type); +static_assert(Direct_Described_Object); } // namespace adminive diff --git a/web_server/app/Pixel_Frame.cpp b/web_server/app/Pixel_Frame.cpp index 8c3ac38..20d5758 100644 --- a/web_server/app/Pixel_Frame.cpp +++ b/web_server/app/Pixel_Frame.cpp @@ -1,7 +1,6 @@ #include "Pixel_Frame.h" #include -#include #include #if defined(_M_X64) || defined(__x86_64__) #include @@ -26,24 +25,6 @@ std::uint8_t flatten(std::uint8_t premultiplied, std::uint8_t alpha, } // namespace -Pixel_Frame_Copy copy_pixel_frame(Image_View image) { - Pixel_Frame_Copy copy; - if (image.empty() || image.format != Pixel_Format::Premultiplied_32 || - image.stride < image.width * static_cast(sizeof(Pixel))) { - return copy; - } - copy.width = image.width; - copy.height = image.height; - copy.pixels.resize(static_cast(image.width) * - static_cast(image.height)); - for (int y = 0; y < image.height; ++y) { - const auto* source = image.data + static_cast(y) * image.stride; - std::memcpy(copy.pixels.data() + static_cast(y) * image.width, - source, static_cast(image.width) * sizeof(Pixel)); - } - return copy; -} - std::string encode_pixel_frame(Image_View image, Color background) { if (image.empty() || image.format != Pixel_Format::Premultiplied_32 || image.stride < image.width * static_cast(sizeof(Pixel))) { @@ -129,13 +110,4 @@ std::string encode_pixel_frame(Image_View image, Color background) { return frame; } -std::string encode_pixel_frame(const Pixel_Frame_Copy& image, Color background) { - if (image.empty()) - return {}; - return encode_pixel_frame( - {reinterpret_cast(image.pixels.data()), image.width, image.height, - image.width * static_cast(sizeof(Pixel)), Pixel_Format::Premultiplied_32}, - background); -} - } // namespace renderive::web diff --git a/web_server/app/Pixel_Frame.h b/web_server/app/Pixel_Frame.h index e8f4c30..91de4b3 100644 --- a/web_server/app/Pixel_Frame.h +++ b/web_server/app/Pixel_Frame.h @@ -2,20 +2,8 @@ #include "render_2D/base/Types.h" #include #include -#include namespace renderive::web { inline constexpr std::size_t pixel_frame_header_size = 16; -struct Pixel_Frame_Copy { - int width{}; - int height{}; - std::vector pixels; - [[nodiscard]] bool empty() const noexcept { - return width <= 0 || height <= 0 || pixels.empty(); - } -}; -[[nodiscard]] Pixel_Frame_Copy copy_pixel_frame(Image_View image); [[nodiscard]] std::string encode_pixel_frame(Image_View image, Color background = Color::black()); -[[nodiscard]] std::string encode_pixel_frame(const Pixel_Frame_Copy& image, - Color background = Color::black()); } // namespace renderive::web diff --git a/web_server/app/Web_Event.h b/web_server/app/Web_Event.h index 8c3dde8..a79d040 100644 --- a/web_server/app/Web_Event.h +++ b/web_server/app/Web_Event.h @@ -32,18 +32,19 @@ struct Gallery_Request { Gallery_Request_Kind kind = Gallery_Request_Kind::Catalog; std::string message; }; -using Web_Event = std::variant; + +template +using Basic_Web_Event = std::variant; + +using Web_Event = Basic_Web_Event; } // namespace renderive::web diff --git a/web_server/app/Web_Event_Adapter.cpp b/web_server/app/Web_Event_Adapter.cpp index 7de67eb..e78bb90 100644 --- a/web_server/app/Web_Event_Adapter.cpp +++ b/web_server/app/Web_Event_Adapter.cpp @@ -1,7 +1,5 @@ #include "Web_Event_Adapter.h" -#include - #include #include #include @@ -10,19 +8,6 @@ namespace renderive::web { namespace { -std::optional parse_json(std::string_view message) { - Json::CharReaderBuilder builder; - builder["collectComments"] = false; - std::unique_ptr reader(builder.newCharReader()); - Json::Value root; - std::string errors; - if (!reader->parse(message.data(), message.data() + message.size(), &root, &errors) || - !root.isObject()) { - return std::nullopt; - } - return root; -} - std::optional finite_number(const Json::Value& root, const char* name) { const auto& value = root[name]; if (!value.isNumeric()) @@ -75,7 +60,8 @@ Key key(const Json::Value& root) { return Key::Unknown; } -std::optional pointer_event(const Json::Value& root, Event_Type type) { +std::optional pointer_event(const Json::Value& root, + Event_Type type) { const auto x = finite_number(root, "x"); const auto y = finite_number(root, "y"); if (!x || !y) @@ -90,7 +76,7 @@ std::optional pointer_event(const Json::Value& root, Event_Type type) return event; } -std::optional wheel_event(const Json::Value& root) { +std::optional wheel_event(const Json::Value& root) { const auto x = finite_number(root, "x"); const auto y = finite_number(root, "y"); const auto pixel_x = finite_number(root, "pixelDeltaX"); @@ -112,7 +98,7 @@ std::optional wheel_event(const Json::Value& root) { return event; } -std::optional key_event(const Json::Value& root, Event_Type type) { +std::optional key_event(const Json::Value& root, Event_Type type) { Key_Event event(type); event.key = key(root); event.native_key = root["nativeKey"].isUInt() ? root["nativeKey"].asUInt() : 0; @@ -121,61 +107,24 @@ std::optional key_event(const Json::Value& root, Event_Type type) { return event; } -std::optional control_event(const Json::Value& root) { - if (!root["control"].isString()) - return std::nullopt; - const std::string control = root["control"].asString(); - if (control == "mode" && root["value"].isString()) { - const std::string value = root["value"].asString(); - if (value == "AM") - return Set_Demo_Mode{Demo_Mode::Am}; - if (value == "FM") - return Set_Demo_Mode{Demo_Mode::Fm}; - if (value == "USB") - return Set_Demo_Mode{Demo_Mode::Usb}; - if (value == "LSB") - return Set_Demo_Mode{Demo_Mode::Lsb}; - return std::nullopt; - } - if (control == "center_frequency_mhz") { - const auto value = finite_number(root, "value"); - return value && *value >= 1.0 && *value <= 6000.0 - ? std::optional(Set_Center_Frequency{*value}) - : std::nullopt; - } - if (control == "bandwidth_khz") { - const auto value = finite_number(root, "value"); - return value && *value >= 10.0 && *value <= 50000.0 - ? std::optional(Set_Bandwidth{*value}) - : std::nullopt; - } - if (control == "gain_db") { - const auto value = finite_number(root, "value"); - return value && *value >= -30.0 && *value <= 80.0 - ? std::optional(Set_Gain{*value}) - : std::nullopt; - } - if (control == "max_hold") { - const auto value = boolean(root, "value"); - return value ? std::optional(Set_Max_Hold{*value}) : std::nullopt; - } - if (control == "smoothing") { - const auto value = boolean(root, "value"); - return value ? std::optional(Set_Smoothing{*value}) : std::nullopt; - } - if (control == "clear_selection") - return Clear_Selection{}; - return std::nullopt; -} - } // namespace -std::optional Web_Event_Adapter::decode(std::string_view message) { - const auto parsed = parse_json(message); - if (!parsed || (*parsed)["category"].asString() != "event" || !(*parsed)["type"].isString()) +std::optional parse_web_event(std::string_view message) { + Json::CharReaderBuilder builder; + builder["collectComments"] = false; + std::unique_ptr reader(builder.newCharReader()); + Json::Value root; + std::string errors; + if (!reader->parse(message.data(), message.data() + message.size(), &root, &errors) || + !root.isObject() || root["category"].asString() != "event" || + !root["type"].isString()) { return std::nullopt; + } + return root; +} - const Json::Value& root = *parsed; +std::optional Web_Transport_Event_Decoder::decode( + const Json::Value& root, std::string_view source) { const std::string type = root["type"].asString(); if (type == "frame") return Frame_Request{}; @@ -187,6 +136,25 @@ std::optional Web_Event_Adapter::decode(std::string_view message) { return Viewport_Resize{{static_cast(std::clamp(*width, 240.0, 1920.0)), static_cast(std::clamp(*height, 180.0, 1200.0))}}; } + Gallery_Request_Kind kind; + if (type == "gallery_catalog") + kind = Gallery_Request_Kind::Catalog; + else if (type == "gallery_open") + kind = Gallery_Request_Kind::Open; + else if (type == "gallery_patch") + kind = Gallery_Request_Kind::Patch; + else if (type == "gallery_action") + kind = Gallery_Request_Kind::Action; + else if (type == "gallery_observe") + kind = Gallery_Request_Kind::Observe; + else + return std::nullopt; + return Gallery_Request{kind, std::string(source)}; +} + +std::optional Render_2D_Web_Event_Decoder::decode( + const Json::Value& root, std::string_view) { + const std::string type = root["type"].asString(); if (type == "show") return Event(Event_Type::Show); if (type == "hide") @@ -205,18 +173,56 @@ std::optional Web_Event_Adapter::decode(std::string_view message) { return key_event(root, Event_Type::Key_Press); if (type == "key_release") return key_event(root, Event_Type::Key_Release); - if (type == "control") - return control_event(root); - if (type == "gallery_catalog") - return Gallery_Request{Gallery_Request_Kind::Catalog, std::string(message)}; - if (type == "gallery_open") - return Gallery_Request{Gallery_Request_Kind::Open, std::string(message)}; - if (type == "gallery_patch") - return Gallery_Request{Gallery_Request_Kind::Patch, std::string(message)}; - if (type == "gallery_action") - return Gallery_Request{Gallery_Request_Kind::Action, std::string(message)}; - if (type == "gallery_observe") - return Gallery_Request{Gallery_Request_Kind::Observe, std::string(message)}; + return std::nullopt; +} + +std::optional Web_Demo_Control_Decoder::decode( + const Json::Value& root, std::string_view) { + if (root["type"].asString() != "control" || !root["control"].isString()) + return std::nullopt; + const std::string control = root["control"].asString(); + if (control == "mode" && root["value"].isString()) { + const std::string value = root["value"].asString(); + if (value == "AM") + return Set_Demo_Mode{Demo_Mode::Am}; + if (value == "FM") + return Set_Demo_Mode{Demo_Mode::Fm}; + if (value == "USB") + return Set_Demo_Mode{Demo_Mode::Usb}; + if (value == "LSB") + return Set_Demo_Mode{Demo_Mode::Lsb}; + return std::nullopt; + } + if (control == "center_frequency_mhz") { + const auto value = finite_number(root, "value"); + return value && *value >= 1.0 && *value <= 6000.0 + ? std::optional(Set_Center_Frequency{*value}) + : std::nullopt; + } + if (control == "bandwidth_khz") { + const auto value = finite_number(root, "value"); + return value && *value >= 10.0 && *value <= 50000.0 + ? std::optional(Set_Bandwidth{*value}) + : std::nullopt; + } + if (control == "gain_db") { + const auto value = finite_number(root, "value"); + return value && *value >= -30.0 && *value <= 80.0 + ? std::optional(Set_Gain{*value}) + : std::nullopt; + } + if (control == "max_hold") { + const auto value = boolean(root, "value"); + return value ? std::optional(Set_Max_Hold{*value}) + : std::nullopt; + } + if (control == "smoothing") { + const auto value = boolean(root, "value"); + return value ? std::optional(Set_Smoothing{*value}) + : std::nullopt; + } + if (control == "clear_selection") + return Clear_Selection{}; return std::nullopt; } diff --git a/web_server/app/Web_Event_Adapter.h b/web_server/app/Web_Event_Adapter.h index ee540f2..9775f5f 100644 --- a/web_server/app/Web_Event_Adapter.h +++ b/web_server/app/Web_Event_Adapter.h @@ -2,14 +2,95 @@ #include "Web_Event.h" +#include + +#include #include #include +#include +#include +#include namespace renderive::web { -class Web_Event_Adapter final { -public: - [[nodiscard]] static std::optional decode(std::string_view message); +using Web_Transport_Events = std::variant; +using Render_2D_Web_Events = std::variant; +using Web_Demo_Control_Events = std::variant; + +struct Web_Transport_Event_Decoder { + using event_type = Web_Transport_Events; + [[nodiscard]] static std::optional decode(const Json::Value& root, + std::string_view source); }; +struct Render_2D_Web_Event_Decoder { + using event_type = Render_2D_Web_Events; + [[nodiscard]] static std::optional decode(const Json::Value& root, + std::string_view source); +}; + +struct Web_Demo_Control_Decoder { + using event_type = Web_Demo_Control_Events; + [[nodiscard]] static std::optional decode(const Json::Value& root, + std::string_view source); +}; + +template +concept Web_Event_Decoder = requires(const Json::Value& root, std::string_view source) { + typename Decoder::event_type; + { Decoder::decode(root, source) } -> + std::same_as>; +}; + +[[nodiscard]] std::optional parse_web_event(std::string_view message); + +template +struct Is_Variant : std::false_type {}; + +template +struct Is_Variant> : std::true_type {}; + +template +class Basic_Web_Event_Adapter final { +public: + [[nodiscard]] static std::optional decode(std::string_view message) { + const auto root = parse_web_event(message); + if (!root) + return std::nullopt; + + std::optional result; + ([&] { + if (result) + return; + auto decoded = Decoders::decode(*root, message); + if (decoded) + result = widen(std::move(*decoded)); + }(), ...); + return result; + } + +private: + template + static Event_Variant widen(Decoded decoded) { + if constexpr (Is_Variant>::value) { + return std::visit([](auto&& value) -> Event_Variant { + return Event_Variant(std::forward(value)); + }, std::move(decoded)); + } else { + return Event_Variant(std::move(decoded)); + } + } +}; + +using Web_Event_Adapter = Basic_Web_Event_Adapter; + } // namespace renderive::web diff --git a/web_server/app/Web_Plot_Session.cpp b/web_server/app/Web_Plot_Session.cpp index 00a7dc0..025bd95 100644 --- a/web_server/app/Web_Plot_Session.cpp +++ b/web_server/app/Web_Plot_Session.cpp @@ -286,7 +286,7 @@ struct Web_Plot_Session::Impl { plot.deactivate_view(); plot.dispatch_event(value); } - else if constexpr (std::is_base_of_v) { + else if constexpr (Event_Object) { plot.dispatch_event(value); } else if constexpr (std::is_same_v) { diff --git a/web_server/tests/Web_Bridge_Tests.cpp b/web_server/tests/Web_Bridge_Tests.cpp index a1317d7..473a518 100644 --- a/web_server/tests/Web_Bridge_Tests.cpp +++ b/web_server/tests/Web_Bridge_Tests.cpp @@ -23,6 +23,26 @@ namespace renderive::web { namespace { +struct Spatial_Test_Event { + int layer{}; +}; + +struct Spatial_Test_Decoder { + using event_type = Spatial_Test_Event; + + static std::optional decode(const Json::Value& root, std::string_view) { + if (root["type"].asString() != "spatial_test" || !root["layer"].isInt()) + return std::nullopt; + return Spatial_Test_Event{root["layer"].asInt()}; + } +}; + +using Extended_Web_Event = Basic_Web_Event; +using Extended_Web_Event_Adapter = Basic_Web_Event_Adapter< + Extended_Web_Event, Web_Transport_Event_Decoder, Render_2D_Web_Event_Decoder, + Web_Demo_Control_Decoder, Spatial_Test_Decoder>; + std::uint32_t read_u32_le(const std::string& value, std::size_t offset) { return static_cast(value[offset]) | (static_cast(static_cast(value[offset + 1])) << 8U) | @@ -247,6 +267,14 @@ TEST(RenderiveWebGallery, CatalogOwnsDashboardFieldsAndDynamicBehavior) { EXPECT_TRUE(action->at("request_frame")); } +TEST(RenderiveWebBridge, ComposesAdditionalEventDecoders) { + const auto event = Extended_Web_Event_Adapter::decode( + R"({"category":"event","type":"spatial_test","layer":7})"); + ASSERT_TRUE(event.has_value()); + ASSERT_TRUE(std::holds_alternative(*event)); + EXPECT_EQ(std::get(*event).layer, 7); +} + TEST(RenderiveWebGallery, AdminiveResourcesPatchTheRealRenderableState) { Gallery_Plot_Session session; auto response = response_json(session.handle(gallery_request( @@ -265,7 +293,7 @@ TEST(RenderiveWebGallery, AdminiveResourcesPatchTheRealRenderableState) { }; const auto& spectrum = find_resource(response, "spectrum"); - EXPECT_EQ(spectrum.at("descriptor").at("name"), "Spectrum_Properties"); + EXPECT_EQ(spectrum.at("descriptor").at("name"), "Gallery_Spectrum"); EXPECT_TRUE(spectrum.at("data").at("frequency_range").is_object()); EXPECT_EQ(spectrum.at("data").at("frequency_point_size"), 512);