diff --git a/Kernel/src/renderive/real_time_data/Double_Buffer_Strategy.hpp b/Kernel/src/renderive/real_time_data/Double_Buffer_Strategy.hpp index 32bbcd0..a45f0dd 100644 --- a/Kernel/src/renderive/real_time_data/Double_Buffer_Strategy.hpp +++ b/Kernel/src/renderive/real_time_data/Double_Buffer_Strategy.hpp @@ -70,6 +70,7 @@ struct Multi_Double_Buffer_Strategy, Mutex, Obs published }; struct Observation { + using Event = Observation_Event; Observation_Event event{}; std::size_t buffer_index{}; std::uint64_t time_ns{}; @@ -98,6 +99,13 @@ struct Multi_Double_Buffer_Strategy, Mutex, Obs observer.observe(Observation{Observation_Event::cache_updated, buffer_index(), observer.now_ns(), current.cache_update_count, current.publish_count}); return *this; } + template + requires requires(Observer& value, Target& target) { + value.bind(target); + } + decltype(auto) bind(Target& target) { + return observer.bind(target); + } template requires std::invocable bool publish(Side_Effect&& side_effect = {}) { @@ -128,6 +136,10 @@ struct Multi_Double_Buffer_Strategy, Mutex, Obs return slot().publish_count; } template + std::uint64_t cache_revision() const noexcept { + return slot().cache_update_count; + } + template const Data& cache_buffer_value() const noexcept { const auto& current = slot(); return current.buffers[current.cache_index]; diff --git a/Kernel/src/renderive/real_time_data/Real_Time_Data.hpp b/Kernel/src/renderive/real_time_data/Real_Time_Data.hpp index a8e2e65..9ca2d5f 100644 --- a/Kernel/src/renderive/real_time_data/Real_Time_Data.hpp +++ b/Kernel/src/renderive/real_time_data/Real_Time_Data.hpp @@ -3,6 +3,5 @@ #include "Attach_Real_Time_Data.hpp" #include "Frame_Strategy_Observer.hpp" #include "History_Real_Time_Data.hpp" -#include "Latest_Real_Time_Data.hpp" #include "Observation.hpp" #include "concept/Real_Time_Data.hpp" diff --git a/Kernel/src/renderive/real_time_data/concept/Real_Time_Data.hpp b/Kernel/src/renderive/real_time_data/concept/Real_Time_Data.hpp index 66de4ce..c27249c 100644 --- a/Kernel/src/renderive/real_time_data/concept/Real_Time_Data.hpp +++ b/Kernel/src/renderive/real_time_data/concept/Real_Time_Data.hpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include "renderive/real_time_data/Observation.hpp" #include "renderive/real_time_data/base/Real_Time_Data_Base.hpp" @@ -26,10 +25,6 @@ concept Real_Time_Data = std::derived_from && require { const_data.update_state() } -> std::same_as; }; template -concept Latest_Real_Time_Data_Source = Real_Time_Data && requires(const That& data) { - { data.snapshot() } -> std::same_as>; -}; -template concept History_Real_Time_Data_Source = Real_Time_Data && requires(const That& data) { typename That::Container_Type; { data.snapshot() } -> std::same_as; diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp index e1c47cf..b84f365 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp @@ -74,14 +74,17 @@ void Renderable_Base::Impl::rebuild_render_graph() { } void Renderable_Base::Impl::reset_render_graph() noexcept { + std::lock_guard lock(render_graph_mutex); render_graph.reset(); invalidate_prepare(); } std::shared_ptr Renderable_Base::Impl::render_graph_snapshot() { + std::lock_guard lock(render_graph_mutex); if (!render_graph) { - std::unordered_map next_identities; + std::pmr::unordered_map next_identities{ + &memory_resource()}; Renderable_Graph_Builder builder( renderable_id, [this, &next_identities](std::string_view key) { @@ -101,7 +104,8 @@ Renderable_Base::Impl::render_graph_snapshot() { build_paint_graph(builder); render_graph = std::make_shared( std::move(builder).finish()); - node_identities = std::move(next_identities); + node_identities.clear(); + node_identities.insert(next_identities.begin(), next_identities.end()); } return render_graph; } diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp b/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp index 4c77010..4cdaf45 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp @@ -80,6 +80,7 @@ public: const Render_Node_Id composite_node_id; std::atomic configuration; std::atomic visible{true}; + std::mutex render_graph_mutex; std::shared_ptr render_graph; std::unordered_map node_identities; std::atomic prepare_revision{1}; diff --git a/Kernel/src/renderive/state/Double_State_Storage.hpp b/Kernel/src/renderive/state/Double_State_Storage.hpp index edc683e..664ad3e 100644 --- a/Kernel/src/renderive/state/Double_State_Storage.hpp +++ b/Kernel/src/renderive/state/Double_State_Storage.hpp @@ -123,6 +123,11 @@ struct Double_State_Storage : State_Strategy_Base { return publish_count; } + [[nodiscard]] State published_state() const { + std::lock_guard lock(mtx); + return *render_state; + } + protected: [[nodiscard]] const State& render_state_value() const noexcept { return *render_state; diff --git a/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp b/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp index 46a9530..235a0f5 100644 --- a/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp +++ b/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp @@ -64,13 +64,12 @@ struct Real_Time_Data_Test_Renderable : Renderable_Test_Harness { std::atomic render_count{}; }; using Real_Time_Data_Test_Bridge = Observer_State; -using Real_Time_Data_Test_Latest = Latest_Real_Time_Data; using Real_Time_Data_Test_History = History_Real_Time_Data, std::mutex, Real_Time_Data_Test_Bridge>; -using Real_Time_Data_Test_Attachment = Attach_Real_Time_Data; +using Real_Time_Data_Test_Attachment = Attach_Real_Time_Data; class Real_Time_Data_Render_Probe : public Renderable_Test_Harness { - class Bound_Latest : public Latest_Real_Time_Data { + class Bound_History : public History_Real_Time_Data { public: - explicit Bound_Latest(Renderable_Base& owner) + explicit Bound_History(Renderable_Base& owner) : binding_(bind_renderable(owner)) {} private: Real_Time_Data_Binding binding_; @@ -79,9 +78,9 @@ public: explicit Real_Time_Data_Render_Probe() : data(*this) {} void prepare_for_test(const Prepare_Render_Context& context) override { const auto& value = context.frame.render_state.get(data); - rendered_value = value.value_or(-1); + rendered_value = value.empty() ? -1 : value.back(); } - Bound_Latest data; + Bound_History data; int rendered_value{-1}; }; TEST(real_time_data_render_state_test, publishes_once_at_frame_boundary_and_reads_without_snapshot) { @@ -102,15 +101,6 @@ TEST(real_time_data_render_state_test, publishes_once_at_frame_boundary_and_read scene.wait_for_render(); EXPECT_EQ(renderable->rendered_value, 2); } -TEST(latest_real_time_data_test, retains_only_latest_value) { - Latest_Real_Time_Data data; - data.update(1); - data.update(2); - ASSERT_TRUE(data.snapshot()); - EXPECT_EQ(*data.snapshot(), 2); - EXPECT_EQ(data.update_state().retained_value_count, 1); - EXPECT_EQ(data.revision(), 2); -} TEST(history_real_time_data_test, retains_all_values_until_explicit_discard) { using Observer = Observer_State; History_Real_Time_Data, std::mutex, Observer> data; @@ -135,10 +125,8 @@ TEST(history_real_time_data_test, retains_latest_requested_value_count) { } TEST(real_time_data_attachment_test, binds_updates_to_renderable_frame_strategy) { Scene2D_Context<> scene; - auto latest = std::make_shared(); auto history = std::make_shared(); - auto renderable = renderive_Owner::make(With_Real_Time_Data(latest, history)); - renderable->discard_stale_frame_on_latest_data_update(true); + auto renderable = renderive_Owner::make(With_Real_Time_Data(history)); { auto attach = scene.attach_builder(); attach.attach(renderable); @@ -147,17 +135,15 @@ TEST(real_time_data_attachment_test, binds_updates_to_renderable_frame_strategy) auto frame = scene.frame_control.acquire_painter(); ASSERT_TRUE(frame); } - latest->update(1); - EXPECT_EQ(scene.frame_control.counter_statistics().manually_discarded_frame_count, 1); - auto frame = scene.frame_control.acquire_renderer(); - EXPECT_FALSE(frame); + history->update(1); + EXPECT_EQ(renderable->real_time_data_attachment_state().total_update_count, + 1U); } TEST(real_time_data_attachment_test, discards_history_older_than_one_frame_interval) { using Strategy = Low_Latency_Strategy; Scene2D_Context scene(Observer_State<>{}, Strategy::Configuration{100'000'000.0}); - auto latest = std::make_shared(); auto history = std::make_shared(); - auto renderable = renderive_Owner::make(With_Real_Time_Data(latest, history)); + auto renderable = renderive_Owner::make(With_Real_Time_Data(history)); renderable->real_time_data_discard_mode = Real_Time_Data_Discard_Mode::retain_frame_interval; history->update(1); history->update(2); @@ -172,22 +158,21 @@ TEST(real_time_data_attachment_test, discards_history_older_than_one_frame_inter EXPECT_EQ(renderable->render_count.load(), 1); } TEST(real_time_data_attachment_test, rejects_null_real_time_data_source) { - std::shared_ptr latest; - EXPECT_THROW((With_Real_Time_Data(latest)), std::invalid_argument); + std::shared_ptr history; + EXPECT_THROW((With_Real_Time_Data(history)), std::invalid_argument); } -using Real_Time_Data_Test_Latest_Attachment = Attach_Real_Time_Data; +using Real_Time_Data_Test_History_Attachment = Attach_Real_Time_Data; TEST(real_time_data_attachment_test, rejects_binding_one_source_to_multiple_renderables) { - auto latest = std::make_shared(); - auto first = renderive_Owner::make(With_Real_Time_Data(latest)); - EXPECT_THROW((renderive_Owner::make(With_Real_Time_Data(latest))), std::logic_error); + auto history = std::make_shared(); + auto first = renderive_Owner::make(With_Real_Time_Data(history)); + EXPECT_THROW((renderive_Owner::make(With_Real_Time_Data(history))), std::logic_error); first.reset(); - EXPECT_NO_THROW((renderive_Owner::make(With_Real_Time_Data(latest)))); + EXPECT_NO_THROW((renderive_Owner::make(With_Real_Time_Data(history)))); } TEST(real_time_data_attachment_test, detached_renderable_no_longer_drives_frame_strategy) { Scene2D_Context<> scene; - auto latest = std::make_shared(); - auto renderable = renderive_Owner::make(With_Real_Time_Data(latest)); - renderable->discard_stale_frame_on_latest_data_update(true); + auto history = std::make_shared(); + auto renderable = renderive_Owner::make(With_Real_Time_Data(history)); { auto attach = scene.attach_builder(); attach.attach(renderable); @@ -196,8 +181,8 @@ TEST(real_time_data_attachment_test, detached_renderable_no_longer_drives_frame_ auto frame = scene.frame_control.acquire_painter(); ASSERT_TRUE(frame); } - latest->update(1); - EXPECT_EQ(scene.frame_control.counter_statistics().manually_discarded_frame_count, 1); + history->update(1); + const auto before_detach = scene.frame_control.state().real_time_data_update_sequence; wait_renderable_edit(scene, [renderable](auto& editor) { editor.detach(renderable); }); @@ -205,19 +190,20 @@ TEST(real_time_data_attachment_test, detached_renderable_no_longer_drives_frame_ auto frame = scene.frame_control.acquire_painter(); ASSERT_TRUE(frame); } - latest->update(2); - EXPECT_EQ(scene.frame_control.counter_statistics().manually_discarded_frame_count, 1); + history->update(2); + EXPECT_EQ(scene.frame_control.state().real_time_data_update_sequence, + before_detach); } TEST(real_time_data_attachment_test, unbind_is_synchronized_with_concurrent_updates) { Scene2D_Context<> scene; - auto latest = std::make_shared(); - auto renderable = renderive_Owner::make(With_Real_Time_Data(latest)); + auto history = std::make_shared(); + auto renderable = renderive_Owner::make(With_Real_Time_Data(history)); std::atomic running{true}; std::atomic updated{}; std::thread updater([&] { int value{}; while (running.load(std::memory_order_acquire)) { - latest->update(++value); + history->update(++value); updated.store(true, std::memory_order_release); } }); @@ -227,20 +213,19 @@ TEST(real_time_data_attachment_test, unbind_is_synchronized_with_concurrent_upda renderable.reset(); running.store(false, std::memory_order_release); updater.join(); - EXPECT_TRUE(latest->revision() > 0); + EXPECT_TRUE(history->revision() > 0); } TEST(real_time_data_attachment_test, runtime_discard_configuration_is_safe_during_updates_and_rendering) { Scene2D_Context<> scene; - auto latest = std::make_shared(); auto history = std::make_shared(); - auto renderable = renderive_Owner::make(With_Real_Time_Data(latest, history)); + auto renderable = renderive_Owner::make(With_Real_Time_Data(history)); { auto attach = scene.attach_builder(); attach.attach(renderable); } std::thread updater([&] { for (int value = 0; value < 1000; ++value) { - latest->update(value); + history->update(value, 1); } }); std::thread configurator([&] { @@ -256,40 +241,40 @@ TEST(real_time_data_attachment_test, runtime_discard_configuration_is_safe_durin } updater.join(); configurator.join(); - EXPECT_EQ(latest->revision(), 1000); + EXPECT_EQ(history->revision(), 1000); } TEST(real_time_data_attachment_test, ignores_updates_after_bound_scene_is_destroyed) { - auto latest = std::make_shared(); - renderive_Owner renderable; + auto history = std::make_shared(); + renderive_Owner renderable; { Scene2D_Context<> scene; - renderable = renderive_Owner::make(With_Real_Time_Data(latest)); + renderable = renderive_Owner::make(With_Real_Time_Data(history)); { auto attach = scene.attach_builder(); attach.attach(renderable); } - latest->update(1); + history->update(1); EXPECT_EQ(scene.frame_control.state().real_time_data_update_sequence, 1); } - EXPECT_NO_THROW(latest->update(2)); + EXPECT_NO_THROW(history->update(2)); EXPECT_THROW(renderable->scene_for_test(), std::logic_error); renderable.reset(); } TEST(real_time_data_attachment_test, source_binding_is_owned_by_renderable_lifetime) { - auto latest = std::make_shared(); - auto renderable = renderive_Owner::make(With_Real_Time_Data(latest)); - EXPECT_THROW((renderive_Owner::make(With_Real_Time_Data(latest))), std::logic_error); + auto history = std::make_shared(); + auto renderable = renderive_Owner::make(With_Real_Time_Data(history)); + EXPECT_THROW((renderive_Owner::make(With_Real_Time_Data(history))), std::logic_error); renderable.reset(); - EXPECT_NO_THROW((renderive_Owner::make(With_Real_Time_Data(latest)))); + EXPECT_NO_THROW((renderive_Owner::make(With_Real_Time_Data(history)))); } TEST(real_time_data_attachment_test, attachment_owns_real_time_data_sources) { Scene2D_Context<> scene; - auto latest = std::make_shared(); - std::weak_ptr weak = latest; - auto renderable = renderive_Owner::make(With_Real_Time_Data(latest)); - latest.reset(); + auto history = std::make_shared(); + std::weak_ptr weak = history; + auto renderable = renderive_Owner::make(With_Real_Time_Data(history)); + history.reset(); EXPECT_FALSE(weak.expired()); - renderable->real_time_data().update(1); + renderable->real_time_data().update(1); renderable.reset(); EXPECT_TRUE(weak.expired()); } @@ -314,8 +299,8 @@ TEST(real_time_data_attachment_test, frame_strategy_observer_can_reacquire_scene Real_Time_Data_Reentrant_Frame_Observer_State observer_state(observer, Real_Time_Data_Test_Time_Source{}); Real_Time_Data_Reentrant_Strategy::Configuration configuration{60.0}; Scene2D_Context scene(std::move(observer_state), configuration); - auto latest = std::make_shared(); - auto renderable = renderive_Owner::make(With_Real_Time_Data(latest)); + auto history = std::make_shared(); + auto renderable = renderive_Owner::make(With_Real_Time_Data(history)); { auto attach = scene.attach_builder(); attach.attach(renderable); @@ -325,7 +310,7 @@ TEST(real_time_data_attachment_test, frame_strategy_observer_can_reacquire_scene reacquired.store(&renderable->scene_for_test() == &scene, std::memory_order_release); }; - latest->update(1); + history->update(1); EXPECT_TRUE(reacquired.load(std::memory_order_acquire)); } class Real_Time_Data_Failing_Memory_Resource : public std::pmr::memory_resource { @@ -351,5 +336,8 @@ TEST(history_real_time_data_test, failed_timestamp_allocation_rolls_back_value_a EXPECT_EQ(state.retained_value_count, 0); EXPECT_EQ(state.update_time_ns, 0); } -static_assert(Unique_Real_Time_Data_Types); -static_assert(!Unique_Real_Time_Data_Types); +using Real_Time_Data_Test_Other_History = History_Real_Time_Data; +static_assert(Unique_Real_Time_Data_Types); +static_assert(!Unique_Real_Time_Data_Types); diff --git a/Kernel/tests/renderive/real_time_data/base/Real_Time_Data_Base_Test.cpp b/Kernel/tests/renderive/real_time_data/base/Real_Time_Data_Base_Test.cpp index 685e743..1ae6588 100644 --- a/Kernel/tests/renderive/real_time_data/base/Real_Time_Data_Base_Test.cpp +++ b/Kernel/tests/renderive/real_time_data/base/Real_Time_Data_Base_Test.cpp @@ -1,12 +1,8 @@ #include #include "renderive/real_time_data/Real_Time_Data.hpp" -static_assert(std::derived_from, Real_Time_Data_Base>); static_assert(std::derived_from, Real_Time_Data_Base>); TEST(real_time_data_base_test, exposes_runtime_retention) { - Latest_Real_Time_Data latest; History_Real_Time_Data history; - Real_Time_Data_Base* latest_base = &latest; Real_Time_Data_Base* history_base = &history; - EXPECT_EQ(latest_base->retention(), Real_Time_Data_Retention::latest); EXPECT_EQ(history_base->retention(), Real_Time_Data_Retention::history); } diff --git a/Kernel/tests/renderive/renderable/Renderable_Inheritance_Test.cpp b/Kernel/tests/renderive/renderable/Renderable_Inheritance_Test.cpp index f4314f5..8eb11ac 100644 --- a/Kernel/tests/renderive/renderable/Renderable_Inheritance_Test.cpp +++ b/Kernel/tests/renderive/renderable/Renderable_Inheritance_Test.cpp @@ -78,6 +78,7 @@ public: struct Renderable_Root : renderive::renderable::render_base { using render_base::render_base; + struct Impl {}; }; struct Product_Properties { diff --git a/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp b/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp index 65a8b5d..366a906 100644 --- a/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp +++ b/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp @@ -318,7 +318,10 @@ TEST(threading_contract_test, low_latency_strategy_keeps_frames_consistent_durin TEST(threading_contract_test, real_time_data_observations_follow_mutation_revision_order) { Threading_Revision_Observer observer; auto observer_data = observer.data; - Latest_Real_Time_Data data(Threading_Revision_Observer_State(observer, Threading_Test_Time_Source{})); + History_Real_Time_Data, std::mutex, + Threading_Revision_Observer_State> + data(Threading_Revision_Observer_State( + observer, Threading_Test_Time_Source{})); constexpr int updater_count = 8; constexpr int updates_per_thread = 500; constexpr int update_count = updater_count * updates_per_thread; @@ -345,8 +348,8 @@ TEST(threading_contract_test, real_time_data_observations_follow_mutation_revisi } } } -TEST(threading_contract_test, latest_real_time_data_supports_concurrent_updates_and_snapshots) { - Latest_Real_Time_Data data; +TEST(threading_contract_test, retained_history_supports_concurrent_latest_updates_and_snapshots) { + History_Real_Time_Data data; constexpr int updater_count = 4; constexpr int updates_per_thread = 1000; constexpr int update_count = updater_count * updates_per_thread; @@ -357,7 +360,7 @@ TEST(threading_contract_test, latest_real_time_data_supports_concurrent_updates_ wait_start(start); while (updates_done.load(std::memory_order_acquire) != updater_count) { const auto snapshot = data.snapshot(); - if (snapshot && !valid_frame(*snapshot)) { + if (!snapshot.empty() && !valid_frame(snapshot.back())) { invalid.fetch_add(1, std::memory_order_relaxed); } const auto state = data.update_state(); @@ -373,7 +376,7 @@ TEST(threading_contract_test, latest_real_time_data_supports_concurrent_updates_ wait_start(start); for (int index = 0; index < updates_per_thread; ++index) { const auto value = static_cast(updater * updates_per_thread + index); - data.update({value, ~value}); + data.update({value, ~value}, 1); } updates_done.fetch_add(1, std::memory_order_release); }); @@ -570,7 +573,9 @@ TEST(threading_contract_test, scene_render_and_render_graph_rebuild_are_serializ EXPECT_EQ(executed.load(std::memory_order_acquire), render_count + 1); } TEST(threading_contract_test, real_time_data_updates_can_race_with_scene_destruction) { - using Data = Latest_Real_Time_Data>; + using Data = History_Real_Time_Data< + int, std::vector, std::mutex, + Observer_State>; struct Renderable : Renderable_Base { explicit Renderable() : Renderable_Base() {} }; diff --git a/Qt/plot/Plot.h b/Qt/plot/Plot.h index b5856ef..f5cca64 100644 --- a/Qt/plot/Plot.h +++ b/Qt/plot/Plot.h @@ -1,5 +1,5 @@ #pragma once -#include "../../render_2D/src/export.h" +#include "render_2D/export.h" #include #include #include diff --git a/Qt/tests/Qt_Bridge_Tests.cpp b/Qt/tests/Qt_Bridge_Tests.cpp index acaf506..6c035dd 100644 --- a/Qt/tests/Qt_Bridge_Tests.cpp +++ b/Qt/tests/Qt_Bridge_Tests.cpp @@ -18,18 +18,20 @@ TEST(Renderive_Qt, WidgetLifecycleDrivesAndStopsKernelScene) { renderive_Owner spectrum; { auto attach = plot.scene()->attach_builder(); - x_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach) - .set_x(20) - .set_y(100) - .set_pixel_length(200) - .set_coord_range({0.0, 10.0}) - .build(); - y_axis = Axis::Builder(root, Orientation::Vertical, &attach) - .set_x(20) - .set_y(10) - .set_pixel_length(90) - .set_coord_range({1.0, 0.0}) - .build(); + x_axis = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Frequency_Axis::Properties::x>(20) + .set<&Frequency_Axis::Properties::y>(100) + .set<&Frequency_Axis::Properties::pixel_length>(200) + .set<&Frequency_Axis::Properties::coordinates>(Range{0.0, 10.0}) + .build(root); + y_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .set<&Axis::Properties::x>(20) + .set<&Axis::Properties::y>(10) + .set<&Axis::Properties::pixel_length>(90) + .set<&Axis::Properties::coordinates>(Range{1.0, 0.0}) + .build(root); spectrum = Spectrum::Builder{&attach} .set<&Spectrum::Properties::frequency_range>(Range{0.0, 10.0}) .set<&Spectrum::Properties::frequency_point_size>(8) diff --git a/render_2D/render_2D/axis/Axis_State_Strategy.hpp b/render_2D/render_2D/axis/Axis_State_Strategy.hpp index 6bafdb3..8a68c3e 100644 --- a/render_2D/render_2D/axis/Axis_State_Strategy.hpp +++ b/render_2D/render_2D/axis/Axis_State_Strategy.hpp @@ -2,11 +2,6 @@ #include "Abs_Axis.h" #include "Axis_Format.h" #include "../render/Blend2D_Cache.h" -#include -#include -#include -#include -#include #include #include #include @@ -14,123 +9,11 @@ #include #include namespace renderive::detail { -template -struct Axis_Renderable_Builder { - using Self = Axis_Renderable_Builder; - Axis_Renderable_Builder( - renderive_Owner<::renderive::Renderable> parent, Orientation orientation, - ::Scene_Base::Attach_Builder* attach_builder = nullptr) - : parent_(std::move(parent)), attach_builder_(attach_builder) { - properties_.orientation = orientation; - } - template Value> - Self& set(Value&& value) { - properties_.*Member = std::forward < Value > (value); - return *this; - } - Self& set_x(int value) { - return set<&Properties::x>(value); - } - Self& set_y(int value) { - return set<&Properties::y>(value); - } - Self& set_orientation(Orientation value) { - return set<&Properties::orientation>(value); - } - Self& set_pixel_length(std::size_t value) { - return set<&Properties::pixel_length>(value); - } - Self& set_tick_length(int value) { - return set<&Properties::tick_length>(value); - } - Self& set_sub_tick_length(int value) { - return set<&Properties::sub_tick_length>(value); - } - Self& set_color(Color value) { - return set<&Properties::color>(value); - } - Self& set_locale(Number_Locale value) { - return set<&Properties::locale>(value); - } - Self& set_unit_text(std::string value) { - return set<&Properties::unit_text>(std::move(value)); - } - Self& set_unit_text_font(Font value) { - return set<&Properties::unit_text_font>(value); - } - Self& set_unit_text_pen(Pen value) { - return set<&Properties::unit_text_pen>(value); - } - Self& set_unit_text_background_brush(Brush value) { - return set<&Properties::unit_text_background_brush>(value); - } - Self& set_label_rotation_degrees(int value) { - return set<&Properties::label_rotation_degrees>(value); - } - Self& set_coord_range(Range value) - requires requires(Properties properties) { properties.coordinates = value; } { - return set<&Properties::coordinates>(value); - } - Self& set_label_precision(int value) - requires requires(Properties properties) { properties.precision = value; } { - return set<&Properties::precision>(value); - } - Self& set_use_wheel(bool value) - requires requires(Properties properties) { properties.wheel = value; } { - return set<&Properties::wheel>(value); - } - Self& set_use_drag(bool value) - requires requires(Properties properties) { properties.drag = value; } { - return set<&Properties::drag>(value); - } - Self& set_visible_time_point_count(int value) - requires requires(Properties properties) { properties.visible_count = value; } { - return set<&Properties::visible_count>(value); - } - Self& set_tick_label_spacing_px(int value) - requires requires(Properties properties) { - properties.tick_label_spacing_px = value; - } { - return set<&Properties::tick_label_spacing_px>(value); - } - Self& set_time_format(std::string value) - requires requires(Properties properties) { properties.format = value; } { - return set<&Properties::format>(std::move(value)); - } - Self& set_font(Font value) - requires requires(Properties properties) { properties.unit_text_font = value; } { - return set<&Properties::unit_text_font>(value); - } - Self& set_newest_at_axis_start(bool value) - requires requires(Properties properties) { properties.newest_at_start = value; } { - return set<&Properties::newest_at_start>(value); - } - renderive_Owner build() const { - if (!parent_) - return {}; - return inheritance::initialize( - renderive_Owner::make(properties_), - [&](renderive_Owner& result) { - if (!attach_builder_) - return; - attach_builder_->attach(result); - attach_builder_->add_display_parent(result, parent_); - attach_builder_->add_dependency_parent(result, parent_); - }); - } -private: - renderive_Owner<::renderive::Renderable> parent_; - Properties properties_; - ::Scene_Base::Attach_Builder* attach_builder_{}; -}; template requires std::is_base_of_v struct Axis_State_Strategy : Renderable { using Render_Base = Renderable; - template - using Business_Builder = - Axis_Renderable_Builder; protected: struct Impl : Render_Base::template next_Impl { using State = typename Layer::State; diff --git a/render_2D/render_2D/axis/Frequency_Axis.h b/render_2D/render_2D/axis/Frequency_Axis.h index e83e0d0..22a60fc 100644 --- a/render_2D/render_2D/axis/Frequency_Axis.h +++ b/render_2D/render_2D/axis/Frequency_Axis.h @@ -5,8 +5,6 @@ namespace detail { struct LIB_DECL Frequency_Axis : Renderable { using State = ::renderive::Axis::Properties; - template - using Business_Builder = Axis_Renderable_Builder; explicit Frequency_Axis(const State& state); protected: struct Impl; diff --git a/render_2D/render_2D/plottable/Performance_Overlay.h b/render_2D/render_2D/plottable/Performance_Overlay.h index 528e936..e2c9feb 100644 --- a/render_2D/render_2D/plottable/Performance_Overlay.h +++ b/render_2D/render_2D/plottable/Performance_Overlay.h @@ -33,7 +33,7 @@ struct Performance_Display_Snapshot { std::vector lines; std::uint64_t frame_count{}; }; -class LIB_DECL Performance_Overlay { +struct LIB_DECL Performance_Overlay { Performance_Overlay(); explicit Performance_Overlay(Performance_Overlay_Options options); ~Performance_Overlay(); diff --git a/render_2D/render_2D/plottable/Plottable.h b/render_2D/render_2D/plottable/Plottable.h index 00e9770..1e644b5 100644 --- a/render_2D/render_2D/plottable/Plottable.h +++ b/render_2D/render_2D/plottable/Plottable.h @@ -47,7 +47,7 @@ private: double value{}; }; namespace detail { -class Paint_Overlay {}; +struct Paint_Overlay {}; template struct Renderable : renderable::render_base { using Render_Base = renderable::render_base; diff --git a/render_2D/render_2D/plottable/Plottable_Real_Time_Data.h b/render_2D/render_2D/plottable/Plottable_Real_Time_Data.h index 3cc1a91..6676990 100644 --- a/render_2D/render_2D/plottable/Plottable_Real_Time_Data.h +++ b/render_2D/render_2D/plottable/Plottable_Real_Time_Data.h @@ -6,6 +6,31 @@ #include namespace renderive::detail { using Plottable_Real_Time_Data_Observer = Observer_State; +struct Double_Buffer_Real_Time_Data_Observer { + static constexpr bool enabled = true; + + template + decltype(auto) bind(Target& target) { + return observer_.bind(target); + } + + template + void observe(const Observation& observation) noexcept { + if (observation.event != Observation::Event::cache_updated) + return; + observer_.observe({ + Real_Time_Data_Observation_Event::updated, + {this, Real_Time_Data_Retention::latest, + observation.cache_update_count, observation.time_ns, + observation.cache_update_count, 1} + }); + } + +private: + Frame_Strategy_Real_Time_Data_Observer observer_; +}; +using Plottable_Double_Buffer_Observer = +Observer_State; inline Plottable_Real_Time_Data_Observer observe_real_time_data( ::renderive::Renderable& renderable) { return Plottable_Real_Time_Data_Observer(Frame_Strategy_Real_Time_Data_Observer(renderable)); diff --git a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp index 95aa32f..a3559eb 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp @@ -22,7 +22,6 @@ RectF axis_content_rect(const Axis_Transform& first, const Axis_Transform& secon } struct Selection_Rectangle_Overlay::Impl : next_Impl, - public Paint_Overlay, Renderable_Event_Handler { struct Observer : next_Observer { diff --git a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.h b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.h index ee61dcc..21c7b03 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.h +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.h @@ -4,7 +4,7 @@ namespace renderive { namespace detail { struct LIB_DECL Selection_Rectangle_Overlay - : Renderable { + : Renderable, Paint_Overlay { struct State; Selection_Rectangle_Overlay( diff --git a/render_2D/render_2D/plottable/Spectrum.cpp b/render_2D/render_2D/plottable/Spectrum.cpp index 0184ac7..75bf0a1 100644 --- a/render_2D/render_2D/plottable/Spectrum.cpp +++ b/render_2D/render_2D/plottable/Spectrum.cpp @@ -152,6 +152,7 @@ struct Spectrum::Impl power_axis(std::move(power)) {} renderive_Owner frequency_axis; renderive_Owner power_axis; + std::shared_ptr real_time_data_binding; Spectrum_Interaction_State interaction; Adaptive_Render_Partitioner partitioner; Spectrum_Prepare_Buffer prepare_buffer; @@ -175,7 +176,10 @@ Spectrum::Spectrum(const State& state, renderive_Owner frequency_axis, renderive_Owner power_axis) : Renderable(With_Attached_Impl{}, state, - std::move(frequency_axis), std::move(power_axis)) {} + std::move(frequency_axis), std::move(power_axis)) { + d_func().real_time_data_binding = + d_func().Spectrum_Real_Time_Data_Strategy::bind(*this); +} Spectrum::~Spectrum() = default; void Spectrum::update_samples(std::span values) { if (get_state() <= 0) diff --git a/render_2D/render_2D/plottable/Spectrum.h b/render_2D/render_2D/plottable/Spectrum.h index 23b1c62..4b96a1f 100644 --- a/render_2D/render_2D/plottable/Spectrum.h +++ b/render_2D/render_2D/plottable/Spectrum.h @@ -1,6 +1,7 @@ #pragma once #include "Hover_Tooltip.h" #include "Plottable.h" +#include "Plottable_Real_Time_Data.h" #include "../axis/Axis.h" #include "../renderable/Render_Partition.h" #include @@ -17,8 +18,12 @@ struct Spectrum_Frame { struct Spectrum_Frame_Tag {}; using Spectrum_Buffer_Layout = ::Double_Buffer_Layout<::Buffered_Data>; struct Spectrum_Real_Time_Data_Strategy -: public ::Multi_Double_Buffer_Strategy { - using Base = ::Multi_Double_Buffer_Strategy; +: public ::Multi_Double_Buffer_Strategy { + using Base = ::Multi_Double_Buffer_Strategy; + using Base::bind; using Base::Base; using Base::cache_buffer_value; using Base::publish; diff --git a/render_2D/render_2D/renderable/Renderable.cpp b/render_2D/render_2D/renderable/Renderable.cpp index e02ce37..4f69668 100644 --- a/render_2D/render_2D/renderable/Renderable.cpp +++ b/render_2D/render_2D/renderable/Renderable.cpp @@ -8,7 +8,7 @@ namespace renderive { Renderable::Renderable(bool cache_enabled) - : Root(std::make_unique(), {.cache_enabled = cache_enabled}) {} + : render_base(std::make_unique(), {.cache_enabled = cache_enabled}) {} Renderable::~Renderable() = default; diff --git a/render_2D/render_2D/scene/Scene.h b/render_2D/render_2D/scene/Scene.h index 857080a..ddc2214 100644 --- a/render_2D/render_2D/scene/Scene.h +++ b/render_2D/render_2D/scene/Scene.h @@ -23,8 +23,8 @@ #include #include namespace renderive { -class Performance_Overlay; -class Renderable; +struct Performance_Overlay; +struct Renderable; enum class Frame_Control_Mode : std::uint8_t { Manual, Low_Latency, @@ -74,7 +74,7 @@ struct Frame_Observer_Snapshot { std::uint64_t queue_wait_ns{}; std::uint64_t end_to_end_ns{}; }; -class Presentation_Sink { +struct Presentation_Sink { virtual ~Presentation_Sink() = default; virtual void request_present(Rect dirty_rect) = 0; }; @@ -195,7 +195,7 @@ inline Rect full_rect(Size size) { } } template -class Basic_Scene2D final : public ::Scene2D_Context { +struct Basic_Scene2D final : ::Scene2D_Context { using Kernel_Scene = ::Scene2D_Context; using Scene_State_Strategy = typename Kernel_Scene::Scene_State_Strategy; static constexpr Frame_Control_Mode Mode = detail::frame_control_mode(); diff --git a/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp b/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp index cc4977f..17805bc 100644 --- a/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp +++ b/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp @@ -1,4 +1,4 @@ -#include "../src/export.h" +#include "../render_2D/export.h" #include #include #include diff --git a/render_2D/tests/render_2D_Integration_Tests.cpp b/render_2D/tests/render_2D_Integration_Tests.cpp index 3ab21cc..4c1d1d0 100644 --- a/render_2D/tests/render_2D_Integration_Tests.cpp +++ b/render_2D/tests/render_2D_Integration_Tests.cpp @@ -1,4 +1,4 @@ -#include "../src/export.h" +#include "../render_2D/export.h" #include "render_2D/plottable/Curve_Sampling.h" #include "render_2D/render/Blend2D_Cache.h" #include @@ -164,18 +164,20 @@ TEST(Renderive_Core2, KernelSceneRendersBusinessObjectsIntoBlend2DFrame) { const auto root = plot.root_renderable(); ASSERT_TRUE(root); auto [frequency_axis, power_axis, spectrum] = build_initial(plot, [&](auto& attach) { - auto frequency = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach) - .set_x(32) - .set_y(150) - .set_pixel_length(270) - .set_coord_range({88.0, 108.0}) - .build(); - auto power = Axis::Builder(root, Orientation::Vertical, &attach) - .set_x(32) - .set_y(8) - .set_pixel_length(142) - .set_coord_range({-120.0, 0.0}) - .build(); + auto frequency = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Frequency_Axis::Properties::x>(32) + .set<&Frequency_Axis::Properties::y>(150) + .set<&Frequency_Axis::Properties::pixel_length>(270) + .set<&Frequency_Axis::Properties::coordinates>(Range{88.0, 108.0}) + .build(root); + auto power = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .set<&Axis::Properties::x>(32) + .set<&Axis::Properties::y>(8) + .set<&Axis::Properties::pixel_length>(142) + .set<&Axis::Properties::coordinates>(Range{-120.0, 0.0}) + .build(root); auto value = Spectrum::Builder{&attach} .set<&Spectrum::Properties::frequency_range>(Range{88.0, 108.0}) .set<&Spectrum::Properties::frequency_point_size>(64) @@ -241,8 +243,12 @@ TEST(Renderive_Core2, StandaloneRenderableBuilderJoinsSceneOnlyInsideRuntimeEdit const auto root = plot.root_renderable(); const auto [frequency, power] = build_initial(plot, [&](auto& attach) { return std::pair{ - Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build(), - Axis::Builder(root, Orientation::Vertical, &attach).build() + Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .build(root), + Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .build(root) }; }); const auto spectrum = Spectrum::Builder{}.build(root, frequency, power); @@ -272,17 +278,18 @@ TEST(Renderive_Core2, TimeAxisUsesOneFontStateAndFormatsConfiguredLabels) { plot.init(); const auto root = plot.root_renderable(); auto axis = build_initial(plot, [&](auto& attach) { - return Time_Axis::Builder(root, Orientation::Horizontal, &attach) - .set_time_format("hh-mm-ss.zzz") - .set_font(Font{18.0, 700, true}) - .set_tick_label_spacing_px(17) - .build(); + return Time_Axis::Builder{&attach} + .set<&Time_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Time_Axis::Properties::format>("hh-mm-ss.zzz") + .set<&Time_Axis::Properties::unit_text_font>(Font{18.0, 700, true}) + .set<&Time_Axis::Properties::tick_label_spacing_px>(17) + .build(root); }); ASSERT_TRUE(axis); const int tick = axis->append_time(Time_Of_Day{3'723'045}); EXPECT_EQ(axis->tick_label(tick), "01-02-03.045"); EXPECT_EQ(axis->get<&Axis_Base_Properties::unit_text_font>(), (Font{18.0, 700, true})); - EXPECT_EQ(axis->get<&Time_Axis_Properties::tick_label_spacing_px>(), 17); + EXPECT_EQ(axis->get<&Time_Axis::Properties::tick_label_spacing_px>(), 17); } TEST(Renderive_Core2, PerformanceOverlayConsumesKernelFrameDiagnostics) { Scene2D plot; @@ -350,23 +357,24 @@ TEST(Renderive_Core2, RetainedAxisAndTimeAxisApisRoundTripWithoutWebAdapters) { const auto root = plot.root_renderable(); ASSERT_TRUE(root); const auto axis = build_initial(plot, [&](auto& attach) { - return Axis::Builder(root, Orientation::Vertical, &attach) - .set_x(31) - .set_y(17) - .set_pixel_length(240) - .set_tick_length(13) - .set_sub_tick_length(7) - .set_color({10, 20, 30, 255}) - .set_unit_text("dB") - .set_unit_text_font({15.0, 650, true}) - .set_unit_text_pen({Color{40, 50, 60, 255}, 2.0}) - .set_unit_text_background_brush({Color{3, 4, 5, 255}, Brush_Style::Solid}) - .set_label_rotation_degrees(27) - .set_coord_range({-120.0, -20.0}) - .set_label_precision(4) - .set_use_wheel(true) - .set_use_drag(true) - .build(); + return Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .set<&Axis::Properties::x>(31) + .set<&Axis::Properties::y>(17) + .set<&Axis::Properties::pixel_length>(240) + .set<&Axis::Properties::tick_length>(13) + .set<&Axis::Properties::sub_tick_length>(7) + .set<&Axis::Properties::color>(Color{10, 20, 30, 255}) + .set<&Axis::Properties::unit_text>("dB") + .set<&Axis::Properties::unit_text_font>(Font{15.0, 650, true}) + .set<&Axis::Properties::unit_text_pen>(Pen{Color{40, 50, 60, 255}, 2.0}) + .set<&Axis::Properties::unit_text_background_brush>(Brush{Color{3, 4, 5, 255}, Brush_Style::Solid}) + .set<&Axis::Properties::label_rotation_degrees>(27) + .set<&Axis::Properties::coordinates>(Range{-120.0, -20.0}) + .set<&Axis::Properties::precision>(4) + .set<&Axis::Properties::wheel>(true) + .set<&Axis::Properties::drag>(true) + .build(root); }); ASSERT_TRUE(axis); EXPECT_EQ(axis->get<&Axis_Base_Properties::x>(), 31); @@ -399,26 +407,27 @@ TEST(Renderive_Core2, RetainedAxisAndTimeAxisApisRoundTripWithoutWebAdapters) { EXPECT_GT(axis->tick_step(numeric_transform.coordinate_range), 0.0); EXPECT_GE(axis->sub_tick_count(axis->tick_step(numeric_transform.coordinate_range)), 0); const auto time_axis = build_initial(plot, [&](auto& attach) { - return Time_Axis::Builder(root, Orientation::Horizontal, &attach) - .set_x(12) - .set_y(280) - .set_pixel_length(300) - .set_visible_time_point_count(32) - .set_tick_label_spacing_px(19) - .set_time_format("hh:mm:ss.zzz") - .set_font({16.0, 700, true}) - .set_newest_at_axis_start(true) - .build(); + return Time_Axis::Builder{&attach} + .set<&Time_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Time_Axis::Properties::x>(12) + .set<&Time_Axis::Properties::y>(280) + .set<&Time_Axis::Properties::pixel_length>(300) + .set<&Time_Axis::Properties::visible_count>(32) + .set<&Time_Axis::Properties::tick_label_spacing_px>(19) + .set<&Time_Axis::Properties::format>("hh:mm:ss.zzz") + .set<&Time_Axis::Properties::unit_text_font>(Font{16.0, 700, true}) + .set<&Time_Axis::Properties::newest_at_start>(true) + .build(root); }); ASSERT_TRUE(time_axis); const int first = time_axis->append_time({3'723'004}); const int second = time_axis->append_time({3'724'005}); - EXPECT_EQ(time_axis->get<&Time_Axis_Properties::visible_count>(), 32); - EXPECT_EQ(time_axis->get<&Time_Axis_Properties::tick_label_spacing_px>(), 19); - EXPECT_EQ(time_axis->get<&Time_Axis_Properties::format>(), "hh:mm:ss.zzz"); + EXPECT_EQ(time_axis->get<&Time_Axis::Properties::visible_count>(), 32); + EXPECT_EQ(time_axis->get<&Time_Axis::Properties::tick_label_spacing_px>(), 19); + EXPECT_EQ(time_axis->get<&Time_Axis::Properties::format>(), "hh:mm:ss.zzz"); EXPECT_EQ(time_axis->get<&Axis_Base_Properties::unit_text_font>(), (Font{16.0, 700, true})); - EXPECT_TRUE(time_axis->get<&Time_Axis_Properties::newest_at_start>()); - EXPECT_THROW(time_axis->set<&Time_Axis_Properties::visible_count>(1), std::out_of_range); + EXPECT_TRUE(time_axis->get<&Time_Axis::Properties::newest_at_start>()); + EXPECT_THROW(time_axis->set<&Time_Axis::Properties::visible_count>(1), std::out_of_range); EXPECT_EQ(time_axis->time_point_count(), 2U); EXPECT_EQ(time_axis->tick_to_time(first), (Time_Of_Day{3'723'004})); EXPECT_EQ(time_axis->tick_to_time(second), (Time_Of_Day{3'724'005})); @@ -431,12 +440,20 @@ TEST(Renderive_Core2, RetainedSpectrumApisRoundTripAndMarkersRemainObservable) { plot.activate_view(); const auto root = plot.root_renderable(); auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) { - auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach) - .set_x(30).set_y(210).set_pixel_length(300) - .set_coord_range({90.0, 110.0}).build(); - auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach) - .set_x(30).set_y(20).set_pixel_length(190) - .set_coord_range({-20.0, -120.0}).build(); + auto frequency_axis = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Frequency_Axis::Properties::x>(30) + .set<&Frequency_Axis::Properties::y>(210) + .set<&Frequency_Axis::Properties::pixel_length>(300) + .set<&Frequency_Axis::Properties::coordinates>(Range{90.0, 110.0}) + .build(root); + auto power_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .set<&Axis::Properties::x>(30) + .set<&Axis::Properties::y>(20) + .set<&Axis::Properties::pixel_length>(190) + .set<&Axis::Properties::coordinates>(Range{-20.0, -120.0}) + .build(root); auto value = Spectrum::Builder{&attach} .set<&Spectrum::Properties::frequency_range>(Range{90.0, 110.0}) .set<&Spectrum::Properties::frequency_point_size>(4) @@ -512,15 +529,27 @@ TEST(Renderive_Core2, RetainedHeatmapSweepAndTraceApisPreserveDataShapes) { plot.activate_view(); const auto root = plot.root_renderable(); auto [frequency, power, time, waterfall] = build_initial(plot, [&](auto& attach) { - auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach) - .set_x(40).set_y(250).set_pixel_length(340) - .set_coord_range({0.0, 4.0}).build(); - auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach) - .set_x(40).set_y(20).set_pixel_length(230) - .set_coord_range({0.0, -120.0}).build(); - auto time_axis = Time_Axis::Builder(root, Orientation::Vertical, &attach) - .set_x(40).set_y(20).set_pixel_length(230) - .set_visible_time_point_count(8).build(); + auto frequency_axis = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Frequency_Axis::Properties::x>(40) + .set<&Frequency_Axis::Properties::y>(250) + .set<&Frequency_Axis::Properties::pixel_length>(340) + .set<&Frequency_Axis::Properties::coordinates>(Range{0.0, 4.0}) + .build(root); + auto power_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .set<&Axis::Properties::x>(40) + .set<&Axis::Properties::y>(20) + .set<&Axis::Properties::pixel_length>(230) + .set<&Axis::Properties::coordinates>(Range{0.0, -120.0}) + .build(root); + auto time_axis = Time_Axis::Builder{&attach} + .set<&Time_Axis::Properties::orientation>(Orientation::Vertical) + .set<&Time_Axis::Properties::x>(40) + .set<&Time_Axis::Properties::y>(20) + .set<&Time_Axis::Properties::pixel_length>(230) + .set<&Time_Axis::Properties::visible_count>(8) + .build(root); auto value = Waterfall::Builder{&attach} .set<&Waterfall::Properties::frequency_range>(Range{0.0, 4.0}) .set<&Waterfall::Properties::power_range>(Range{-120.0, 0.0}) @@ -614,12 +643,20 @@ TEST(Renderive_Core2, RetainedSelectionAndConstellationApisDriveInteractionAndLa plot.activate_view(); const auto root = plot.root_renderable(); auto [horizontal, vertical, selection] = build_initial(plot, [&](auto& attach) { - auto horizontal_axis = Axis::Builder(root, Orientation::Horizontal, &attach) - .set_x(30).set_y(230).set_pixel_length(300) - .set_coord_range({-3.0, 3.0}).build(); - auto vertical_axis = Axis::Builder(root, Orientation::Vertical, &attach) - .set_x(30).set_y(20).set_pixel_length(210) - .set_coord_range({3.0, -3.0}).build(); + auto horizontal_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Axis::Properties::x>(30) + .set<&Axis::Properties::y>(230) + .set<&Axis::Properties::pixel_length>(300) + .set<&Axis::Properties::coordinates>(Range{-3.0, 3.0}) + .build(root); + auto vertical_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .set<&Axis::Properties::x>(30) + .set<&Axis::Properties::y>(20) + .set<&Axis::Properties::pixel_length>(210) + .set<&Axis::Properties::coordinates>(Range{3.0, -3.0}) + .build(root); auto value = Selection_Rectangle_Overlay::Builder{&attach} .set<&Selection_Rectangle_Overlay::Properties::label_font>(Font{14.0, 600, true}) .set<&Selection_Rectangle_Overlay::Properties::label_pen>(Pen{Color{20, 220, 180, 255}, 2.0}) @@ -638,16 +675,16 @@ TEST(Renderive_Core2, RetainedSelectionAndConstellationApisDriveInteractionAndLa Pointer_Event press(Event_Type::Pointer_Press); press.position = {80.0, 70.0}; press.button = Mouse_Button::Left; - selection->handle_event(press); + selection->dispatch_event(press); EXPECT_TRUE(press.is_accepted()); Pointer_Event move(Event_Type::Pointer_Move); move.position = {260.0, 180.0}; - selection->handle_event(move); + selection->dispatch_event(move); EXPECT_TRUE(move.is_accepted()); Pointer_Event release(Event_Type::Pointer_Release); release.position = {260.0, 180.0}; release.button = Mouse_Button::Left; - selection->handle_event(release); + selection->dispatch_event(release); EXPECT_TRUE(release.is_accepted()); ASSERT_EQ(selection->selected_regions().size(), 1U); EXPECT_FALSE(selection->selected_regions().front().empty()); @@ -683,20 +720,29 @@ TEST(Renderive_Core2, PlottablePropertiesPublishOnlyAtFrameBoundary) { plot.init(); const auto root = plot.root_renderable(); const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) { - auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build(); - auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach).build(); + auto frequency_axis = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .build(root); + auto power_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .build(root); auto value = Spectrum::Builder{&attach}.build(root, frequency_axis, power_axis); return std::tuple{frequency_axis, power_axis, value}; }); ASSERT_TRUE(spectrum); - const auto state = renderive_dynamic_owner_cast<::State_Strategy_Base>(spectrum); - ASSERT_TRUE(state); - EXPECT_EQ(state->state_revision(), 0U); + EXPECT_EQ(spectrum->observation().event, + Renderable_Observer_Event::None); spectrum->set<&Spectrum::Properties::frequency_point_size>(128); EXPECT_EQ(spectrum->get<&Spectrum::Properties::frequency_point_size>(), 128); - EXPECT_EQ(state->state_revision(), 0U); + const auto cached = spectrum->observation(); + EXPECT_EQ(cached.event, Renderable_Observer_Event::Cache_Updated); + EXPECT_EQ(cached.cache_update_count, 1U); + EXPECT_EQ(cached.publish_count, 0U); ASSERT_TRUE(plot.prepare_frame()); - EXPECT_EQ(state->state_revision(), 1U); + const auto published = spectrum->observation(); + EXPECT_EQ(published.event, Renderable_Observer_Event::Published); + EXPECT_EQ(published.cache_update_count, 1U); + EXPECT_EQ(published.publish_count, 1U); ASSERT_TRUE(plot.render_prepared_frame()); } TEST(Renderive_Core2, EveryStatefulRenderableOwnsItsStateObserver) { @@ -704,8 +750,12 @@ TEST(Renderive_Core2, EveryStatefulRenderableOwnsItsStateObserver) { plot.init(); const auto root = plot.root_renderable(); const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) { - auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build(); - auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach).build(); + auto frequency_axis = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .build(root); + auto power_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .build(root); auto value = Spectrum::Builder{&attach}.build(root, frequency_axis, power_axis); return std::tuple{frequency_axis, power_axis, value}; }); @@ -751,8 +801,12 @@ TEST(Renderive_Core2, PlottableDataUpdatesReachKernelRealTimeDataStrategy) { plot.init(); const auto root = plot.root_renderable(); const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) { - auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build(); - auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach).build(); + auto frequency_axis = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .build(root); + auto power_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .build(root); auto value = Spectrum::Builder{&attach}.build(root, frequency_axis, power_axis); return std::tuple{frequency_axis, power_axis, value}; }); @@ -822,17 +876,20 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { plot.set_viewport_size({320, 180}); const auto root = plot.root_renderable(); const auto [frequency, power, time, spectrum, waterfall, afterglow] = build_initial(plot, [&](auto& attach) { - auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach) - .set_pixel_length(320) - .set_coord_range({0.0, 10.0}) - .build(); - auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach) - .set_pixel_length(180) - .set_coord_range({-120.0, 0.0}) - .build(); - auto time_axis = Time_Axis::Builder(root, Orientation::Vertical, &attach) - .set_pixel_length(180) - .build(); + auto frequency_axis = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Frequency_Axis::Properties::pixel_length>(320) + .set<&Frequency_Axis::Properties::coordinates>(Range{0.0, 10.0}) + .build(root); + auto power_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .set<&Axis::Properties::pixel_length>(180) + .set<&Axis::Properties::coordinates>(Range{-120.0, 0.0}) + .build(root); + auto time_axis = Time_Axis::Builder{&attach} + .set<&Time_Axis::Properties::orientation>(Orientation::Vertical) + .set<&Time_Axis::Properties::pixel_length>(180) + .build(root); auto spectrum_value = Spectrum::Builder{&attach} .set<&Spectrum::Properties::partition_mode>(Render_Partition_Mode::Fixed) .set<&Spectrum::Properties::partition_count>(4) @@ -937,13 +994,15 @@ TEST(Renderive_Core2, DynamicWaterfallCaptureStressPreservesPlansSlotsAndExactSe plot.set_viewport_size({640, 360}); const auto root = plot.root_renderable(); const auto [frequency, time, waterfall] = build_initial(plot, [&](auto& attach) { - auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach) - .set_pixel_length(640) - .set_coord_range({0.0, 32'767.0}) - .build(); - auto time_axis = Time_Axis::Builder(root, Orientation::Vertical, &attach) - .set_pixel_length(360) - .build(); + auto frequency_axis = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Frequency_Axis::Properties::pixel_length>(640) + .set<&Frequency_Axis::Properties::coordinates>(Range{0.0, 32'767.0}) + .build(root); + auto time_axis = Time_Axis::Builder{&attach} + .set<&Time_Axis::Properties::orientation>(Orientation::Vertical) + .set<&Time_Axis::Properties::pixel_length>(360) + .build(root); auto value = Waterfall::Builder{&attach} .set<&Waterfall::Properties::partition_mode>(Render_Partition_Mode::Fixed) .set<&Waterfall::Properties::partition_count>(64) @@ -996,8 +1055,12 @@ TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { plot.set_viewport_size({320, 180}); const auto root = plot.root_renderable(); const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) { - auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build(); - auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach).build(); + auto frequency_axis = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .build(root); + auto power_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .build(root); auto value = Spectrum::Builder{&attach}.build(root, frequency_axis, power_axis); return std::tuple{frequency_axis, power_axis, value}; }); @@ -1048,8 +1111,12 @@ TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) { plot.init(); const auto root = plot.root_renderable(); const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) { - auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build(); - auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach).build(); + auto frequency_axis = Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .build(root); + auto power_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .build(root); auto value = Spectrum::Builder{&attach}.build(root, frequency_axis, power_axis); return std::tuple{frequency_axis, power_axis, value}; }); @@ -1085,8 +1152,12 @@ TEST(Renderive_Core2, InteractionOverlayPaintsAboveItsAxes) { plot.init(); const auto root = plot.root_renderable(); const auto [horizontal, vertical, selection] = build_initial(plot, [&](auto& attach) { - auto horizontal_axis = Axis::Builder(root, Orientation::Horizontal, &attach).build(); - auto vertical_axis = Axis::Builder(root, Orientation::Vertical, &attach).build(); + auto horizontal_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Horizontal) + .build(root); + auto vertical_axis = Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .build(root); auto value = Selection_Rectangle_Overlay::Builder{&attach}.build(root, horizontal_axis, vertical_axis); return std::tuple{horizontal_axis, vertical_axis, value}; }); diff --git a/render_3D/datoviz/CMakeLists.txt b/render_3D/datoviz/CMakeLists.txt index 38e65df..355e5e8 100644 --- a/render_3D/datoviz/CMakeLists.txt +++ b/render_3D/datoviz/CMakeLists.txt @@ -313,59 +313,82 @@ set(Renderive_render_3D_datoviz_font_sources "${Renderive_render_3D_datoviz_root}/data/assets/fonts/RobotoMono-Medium.ttf") set(Renderive_render_3D_datoviz_fonts_c "${Renderive_render_3D_datoviz_generated_dir}/_fonts.c") -string(REPLACE ";" "\\;" Renderive_render_3D_datoviz_glsl_text_sources_escaped - "${Renderive_render_3D_datoviz_glsl_text_sources}") -string(REPLACE ";" "\\;" Renderive_render_3D_datoviz_wgsl_sources_escaped - "${Renderive_render_3D_datoviz_wgsl_sources}") -string(REPLACE ";" "\\;" Renderive_render_3D_datoviz_scene_spirv_outputs_escaped - "${Renderive_render_3D_datoviz_scene_spirv_outputs}") -string(REPLACE ";" "\\;" Renderive_render_3D_datoviz_font_sources_escaped - "${Renderive_render_3D_datoviz_font_sources}") +function(Renderive_render_3D_write_resource_manifest output) + file(WRITE "${output}" "set(RESOURCE_FILES)\n") + foreach(resource IN LISTS ARGN) + file(APPEND "${output}" + "list(APPEND RESOURCE_FILES [==[${resource}]==])\n") + endforeach() +endfunction() +set(Renderive_render_3D_datoviz_spirv_manifest + "${Renderive_render_3D_datoviz_generated_dir}/spirv_sources.cmake") +set(Renderive_render_3D_datoviz_glsl_manifest + "${Renderive_render_3D_datoviz_generated_dir}/glsl_sources.cmake") +set(Renderive_render_3D_datoviz_wgsl_manifest + "${Renderive_render_3D_datoviz_generated_dir}/wgsl_sources.cmake") +set(Renderive_render_3D_datoviz_font_manifest + "${Renderive_render_3D_datoviz_generated_dir}/font_sources.cmake") +Renderive_render_3D_write_resource_manifest( + "${Renderive_render_3D_datoviz_spirv_manifest}" + ${Renderive_render_3D_datoviz_scene_spirv_outputs}) +Renderive_render_3D_write_resource_manifest( + "${Renderive_render_3D_datoviz_glsl_manifest}" + ${Renderive_render_3D_datoviz_glsl_text_sources}) +Renderive_render_3D_write_resource_manifest( + "${Renderive_render_3D_datoviz_wgsl_manifest}" + ${Renderive_render_3D_datoviz_wgsl_sources}) +Renderive_render_3D_write_resource_manifest( + "${Renderive_render_3D_datoviz_font_manifest}" + ${Renderive_render_3D_datoviz_font_sources}) add_custom_command( OUTPUT "${Renderive_render_3D_datoviz_shaders_c}" COMMAND "${CMAKE_COMMAND}" - "-DFILES=${Renderive_render_3D_datoviz_scene_spirv_outputs_escaped}" + "-DMANIFEST=${Renderive_render_3D_datoviz_spirv_manifest}" -DPREFIX=shader "-DOUTPUT=${Renderive_render_3D_datoviz_shaders_c}" -P "${Renderive_render_3D_datoviz_root}/cmake/embed_resources.cmake" DEPENDS ${Renderive_render_3D_datoviz_scene_spirv_outputs} + "${Renderive_render_3D_datoviz_spirv_manifest}" "${Renderive_render_3D_datoviz_root}/cmake/embed_resources.cmake" COMMENT "Embedding Datoviz SPIR-V scene shaders" VERBATIM) add_custom_command( OUTPUT "${Renderive_render_3D_datoviz_glsl_shaders_c}" COMMAND "${CMAKE_COMMAND}" - "-DFILES=${Renderive_render_3D_datoviz_glsl_text_sources_escaped}" + "-DMANIFEST=${Renderive_render_3D_datoviz_glsl_manifest}" -DPREFIX=glsl "-DOUTPUT=${Renderive_render_3D_datoviz_glsl_shaders_c}" -P "${Renderive_render_3D_datoviz_root}/cmake/embed_text_resources.cmake" DEPENDS ${Renderive_render_3D_datoviz_glsl_text_sources} + "${Renderive_render_3D_datoviz_glsl_manifest}" "${Renderive_render_3D_datoviz_root}/cmake/embed_text_resources.cmake" COMMENT "Embedding Datoviz GLSL scene shaders" VERBATIM) add_custom_command( OUTPUT "${Renderive_render_3D_datoviz_wgsl_shaders_c}" COMMAND "${CMAKE_COMMAND}" - "-DFILES=${Renderive_render_3D_datoviz_wgsl_sources_escaped}" + "-DMANIFEST=${Renderive_render_3D_datoviz_wgsl_manifest}" -DPREFIX=wgsl "-DOUTPUT=${Renderive_render_3D_datoviz_wgsl_shaders_c}" -P "${Renderive_render_3D_datoviz_root}/cmake/embed_text_resources.cmake" DEPENDS ${Renderive_render_3D_datoviz_wgsl_sources} + "${Renderive_render_3D_datoviz_wgsl_manifest}" "${Renderive_render_3D_datoviz_root}/cmake/embed_text_resources.cmake" COMMENT "Embedding Datoviz WGSL scene shaders" VERBATIM) add_custom_command( OUTPUT "${Renderive_render_3D_datoviz_fonts_c}" COMMAND "${CMAKE_COMMAND}" - "-DFILES=${Renderive_render_3D_datoviz_font_sources_escaped}" + "-DMANIFEST=${Renderive_render_3D_datoviz_font_manifest}" -DPREFIX=font "-DOUTPUT=${Renderive_render_3D_datoviz_fonts_c}" -P "${Renderive_render_3D_datoviz_root}/cmake/embed_resources.cmake" DEPENDS ${Renderive_render_3D_datoviz_font_sources} + "${Renderive_render_3D_datoviz_font_manifest}" "${Renderive_render_3D_datoviz_root}/cmake/embed_resources.cmake" COMMENT "Embedding Datoviz scene fonts" VERBATIM) diff --git a/render_3D/datoviz/cmake/embed_resources.cmake b/render_3D/datoviz/cmake/embed_resources.cmake index 4a2801a..f0d94f6 100644 --- a/render_3D/datoviz/cmake/embed_resources.cmake +++ b/render_3D/datoviz/cmake/embed_resources.cmake @@ -58,4 +58,8 @@ function(create_resources files prefix output) file(APPEND "${output}" " return 0;\n}\n") endfunction() +if(DEFINED MANIFEST) + include("${MANIFEST}") + set(FILES "${RESOURCE_FILES}") +endif() create_resources("${FILES}" "${PREFIX}" "${OUTPUT}") diff --git a/render_3D/datoviz/cmake/embed_text_resources.cmake b/render_3D/datoviz/cmake/embed_text_resources.cmake index d61e6b8..5d6c805 100644 --- a/render_3D/datoviz/cmake/embed_text_resources.cmake +++ b/render_3D/datoviz/cmake/embed_text_resources.cmake @@ -85,4 +85,8 @@ function(create_text_resources files prefix output) file(APPEND "${output}" " return 0;\n}\n") endfunction() +if(DEFINED MANIFEST) + include("${MANIFEST}") + set(FILES "${RESOURCE_FILES}") +endif() create_text_resources("${FILES}" "${PREFIX}" "${OUTPUT}") diff --git a/render_3D/render_3D/Point_Demo.cpp b/render_3D/render_3D/Point_Demo.cpp index 4c6751c..359424b 100644 --- a/render_3D/render_3D/Point_Demo.cpp +++ b/render_3D/render_3D/Point_Demo.cpp @@ -21,7 +21,7 @@ std::vector point_demo_data(float phase_radians) { Point_Demo make_point_demo(Scene_Options options) { Point_State state; state.style = {{12, 16, 24, 255}, 2.0F, Point_Aspect::Outline}; - auto visual = std::make_shared(point_demo_data(), state); + auto visual = Point_Visual::Builder{state}.build(point_demo_data()); auto scene = std::make_unique(options, visual); return {std::move(visual), std::move(scene)}; } diff --git a/render_3D/render_3D/Point_Scene.cpp b/render_3D/render_3D/Point_Scene.cpp index 4ad06ca..3f571f8 100644 --- a/render_3D/render_3D/Point_Scene.cpp +++ b/render_3D/render_3D/Point_Scene.cpp @@ -182,7 +182,7 @@ bool Point_Scene::prepare_frame() { auto scene = impl_->states.publish_state(); lease->scene = std::move(scene.state); lease->scene_revision = scene.revision; - lease->point = detail::Point_State_Access::publish(*impl_->visual); + lease->point = detail::publish_point(*impl_->visual); lease->input = impl_->input.drain(); return true; } diff --git a/render_3D/render_3D/Point_Visual.cpp b/render_3D/render_3D/Point_Visual.cpp index 05847fc..4fa9c1a 100644 --- a/render_3D/render_3D/Point_Visual.cpp +++ b/render_3D/render_3D/Point_Visual.cpp @@ -1,62 +1,77 @@ #include "Point_Visual.h" #include "detail/Point_Core.h" +#include "renderable/Renderable_p.h" #include -#include #include #include #include #include -namespace renderive::render_3d { +namespace renderive::render_3d::detail { namespace { -void validate(const Point_State& state) { - if (!std::isfinite(state.style.stroke_width_px) || - state.style.stroke_width_px < 0.0F) - throw std::invalid_argument("point stroke width must be finite and nonnegative"); - for (float value : state.transform.values) { - if (!std::isfinite(value)) - throw std::invalid_argument("point transform must contain finite values"); - } -} - -void validate(const std::vector& points) { +void validate_points(const std::vector& points) { for (const auto& point : points) { - if (!std::isfinite(point.position.x) || !std::isfinite(point.position.y) || - !std::isfinite(point.position.z) || !std::isfinite(point.diameter_px) || - point.diameter_px <= 0.0F) - throw std::invalid_argument("point payload contains invalid coordinates or diameter"); + if (!std::isfinite(point.position.x) || + !std::isfinite(point.position.y) || + !std::isfinite(point.position.z) || + !std::isfinite(point.diameter_px) || point.diameter_px <= 0.0F) + throw std::invalid_argument( + "point payload contains invalid coordinates or diameter"); } } -} // namespace - -namespace detail { struct Point_Payload_Tag {}; using Point_Payload = std::shared_ptr>; using Point_Buffer_Layout = ::Double_Buffer_Layout< ::Buffered_Data>; using Point_Buffer_Strategy = ::Multi_Double_Buffer_Strategy; -using Point_State_Strategy = - ::Double_State_Storage; -} // namespace detail -struct Point_Visual::Impl final : detail::Point_Buffer_Strategy, - detail::Point_State_Strategy { - Impl(std::vector points, const Point_State& initial) - : detail::Point_State_Strategy(initial) { - validate(initial); +struct Point_Data_Observation { + std::uint64_t revision{}; + std::size_t point_count{}; +}; + +} // namespace + +void Point_Visual::State_Validator::operator()(const State& state) const { + if (!std::isfinite(state.style.stroke_width_px) || + state.style.stroke_width_px < 0.0F) + throw std::invalid_argument( + "point stroke width must be finite and nonnegative"); + for (float value : state.transform.values) { + if (!std::isfinite(value)) + throw std::invalid_argument( + "point transform must contain finite values"); + } +} + +struct Point_Visual::Impl : next_Impl, Point_Buffer_Strategy { + struct Observer : next_Observer { + static void handle(Impl& impl, + const Renderable_Event_View& observation) noexcept { + if (const auto* data = + observation.payload_if()) { + impl.observe_data(data->revision, data->point_count); + } + } + }; + + explicit Impl(std::vector points) { update_points(std::move(points)); } void update_points(std::vector points) { - validate(points); - write( + validate_points(points); + const std::size_t count = points.size(); + write( std::make_shared>(std::move(points))); + report_observation(Point_Data_Observation{ + cache_revision(), count}); } void edit_points(const std::function&)>& edit) { @@ -68,61 +83,56 @@ struct Point_Visual::Impl final : detail::Point_Buffer_Strategy, update_points(std::move(next)); } - [[nodiscard]] const detail::Point_Payload& points() const noexcept { - return render_buffer_value(); + [[nodiscard]] const Point_Payload& points() const noexcept { + return render_buffer_value(); } [[nodiscard]] std::size_t point_count() const noexcept { - return points() ? points()->size() : 0U; + const auto& cached = cache_buffer_value(); + return cached ? cached->size() : 0U; } - void configure(Point_State state) { - validate(state); - update([state = std::move(state)](Point_State& target) mutable { - target = std::move(state); - }); - } - - [[nodiscard]] detail::Published_Point publish_frame() { - detail::Point_State_Strategy::publish(); - detail::Point_Buffer_Strategy::publish(); - return { - detail::Point_State_Strategy::render_state_value(), - detail::Point_Buffer_Strategy::render_buffer_value< - detail::Point_Payload_Tag>(), - detail::Point_State_Strategy::state_revision(), - detail::Point_Buffer_Strategy::revision()}; - } }; -Point_Visual::Point_Visual(std::vector points, Point_State initial) - : impl_(std::make_unique(std::move(points), initial)) {} +Point_Visual::Point_Visual(const State& state, std::vector points) + : Renderable(With_Attached_Impl{}, state, std::move(points)) { + State_Validator{}(state); +} Point_Visual::~Point_Visual() = default; void Point_Visual::update_points(std::vector points) { - impl_->update_points(std::move(points)); + d_func().update_points(std::move(points)); + d_func().changed(); } void Point_Visual::edit_points( const std::function&)>& edit) { - impl_->edit_points(edit); + d_func().edit_points(edit); + d_func().changed(); } std::size_t Point_Visual::point_count() const { - return impl_->point_count(); + return d_func().point_count(); } std::uint64_t Point_Visual::data_revision() const { - return impl_->revision(); + return d_func().cache_revision(); } -void Point_Visual::configure(Point_State state) { - impl_->configure(std::move(state)); +Published_Point Point_Visual::publish_frame() { + publish_state(); + d_func().Point_Buffer_Strategy::publish(); + return { + render_state(), + d_func().points(), + state_revision(), + d_func().Point_Buffer_Strategy::revision(), + }; } -detail::Published_Point detail::Point_State_Access::publish(Point_Visual& visual) { - return visual.impl_->publish_frame(); +Published_Point publish_point(Point_Visual& visual) { + return visual.publish_frame(); } -} // namespace renderive::render_3d +} // namespace renderive::render_3d::detail diff --git a/render_3D/render_3D/Point_Visual.h b/render_3D/render_3D/Point_Visual.h index 97a8a25..d28af7f 100644 --- a/render_3D/render_3D/Point_Visual.h +++ b/render_3D/render_3D/Point_Visual.h @@ -1,18 +1,24 @@ #pragma once + +#include "renderable/Inheritance.h" + #include #include #include #include #include +#include #include -#include + namespace renderive::render_3d { + struct Vec3 { float x{}; float y{}; float z{}; bool operator==(const Vec3&) const = default; }; + struct Rgba8 { std::uint8_t red{255}; std::uint8_t green{255}; @@ -20,58 +26,93 @@ struct Rgba8 { std::uint8_t alpha{255}; bool operator==(const Rgba8&) const = default; }; + struct Matrix4 { std::array values{ 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F, - 0.0F, 0.0F, 0.0F, 1.0F - }; + 0.0F, 0.0F, 0.0F, 1.0F}; bool operator==(const Matrix4&) const = default; }; + struct Point { Vec3 position; Rgba8 color; float diameter_px{8.0F}; bool operator==(const Point&) const = default; }; + enum class Point_Aspect : std::uint8_t { Filled, Stroke, Outline, }; + struct Point_Style { Rgba8 edge_color{0, 0, 0, 255}; float stroke_width_px{}; Point_Aspect aspect{Point_Aspect::Filled}; bool operator==(const Point_Style&) const = default; }; + namespace detail { struct Published_Point; -class Point_State_Access; -} -// Thread-safe logical point visual. Datoviz resources deliberately do not live here. -struct Point_Visual final : Non_Copyable { - struct State { + +struct Point_Visual : Renderable { + struct State : next_State { Point_Style style; Matrix4 transform; bool visible{true}; bool depth_test{true}; - bool operator==(const State&) const = default; + bool operator==(const State& other) const { + return style == other.style && transform == other.transform && + visible == other.visible && depth_test == other.depth_test; + } }; - explicit Point_Visual(std::vector points = {}, State initial = {}); - ~Point_Visual(); + struct State_Validator { + void operator()(const State& state) const; + }; + + template + using Business_Builder = + ::renderive::render_3d::Renderable_Builder< + Product, Properties, State_Validator>; + + explicit Point_Visual(const State& state, + std::vector points = {}); + ~Point_Visual() override; + void update_points(std::vector points); void edit_points(const std::function&)>& edit); [[nodiscard]] std::size_t point_count() const; [[nodiscard]] std::uint64_t data_revision() const; - void configure(State state); + +protected: + template Value> + void set_state(Value&& value) { + auto next = Renderable::template state(); + next.*Member = std::forward(value); + State_Validator{}(next); + Renderable::template update_state( + [next = std::move(next)](State_Type& target) mutable { + target = std::move(next); + }); + } + private: struct Impl; - std::unique_ptr impl_; - friend class detail::Point_State_Access; + [[nodiscard]] Published_Point publish_frame(); + + friend Published_Point publish_point(Point_Visual& visual); }; -using Point_State = Point_Visual::State; +[[nodiscard]] Published_Point publish_point(Point_Visual& visual); +} // namespace detail + +using Point_Visual = ::renderive::renderable::attach; +using Point_State = Point_Visual::Properties; + } // namespace renderive::render_3d diff --git a/render_3D/render_3D/detail/Point_Core.h b/render_3D/render_3D/detail/Point_Core.h index cda859b..0f461b5 100644 --- a/render_3D/render_3D/detail/Point_Core.h +++ b/render_3D/render_3D/detail/Point_Core.h @@ -24,11 +24,6 @@ struct Published_Point { std::uint64_t data_revision{}; }; -class Point_State_Access final { -public: - [[nodiscard]] static Published_Point publish(Point_Visual& visual); -}; - struct Scene_State { Extent viewport{560, 320}; Clear_Color clear_color; diff --git a/render_3D/render_3D/renderable/Inheritance.h b/render_3D/render_3D/renderable/Inheritance.h new file mode 100644 index 0000000..31012f7 --- /dev/null +++ b/render_3D/render_3D/renderable/Inheritance.h @@ -0,0 +1,115 @@ +#pragma once + +#include "Renderable.h" +#include "Renderable_Builder.h" +#include "Renderable_p.h" + +#include +#include +#include + +#include +#include +#include + +namespace renderive::render_3d::detail { + +template +struct Renderable + : ::renderive::renderable::render_base { + using Render_Base = + ::renderive::renderable::render_base; + +protected: + struct State : Render_Base::template next_State {}; + + template + using next_State = + ::renderive::renderable_inheritance::State_Node; + + template + using next_Impl = ::renderive::renderable_inheritance::Impl_Node< + Next, typename Parent::Impl>; + + template + using Business_Builder = + ::renderive::render_3d::Renderable_Builder; + + using State_Observer = Observer_State; + + template + Renderable(With_Attached_Impl, State_Type state, + Args&&... args) + : Render_Base( + std::make_unique>( + std::move(state), + With_Observer{ + State_Observer(Renderable_State_Observer(this))}, + std::forward(args)...)) {} + + using Render_Base::Render_Base; + + template Value> + void set_state(Value&& value) { + state_storage().template set( + std::forward(value)); + this->d_func().changed(); + } + + template + auto get_state() const { + return state_storage().template get(); + } + + template + requires std::invocable + void update_state(Update&& update) { + state_storage().update(std::forward(update)); + this->d_func().changed(); + } + + template + State_Type state() const { + return state_storage().read( + [](const State_Type& value) { return value; }); + } + + template + void publish_state() { + state_storage().publish(); + } + + template + State_Type render_state() const { + return state_storage().published_state(); + } + + template + std::uint64_t state_revision() const noexcept { + return state_storage().state_revision(); + } + +private: + template + using State_Storage = Double_State_Storage< + State_Type, Atomic_Spin_Mutex, State_Observer>; + + template + State_Storage& state_storage() noexcept { + return dynamic_cast&>( + this->template d_func< + ::renderive::render_3d::Renderable::Impl>()); + } + + template + const State_Storage& state_storage() const noexcept { + return dynamic_cast&>( + this->template d_func< + ::renderive::render_3d::Renderable::Impl>()); + } +}; + +} // namespace renderive::render_3d::detail diff --git a/render_3D/render_3D/renderable/Renderable.cpp b/render_3D/render_3D/renderable/Renderable.cpp new file mode 100644 index 0000000..885b1bb --- /dev/null +++ b/render_3D/render_3D/renderable/Renderable.cpp @@ -0,0 +1,33 @@ +#include "Renderable.h" +#include "Renderable_p.h" + +namespace renderive::render_3d { + +Renderable::Renderable() : render_base(std::make_unique()) {} + +Renderable::~Renderable() = default; + +Renderable_Observation Renderable::observation() const noexcept { + const auto& impl = d_func(); + std::lock_guard lock(impl.observation_mutex); + return impl.observation; +} + +void Renderable::Impl::observe( + const detail::Renderable_Event_View& value) noexcept { + std::lock_guard lock(observation_mutex); + observation.event = value.event; + observation.event_time_ns = value.time_ns; + observation.cache_update_count = value.cache_update_count; + observation.publish_count = value.publish_count; +} + +void Renderable::Impl::observe_data(std::uint64_t revision, + std::size_t item_count) noexcept { + std::lock_guard lock(observation_mutex); + observation.event = Renderable_Observer_Event::Data_Updated; + observation.data_revision = revision; + observation.item_count = item_count; +} + +} // namespace renderive::render_3d diff --git a/render_3D/render_3D/renderable/Renderable.h b/render_3D/render_3D/renderable/Renderable.h new file mode 100644 index 0000000..ebef646 --- /dev/null +++ b/render_3D/render_3D/renderable/Renderable.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace renderive::render_3d { + +struct Renderable; + +enum struct Renderable_Observer_Event { + None, + Cache_Updated, + Published, + Data_Updated, +}; + +struct Renderable_Observation { + Renderable_Observer_Event event{Renderable_Observer_Event::None}; + std::uint64_t event_time_ns{}; + std::uint64_t cache_update_count{}; + std::uint64_t publish_count{}; + std::uint64_t data_revision{}; + std::size_t item_count{}; +}; + +namespace detail { +struct Renderable_State_Observer; + +struct Renderable_Event_View { + Renderable_Observer_Event event{Renderable_Observer_Event::None}; + std::uint64_t time_ns{}; + std::uint64_t cache_update_count{}; + std::uint64_t publish_count{}; + const std::type_info* payload_type{&typeid(void)}; + const void* payload{}; + + template + [[nodiscard]] const Payload* payload_if() const noexcept { + return *payload_type == typeid(Payload) + ? static_cast(payload) + : nullptr; + } +}; +} // namespace detail + +struct Renderable + : ::renderive::renderable::render_base { + Renderable(); + ~Renderable() override; + + [[nodiscard]] Renderable_Observation observation() const noexcept; + +protected: + struct Impl; + + template + explicit Renderable(With_Attached_Impl) + : render_base(std::make_unique()) {} + + template + explicit Renderable(std::unique_ptr implementation) + : render_base(std::move(implementation)) {} + +private: + template + void report_state_observation(const Observation& observation); + + friend struct detail::Renderable_State_Observer; +}; + +namespace detail { +struct Renderable_State_Observer { + static constexpr bool enabled = true; + + explicit Renderable_State_Observer(Renderable* renderable) noexcept + : renderable_(renderable) {} + + template + void observe(const Observation& observation) noexcept { + renderable_->report_state_observation(observation); + } + +private: + Renderable* renderable_; +}; +} // namespace detail + +} // namespace renderive::render_3d diff --git a/render_3D/render_3D/renderable/Renderable_Builder.h b/render_3D/render_3D/renderable/Renderable_Builder.h new file mode 100644 index 0000000..79fff87 --- /dev/null +++ b/render_3D/render_3D/renderable/Renderable_Builder.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace renderive::render_3d { + +template Validator_Type = + No_Property_Validator> +struct Renderable_Builder { + using Product = Product_Type; + using Properties = Properties_Type; + using Validator = Validator_Type; + using Self = Renderable_Builder; + + Renderable_Builder() + requires std::default_initializable && + std::default_initializable + = default; + + explicit Renderable_Builder(Properties properties) + requires std::default_initializable + : properties_(std::move(properties)) {} + + Renderable_Builder(Properties properties, Validator validator) + : properties_(std::move(properties)), validator_(std::move(validator)) {} + + template Value> + Self& set(Value&& value) { + properties_.*Member = std::forward(value); + return *this; + } + + template Configure> + Self& configure(Configure&& configure) { + std::invoke(std::forward(configure), properties_); + return *this; + } + + template + requires std::constructible_from + std::shared_ptr build(Args&&... args) const { + validator_(properties_); + auto result = std::make_shared(properties_, + std::forward(args)...); + return ::renderive::inheritance::initialize( + std::move(result), [](auto&) {}); + } + +private: + Properties properties_{}; + [[no_unique_address]] Validator validator_{}; +}; + +} // namespace renderive::render_3d diff --git a/render_3D/render_3D/renderable/Renderable_p.h b/render_3D/render_3D/renderable/Renderable_p.h new file mode 100644 index 0000000..ea0d7fa --- /dev/null +++ b/render_3D/render_3D/renderable/Renderable_p.h @@ -0,0 +1,78 @@ +#pragma once + +#include "Renderable.h" + +#include + +#include + +namespace renderive::render_3d::detail { + +template +Renderable_Event_View renderable_event_view( + const Observation& observation) noexcept { + return { + observation.event == decltype(observation.event)::cache_updated + ? Renderable_Observer_Event::Cache_Updated + : Renderable_Observer_Event::Published, + observation.time_ns, + observation.cache_update_count, + observation.publish_count, + &typeid(observation.state), + &observation.state, + }; +} + +template +Renderable_Event_View renderable_payload_view(const Payload& payload) noexcept { + return { + Renderable_Observer_Event::Data_Updated, + 0, + 0, + 0, + &typeid(payload), + &payload, + }; +} + +} // namespace renderive::render_3d::detail + +namespace renderive::render_3d { + +struct Renderable::Impl + : ::renderive::renderable_inheritance::Impl_Root< + Renderable::Impl, ::Renderable_Base::Impl, + detail::Renderable_Event_View> { + struct Observer + : ::renderive::renderable_inheritance::Observer_Root { + static void handle( + Impl& impl, const detail::Renderable_Event_View& value) noexcept { + impl.observe(value); + } + }; + + virtual ~Impl() = default; + +protected: + template + void report_observation(const Payload& payload) { + dispatch(detail::renderable_payload_view(payload)); + } + void observe_data(std::uint64_t revision, + std::size_t item_count) noexcept; + +private: + void observe(const detail::Renderable_Event_View& value) noexcept; + + mutable std::mutex observation_mutex; + Renderable_Observation observation; + + friend struct Renderable; +}; + +template +void Renderable::report_state_observation(const Observation& observation) { + d_func().dispatch(detail::renderable_event_view(observation)); +} + +} // namespace renderive::render_3d diff --git a/render_3D/tests/Point_State_Tests.cpp b/render_3D/tests/Point_State_Tests.cpp index 1ea3e26..c87d3b2 100644 --- a/render_3D/tests/Point_State_Tests.cpp +++ b/render_3D/tests/Point_State_Tests.cpp @@ -12,15 +12,30 @@ namespace renderive::render_3d { namespace { +static_assert(std::derived_from); +static_assert(std::derived_from); + TEST(PointState, UsesKernelDoubleStateAtFrameBoundary) { - Point_Visual visual({{{1.0F, 2.0F, 3.0F}, {1, 2, 3, 255}, 7.0F}}); + auto visual = Point_Visual::Builder{}.build( + std::vector{{{1.0F, 2.0F, 3.0F}, {1, 2, 3, 255}, 7.0F}}); + ASSERT_TRUE(visual); + const auto initial_observation = visual->observation(); + EXPECT_EQ(initial_observation.event, + Renderable_Observer_Event::Data_Updated); + EXPECT_EQ(initial_observation.data_revision, 1U); + EXPECT_EQ(initial_observation.item_count, 1U); Point_State configured; configured.visible = false; configured.style.stroke_width_px = 3.0F; - visual.configure(configured); + visual->set<&Point_State::visible>(configured.visible); + visual->set<&Point_State::style>(configured.style); + EXPECT_EQ(visual->observation().event, + Renderable_Observer_Event::Cache_Updated); - const auto published = detail::Point_State_Access::publish(visual); + const auto published = detail::publish_point(*visual); + EXPECT_EQ(visual->observation().event, + Renderable_Observer_Event::Published); EXPECT_EQ(published.state, configured); ASSERT_EQ(published.data->size(), 1U); EXPECT_EQ(published.data->front().position, (Vec3{1.0F, 2.0F, 3.0F})); @@ -29,7 +44,8 @@ TEST(PointState, UsesKernelDoubleStateAtFrameBoundary) { } TEST(PointState, PublishesImmutableBulkPayloadsThroughDoubleBuffer) { - Point_Visual visual; + auto visual = Point_Visual::Builder{}.build(); + ASSERT_TRUE(visual); constexpr int update_count = 500; constexpr std::uint64_t initial_data_revision = 1; std::atomic done{}; @@ -41,13 +57,13 @@ TEST(PointState, PublishesImmutableBulkPayloadsThroughDoubleBuffer) { point.position.x = static_cast(value); point.diameter_px = static_cast(value % 12 + 1); } - visual.update_points(std::move(points)); + visual->update_points(std::move(points)); } done.store(true, std::memory_order_release); }); do { - const auto published = detail::Point_State_Access::publish(visual); + const auto published = detail::publish_point(*visual); if (!published.data->empty()) { const float expected = published.data->front().position.x; for (const auto& point : *published.data) @@ -56,7 +72,7 @@ TEST(PointState, PublishesImmutableBulkPayloadsThroughDoubleBuffer) { } while (!done.load(std::memory_order_acquire)); writer.join(); - const auto published = detail::Point_State_Access::publish(visual); + const auto published = detail::publish_point(*visual); EXPECT_GT(published.data_revision, 0U); EXPECT_LE(published.data_revision, initial_data_revision + update_count); @@ -65,17 +81,21 @@ TEST(PointState, PublishesImmutableBulkPayloadsThroughDoubleBuffer) { } TEST(PointState, RejectsInvalidStateAndPayloadAtTheOwningBoundary) { - Point_Visual visual; + auto visual = Point_Visual::Builder{}.build(); + ASSERT_TRUE(visual); Point_State invalid; invalid.style.stroke_width_px = -1.0F; - EXPECT_THROW(visual.configure(invalid), std::invalid_argument); + EXPECT_THROW(visual->set<&Point_State::style>(invalid.style), + std::invalid_argument); - EXPECT_THROW(visual.update_points(std::vector{{{}, {}, 0.0F}}), + EXPECT_THROW(visual->update_points(std::vector{{{}, {}, 0.0F}}), std::invalid_argument); } TEST(PointFrameControl, UsesDedicatedThreeDimensionalFrameStrategy) { - Point_Visual visual(std::vector{{{}, {}, 8.0F}}); + auto visual = Point_Visual::Builder{}.build( + std::vector{{{}, {}, 8.0F}}); + ASSERT_TRUE(visual); detail::Scene_State_Buffer scene({{320, 180}, {}}); detail::Input_Collector input; const auto prepare = [&](detail::Point_Frame_Strategy& strategy) { @@ -85,7 +105,7 @@ TEST(PointFrameControl, UsesDedicatedThreeDimensionalFrameStrategy) { auto published_scene = scene.publish_state(); lease->scene = std::move(published_scene.state); lease->scene_revision = published_scene.revision; - lease->point = detail::Point_State_Access::publish(visual); + lease->point = detail::publish_point(*visual); lease->input = input.drain(); return true; }; @@ -109,7 +129,7 @@ TEST(PointFrameControl, UsesDedicatedThreeDimensionalFrameStrategy) { detail::Point_Frame_Strategy playback(Frame_Mode::Playback, 60.0); EXPECT_TRUE(prepare(playback)); - visual.update_points(std::vector(2, Point{{}, {}, 8.0F})); + visual->update_points(std::vector(2, Point{{}, {}, 8.0F})); EXPECT_TRUE(prepare(playback)); auto playback_status = playback.status(); EXPECT_EQ(playback_status.produced_frame_count, 2U); diff --git a/web_server/app/Gallery_Plot_Session.cpp b/web_server/app/Gallery_Plot_Session.cpp index cd8e1bd..f0b3430 100644 --- a/web_server/app/Gallery_Plot_Session.cpp +++ b/web_server/app/Gallery_Plot_Session.cpp @@ -955,36 +955,40 @@ private: else if (case_id_ == "constellation") coordinate_range = {-1.25, 1.25}; if (case_id_ == "frequency_trace") { - time_axis_ = Gallery_Time_Axis::Builder(axes_node_, Orientation::Horizontal, &attach) - .set_visible_time_point_count(80) - .set_tick_label_spacing_px(28) - .set_time_format("mm:ss.zzz") - .set_color(axis_color) - .build(); + time_axis_ = Gallery_Time_Axis::Builder{&attach} + .set<&Gallery_Time_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Gallery_Time_Axis::Properties::visible_count>(80) + .set<&Gallery_Time_Axis::Properties::tick_label_spacing_px>(28) + .set<&Gallery_Time_Axis::Properties::format>("mm:ss.zzz") + .set<&Gallery_Time_Axis::Properties::color>(axis_color) + .build(axes_node_); } else if (case_id_ == "sweep_spectrum" || case_id_ == "constellation") { - numeric_domain_axis_ = Gallery_Axis::Builder(axes_node_, Orientation::Horizontal, &attach) - .set_coord_range(coordinate_range) - .set_use_wheel(true) - .set_use_drag(true) - .set_color(axis_color) - .build(); + numeric_domain_axis_ = Gallery_Axis::Builder{&attach} + .set<&Gallery_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Gallery_Axis::Properties::coordinates>(coordinate_range) + .set<&Gallery_Axis::Properties::wheel>(true) + .set<&Gallery_Axis::Properties::drag>(true) + .set<&Gallery_Axis::Properties::color>(axis_color) + .build(axes_node_); } else { - frequency_domain_axis_ = Gallery_Frequency_Axis::Builder(axes_node_, Orientation::Horizontal, &attach) - .set_coord_range(coordinate_range) - .set_use_wheel(true) - .set_use_drag(true) - .set_color(axis_color) - .build(); + frequency_domain_axis_ = Gallery_Frequency_Axis::Builder{&attach} + .set<&Gallery_Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Gallery_Frequency_Axis::Properties::coordinates>(coordinate_range) + .set<&Gallery_Frequency_Axis::Properties::wheel>(true) + .set<&Gallery_Frequency_Axis::Properties::drag>(true) + .set<&Gallery_Frequency_Axis::Properties::color>(axis_color) + .build(axes_node_); } if (case_id_ == "waterfall") { - time_axis_ = Gallery_Time_Axis::Builder(axes_node_, Orientation::Vertical, &attach) - .set_visible_time_point_count(80) - .set_tick_label_spacing_px(28) - .set_time_format("mm:ss.zzz") - .set_color(axis_color) - .build(); + time_axis_ = Gallery_Time_Axis::Builder{&attach} + .set<&Gallery_Time_Axis::Properties::orientation>(Orientation::Vertical) + .set<&Gallery_Time_Axis::Properties::visible_count>(80) + .set<&Gallery_Time_Axis::Properties::tick_label_spacing_px>(28) + .set<&Gallery_Time_Axis::Properties::format>("mm:ss.zzz") + .set<&Gallery_Time_Axis::Properties::color>(axis_color) + .build(axes_node_); } else { Range value_range{-120.0, -20.0}; @@ -992,19 +996,21 @@ private: value_range = {-1.0, 1.0}; else if (case_id_ == "constellation") value_range = {-1.25, 1.25}; - value_axis_ = Gallery_Axis::Builder(axes_node_, Orientation::Vertical, &attach) - .set_coord_range(value_range) - .set_label_precision(case_id_ == "constellation" ? 2 : 0) - .set_color(axis_color) - .build(); + value_axis_ = Gallery_Axis::Builder{&attach} + .set<&Gallery_Axis::Properties::orientation>(Orientation::Vertical) + .set<&Gallery_Axis::Properties::coordinates>(value_range) + .set<&Gallery_Axis::Properties::precision>(case_id_ == "constellation" ? 2 : 0) + .set<&Gallery_Axis::Properties::color>(axis_color) + .build(axes_node_); } if (case_id_ == "axis_lab") { - time_axis_ = Gallery_Time_Axis::Builder(axes_node_, Orientation::Horizontal, &attach) - .set_visible_time_point_count(80) - .set_tick_label_spacing_px(28) - .set_time_format("mm:ss.zzz") - .set_color({69, 221, 190, 255}) - .build(); + time_axis_ = Gallery_Time_Axis::Builder{&attach} + .set<&Gallery_Time_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Gallery_Time_Axis::Properties::visible_count>(80) + .set<&Gallery_Time_Axis::Properties::tick_label_spacing_px>(28) + .set<&Gallery_Time_Axis::Properties::format>("mm:ss.zzz") + .set<&Gallery_Time_Axis::Properties::color>(Color{69, 221, 190, 255}) + .build(axes_node_); } const auto style = [this](const renderive_Owner& axis) { if (!axis) diff --git a/web_server/app/Gallery_Renderable_Types.h b/web_server/app/Gallery_Renderable_Types.h index c60db71..8c4c4a0 100644 --- a/web_server/app/Gallery_Renderable_Types.h +++ b/web_server/app/Gallery_Renderable_Types.h @@ -38,8 +38,7 @@ struct Gallery_Property_Accessor { }; template -class Gallery_Plottable : public Base { -public: +struct Gallery_Plottable : Base { using Properties = typename Base::Properties; using Base::Base; @@ -101,8 +100,7 @@ public: }; template -class Gallery_Axis_Base : public Base { -public: +struct Gallery_Axis_Base : Base { using Properties = Properties_Type; using Base::Base; @@ -124,23 +122,21 @@ private: class Gallery_Axis final : public Gallery_Axis_Base { public: using Gallery_Axis_Base::Gallery_Axis_Base; - using Builder = detail::Axis_Renderable_Builder; + using Builder = Renderable_Builder; }; class Gallery_Frequency_Axis final : public Gallery_Axis_Base { public: using Gallery_Axis_Base::Gallery_Axis_Base; - using Builder = - detail::Axis_Renderable_Builder; + using Builder = Renderable_Builder; }; class Gallery_Time_Axis final - : public Gallery_Axis_Base { + : public Gallery_Axis_Base { public: using Gallery_Axis_Base::Gallery_Axis_Base; - using Builder = - detail::Axis_Renderable_Builder; + using Builder = Renderable_Builder; }; } // namespace renderive::web diff --git a/web_server/app/Gallery_Renderables.h b/web_server/app/Gallery_Renderables.h index b50b08a..9625423 100644 --- a/web_server/app/Gallery_Renderables.h +++ b/web_server/app/Gallery_Renderables.h @@ -329,7 +329,7 @@ struct Type_Descriptor { static auto get() { using T = renderive::web::Gallery_Time_Axis; using B = renderive::Axis_Base_Properties; - using P = renderive::Time_Axis_Properties; + using P = renderive::Time_Axis::Properties; using namespace renderive::web::gallery_adminive; return adminive::object("time_axis", "时间轴", readonly_property("x", "X 位置"), diff --git a/web_server/app/Web_Plot_Session.cpp b/web_server/app/Web_Plot_Session.cpp index 0e4d50e..0830e0f 100644 --- a/web_server/app/Web_Plot_Session.cpp +++ b/web_server/app/Web_Plot_Session.cpp @@ -84,43 +84,47 @@ struct Web_Plot_Session::Impl { const auto overlay = make_group("Web_Overlay"); axes->set_cache_mode(Renderable_Cache_Mode::Local_Pixel); spectrum_frequency_axis = - Frequency_Axis::Builder(axes, Orientation::Horizontal, &attach) - .set_coord_range(initial_frequency) - .set_label_precision(2) - .set_tick_length(8) - .set_sub_tick_length(4) - .set_color(axis_color) - .set_use_wheel(true) - .set_use_drag(true) - .build(); + Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Frequency_Axis::Properties::coordinates>(initial_frequency) + .set<&Frequency_Axis::Properties::precision>(2) + .set<&Frequency_Axis::Properties::tick_length>(8) + .set<&Frequency_Axis::Properties::sub_tick_length>(4) + .set<&Frequency_Axis::Properties::color>(axis_color) + .set<&Frequency_Axis::Properties::wheel>(true) + .set<&Frequency_Axis::Properties::drag>(true) + .build(axes); spectrum_power_axis = - Axis::Builder(axes, Orientation::Vertical, &attach) - .set_coord_range({-20.0, -120.0}) - .set_label_precision(0) - .set_tick_length(-8) - .set_sub_tick_length(-4) - .set_color(axis_color) - .set_unit_text("dBm") - .build(); + Axis::Builder{&attach} + .set<&Axis::Properties::orientation>(Orientation::Vertical) + .set<&Axis::Properties::coordinates>(Range{-20.0, -120.0}) + .set<&Axis::Properties::precision>(0) + .set<&Axis::Properties::tick_length>(-8) + .set<&Axis::Properties::sub_tick_length>(-4) + .set<&Axis::Properties::color>(axis_color) + .set<&Axis::Properties::unit_text>("dBm") + .build(axes); waterfall_frequency_axis = - Frequency_Axis::Builder(axes, Orientation::Horizontal, &attach) - .set_coord_range(initial_frequency) - .set_label_precision(2) - .set_tick_length(8) - .set_sub_tick_length(4) - .set_color(axis_color) - .set_use_wheel(true) - .set_use_drag(true) - .build(); + Frequency_Axis::Builder{&attach} + .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) + .set<&Frequency_Axis::Properties::coordinates>(initial_frequency) + .set<&Frequency_Axis::Properties::precision>(2) + .set<&Frequency_Axis::Properties::tick_length>(8) + .set<&Frequency_Axis::Properties::sub_tick_length>(4) + .set<&Frequency_Axis::Properties::color>(axis_color) + .set<&Frequency_Axis::Properties::wheel>(true) + .set<&Frequency_Axis::Properties::drag>(true) + .build(axes); waterfall_time_axis = - Time_Axis::Builder(axes, Orientation::Vertical, &attach) - .set_visible_time_point_count(72) - .set_tick_label_spacing_px(34) - .set_time_format("mm:ss") - .set_tick_length(-8) - .set_sub_tick_length(-4) - .set_color(axis_color) - .build(); + Time_Axis::Builder{&attach} + .set<&Time_Axis::Properties::orientation>(Orientation::Vertical) + .set<&Time_Axis::Properties::visible_count>(72) + .set<&Time_Axis::Properties::tick_label_spacing_px>(34) + .set<&Time_Axis::Properties::format>("mm:ss") + .set<&Time_Axis::Properties::tick_length>(-8) + .set<&Time_Axis::Properties::sub_tick_length>(-4) + .set<&Time_Axis::Properties::color>(axis_color) + .build(axes); spectrum = Spectrum::Builder{&attach} .set<&Spectrum::Properties::frequency_range>(initial_frequency) .set<&Spectrum::Properties::frequency_point_size>(768)