From bb64801679ae376374eea59c0c35ac9d5fae73a3 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Fri, 14 Aug 2026 17:53:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AD=E9=97=B4=E7=8A=B6=E6=80=81=E6=9A=82?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Kernel/src/renderive/base/Attached_Impl.hpp | 4 + .../base/property/Attach_Builder.hpp | 1 + Kernel/src/renderive/inheritance/Dispatch.hpp | 60 ++ .../src/renderive/inheritance/Inheritance.hpp | 5 + .../src/renderive/inheritance/Initialize.hpp | 51 ++ Kernel/src/renderive/inheritance/Registry.hpp | 170 ++++++ .../real_time_data/Double_Buffer_Strategy.hpp | 57 +- .../src/renderive/renderable/Inheritance.hpp | 176 ++++++ .../renderable/base/Renderable_Base.cpp | 14 + .../renderable/base/Renderable_Base_p.hpp | 6 +- .../src/renderive/scene/Scene2D_Context.hpp | 14 +- .../src/renderive/scene/Scene3D_Context.hpp | 19 +- .../src/renderive/scene/base/Scene_Base.cpp | 5 +- .../renderive/state/Attach_State_Builder.hpp | 5 - Kernel/src/renderive/state/Concepts.hpp | 4 +- ..._Strategy.hpp => Double_State_Storage.hpp} | 86 ++- Kernel/src/renderive/state/State_Strategy.hpp | 6 +- Kernel/src/renderive/state/Stateful_Impl.hpp | 36 ++ ..._Strategy.hpp => Triple_State_Storage.hpp} | 95 ++-- .../inheritance/Inheritance_Test.cpp | 210 +++++++ .../real_time_data/Real_Time_Data_Test.cpp | 7 +- .../Renderable_Inheritance_Test.cpp | 174 ++++++ .../tests/renderive/state/Concepts_Test.cpp | 4 +- .../state/Double_State_Strategy_Test.cpp | 53 +- .../renderive/state/State_Test_Types.hpp | 26 +- .../state/Triple_State_Strategy_Test.cpp | 15 +- .../threading/Threading_Contract_Test.cpp | 11 +- logs/renderive-web-performance.jsonl | 21 + render_2D/render_2D/axis/Abs_Axis.cpp | 3 +- render_2D/render_2D/axis/Abs_Axis.h | 16 +- render_2D/render_2D/axis/Axis_Builder.h | 80 --- .../render_2D/axis/Axis_State_Strategy.hpp | 535 ++++++++++++------ render_2D/render_2D/axis/Frequency_Axis.cpp | 36 +- render_2D/render_2D/axis/Frequency_Axis.h | 21 +- render_2D/render_2D/axis/Numeric_Axis.cpp | 78 ++- render_2D/render_2D/axis/Numeric_Axis.h | 43 +- render_2D/render_2D/axis/Numeric_Axis_p.h | 23 + render_2D/render_2D/axis/Time_Axis.cpp | 95 ++-- render_2D/render_2D/axis/Time_Axis.h | 46 +- render_2D/render_2D/base/Types.h | 117 ++-- render_2D/render_2D/plottable/Afterglow.cpp | 206 +++---- render_2D/render_2D/plottable/Afterglow.h | 47 +- .../plottable/Constellation_Diagram.cpp | 57 +- .../plottable/Constellation_Diagram.h | 37 +- .../render_2D/plottable/Frequency_Trace.cpp | 59 +- .../render_2D/plottable/Frequency_Trace.h | 21 +- .../render_2D/plottable/Performance_Overlay.h | 15 - render_2D/render_2D/plottable/Plottable.h | 149 +++-- .../plottable/Plottable_Real_Time_Data.h | 10 +- .../plottable/Selection_Rectangle_Overlay.cpp | 119 ++-- .../plottable/Selection_Rectangle_Overlay.h | 32 +- render_2D/render_2D/plottable/Spectrum.cpp | 376 ++++++------ render_2D/render_2D/plottable/Spectrum.h | 101 ++-- .../render_2D/plottable/Sweep_Spectrum.cpp | 85 +-- .../render_2D/plottable/Sweep_Spectrum.h | 34 +- render_2D/render_2D/plottable/Waterfall.cpp | 240 ++++---- render_2D/render_2D/plottable/Waterfall.h | 47 +- render_2D/render_2D/render/Blend2D_Cache.h | 25 +- .../render_2D/renderable/Render_Partition.h | 45 +- render_2D/render_2D/renderable/Renderable.cpp | 143 +++-- render_2D/render_2D/renderable/Renderable.h | 115 ++-- .../render_2D/renderable/Renderable_Builder.h | 21 +- render_2D/render_2D/renderable/Renderable_p.h | 112 +++- render_2D/render_2D/scene/Scene.cpp | 6 +- render_2D/render_2D/scene/Scene.h | 24 +- render_3D/render_3D/Point_Visual.cpp | 120 ++-- render_3D/render_3D/Point_Visual.h | 55 +- render_3D/render_3D/detail/Point_Core.h | 8 +- render_3D/tests/Point_State_Tests.cpp | 4 +- .../web_server/app/Gallery_Plot_Session.cpp | 14 +- .../web_server/app/Gallery_Renderable_Types.h | 2 +- .../web_server/app/Gallery_Renderables.h | 14 +- web_server/app/Gallery_Plot_Session.cpp | 14 +- web_server/app/Gallery_Renderable_Types.h | 8 +- web_server/app/Gallery_Renderables.h | 14 +- 75 files changed, 2984 insertions(+), 1823 deletions(-) create mode 100644 Kernel/src/renderive/base/Attached_Impl.hpp create mode 100644 Kernel/src/renderive/inheritance/Dispatch.hpp create mode 100644 Kernel/src/renderive/inheritance/Inheritance.hpp create mode 100644 Kernel/src/renderive/inheritance/Initialize.hpp create mode 100644 Kernel/src/renderive/inheritance/Registry.hpp create mode 100644 Kernel/src/renderive/renderable/Inheritance.hpp delete mode 100644 Kernel/src/renderive/state/Attach_State_Builder.hpp rename Kernel/src/renderive/state/{Double_State_Strategy.hpp => Double_State_Storage.hpp} (60%) create mode 100644 Kernel/src/renderive/state/Stateful_Impl.hpp rename Kernel/src/renderive/state/{Triple_State_Strategy.hpp => Triple_State_Storage.hpp} (54%) create mode 100644 Kernel/tests/renderive/inheritance/Inheritance_Test.cpp create mode 100644 Kernel/tests/renderive/renderable/Renderable_Inheritance_Test.cpp delete mode 100644 render_2D/render_2D/axis/Axis_Builder.h create mode 100644 render_2D/render_2D/axis/Numeric_Axis_p.h diff --git a/Kernel/src/renderive/base/Attached_Impl.hpp b/Kernel/src/renderive/base/Attached_Impl.hpp new file mode 100644 index 0000000..7a2686d --- /dev/null +++ b/Kernel/src/renderive/base/Attached_Impl.hpp @@ -0,0 +1,4 @@ +#pragma once + +template +struct With_Attached_Impl {}; diff --git a/Kernel/src/renderive/base/property/Attach_Builder.hpp b/Kernel/src/renderive/base/property/Attach_Builder.hpp index 9c96ceb..047b080 100644 --- a/Kernel/src/renderive/base/property/Attach_Builder.hpp +++ b/Kernel/src/renderive/base/property/Attach_Builder.hpp @@ -1,5 +1,6 @@ #pragma once #include "Property_Builder.hpp" + template class Builder_Type = Property_Builder, class... Builder_Args> struct Attach_Builder : That { using Self = Attach_Builder; diff --git a/Kernel/src/renderive/inheritance/Dispatch.hpp b/Kernel/src/renderive/inheritance/Dispatch.hpp new file mode 100644 index 0000000..454fc42 --- /dev/null +++ b/Kernel/src/renderive/inheritance/Dispatch.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "Registry.hpp" + +#include + +namespace renderive::inheritance { + +template +struct Dispatcher { + virtual ~Dispatcher() = default; + virtual void dispatch(const Event& event) = 0; +}; + +template +concept Handles = requires(Layer& layer, const Event& event) { + typename Layer::Handler; + typename Layer::Handler::Owner; + requires std::same_as; + Layer::Handler::handle(layer, event); +}; + +template +struct Dispatch_Root + : Type_Node, Dispatcher { + using Registered_Node = + Type_Node; + using Registered_Node::Registered_Node; + + template + void dispatch_for(const Event& event) { + if constexpr (std::same_as && + Handles) + Layer::Handler::handle(static_cast(*this), event); + } + + void dispatch(const Event& event) override { dispatch_for(event); } +}; + +template +struct Dispatch_Node + : Type_Node { + using Registered_Node = + Type_Node; + using Registered_Node::Registered_Node; + + template + void dispatch_for(const Event& event) { + Parent::template dispatch_for(event); + if constexpr (std::same_as && + Handles) + Layer::Handler::handle(static_cast(*this), event); + } + + void dispatch(const Event& event) override { dispatch_for(event); } +}; + +} // namespace renderive::inheritance diff --git a/Kernel/src/renderive/inheritance/Inheritance.hpp b/Kernel/src/renderive/inheritance/Inheritance.hpp new file mode 100644 index 0000000..45af5b5 --- /dev/null +++ b/Kernel/src/renderive/inheritance/Inheritance.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include "Dispatch.hpp" +#include "Initialize.hpp" +#include "Registry.hpp" diff --git a/Kernel/src/renderive/inheritance/Initialize.hpp b/Kernel/src/renderive/inheritance/Initialize.hpp new file mode 100644 index 0000000..2ae07c2 --- /dev/null +++ b/Kernel/src/renderive/inheritance/Initialize.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "Registry.hpp" + +#include +#include +#include +#include + +namespace renderive::inheritance { + +template +requires (std::invocable&> && ...) +[[nodiscard]] constexpr std::remove_cvref_t +initialize(Target&& target, Initializers&&... initializers) { + std::remove_cvref_t result(std::forward(target)); + (std::invoke(std::forward(initializers), result), ...); + return result; +} + +namespace detail { + +template +constexpr void initialize_registry(Target& target, Initializer& initializer) { + if constexpr (Position < + Next_Chain_Position_V) { + using Layer = Type_At_Position_T; + if constexpr (!std::is_void_v) { + std::invoke(initializer, std::type_identity{}, target); + } + initialize_registry(target, initializer); + } +} + +} // namespace detail + +template +requires requires { + typename std::remove_cvref_t::Inheritance_Registry; +} +constexpr void initialize(Registered& registered, Initializer&& initializer) { + using Registry_Type = + typename std::remove_cvref_t::Inheritance_Registry; + auto&& value = initializer; + detail::initialize_registry(registered, value); +} + +} // namespace renderive::inheritance diff --git a/Kernel/src/renderive/inheritance/Registry.hpp b/Kernel/src/renderive/inheritance/Registry.hpp new file mode 100644 index 0000000..37baad0 --- /dev/null +++ b/Kernel/src/renderive/inheritance/Registry.hpp @@ -0,0 +1,170 @@ +#pragma once + +#include +#include +#include + +namespace renderive::inheritance { + +inline constexpr std::size_t Automatic_Position = + std::numeric_limits::max(); + +template +struct Registration { + using Chain_Type = Chain; + using Registered_Type = Type; + static constexpr std::size_t position = Position; +}; + +template +struct Registry { + static constexpr std::size_t size = sizeof...(Registrations); +}; + +template +struct Chain_Registration_Count; + +template +struct Chain_Registration_Count, Chain> + : std::integral_constant< + std::size_t, + (std::size_t{} + ... + + static_cast(std::is_same_v< + typename Registrations::Chain_Type, Chain>))> {}; + +template +inline constexpr std::size_t Chain_Registration_Count_V = + Chain_Registration_Count::value; + +template +struct Next_Chain_Position; + +template +struct Next_Chain_Position, Chain> { +private: + static consteval std::size_t calculate() { + std::size_t result{}; + ((result = std::is_same_v + ? result > Registrations::position + ? result + : Registrations::position + 1 + : result), + ...); + return result; + } + +public: + static constexpr std::size_t value = calculate(); +}; + +template +inline constexpr std::size_t Next_Chain_Position_V = + Next_Chain_Position::value; + +template +struct Type_At_Position; + +template +struct Type_At_Position, Chain, Position> { + using Type = void; +}; + +template +struct Type_At_Position, Chain, Position> { + using Type = std::conditional_t< + std::is_same_v && + First::position == Position, + typename First::Registered_Type, + typename Type_At_Position, Chain, Position>::Type>; +}; + +template +using Type_At_Position_T = + typename Type_At_Position::Type; + +template +struct Append_Registration; + +template +struct Append_Registration, Registration_Type> { + static_assert(std::is_void_v, + typename Registration_Type::Chain_Type, + Registration_Type::position>>, + "inheritance chain position is already registered"); + using Type = Registry; +}; + +template +struct Merge_Registries; + +template <> +struct Merge_Registries<> { + using Type = Registry<>; +}; + +template +struct Merge_Registries, Rest...> { +private: + using Tail = typename Merge_Registries::Type; + + template + struct Append_All; + + template + struct Append_All { + using Type = Result; + }; + + template + struct Append_All { + using Type = typename Append_All< + typename Append_Registration::Type, + Values...>::Type; + }; + +public: + using Type = typename Append_All::Type; +}; + +template +using Merge_Registries_T = typename Merge_Registries::Type; + +template +struct Chain_View { + static constexpr std::size_t count = + Chain_Registration_Count_V; + static constexpr std::size_t next_position = + Next_Chain_Position_V; + + template + using Type = Type_At_Position_T; +}; + +struct Type_Root { + using Inheritance_Registry = Registry<>; +}; + +template +struct Type_Node : Parent { +private: + static constexpr std::size_t next_position = + Next_Chain_Position_V; + +public: + static constexpr std::size_t Inheritance_Position = + Requested_Position == Automatic_Position ? next_position + : Requested_Position; + static_assert(Inheritance_Position >= next_position, + "child inheritance position must follow its parent chain"); + + using Inheritance_Chain_Tag = Chain; + using Inheritance_Registry = + typename Append_Registration< + typename Parent::Inheritance_Registry, + Registration>::Type; + using Parent::Parent; +}; + +} // namespace renderive::inheritance 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 91bda9d..32bbcd0 100644 --- a/Kernel/src/renderive/real_time_data/Double_Buffer_Strategy.hpp +++ b/Kernel/src/renderive/real_time_data/Double_Buffer_Strategy.hpp @@ -5,13 +5,13 @@ #include #include #include -#include #include #include #include #include "renderive/base/Atomic_Mutex.hpp" #include "renderive/base/Concepts.hpp" #include "renderive/base/observer/Observer.hpp" +// 多域双缓冲,只锁交换 template struct Buffered_Data { using Tag = Tag_Type; @@ -46,8 +46,8 @@ struct Entry_Index { static_assert(match_count == 1); static constexpr std::size_t value = []() consteval { constexpr bool matches[] = {std::same_as...}; - for(std::size_t i = 0; i < sizeof...(Entries); ++i) { - if(matches[i]) { + for (std::size_t i = 0; i < sizeof...(Entries); ++i) { + if (matches[i]) { return i; } } @@ -60,10 +60,10 @@ struct No_Publish_Effect { void operator()() const noexcept {} }; } -template > +template > struct Multi_Double_Buffer_Strategy; -template -struct Multi_Double_Buffer_Strategy, Mutex, Observer> : That { +template +struct Multi_Double_Buffer_Strategy, Mutex, Observer> { using Self = Multi_Double_Buffer_Strategy; enum class Observation_Event { cache_updated, @@ -80,12 +80,9 @@ struct Multi_Double_Buffer_Strategy, Mute static_assert(double_buffer_detail::Unique_Tags::value); static_assert((std::default_initializable && ...)); static_assert(Timed_Struct_Observer); - Multi_Double_Buffer_Strategy() requires std::default_initializable : That() {} - explicit Multi_Double_Buffer_Strategy(With_Observer option) requires std::default_initializable : That(), observer(std::move(option.observer)) {} - template - explicit Multi_Double_Buffer_Strategy(std::in_place_t, Args&&... args) : That(std::forward(args)...) {} - template - Multi_Double_Buffer_Strategy(std::in_place_t, With_Observer option, Args&&... args) : That(std::forward(args)...), observer(std::move(option.observer)) {} + Multi_Double_Buffer_Strategy() = default; + explicit Multi_Double_Buffer_Strategy(With_Observer option) + : observer(std::move(option.observer)) {} template using Data = typename double_buffer_detail::Entry_Of::Data; template @@ -94,20 +91,15 @@ struct Multi_Double_Buffer_Strategy, Mute } template Self& write(Data value) { - std::optional observation; - { - std::lock_guard lock(mtx); - auto& current = slot(); - current.buffers[current.cache_index] = std::move(value); - current.dirty = true; - ++current.cache_update_count; - observation.emplace(Observation_Event::cache_updated, buffer_index(), observer.now_ns(), current.cache_update_count, current.publish_count); - } - observer.observe(*observation); + auto& current = slot(); + current.buffers[current.cache_index] = std::move(value); + current.dirty = true; + ++current.cache_update_count; + observer.observe(Observation{Observation_Event::cache_updated, buffer_index(), observer.now_ns(), current.cache_update_count, current.publish_count}); return *this; } template - requires std::invocable + requires std::invocable bool publish(Side_Effect&& side_effect = {}) { std::array observations{}; std::size_t observation_count{}; @@ -115,28 +107,31 @@ struct Multi_Double_Buffer_Strategy, Mute { std::lock_guard lock(mtx); (publish_entry(observations, observation_count, published), ...); - if(published) { + if (published) { ++publish_revision; } } - for(std::size_t i = 0; i < observation_count; ++i) { + for (std::size_t i = 0; i < observation_count; ++i) { observer.observe(observations[i]); } - if(!published) { + if (!published) { return false; } std::invoke(std::forward(side_effect)); return true; } - std::uint64_t revision() const { - std::lock_guard lock(mtx); + std::uint64_t revision() const noexcept { return publish_revision; } template - std::uint64_t buffer_revision() const { - std::lock_guard lock(mtx); + std::uint64_t buffer_revision() const noexcept { return slot().publish_count; } + template + const Data& cache_buffer_value() const noexcept { + const auto& current = slot(); + return current.buffers[current.cache_index]; + } protected: template const Data& render_buffer_value() const noexcept { @@ -156,7 +151,7 @@ private: void publish_entry(std::array& observations, std::size_t& observation_count, bool& published) { using Tag = typename Entry::Tag; auto& current = slot(); - if(!current.dirty) { + if (!current.dirty) { return; } std::swap(current.render_index, current.cache_index); diff --git a/Kernel/src/renderive/renderable/Inheritance.hpp b/Kernel/src/renderive/renderable/Inheritance.hpp new file mode 100644 index 0000000..94da11d --- /dev/null +++ b/Kernel/src/renderive/renderable/Inheritance.hpp @@ -0,0 +1,176 @@ +#pragma once + +#include "renderive/inheritance/Inheritance.hpp" + +#include +#include + +namespace renderive::renderable_inheritance { + +struct Builder_Chain {}; +struct State_Chain {}; +struct Impl_Chain {}; + +template +struct Builder_Root + : inheritance::Type_Node {}; + +template +struct Builder_Node + : inheritance::Type_Node { + using Builder_Base = + inheritance::Type_Node; + using Builder_Base::Builder_Base; +}; + +template +struct Builder + : Builder_Node, Business_Builder { + using Business_Builder::Business_Builder; +}; + +template +struct State_Root + : inheritance::Type_Node {}; + +template +struct State_Node : inheritance::Type_Node { + using State_Base = inheritance::Type_Node; + using State_Base::State_Base; +}; + +template +struct Observer_Root { + using Owner = Layer; +}; + +template +struct Observer_Node : Parent { + using Owner = Layer; +}; + +template +struct Observer_Handler { + using Owner = Layer; + + static void handle(Layer& layer, const Event& event) + requires requires { + typename Layer::Observer; + typename Layer::Observer::Owner; + requires std::same_as; + Layer::Observer::handle(layer, event); + } + { + Layer::Observer::handle(layer, event); + } +}; + +template +struct Impl_Root + : inheritance::Dispatch_Root { + using Impl_Base = + inheritance::Dispatch_Root; + using Renderable_Event = Event; + using Handler = Observer_Handler; + using Impl_Base::Impl_Base; +}; + +template +struct Impl_Node + : inheritance::Dispatch_Node { + using Impl_Base = + inheritance::Dispatch_Node; + using Renderable_Event = typename Parent::Renderable_Event; + using Handler = Observer_Handler; + using next_Observer = Observer_Node; + using Impl_Base::Impl_Base; +}; + +} // namespace renderive::renderable_inheritance + +namespace renderive::renderable { + +template +struct render_base; + +template +struct render_base : Parent { +protected: + struct Builder_Layer + : renderable_inheritance::Builder_Root {}; + struct State : renderable_inheritance::State_Root {}; + + using Parent::Parent; +}; + +template +struct render_base : Parent { +protected: + struct Builder_Layer + : renderable_inheritance::Builder_Node< + Builder_Layer, typename Parent::Builder_Layer> {}; + + template + using next_State = renderable_inheritance::State_Node< + Next, typename Parent::State>; + + template + using next_Impl = renderable_inheritance::Impl_Node< + Next, typename Parent::Impl>; + + using Parent::Parent; +}; + +template +struct attach : Control { + using State = typename Control::State; + using Properties = State; + using Self = attach; + +protected: + struct Builder_Layer + : renderable_inheritance::Builder_Node< + Builder_Layer, typename Control::Builder_Layer> {}; + +public: + struct Builder + : renderable_inheritance::Builder< + Builder, Builder_Layer, + typename Control::template Business_Builder> { + using Builder_Base = renderable_inheritance::Builder< + Builder, Builder_Layer, + typename Control::template Business_Builder>; + using Builder_Base::Builder_Base; + }; + + template + Self& set(Value&& value) { + if constexpr (requires(Self& self, Value&& input) { + self.template set_state( + std::forward(input)); + }) { + this->template set_state( + std::forward(value)); + } else { + Control::template set(std::forward(value)); + } + return *this; + } + + template + auto get() const { + if constexpr (requires(const Self& self) { + self.template get_state(); + }) { + return this->template get_state(); + } else { + return Control::template get(); + } + } + + using Control::Control; +}; + +} // namespace renderive::renderable diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp index 1d308de..e1c47cf 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp @@ -258,3 +258,17 @@ void Renderable_Base::discard_stale_frame_on_latest_data_update( bool enabled) noexcept { d_func().discard_stale_frame_on_latest_data_update(enabled); } + +void Renderable_Base::Impl::changed() noexcept { + invalidate_prepare(); + notify_scene_model_dirty(); +} + +void Renderable_Base::Impl::paint_changed() noexcept { + invalidate_paint(); + notify_scene_model_dirty(); +} + +void Renderable_Base::Impl::render_graph_changed() { + rebuild_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 c01f432..4c77010 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp @@ -1,6 +1,7 @@ #pragma once #include "Renderable_Base.hpp" +#include "renderive/inheritance/Inheritance.hpp" #include #include @@ -14,7 +15,7 @@ #include "renderive/scene/base/Scene_Lifetime.hpp" #include "renderive/state/Render_State_View.hpp" -class Renderable_Base::Impl { +class Renderable_Base::Impl : public renderive::inheritance::Type_Root { public: struct Real_Time_Data_State { std::shared_ptr scene_lifetime; @@ -33,6 +34,9 @@ public: std::pmr::memory_resource& resource) noexcept; void invalidate_prepare() noexcept; void invalidate_paint() noexcept; + void changed() noexcept; + void paint_changed() noexcept; + void render_graph_changed(); void set_configuration(Renderable_Configuration value); void set_visible(bool value); void discard_stale_frame_on_latest_data_update(bool enabled) noexcept; diff --git a/Kernel/src/renderive/scene/Scene2D_Context.hpp b/Kernel/src/renderive/scene/Scene2D_Context.hpp index c20d1d8..c94629b 100644 --- a/Kernel/src/renderive/scene/Scene2D_Context.hpp +++ b/Kernel/src/renderive/scene/Scene2D_Context.hpp @@ -8,7 +8,7 @@ #include "renderive/base/observer/Observer.hpp" #include "renderive/frame_control/Frame_Control.hpp" #include "renderive/renderable/color/Concepts.hpp" -#include "renderive/state/Double_State_Strategy.hpp" +#include "renderive/state/Double_State_Storage.hpp" #include "base/Scene_Base.hpp" #include "base/Frame_Viewport.hpp" struct Scene2D_Frame_Data : Abstract_Frame {}; @@ -23,9 +23,13 @@ template concept Scene2D_Frame_Control_Constructible = std::constructible_from || std::constructible_from; template , Color_Cache_Type Cache = Recording_Color_Cache, class State = Scene2D_State, class State_Observer = Observer_State<>, class Scene_Observer = Observer_State<>> requires Frame_Control_Strategy_For && Scene2D_State_Value -class Scene2D_Context : public Double_State_Strategy, public Scene_Compositor { +class Scene2D_Context : public Scene_2D_Base, + public Double_State_Storage, + public Scene_Compositor { public: - using Scene_State_Strategy = Double_State_Strategy; + using Scene_State_Strategy = + Double_State_Storage; using Frame_Control = Strategy; using Frame = typename Frame_Control::Frame; using Painter_Lease = typename Frame_Control::Painter_Lease; @@ -36,7 +40,7 @@ public: template requires Scene2D_Frame_Control_Constructible explicit Scene2D_Context(std::pmr::memory_resource& memory_resource, Args&&... args) - : Scene_State_Strategy(State{}, memory_resource), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())) {} + : Scene_2D_Base(memory_resource), Scene_State_Strategy(State{}), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())) {} template requires Scene2D_Frame_Control_Constructible Scene2D_Context(With_Observer state_observer, With_Observer scene_observer, Args&&... args) @@ -44,7 +48,7 @@ public: template requires Scene2D_Frame_Control_Constructible Scene2D_Context(std::pmr::memory_resource& memory_resource, With_Observer state_observer, With_Observer scene_observer, Args&&... args) - : Scene_State_Strategy(State{}, std::move(state_observer), memory_resource), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())), scene_observer_(std::move(scene_observer.observer)) {} + : Scene_2D_Base(memory_resource), Scene_State_Strategy(State{}, std::move(state_observer)), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())), scene_observer_(std::move(scene_observer.observer)) {} ~Scene2D_Context() override { this->shutdown(); } diff --git a/Kernel/src/renderive/scene/Scene3D_Context.hpp b/Kernel/src/renderive/scene/Scene3D_Context.hpp index fe25493..9173dd2 100644 --- a/Kernel/src/renderive/scene/Scene3D_Context.hpp +++ b/Kernel/src/renderive/scene/Scene3D_Context.hpp @@ -4,7 +4,7 @@ #include #include "renderive/base/observer/Observer.hpp" #include "renderive/frame_control/Frame_Control.hpp" -#include "renderive/state/Triple_State_Strategy.hpp" +#include "renderive/state/Triple_State_Storage.hpp" #include "base/Scene_Base.hpp" struct Scene3D_Frame_Data : Abstract_Frame {}; struct Scene3D_State { @@ -14,9 +14,12 @@ template concept Scene3D_Frame_Control_Constructible = std::constructible_from || std::constructible_from; template , class State = Scene3D_State, class State_Observer = Observer_State<>, class Scene_Observer = Observer_State<>> requires Frame_Control_Strategy_For && State_Value -class Scene3D_Context final : public Triple_State_Strategy { +class Scene3D_Context final : public Scene_3D_Base, + public Triple_State_Storage< + State, Atomic_Spin_Mutex, State_Observer> { public: - using Scene_State_Strategy = Triple_State_Strategy; + using Scene_State_Strategy = + Triple_State_Storage; using Frame_Control = Strategy; using Frame = typename Frame_Control::Frame; using Painter_Lease = typename Frame_Control::Painter_Lease; @@ -27,7 +30,9 @@ public: template requires Scene3D_Frame_Control_Constructible explicit Scene3D_Context(std::pmr::memory_resource& memory_resource, Args&&... args) - : Scene_State_Strategy(State{}, memory_resource), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)) {} + : Scene_3D_Base(memory_resource), Scene_State_Strategy(State{}), + frame_control(make_frame_control(this->memory_resource(), + std::forward(args)...)) {} template requires Scene3D_Frame_Control_Constructible Scene3D_Context(With_Observer state_observer, With_Observer scene_observer, Args&&... args) @@ -35,7 +40,11 @@ public: template requires Scene3D_Frame_Control_Constructible Scene3D_Context(std::pmr::memory_resource& memory_resource, With_Observer state_observer, With_Observer scene_observer, Args&&... args) - : Scene_State_Strategy(State{}, std::move(state_observer), memory_resource), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), scene_observer_(std::move(scene_observer.observer)) {} + : Scene_3D_Base(memory_resource), + Scene_State_Strategy(State{}, std::move(state_observer)), + frame_control(make_frame_control(this->memory_resource(), + std::forward(args)...)), + scene_observer_(std::move(scene_observer.observer)) {} ~Scene3D_Context() override { this->shutdown(); } diff --git a/Kernel/src/renderive/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index 76de4dc..6308c9e 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -539,7 +539,8 @@ void Scene_Base::publish_frame_state() { } } for (const Renderable& renderable : renderables) { - if (auto* state = dynamic_cast(renderable.get())) + if (auto* state = + dynamic_cast(renderable->d_ptr.get())) state->publish(); renderable->d_func().publish_real_time_data(); } @@ -1174,7 +1175,7 @@ void Scene_Base::execute_taskflow(Render_Task& task) { if (!state.paint_required) continue; if (auto* completion = dynamic_cast( - state.owner_.get())) { + state.owner_->d_ptr.get())) { completion->render_frame_completed( snapshot.next_refresh_interval_ns_); } diff --git a/Kernel/src/renderive/state/Attach_State_Builder.hpp b/Kernel/src/renderive/state/Attach_State_Builder.hpp deleted file mode 100644 index d7278e1..0000000 --- a/Kernel/src/renderive/state/Attach_State_Builder.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include "../base/property/Attach_Builder.hpp" -#include "Optional_State_Validator.hpp" -template class Builder_Type = Property_Builder, class... Builder_Args> -using Attach_State_Builder = Attach_Builder, Builder_Args...>; diff --git a/Kernel/src/renderive/state/Concepts.hpp b/Kernel/src/renderive/state/Concepts.hpp index 05d33cd..1e11767 100644 --- a/Kernel/src/renderive/state/Concepts.hpp +++ b/Kernel/src/renderive/state/Concepts.hpp @@ -9,13 +9,13 @@ concept Has_State_Validator = requires(const State& state) { { Product::validate_state(state) } -> std::same_as; }; template -concept Double_State_Strategy_Type = std::derived_from && requires(That& strategy, const That& const_strategy) { +concept Double_State_Storage_Type = std::derived_from && requires(That& strategy, const That& const_strategy) { typename That::State; typename That::Observation; { strategy.publish() } -> std::same_as; { const_strategy.state_revision() } -> std::same_as; }; template -concept Triple_State_Strategy_Type = Double_State_Strategy_Type && requires(That& strategy) { +concept Triple_State_Storage_Type = Double_State_Storage_Type && requires(That& strategy) { { strategy.acquire_render_state() } -> std::same_as; }; diff --git a/Kernel/src/renderive/state/Double_State_Strategy.hpp b/Kernel/src/renderive/state/Double_State_Storage.hpp similarity index 60% rename from Kernel/src/renderive/state/Double_State_Strategy.hpp rename to Kernel/src/renderive/state/Double_State_Storage.hpp index b6acb20..edc683e 100644 --- a/Kernel/src/renderive/state/Double_State_Strategy.hpp +++ b/Kernel/src/renderive/state/Double_State_Storage.hpp @@ -1,25 +1,31 @@ #pragma once + +#include "Concepts.hpp" +#include "Render_State_View.hpp" +#include "base/State_Strategy_Base.hpp" +#include "renderive/base/Atomic_Mutex.hpp" +#include "renderive/base/Concepts.hpp" +#include "renderive/base/observer/Observer.hpp" +#include "renderive/base/property/Concepts.hpp" + #include #include #include #include #include #include -#include "renderive/base/Atomic_Mutex.hpp" -#include "renderive/base/Concepts.hpp" -#include "renderive/base/observer/Observer.hpp" -#include "renderive/base/property/Concepts.hpp" -#include "Concepts.hpp" -#include "Render_State_View.hpp" -#include "base/State_Strategy_Base.hpp" -template > -struct Double_State_Strategy : That, State_Strategy_Base { - using Self = Double_State_Strategy; + +template > +struct Double_State_Storage : State_Strategy_Base { + using Self = Double_State_Storage; using State = State_Type; + enum class Observation_Event { cache_updated, published }; + struct Observation { Observation_Event event{}; std::uint64_t time_ns{}; @@ -27,16 +33,28 @@ struct Double_State_Strategy : That, State_Strategy_Base { std::uint64_t publish_count{}; State state; }; + static_assert(Timed_Struct_Observer); - Double_State_Strategy() requires std::default_initializable && std::default_initializable : That(), states{}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {} - explicit Double_State_Strategy(With_Observer option) requires std::default_initializable && std::default_initializable - : That(), observer(std::move(option.observer)), states{}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {} - template - explicit Double_State_Strategy(const State& state, Args&&... args) - : That(std::forward(args)...), states{state, state, state}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {} - template - Double_State_Strategy(const State& state, With_Observer option, Args&&... args) - : That(std::forward(args)...), observer(std::move(option.observer)), states{state, state, state}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {} + + Double_State_Storage() requires std::default_initializable + : states{}, render_state(&states[0]), cache_state(&states[1]), + scratch_state(&states[2]) {} + + explicit Double_State_Storage(With_Observer option) + requires std::default_initializable + : observer(std::move(option.observer)), states{}, + render_state(&states[0]), cache_state(&states[1]), + scratch_state(&states[2]) {} + + explicit Double_State_Storage(const State& state) + : states{state, state, state}, render_state(&states[0]), + cache_state(&states[1]), scratch_state(&states[2]) {} + + Double_State_Storage(const State& state, With_Observer option) + : observer(std::move(option.observer)), states{state, state, state}, + render_state(&states[0]), cache_state(&states[1]), + scratch_state(&states[2]) {} + template Value> Self& set(Value&& value) { std::optional observation; @@ -44,27 +62,32 @@ struct Double_State_Strategy : That, State_Strategy_Base { std::lock_guard lock(mtx); cache_state->*Member = std::forward(value); ++cache_update_count; - observation.emplace(Observation_Event::cache_updated, observer.now_ns(), cache_update_count, publish_count, *cache_state); + observation.emplace(Observation_Event::cache_updated, + observer.now_ns(), cache_update_count, + publish_count, *cache_state); } observer.observe(*observation); return *this; } + template auto get() const { std::lock_guard lock(mtx); const auto& value = cache_state->*Member; - if constexpr (Validated_Property_Value>) { + if constexpr (Validated_Property_Value< + std::remove_cvref_t>) return value.get(); - } else { + else return value; - } } + template requires std::invocable auto read(Read&& value) const { std::lock_guard lock(mtx); return std::invoke(std::forward(value), *cache_state); } + template requires std::invocable Self& update(Update&& value) { @@ -73,32 +96,41 @@ struct Double_State_Strategy : That, State_Strategy_Base { std::lock_guard lock(mtx); std::invoke(std::forward(value), *cache_state); ++cache_update_count; - observation.emplace(Observation_Event::cache_updated, observer.now_ns(), cache_update_count, publish_count, *cache_state); + observation.emplace(Observation_Event::cache_updated, + observer.now_ns(), cache_update_count, + publish_count, *cache_state); } observer.observe(*observation); return *this; } + void publish() override { std::optional observation; { std::lock_guard lock(mtx); *scratch_state = *cache_state; - observation.emplace(Observation_Event::published, observer.now_ns(), cache_update_count, publish_count + 1, *cache_state); + observation.emplace(Observation_Event::published, + observer.now_ns(), cache_update_count, + publish_count + 1, *cache_state); std::swap(render_state, scratch_state); ++publish_count; } observer.observe(*observation); } - std::uint64_t state_revision() const override { + + [[nodiscard]] std::uint64_t state_revision() const override { std::lock_guard lock(mtx); return publish_count; } + protected: - const State& render_state_value() const noexcept { + [[nodiscard]] const State& render_state_value() const noexcept { return *render_state; } + private: friend class Render_State_View; + Observer observer; State states[3]; State* render_state; diff --git a/Kernel/src/renderive/state/State_Strategy.hpp b/Kernel/src/renderive/state/State_Strategy.hpp index ea7ca96..ce1905d 100644 --- a/Kernel/src/renderive/state/State_Strategy.hpp +++ b/Kernel/src/renderive/state/State_Strategy.hpp @@ -1,7 +1,7 @@ #pragma once -#include "Attach_State_Builder.hpp" #include "Concepts.hpp" -#include "Double_State_Strategy.hpp" +#include "Double_State_Storage.hpp" +#include "Stateful_Impl.hpp" #include "Optional_State_Validator.hpp" -#include "Triple_State_Strategy.hpp" +#include "Triple_State_Storage.hpp" #include "base/State_Strategy_Base.hpp" diff --git a/Kernel/src/renderive/state/Stateful_Impl.hpp b/Kernel/src/renderive/state/Stateful_Impl.hpp new file mode 100644 index 0000000..a2d458e --- /dev/null +++ b/Kernel/src/renderive/state/Stateful_Impl.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "Double_State_Storage.hpp" +#include "renderive/inheritance/Inheritance.hpp" + +#include + +template > +struct Stateful_Impl : Implementation, + Double_State_Storage { + using State_Storage = Double_State_Storage; + + template + Stateful_Impl(const State& state, With_Observer observer, + Implementation_Args&&... implementation_args) + : Implementation( + std::forward(implementation_args)...), + State_Storage(state, std::move(observer)) {} + + void publish() override { + State_Storage::publish(); + } + + [[nodiscard]] std::uint64_t state_revision() const override { + const auto own_revision = State_Storage::state_revision(); + if constexpr (requires(const Implementation& implementation) { + implementation.dependent_state_revision(); + }) + return own_revision + static_cast(*this) + .dependent_state_revision(); + return own_revision; + } + +}; diff --git a/Kernel/src/renderive/state/Triple_State_Strategy.hpp b/Kernel/src/renderive/state/Triple_State_Storage.hpp similarity index 54% rename from Kernel/src/renderive/state/Triple_State_Strategy.hpp rename to Kernel/src/renderive/state/Triple_State_Storage.hpp index bb7b153..41dd6c9 100644 --- a/Kernel/src/renderive/state/Triple_State_Strategy.hpp +++ b/Kernel/src/renderive/state/Triple_State_Storage.hpp @@ -1,25 +1,31 @@ #pragma once + +#include "Concepts.hpp" +#include "Render_State_View.hpp" +#include "base/State_Strategy_Base.hpp" +#include "renderive/base/Atomic_Mutex.hpp" +#include "renderive/base/Concepts.hpp" +#include "renderive/base/observer/Observer.hpp" +#include "renderive/base/property/Concepts.hpp" + #include #include #include #include #include -#include "renderive/base/Atomic_Mutex.hpp" -#include "renderive/base/Concepts.hpp" -#include "renderive/base/observer/Observer.hpp" -#include "renderive/base/property/Concepts.hpp" -#include "Concepts.hpp" -#include "Render_State_View.hpp" -#include "base/State_Strategy_Base.hpp" -template > -struct Triple_State_Strategy : That, State_Strategy_Base { - using Self = Triple_State_Strategy; + +template > +struct Triple_State_Storage : State_Strategy_Base { + using Self = Triple_State_Storage; using State = State_Type; + enum class Observation_Event { cache_updated, published, render_acquired }; + struct Observation { Observation_Event event{}; std::uint64_t time_ns{}; @@ -28,19 +34,30 @@ struct Triple_State_Strategy : That, State_Strategy_Base { std::uint64_t render_revision{}; State state; }; + static_assert(Timed_Struct_Observer); - Triple_State_Strategy() requires std::default_initializable && std::default_initializable - : That(), states{}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]), scratch_state(&states[3]) {} - explicit Triple_State_Strategy(With_Observer option) requires std::default_initializable && std::default_initializable - : That(), observer(std::move(option.observer)), states{}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]), scratch_state(&states[3]) {} - template - requires std::constructible_from - explicit Triple_State_Strategy(const State& state, Args&&... args) - : That(std::forward(args)...), states{state, state, state, state}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]), scratch_state(&states[3]) {} - template - requires std::constructible_from - Triple_State_Strategy(const State& state, With_Observer option, Args&&... args) - : That(std::forward(args)...), observer(std::move(option.observer)), states{state, state, state, state}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]), scratch_state(&states[3]) {} + + Triple_State_Storage() requires std::default_initializable + : states{}, render_state(&states[0]), published_state(&states[1]), + cache_state(&states[2]), scratch_state(&states[3]) {} + + explicit Triple_State_Storage(With_Observer option) + requires std::default_initializable + : observer(std::move(option.observer)), states{}, + render_state(&states[0]), published_state(&states[1]), + cache_state(&states[2]), scratch_state(&states[3]) {} + + explicit Triple_State_Storage(const State& state) + : states{state, state, state, state}, render_state(&states[0]), + published_state(&states[1]), cache_state(&states[2]), + scratch_state(&states[3]) {} + + Triple_State_Storage(const State& state, With_Observer option) + : observer(std::move(option.observer)), + states{state, state, state, state}, render_state(&states[0]), + published_state(&states[1]), cache_state(&states[2]), + scratch_state(&states[3]) {} + template Value> Self& set(Value&& value) { Observation observation; @@ -48,59 +65,73 @@ struct Triple_State_Strategy : That, State_Strategy_Base { std::lock_guard lock(mtx); cache_state->*Member = std::forward(value); ++cache_update_count; - observation = {Observation_Event::cache_updated, observer.now_ns(), cache_update_count, publish_count, render_revision, *cache_state}; + observation = {Observation_Event::cache_updated, + observer.now_ns(), cache_update_count, + publish_count, render_revision, *cache_state}; } observer.observe(observation); return *this; } + template auto get() const { std::lock_guard lock(mtx); const auto& value = cache_state->*Member; - if constexpr (Validated_Property_Value>) { + if constexpr (Validated_Property_Value< + std::remove_cvref_t>) return value.get(); - } else { + else return value; - } } + void publish() override { std::optional observation; { std::lock_guard lock(mtx); *scratch_state = *cache_state; - observation.emplace(Observation_Event::published, observer.now_ns(), cache_update_count, publish_count + 1, render_revision, *cache_state); + observation.emplace( + Observation_Event::published, observer.now_ns(), + cache_update_count, publish_count + 1, render_revision, + *cache_state); std::swap(published_state, scratch_state); ++publish_count; published_revision = publish_count; } observer.observe(*observation); } + std::uint64_t acquire_render_state() { std::optional observation; std::uint64_t revision{}; { std::lock_guard lock(mtx); if (render_revision != published_revision) { - observation.emplace(Observation_Event::render_acquired, observer.now_ns(), cache_update_count, publish_count, published_revision, *published_state); + observation.emplace( + Observation_Event::render_acquired, observer.now_ns(), + cache_update_count, publish_count, published_revision, + *published_state); std::swap(render_state, published_state); render_revision = published_revision; } revision = render_revision; } - if (observation) { + if (observation) observer.observe(*observation); - } return revision; } - std::uint64_t state_revision() const override { + + [[nodiscard]] std::uint64_t state_revision() const override { std::lock_guard lock(mtx); return publish_count; } + private: friend class Render_State_View; - const State& render_state_value() const noexcept { + + [[nodiscard]] const State& render_state_value() const noexcept { return *render_state; } + Observer observer; State states[4]; State* render_state; diff --git a/Kernel/tests/renderive/inheritance/Inheritance_Test.cpp b/Kernel/tests/renderive/inheritance/Inheritance_Test.cpp new file mode 100644 index 0000000..2f25ef2 --- /dev/null +++ b/Kernel/tests/renderive/inheritance/Inheritance_Test.cpp @@ -0,0 +1,210 @@ +#include + +#include "renderive/inheritance/Inheritance.hpp" + +#include +#include + +namespace { + +namespace inheritance = renderive::inheritance; + +struct Object_Chain {}; +struct Implementation_Chain {}; + +enum class Registration_Position : std::size_t { + root, + reserved, + implementation_parent, + object_branch +}; + +constexpr auto position(Registration_Position value) noexcept { + return static_cast(value); +} + +enum class Trace_Step { + parent, + child, + object_branch +}; + +enum class Test_Payload { + ready +}; + +struct Test_Event { + Test_Payload payload{Test_Payload::ready}; +}; + +struct Trace { + std::vector order; +}; + +struct Test_Root : inheritance::Type_Root { + explicit Test_Root(Trace& trace) : trace(&trace) {} + Trace* trace; +}; + +struct Parent_Impl + : inheritance::Dispatch_Root< + Parent_Impl, Test_Root, Implementation_Chain, Test_Event, + position(Registration_Position::implementation_parent)> { + using Dispatch_Root::Dispatch_Root; + + struct Event_Handler { + using Owner = Parent_Impl; + static void handle(Parent_Impl& impl, const Test_Event&) { + impl.trace->order.push_back(Trace_Step::parent); + } + }; + using Handler = Event_Handler; +}; + +struct Child_Impl + : inheritance::Dispatch_Node { + using Dispatch_Node::Dispatch_Node; + + struct Event_Handler : Parent_Impl::Event_Handler { + using Owner = Child_Impl; + static void handle(Child_Impl& impl, const Test_Event&) { + impl.trace->order.push_back(Trace_Step::child); + } + }; + using Handler = Event_Handler; +}; + +struct Object_Node + : inheritance::Type_Node< + Object_Node, Test_Root, Object_Chain, + position(Registration_Position::object_branch)> { + using Type_Node::Type_Node; +}; + +struct Impl_With_Object_Branch + : inheritance::Dispatch_Node< + Impl_With_Object_Branch, Child_Impl, Object_Chain, Test_Event, + position(Registration_Position::object_branch)> { + using Dispatch_Node::Dispatch_Node; + + struct Event_Handler : Child_Impl::Event_Handler { + using Owner = Impl_With_Object_Branch; + static void handle(Impl_With_Object_Branch& impl, + const Test_Event&) { + impl.trace->order.push_back(Trace_Step::object_branch); + } + }; + using Handler = Event_Handler; +}; + +struct Reverse_Declared_Registry { + using Inheritance_Registry = inheritance::Registry< + inheritance::Registration< + Implementation_Chain, + position(Registration_Position::object_branch), Child_Impl>, + inheritance::Registration< + Implementation_Chain, + position(Registration_Position::implementation_parent), + Parent_Impl>>; + + Trace* trace; +}; + +using Implementation_View = inheritance::Chain_View< + typename Child_Impl::Inheritance_Registry, Implementation_Chain>; +using Combined_Registry = inheritance::Merge_Registries_T< + typename Child_Impl::Inheritance_Registry, + typename Object_Node::Inheritance_Registry>; + +inline constexpr std::size_t Implementation_Layer_Count = + position(Registration_Position::object_branch) - + position(Registration_Position::reserved); +inline constexpr std::size_t Object_Layer_Count = + position(Registration_Position::reserved) - + position(Registration_Position::root); + +static_assert(Implementation_View::count == Implementation_Layer_Count); +static_assert(Implementation_View::next_position == + position(Registration_Position::object_branch) + + Object_Layer_Count); +static_assert(std::same_as< + Implementation_View::Type, + Parent_Impl>); +static_assert(std::same_as< + Implementation_View::Type, + Child_Impl>); +static_assert(inheritance::Chain_Registration_Count_V< + Combined_Registry, Implementation_Chain> == + Implementation_Layer_Count); +static_assert(inheritance::Chain_Registration_Count_V == + Object_Layer_Count); +static_assert(std::same_as< + inheritance::Type_At_Position_T< + Combined_Registry, Object_Chain, + position(Registration_Position::object_branch)>, + Object_Node>); + +TEST(inheritance_test, dispatches_parent_before_child) { + Trace trace; + Child_Impl child(trace); + + child.dispatch(Test_Event{}); + + EXPECT_EQ(trace.order, + (std::vector{Trace_Step::parent, Trace_Step::child})); +} + +TEST(inheritance_test, exposes_named_registration_positions) { + EXPECT_EQ(Parent_Impl::Inheritance_Position, + position(Registration_Position::implementation_parent)); + EXPECT_EQ(Child_Impl::Inheritance_Position, + position(Registration_Position::object_branch)); +} + +TEST(inheritance_test, dispatches_only_the_selected_chain) { + Trace trace; + Impl_With_Object_Branch value(trace); + const Test_Event event{}; + + value.dispatch(event); + EXPECT_EQ(trace.order, (std::vector{Trace_Step::object_branch})); + + trace.order.clear(); + value.dispatch_for(event); + EXPECT_EQ(trace.order, + (std::vector{Trace_Step::parent, Trace_Step::child})); +} + +TEST(inheritance_test, initializes_registered_layers_by_position) { + Trace trace; + Reverse_Declared_Registry registered{&trace}; + + inheritance::initialize( + registered, + [&](std::type_identity, + Reverse_Declared_Registry& value) { + if constexpr (std::same_as) + value.trace->order.push_back(Trace_Step::parent); + else if constexpr (std::same_as) + value.trace->order.push_back(Trace_Step::child); + }); + + EXPECT_EQ(trace.order, + (std::vector{Trace_Step::parent, Trace_Step::child})); +} + +TEST(inheritance_test, initializes_final_value_with_ordered_steps) { + auto initialized = inheritance::initialize( + Trace{}, + [](Trace& value) { value.order.push_back(Trace_Step::parent); }, + [](Trace& value) { value.order.push_back(Trace_Step::child); }); + + EXPECT_EQ(initialized.order, + (std::vector{Trace_Step::parent, Trace_Step::child})); +} + +} // namespace 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 e3342c7..46a9530 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 @@ -15,17 +15,14 @@ #include "renderive/state/base/State_Strategy_Base.hpp" namespace { -struct Double_Buffer_Test_Base {}; struct Double_Buffer_Test_Tag {}; using Double_Buffer_Test_Layout = Double_Buffer_Layout>>; class Double_Buffer_Test_Strategy final - : public Multi_Double_Buffer_Strategy { - using Base = Multi_Double_Buffer_Strategy; public: using Base::buffer_revision; diff --git a/Kernel/tests/renderive/renderable/Renderable_Inheritance_Test.cpp b/Kernel/tests/renderive/renderable/Renderable_Inheritance_Test.cpp new file mode 100644 index 0000000..f4314f5 --- /dev/null +++ b/Kernel/tests/renderive/renderable/Renderable_Inheritance_Test.cpp @@ -0,0 +1,174 @@ +#include + +#include "renderive/renderable/Inheritance.hpp" + +#include +#include + +namespace { + +namespace renderable_chain = renderive::renderable_inheritance; + +enum class Visit { + parent, + child +}; + +struct Event {}; + +struct Impl_Base : renderive::inheritance::Type_Root { + explicit Impl_Base(std::vector& visits) : visits(&visits) {} + std::vector* visits; +}; + +struct Parent_Impl + : renderable_chain::Impl_Root { + using Impl_Root::Impl_Root; + + struct Observer : renderable_chain::Observer_Root { + static void handle(Parent_Impl& impl, const Event&) { + impl.visits->push_back(Visit::parent); + } + }; +}; + +struct Child_Impl : renderable_chain::Impl_Node { + using Impl_Node::Impl_Node; + + struct Observer + : renderable_chain::Observer_Node { + static void handle(Child_Impl& impl, const Event&) { + impl.visits->push_back(Visit::child); + } + }; +}; + +struct Parent_State : renderable_chain::State_Root {}; +struct Child_State + : renderable_chain::State_Node {}; + +struct Parent_Builder_Layer + : renderable_chain::Builder_Root {}; +struct Child_Builder_Layer + : renderable_chain::Builder_Node {}; + +struct Business_Builder { + explicit Business_Builder(bool& initialized) : initialized(&initialized) {} + void build() const { *initialized = true; } + bool* initialized; +}; + +struct Final_Builder + : renderable_chain::Builder { + using Builder::Builder; +}; + +struct Root_Base { +public: + explicit Root_Base(bool& constructed) : constructed(&constructed) { + constructed = true; + } + + bool* constructed; +}; + +struct Renderable_Root + : renderive::renderable::render_base { + using render_base::render_base; +}; + +struct Product_Properties { + bool enabled{}; +}; + +template +struct Product_Business_Builder { + using Product = Product_Type; + using Properties = Properties_Type; + + explicit Product_Business_Builder(bool& initialized) + : initialized(&initialized) {} + + void build() const { *initialized = true; } + + bool* initialized; +}; + +struct Visual_Control + : renderive::renderable::render_base { + using State = Product_Properties; + + template + using Business_Builder = Product_Business_Builder; + + using render_base::render_base; + +protected: + template + void set_state(Value&& value) { + state.*Member = std::forward(value); + } + + template + auto get_state() const { + return state.*Member; + } + +private: + State state; +}; + +using Renderable_Product = renderive::renderable::attach; + +TEST(renderable_inheritance_test, dispatches_nested_observers_parent_first) { + std::vector visits; + Child_Impl impl(visits); + + impl.dispatch(Event{}); + + EXPECT_EQ(visits, (std::vector{Visit::parent, Visit::child})); +} + +TEST(renderable_inheritance_test, preserves_business_builder_capabilities) { + bool initialized{}; + Final_Builder builder(initialized); + + builder.build(); + + EXPECT_TRUE(initialized); + static_assert(std::same_as< + renderive::inheritance::Type_At_Position_T< + typename Final_Builder::Inheritance_Registry, + renderable_chain::Builder_Chain, + Final_Builder::Inheritance_Position>, + Final_Builder>); + static_assert(std::same_as< + renderive::inheritance::Type_At_Position_T< + typename Child_State::Inheritance_Registry, + renderable_chain::State_Chain, + Child_State::Inheritance_Position>, + Child_State>); +} + +TEST(renderable_inheritance_test, + composes_root_control_and_product_without_business_boilerplate) { + bool constructed{}; + Renderable_Product product(constructed); + EXPECT_TRUE(constructed); + + bool initialized{}; + Renderable_Product::Builder builder(initialized); + builder.build(); + EXPECT_TRUE(initialized); + + static_assert(std::same_as); + static_assert(std::same_as< + typename Renderable_Product::Builder::Product, + Renderable_Product>); +} + +} // namespace diff --git a/Kernel/tests/renderive/state/Concepts_Test.cpp b/Kernel/tests/renderive/state/Concepts_Test.cpp index 6f58fb5..61cbd58 100644 --- a/Kernel/tests/renderive/state/Concepts_Test.cpp +++ b/Kernel/tests/renderive/state/Concepts_Test.cpp @@ -2,9 +2,9 @@ #include "State_Test_Types.hpp" static_assert(State_Value); static_assert(std::derived_from); -static_assert(Double_State_Strategy_Type); +static_assert(Double_State_Storage_Type); static_assert(!Has_State_Validator); -static_assert(Has_State_Validator); +static_assert(Has_State_Validator); TEST(state_concepts_test, concepts_compile) { SUCCEED(); } diff --git a/Kernel/tests/renderive/state/Double_State_Strategy_Test.cpp b/Kernel/tests/renderive/state/Double_State_Strategy_Test.cpp index 80564a0..26c7936 100644 --- a/Kernel/tests/renderive/state/Double_State_Strategy_Test.cpp +++ b/Kernel/tests/renderive/state/Double_State_Strategy_Test.cpp @@ -2,23 +2,25 @@ #include #include #include "State_Test_Types.hpp" -TEST(double_state_strategy_test, builds_without_state_validator) { - auto product = State_Plain_Product::Builder{}.set<&State_Plain_Value::count>(10).set<&State_Plain_Value::name>("alpha").build(); - EXPECT_EQ(product->get<&State_Plain_Value::count>(), 10); - EXPECT_EQ(product->get<&State_Plain_Value::name>(), "alpha"); +TEST(double_state_strategy_test, constructs_from_state) { + State_Plain_Strategy state({.count = 10, .name = "alpha"}); + EXPECT_EQ(state.get<&State_Plain_Value::count>(), 10); + EXPECT_EQ(state.get<&State_Plain_Value::name>(), "alpha"); } TEST(double_state_strategy_test, calls_optional_state_validator_when_present) { - auto builder = State_Checked_Product::Builder{}.set<&State_Checked_Value::minimum>(10).set<&State_Checked_Value::maximum>(5); - EXPECT_THROW(builder.build(1, "invalid"), std::invalid_argument); + State_Checked_Value state{.minimum = 10, .maximum = 5}; + EXPECT_THROW(State_Checked_Validator::validate_state(state), + std::invalid_argument); } TEST(double_state_strategy_test, keeps_cache_separate_until_publish) { State_Render_State_Reader render; - auto product = State_Checked_Product::Builder{}.set<&State_Checked_Value::percent>(20).set<&State_Checked_Value::batch_size>(4).build(1, "state"); - product->set<&State_Checked_Value::percent>(60); - EXPECT_EQ(product->get<&State_Checked_Value::percent>(), 60); - EXPECT_EQ(render.read(*product).percent.get(), 20); - product->publish(); - EXPECT_EQ(render.read(*product).percent.get(), 60); + State_Checked_Strategy state( + State_Checked_Value{.percent = 20, .batch_size = 4}); + state.set<&State_Checked_Value::percent>(60); + EXPECT_EQ(state.get<&State_Checked_Value::percent>(), 60); + EXPECT_EQ(render.read(state).percent.get(), 20); + state.publish(); + EXPECT_EQ(render.read(state).percent.get(), 60); } TEST(double_state_strategy_test, keeps_acquired_render_reference_stable_until_publish) { State_Render_State_Reader render; @@ -30,22 +32,18 @@ TEST(double_state_strategy_test, keeps_acquired_render_reference_stable_until_pu EXPECT_EQ(render.read(strategy).count, 2); } TEST(double_state_strategy_test, keeps_cached_value_when_runtime_validation_fails) { - auto product = State_Checked_Product::Builder{}.set<&State_Checked_Value::percent>(20).set<&State_Checked_Value::batch_size>(4).build(1, "state"); - EXPECT_THROW((product->set<&State_Checked_Value::percent>(200)), std::out_of_range); - EXPECT_EQ(product->get<&State_Checked_Value::percent>(), 20); + State_Checked_Strategy state( + State_Checked_Value{.percent = 20, .batch_size = 4}); + EXPECT_THROW((state.set<&State_Checked_Value::percent>(200)), + std::out_of_range); + EXPECT_EQ(state.get<&State_Checked_Value::percent>(), 20); } TEST(double_state_strategy_test, supports_atomic_wait_mutex) { State_Render_State_Reader render; - auto product = State_Wait_Product::Builder{State_Plain_Value{.count = 1, .name = "wait"}}.build(); - product->set<&State_Plain_Value::count>(2); - product->publish(); - EXPECT_EQ(render.read(*product).count, 2); -} -TEST(double_state_strategy_test, passes_additional_builder_template_arguments) { - static_assert(std::same_as); - static_assert(std::same_as); - auto product = State_Tagged_Product::Builder{State_Plain_Value{.count = 3, .name = "tagged"}}.build(); - EXPECT_EQ(product->get<&State_Plain_Value::count>(), 3); + State_Wait_Strategy state({.count = 1, .name = "wait"}); + state.set<&State_Plain_Value::count>(2); + state.publish(); + EXPECT_EQ(render.read(state).count, 2); } struct Double_State_Observer_Test_Time_Source { std::uint64_t value{100}; @@ -70,7 +68,8 @@ struct Double_State_Observer_Test_Recorder { }; TEST(double_state_strategy_test, notifies_observer_for_cache_update_and_publish) { using Observer = Observer_State; - using Strategy = Double_State_Strategy; + using Strategy = + Double_State_Storage; Double_State_Observer_Test_Recorder recorder; Strategy strategy(State_Plain_Value{}, With_Observer(Observer(recorder, Double_State_Observer_Test_Time_Source{}))); strategy.set<&State_Plain_Value::count>(8); @@ -99,7 +98,7 @@ struct Double_State_Throwing_Assignment_State { }; TEST(double_state_strategy_test, failed_publish_keeps_render_state_and_revision_unchanged) { State_Render_State_Reader render; - using Strategy = Double_State_Strategy; + using Strategy = Double_State_Storage; Double_State_Throwing_Assignment_State::throw_on_copy_assignment = false; Strategy strategy(Double_State_Throwing_Assignment_State(1, 2)); strategy.set<&Double_State_Throwing_Assignment_State::first>(10); diff --git a/Kernel/tests/renderive/state/State_Test_Types.hpp b/Kernel/tests/renderive/state/State_Test_Types.hpp index 3bd7ba3..9d31835 100644 --- a/Kernel/tests/renderive/state/State_Test_Types.hpp +++ b/Kernel/tests/renderive/state/State_Test_Types.hpp @@ -27,9 +27,7 @@ struct State_Plain_Value { int count{}; std::string name; }; -struct State_Plain_Base {}; -using State_Plain_Strategy = Double_State_Strategy; -using State_Plain_Product = Attach_State_Builder; +using State_Plain_Strategy = Double_State_Storage; struct State_Checked_Value { struct Even_Validator { void operator()(const int& value) const { @@ -43,27 +41,13 @@ struct State_Checked_Value { int minimum{}; int maximum{}; }; -struct State_Checked_Base { - State_Checked_Base(int id, std::string name) : id(id), name(std::move(name)) {} +struct State_Checked_Validator { static void validate_state(const State_Checked_Value& state) { if (state.minimum > state.maximum) { throw std::invalid_argument("minimum must not exceed maximum"); } } - int id; - std::string name; }; -using State_Checked_Strategy = Double_State_Strategy; -using State_Checked_Product = Attach_State_Builder; -using State_Wait_Product = Attach_State_Builder, State_Plain_Value>; -struct State_Builder_Tag {}; -struct State_Builder_Mode {}; -template Validator, class Tag_Type, class Mode_Type> -class State_Tagged_Builder : public Property_Builder { -public: - using Base = Property_Builder; - using Tag = Tag_Type; - using Mode = Mode_Type; - using Base::Base; -}; -using State_Tagged_Product = Attach_State_Builder; +using State_Checked_Strategy = Double_State_Storage; +using State_Wait_Strategy = + Double_State_Storage; diff --git a/Kernel/tests/renderive/state/Triple_State_Strategy_Test.cpp b/Kernel/tests/renderive/state/Triple_State_Strategy_Test.cpp index 4fb0b6b..6312337 100644 --- a/Kernel/tests/renderive/state/Triple_State_Strategy_Test.cpp +++ b/Kernel/tests/renderive/state/Triple_State_Strategy_Test.cpp @@ -5,13 +5,12 @@ #include #include "renderive/state/State_Strategy.hpp" #include "State_Test_Types.hpp" -struct Triple_State_Test_Base {}; struct Triple_State_Test_State { int value{}; }; TEST(triple_state_strategy_test, publishes_then_acquires_render_state) { State_Render_State_Reader render; - Triple_State_Strategy strategy; + Triple_State_Storage strategy; strategy.set<&Triple_State_Test_State::value>(7); strategy.publish(); EXPECT_EQ(render.read(strategy).value, 0); @@ -20,7 +19,7 @@ TEST(triple_state_strategy_test, publishes_then_acquires_render_state) { } TEST(triple_state_strategy_test, returns_render_state_snapshot) { State_Render_State_Reader render; - Triple_State_Strategy strategy; + Triple_State_Storage strategy; const auto snapshot = render.read(strategy); strategy.set<&Triple_State_Test_State::value>(7); strategy.publish(); @@ -28,16 +27,14 @@ TEST(triple_state_strategy_test, returns_render_state_snapshot) { EXPECT_EQ(snapshot.value, 0); EXPECT_EQ(render.read(strategy).value, 7); } -struct Multi_State_A_Base {}; -struct Multi_State_B_Base {}; struct Multi_State_A { int value{}; }; struct Multi_State_B { int value{}; }; -struct Multi_State_A_Strategy : Double_State_Strategy {}; -struct Multi_State_B_Strategy : Double_State_Strategy {}; +struct Multi_State_A_Strategy : Double_State_Storage {}; +struct Multi_State_B_Strategy : Double_State_Storage {}; struct Multi_State_Product : Multi_State_A_Strategy, Multi_State_B_Strategy {}; TEST(double_state_strategy_test, supports_named_base_access_in_multiple_inheritance) { State_Render_State_Reader render; @@ -51,7 +48,7 @@ TEST(double_state_strategy_test, supports_named_base_access_in_multiple_inherita } TEST(triple_state_strategy_test, concurrently_acquires_render_revision_without_data_race) { State_Render_State_Reader render; - Triple_State_Strategy strategy; + Triple_State_Storage strategy; std::atomic finished{}; auto acquire = [&] { while (!finished.load(std::memory_order_acquire)) { @@ -91,7 +88,7 @@ struct Triple_State_Throwing_Assignment_State { }; TEST(triple_state_strategy_test, failed_publish_keeps_published_revision_and_render_state_unchanged) { State_Render_State_Reader render; - using Strategy = Triple_State_Strategy; + using Strategy = Triple_State_Storage; Triple_State_Throwing_Assignment_State::throw_on_copy_assignment = false; Strategy strategy(Triple_State_Throwing_Assignment_State(1, 2)); strategy.set<&Triple_State_Throwing_Assignment_State::first>(10); diff --git a/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp b/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp index 1c4fff6..65a8b5d 100644 --- a/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp +++ b/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp @@ -14,8 +14,8 @@ #include "renderive/renderable/Renderable.hpp" #include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" -#include "renderive/state/Double_State_Strategy.hpp" -#include "renderive/state/Triple_State_Strategy.hpp" +#include "renderive/state/Double_State_Storage.hpp" +#include "renderive/state/Triple_State_Storage.hpp" namespace { struct Threading_Test_Frame { std::uint64_t value{}; @@ -66,12 +66,13 @@ struct Threading_Revision_Observer { } }; using Threading_Revision_Observer_State = Observer_State; -struct Threading_Test_State_Base {}; struct Threading_Test_State_Value { Threading_Test_Frame frame; }; -using Threading_Test_Double_State = Double_State_Strategy; -using Threading_Test_Triple_State = Triple_State_Strategy; +using Threading_Test_Double_State = + Double_State_Storage; +using Threading_Test_Triple_State = + Triple_State_Storage; struct Threading_Test_Scene_Renderable : Renderable_Test_Harness { Threading_Test_Scene_Renderable(std::atomic& render_count, std::atomic& maximum_sequence) : Renderable_Test_Harness({.cache_enabled = false}), render_count(&render_count), maximum_sequence(&maximum_sequence) {} diff --git a/logs/renderive-web-performance.jsonl b/logs/renderive-web-performance.jsonl index 1294c63..2640838 100644 --- a/logs/renderive-web-performance.jsonl +++ b/logs/renderive-web-performance.jsonl @@ -5267,3 +5267,24 @@ {"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":22.70189257423158,"bottleneck_duration_ns":13011800,"case":"spectrum","client_changed_pixel_frames":54,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":147,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":44.80000001192093,"client_last_pixel_change_age_ms":11.099999988079162,"client_last_pixel_receive_age_ms":11.099999988079162,"client_overwritten_pixel_frames":140,"client_presentation_fps":31.003100310031,"client_transport_fps":91.24461759501276,"configured_fps":30.0,"consumed_frame_count":58,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":13153500,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":3300,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":3.6926,"last_pixel_request_ms":19.7477,"last_pixel_snapshot_ms":16.0551,"last_render_ms":13.17,"latest_sequence":58,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":174,"paint_duration_ns":134800,"paint_lease_wait_ns":600,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":202,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":64.42684742147688,"pixel_response_fps":89.87919831794615,"produced_frame_count":58,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":3300,"render_duration_ns":13011800,"render_finish_state_wait_ns":200,"render_lease_wait_ns":300,"render_state_wait_ns":0,"session_id":3,"successful_render_count":57,"target_interval_ns":33333333,"unix_ms":1786681597681,"websocket_buffered_bytes":0} {"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":23.233684397671784,"bottleneck_duration_ns":13537700,"case":"frequency_trace","client_changed_pixel_frames":56,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":240,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":4.0,"client_last_pixel_change_age_ms":9.199999988079071,"client_last_pixel_receive_age_ms":0.0,"client_overwritten_pixel_frames":235,"client_presentation_fps":32.0,"client_transport_fps":140.88759183020983,"configured_fps":30.0,"consumed_frame_count":59,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":13659400,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2300,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.5072,"last_pixel_request_ms":0.5209,"last_pixel_snapshot_ms":0.0137,"last_render_ms":13.6886,"latest_sequence":59,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":236,"paint_duration_ns":117600,"paint_lease_wait_ns":1000,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":297,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":100.54789524624427,"pixel_response_fps":140.27016032879325,"produced_frame_count":59,"publish_state_wait_ns":0,"queue_wait_ns":0,"ready_wait_ns":2300,"render_duration_ns":13537700,"render_finish_state_wait_ns":400,"render_lease_wait_ns":400,"render_state_wait_ns":100,"session_id":7,"successful_render_count":58,"target_interval_ns":33333333,"unix_ms":1786681597733,"websocket_buffered_bytes":0} {"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":21.28346165571548,"bottleneck_duration_ns":13042900,"case":"waterfall","client_changed_pixel_frames":53,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":229,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":22.899999976158142,"client_last_pixel_change_age_ms":26.499999952316102,"client_last_pixel_receive_age_ms":3.59999997615796,"client_overwritten_pixel_frames":224,"client_presentation_fps":28.87788778877888,"client_transport_fps":120.33572656777129,"configured_fps":30.0,"consumed_frame_count":57,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":14087200,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2400,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.2599,"last_pixel_request_ms":17.535,"last_pixel_snapshot_ms":17.2751,"last_render_ms":14.099,"latest_sequence":57,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":228,"paint_duration_ns":1040300,"paint_lease_wait_ns":400,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":283,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":84.75022045663204,"pixel_response_fps":118.23148542531422,"produced_frame_count":57,"publish_state_wait_ns":300,"queue_wait_ns":0,"ready_wait_ns":2400,"render_duration_ns":13042900,"render_finish_state_wait_ns":200,"render_lease_wait_ns":300,"render_state_wait_ns":0,"session_id":6,"successful_render_count":56,"target_interval_ns":33333333,"unix_ms":1786681597749,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":0.0,"bottleneck_duration_ns":6102200,"case":"afterglow","client_changed_pixel_frames":0,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":0,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":0.0,"client_last_pixel_change_age_ms":0.0,"client_last_pixel_receive_age_ms":0.0,"client_overwritten_pixel_frames":0,"client_presentation_fps":0.0,"client_transport_fps":0.0,"configured_fps":30.0,"consumed_frame_count":2,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":6600600,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2200,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.0,"last_pixel_request_ms":0.0,"last_pixel_snapshot_ms":0.0,"last_render_ms":6.6174,"latest_sequence":2,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":8,"paint_duration_ns":493700,"paint_lease_wait_ns":700,"paint_state_wait_ns":200,"pending_frames":0,"pixel_frame_count":0,"pixel_payload_bytes":0,"pixel_payload_megabytes_per_second":0.0,"pixel_response_fps":0.0,"produced_frame_count":2,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":2200,"render_duration_ns":6102200,"render_finish_state_wait_ns":200,"render_lease_wait_ns":200,"render_state_wait_ns":100,"session_id":4,"successful_render_count":1,"target_interval_ns":33333333,"unix_ms":1786686081200,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":0.0,"bottleneck_duration_ns":2553200,"case":"sweep_spectrum","client_changed_pixel_frames":0,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":0,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":0.0,"client_last_pixel_change_age_ms":0.0,"client_last_pixel_receive_age_ms":0.0,"client_overwritten_pixel_frames":0,"client_presentation_fps":0.0,"client_transport_fps":0.0,"configured_fps":30.0,"consumed_frame_count":2,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":2607800,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":1600,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.0,"last_pixel_request_ms":0.0,"last_pixel_snapshot_ms":0.0,"last_render_ms":2.6204,"latest_sequence":2,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":8,"paint_duration_ns":52200,"paint_lease_wait_ns":300,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":0,"pixel_payload_bytes":0,"pixel_payload_megabytes_per_second":0.0,"pixel_response_fps":0.0,"produced_frame_count":2,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":1600,"render_duration_ns":2553200,"render_finish_state_wait_ns":200,"render_lease_wait_ns":200,"render_state_wait_ns":100,"session_id":5,"successful_render_count":1,"target_interval_ns":33333333,"unix_ms":1786686081206,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":0.0,"bottleneck_duration_ns":3027800,"case":"axis_lab","client_changed_pixel_frames":0,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":0,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":0.0,"client_last_pixel_change_age_ms":0.0,"client_last_pixel_receive_age_ms":0.0,"client_overwritten_pixel_frames":0,"client_presentation_fps":0.0,"client_transport_fps":0.0,"configured_fps":30.0,"consumed_frame_count":2,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":3164100,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2400,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.0,"last_pixel_request_ms":0.0,"last_pixel_snapshot_ms":0.0,"last_render_ms":3.1765,"latest_sequence":2,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":6,"paint_duration_ns":131100,"paint_lease_wait_ns":500,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":0,"pixel_payload_bytes":0,"pixel_payload_megabytes_per_second":0.0,"pixel_response_fps":0.0,"produced_frame_count":2,"publish_state_wait_ns":400,"queue_wait_ns":0,"ready_wait_ns":2400,"render_duration_ns":3027800,"render_finish_state_wait_ns":200,"render_lease_wait_ns":200,"render_state_wait_ns":0,"session_id":2,"successful_render_count":1,"target_interval_ns":33333333,"unix_ms":1786686081209,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":0.0,"bottleneck_duration_ns":11948400,"case":"frequency_trace","client_changed_pixel_frames":0,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":0,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":0.0,"client_last_pixel_change_age_ms":0.0,"client_last_pixel_receive_age_ms":0.0,"client_overwritten_pixel_frames":0,"client_presentation_fps":0.0,"client_transport_fps":0.0,"configured_fps":30.0,"consumed_frame_count":2,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":12050200,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":1600,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.0,"last_pixel_request_ms":0.0,"last_pixel_snapshot_ms":0.0,"last_render_ms":12.0667,"latest_sequence":2,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":8,"paint_duration_ns":99300,"paint_lease_wait_ns":500,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":0,"pixel_payload_bytes":0,"pixel_payload_megabytes_per_second":0.0,"pixel_response_fps":0.0,"produced_frame_count":2,"publish_state_wait_ns":0,"queue_wait_ns":0,"ready_wait_ns":1600,"render_duration_ns":11948400,"render_finish_state_wait_ns":100,"render_lease_wait_ns":200,"render_state_wait_ns":100,"session_id":7,"successful_render_count":1,"target_interval_ns":33333333,"unix_ms":1786686081255,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":0.0,"bottleneck_duration_ns":4447900,"case":"waterfall","client_changed_pixel_frames":0,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":0,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":0.0,"client_last_pixel_change_age_ms":0.0,"client_last_pixel_receive_age_ms":0.0,"client_overwritten_pixel_frames":0,"client_presentation_fps":0.0,"client_transport_fps":0.0,"configured_fps":30.0,"consumed_frame_count":2,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":4989900,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2100,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.0,"last_pixel_request_ms":0.0,"last_pixel_snapshot_ms":0.0,"last_render_ms":5.0031,"latest_sequence":2,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":8,"paint_duration_ns":538200,"paint_lease_wait_ns":600,"paint_state_wait_ns":200,"pending_frames":0,"pixel_frame_count":0,"pixel_payload_bytes":0,"pixel_payload_megabytes_per_second":0.0,"pixel_response_fps":0.0,"produced_frame_count":2,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":2100,"render_duration_ns":4447900,"render_finish_state_wait_ns":200,"render_lease_wait_ns":300,"render_state_wait_ns":100,"session_id":6,"successful_render_count":1,"target_interval_ns":33333333,"unix_ms":1786686081324,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":0.0,"bottleneck_duration_ns":3729300,"case":"constellation","client_changed_pixel_frames":0,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":0,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":0.0,"client_last_pixel_change_age_ms":0.0,"client_last_pixel_receive_age_ms":0.0,"client_overwritten_pixel_frames":0,"client_presentation_fps":0.0,"client_transport_fps":0.0,"configured_fps":30.0,"consumed_frame_count":2,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":5053000,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":3500,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.0,"last_pixel_request_ms":0.0,"last_pixel_snapshot_ms":0.0,"last_render_ms":5.0641,"latest_sequence":2,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":54,"paint_duration_ns":1317800,"paint_lease_wait_ns":400,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":0,"pixel_payload_bytes":0,"pixel_payload_megabytes_per_second":0.0,"pixel_response_fps":0.0,"produced_frame_count":2,"publish_state_wait_ns":200,"queue_wait_ns":0,"ready_wait_ns":3500,"render_duration_ns":3729300,"render_finish_state_wait_ns":100,"render_lease_wait_ns":200,"render_state_wait_ns":100,"session_id":9,"successful_render_count":1,"target_interval_ns":33333333,"unix_ms":1786686081334,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":0.0,"bottleneck_duration_ns":6116600,"case":"spectrum","client_changed_pixel_frames":0,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":0,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":0.0,"client_last_pixel_change_age_ms":0.0,"client_last_pixel_receive_age_ms":0.0,"client_overwritten_pixel_frames":0,"client_presentation_fps":0.0,"client_transport_fps":0.0,"configured_fps":30.0,"consumed_frame_count":2,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":6496100,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2500,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.0,"last_pixel_request_ms":0.0,"last_pixel_snapshot_ms":0.0,"last_render_ms":6.5091,"latest_sequence":2,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":6,"paint_duration_ns":374400,"paint_lease_wait_ns":600,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":0,"pixel_payload_bytes":0,"pixel_payload_megabytes_per_second":0.0,"pixel_response_fps":0.0,"produced_frame_count":2,"publish_state_wait_ns":200,"queue_wait_ns":0,"ready_wait_ns":2500,"render_duration_ns":6116600,"render_finish_state_wait_ns":200,"render_lease_wait_ns":300,"render_state_wait_ns":100,"session_id":3,"successful_render_count":1,"target_interval_ns":33333333,"unix_ms":1786686081356,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":0.0,"bottleneck_duration_ns":3808900,"case":"selection_overlay","client_changed_pixel_frames":0,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":0,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":0.0,"client_last_pixel_change_age_ms":0.0,"client_last_pixel_receive_age_ms":0.0,"client_overwritten_pixel_frames":0,"client_presentation_fps":0.0,"client_transport_fps":0.0,"configured_fps":30.0,"consumed_frame_count":2,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":3871300,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2800,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.0,"last_pixel_request_ms":0.0,"last_pixel_snapshot_ms":0.0,"last_render_ms":3.8875,"latest_sequence":2,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":6,"paint_duration_ns":57600,"paint_lease_wait_ns":400,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":0,"pixel_payload_bytes":0,"pixel_payload_megabytes_per_second":0.0,"pixel_response_fps":0.0,"produced_frame_count":2,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":2800,"render_duration_ns":3808900,"render_finish_state_wait_ns":400,"render_lease_wait_ns":200,"render_state_wait_ns":0,"session_id":8,"successful_render_count":1,"target_interval_ns":33333333,"unix_ms":1786686081434,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":24.65677501175217,"bottleneck_duration_ns":13455700,"case":"axis_lab","client_changed_pixel_frames":1,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":0,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":12.800000011920929,"client_last_pixel_change_age_ms":663.2999999761582,"client_last_pixel_receive_age_ms":663.2999999761582,"client_overwritten_pixel_frames":0,"client_presentation_fps":0.0,"client_transport_fps":0.0,"configured_fps":30.0,"consumed_frame_count":26,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":13718900,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":1500,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.5493,"last_pixel_request_ms":0.5528,"last_pixel_snapshot_ms":0.0035,"last_render_ms":13.7435,"latest_sequence":26,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":78,"paint_duration_ns":259800,"paint_lease_wait_ns":700,"paint_state_wait_ns":0,"pending_frames":0,"pixel_frame_count":18,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":12.489090891015703,"pixel_response_fps":17.423007983939677,"produced_frame_count":26,"publish_state_wait_ns":0,"queue_wait_ns":0,"ready_wait_ns":1500,"render_duration_ns":13455700,"render_finish_state_wait_ns":300,"render_lease_wait_ns":200,"render_state_wait_ns":100,"session_id":2,"successful_render_count":25,"target_interval_ns":33333333,"unix_ms":1786686082213,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":27.645963487745636,"bottleneck_duration_ns":15475000,"case":"afterglow","client_changed_pixel_frames":16,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":109,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":2.600000023841858,"client_last_pixel_change_age_ms":23.700000023841767,"client_last_pixel_receive_age_ms":1.29999998807898,"client_overwritten_pixel_frames":107,"client_presentation_fps":27.208706786171575,"client_transport_fps":196.35787806809185,"configured_fps":30.0,"consumed_frame_count":27,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":16709400,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":1800,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.248,"last_pixel_request_ms":0.2533,"last_pixel_snapshot_ms":0.0053,"last_render_ms":16.7176,"latest_sequence":27,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":108,"paint_duration_ns":1230400,"paint_lease_wait_ns":600,"paint_state_wait_ns":200,"pending_frames":0,"pixel_frame_count":163,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":119.02813327020655,"pixel_response_fps":166.05116692457554,"produced_frame_count":27,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":1800,"render_duration_ns":15475000,"render_finish_state_wait_ns":100,"render_lease_wait_ns":300,"render_state_wait_ns":100,"session_id":4,"successful_render_count":26,"target_interval_ns":33333333,"unix_ms":1786686082213,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":28.19802866453685,"bottleneck_duration_ns":2282800,"case":"sweep_spectrum","client_changed_pixel_frames":17,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":121,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":3.899999976158142,"client_last_pixel_change_age_ms":22.599999988079162,"client_last_pixel_receive_age_ms":0.0,"client_overwritten_pixel_frames":118,"client_presentation_fps":28.959000152415783,"client_transport_fps":206.69885334570904,"configured_fps":30.0,"consumed_frame_count":28,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":2327600,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":1900,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":5.6133,"last_pixel_request_ms":5.6167,"last_pixel_snapshot_ms":0.0034,"last_render_ms":2.335,"latest_sequence":28,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":112,"paint_duration_ns":42100,"paint_lease_wait_ns":300,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":181,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":128.33001931800396,"pixel_response_fps":179.0278388289379,"produced_frame_count":28,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":1900,"render_duration_ns":2282800,"render_finish_state_wait_ns":200,"render_lease_wait_ns":700,"render_state_wait_ns":100,"session_id":5,"successful_render_count":27,"target_interval_ns":33333333,"unix_ms":1786686082233,"websocket_buffered_bytes":35} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":27.996696389826003,"bottleneck_duration_ns":1660300,"case":"frequency_trace","client_changed_pixel_frames":18,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":121,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":2.0,"client_last_pixel_change_age_ms":0.6000000238418579,"client_last_pixel_receive_age_ms":0.6000000238418579,"client_overwritten_pixel_frames":119,"client_presentation_fps":30.429212043561815,"client_transport_fps":211.91646192034128,"configured_fps":30.0,"consumed_frame_count":29,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":1736500,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2000,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.4221,"last_pixel_request_ms":0.4263,"last_pixel_snapshot_ms":0.0042,"last_render_ms":1.7505,"latest_sequence":29,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":116,"paint_duration_ns":73100,"paint_lease_wait_ns":300,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":187,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":143.80233346089406,"pixel_response_fps":200.61261671181174,"produced_frame_count":29,"publish_state_wait_ns":0,"queue_wait_ns":0,"ready_wait_ns":2000,"render_duration_ns":1660300,"render_finish_state_wait_ns":300,"render_lease_wait_ns":200,"render_state_wait_ns":0,"session_id":7,"successful_render_count":28,"target_interval_ns":33333333,"unix_ms":1786686082283,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":28.17299386893047,"bottleneck_duration_ns":4063700,"case":"waterfall","client_changed_pixel_frames":19,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":119,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":4.299999952316284,"client_last_pixel_change_age_ms":14.0,"client_last_pixel_receive_age_ms":1.100000023841858,"client_overwritten_pixel_frames":117,"client_presentation_fps":31.95909236177693,"client_transport_fps":212.63386622712895,"configured_fps":30.0,"consumed_frame_count":30,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":5444800,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2300,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.1901,"last_pixel_request_ms":0.1935,"last_pixel_snapshot_ms":0.0034,"last_render_ms":5.4587,"latest_sequence":30,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":120,"paint_duration_ns":1376800,"paint_lease_wait_ns":500,"paint_state_wait_ns":0,"pending_frames":0,"pixel_frame_count":177,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":126.21023892683355,"pixel_response_fps":176.07062192645472,"produced_frame_count":30,"publish_state_wait_ns":200,"queue_wait_ns":0,"ready_wait_ns":2300,"render_duration_ns":4063700,"render_finish_state_wait_ns":200,"render_lease_wait_ns":200,"render_state_wait_ns":0,"session_id":6,"successful_render_count":29,"target_interval_ns":33333333,"unix_ms":1786686082336,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":26.07602726509411,"automatic_scheduler_interval_ns":38349400,"automatic_scheduler_limit_source":"render_limited","backend_render_fps":22.36296887521992,"bottleneck_duration_ns":38349400,"case":"constellation","client_changed_pixel_frames":14,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":85,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":37.0,"client_last_pixel_change_age_ms":67.09999999999991,"client_last_pixel_receive_age_ms":30.09999999999991,"client_overwritten_pixel_frames":78,"client_presentation_fps":31.95909236177693,"client_transport_fps":153.0054644808743,"configured_fps":30.0,"consumed_frame_count":25,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":38615500,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":1500,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.2163,"last_pixel_request_ms":61.1711,"last_pixel_snapshot_ms":60.9548,"last_render_ms":38.6287,"latest_sequence":25,"limit_state":"render_limited","next_refresh_interval_ns":38349400,"observation_count":675,"paint_duration_ns":263000,"paint_lease_wait_ns":300,"paint_state_wait_ns":0,"pending_frames":0,"pixel_frame_count":103,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":75.26353941639366,"pixel_response_fps":104.99701376140273,"produced_frame_count":25,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":1500,"render_duration_ns":38349400,"render_finish_state_wait_ns":400,"render_lease_wait_ns":200,"render_state_wait_ns":100,"session_id":9,"successful_render_count":24,"target_interval_ns":33333333,"unix_ms":1786686082351,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":28.237340494322883,"bottleneck_duration_ns":9984400,"case":"spectrum","client_changed_pixel_frames":18,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":83,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":7.800000011920929,"client_last_pixel_change_age_ms":23.59999999999991,"client_last_pixel_receive_age_ms":0.7999999880789801,"client_overwritten_pixel_frames":79,"client_presentation_fps":31.968336124219828,"client_transport_fps":154.5356204548224,"configured_fps":30.0,"consumed_frame_count":30,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":10080600,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2000,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":1.1677,"last_pixel_request_ms":1.1712,"last_pixel_snapshot_ms":0.0035,"last_render_ms":10.1315,"latest_sequence":30,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":90,"paint_duration_ns":92100,"paint_lease_wait_ns":600,"paint_state_wait_ns":200,"pending_frames":0,"pixel_frame_count":129,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":95.61091858822574,"pixel_response_fps":133.3827908252965,"produced_frame_count":30,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":2000,"render_duration_ns":9984400,"render_finish_state_wait_ns":400,"render_lease_wait_ns":100,"render_state_wait_ns":100,"session_id":3,"successful_render_count":29,"target_interval_ns":33333333,"unix_ms":1786686082375,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":26.292280768389883,"bottleneck_duration_ns":10566200,"case":"selection_overlay","client_changed_pixel_frames":19,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":83,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":9.200000047683716,"client_last_pixel_change_age_ms":21.300000023841903,"client_last_pixel_receive_age_ms":8.099999976158188,"client_overwritten_pixel_frames":81,"client_presentation_fps":30.511060259344013,"client_transport_fps":153.96341463414635,"configured_fps":30.0,"consumed_frame_count":29,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":10626800,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2400,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":1.1417,"last_pixel_request_ms":1.1447,"last_pixel_snapshot_ms":0.003,"last_render_ms":10.6388,"latest_sequence":29,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":87,"paint_duration_ns":55800,"paint_lease_wait_ns":400,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":132,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":91.32359355519826,"pixel_response_fps":127.40172311332091,"produced_frame_count":29,"publish_state_wait_ns":0,"queue_wait_ns":0,"ready_wait_ns":2400,"render_duration_ns":10566200,"render_finish_state_wait_ns":300,"render_lease_wait_ns":200,"render_state_wait_ns":100,"session_id":8,"successful_render_count":28,"target_interval_ns":33333333,"unix_ms":1786686082465,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":24.848867119440637,"bottleneck_duration_ns":18722000,"case":"axis_lab","client_changed_pixel_frames":18,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":36,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":2.800000011920929,"client_last_pixel_change_age_ms":19.90000002384204,"client_last_pixel_receive_age_ms":13.699999976158324,"client_overwritten_pixel_frames":35,"client_presentation_fps":27.165228507510378,"client_transport_fps":84.5390993301649,"configured_fps":30.0,"consumed_frame_count":52,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":19169900,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2600,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.22,"last_pixel_request_ms":16.0499,"last_pixel_snapshot_ms":15.8299,"last_render_ms":19.1816,"latest_sequence":52,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":156,"paint_duration_ns":443300,"paint_lease_wait_ns":500,"paint_state_wait_ns":300,"pending_frames":0,"pixel_frame_count":101,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":59.18314471503261,"pixel_response_fps":82.56392814199546,"produced_frame_count":52,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":2600,"render_duration_ns":18722000,"render_finish_state_wait_ns":200,"render_lease_wait_ns":400,"render_state_wait_ns":100,"session_id":2,"successful_render_count":51,"target_interval_ns":33333333,"unix_ms":1786686083236,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":21.256160111115058,"bottleneck_duration_ns":5742300,"case":"sweep_spectrum","client_changed_pixel_frames":50,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":234,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":7.600000023841858,"client_last_pixel_change_age_ms":37.20000002384177,"client_last_pixel_receive_age_ms":18.29999998807898,"client_overwritten_pixel_frames":226,"client_presentation_fps":27.86377708978328,"client_transport_fps":109.60868674585807,"configured_fps":30.0,"consumed_frame_count":53,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":5834800,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":1600,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.3051,"last_pixel_request_ms":18.4197,"last_pixel_snapshot_ms":18.1146,"last_render_ms":5.8486,"latest_sequence":53,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":212,"paint_duration_ns":89100,"paint_lease_wait_ns":600,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":285,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":77.00792897616488,"pixel_response_fps":107.43053862659997,"produced_frame_count":53,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":1600,"render_duration_ns":5742300,"render_finish_state_wait_ns":200,"render_lease_wait_ns":200,"render_state_wait_ns":100,"session_id":5,"successful_render_count":52,"target_interval_ns":33333333,"unix_ms":1786686083357,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":19.079272469182204,"automatic_scheduler_interval_ns":52412900,"automatic_scheduler_limit_source":"render_limited","backend_render_fps":12.503945255226885,"bottleneck_duration_ns":52412900,"case":"constellation","client_changed_pixel_frames":19,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":89,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":74.90000003576279,"client_last_pixel_change_age_ms":190.99999998807925,"client_last_pixel_receive_age_ms":22.499999988079253,"client_overwritten_pixel_frames":78,"client_presentation_fps":18.57776860357106,"client_transport_fps":40.0,"configured_fps":30.0,"consumed_frame_count":38,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":52518000,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":1600,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.1804,"last_pixel_request_ms":116.7457,"last_pixel_snapshot_ms":116.5653,"last_render_ms":52.5366,"latest_sequence":38,"limit_state":"render_limited","next_refresh_interval_ns":52412900,"observation_count":1029,"paint_duration_ns":102700,"paint_lease_wait_ns":300,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":115,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":8.616067099971232,"pixel_response_fps":12.019914594500168,"produced_frame_count":38,"publish_state_wait_ns":0,"queue_wait_ns":0,"ready_wait_ns":1600,"render_duration_ns":52412900,"render_finish_state_wait_ns":200,"render_lease_wait_ns":100,"render_state_wait_ns":100,"session_id":9,"successful_render_count":37,"target_interval_ns":33333333,"unix_ms":1786686083360,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":23.5250773744909,"bottleneck_duration_ns":3075300,"case":"frequency_trace","client_changed_pixel_frames":51,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":249,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":31.30000001192093,"client_last_pixel_change_age_ms":16.70000001192102,"client_last_pixel_receive_age_ms":16.70000001192102,"client_overwritten_pixel_frames":242,"client_presentation_fps":28.883845677738805,"client_transport_fps":122.87528159368662,"configured_fps":30.0,"consumed_frame_count":55,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":3322700,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":2200,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.457,"last_pixel_request_ms":18.4029,"last_pixel_snapshot_ms":17.9459,"last_render_ms":3.3366,"latest_sequence":55,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":220,"paint_duration_ns":243500,"paint_lease_wait_ns":300,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":301,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":85.90100223923794,"pixel_response_fps":119.83689292543406,"produced_frame_count":55,"publish_state_wait_ns":200,"queue_wait_ns":0,"ready_wait_ns":2200,"render_duration_ns":3075300,"render_finish_state_wait_ns":400,"render_lease_wait_ns":200,"render_state_wait_ns":0,"session_id":7,"successful_render_count":54,"target_interval_ns":33333333,"unix_ms":1786686083361,"websocket_buffered_bytes":0} +{"automatic_low_latency_scheduler":true,"automatic_scheduler_fps":30.000000300000004,"automatic_scheduler_interval_ns":33333333,"automatic_scheduler_limit_source":"frequency_limited","backend_render_fps":22.151737184540384,"bottleneck_duration_ns":4807900,"case":"afterglow","client_changed_pixel_frames":51,"client_display_interval_deviation_ms":0.0,"client_display_interval_latest_ms":0.0,"client_display_interval_ms":0.0,"client_display_interval_p95_ms":0.0,"client_display_interval_p99_ms":0.0,"client_duplicate_pixel_frames":223,"client_frame_request_timeout_count":0,"client_frame_round_trip_ms":2.899999976158142,"client_last_pixel_change_age_ms":21.20000001192102,"client_last_pixel_receive_age_ms":5.70000001192102,"client_overwritten_pixel_frames":217,"client_presentation_fps":29.002900290029,"client_transport_fps":116.51676206334625,"configured_fps":30.0,"consumed_frame_count":54,"consumer_feedback_enabled":false,"consumer_feedback_interval_ns":0,"consumer_feedback_safety_interval_ns":0,"consumer_feedback_sample_interval_ns":0,"consumer_feedback_smoothed_interval_ns":0,"consumer_feedback_variation_ns":0,"consumer_interval_ns":0,"consumer_safety_interval_ns":0,"consumer_sample_interval_ns":0,"consumer_smoothed_interval_ns":0,"consumer_variation_ns":0,"dropped_frames":0,"end_to_end_ns":5251600,"event":"gallery_performance","failed_operations":0,"failed_render_count":0,"frame_age_at_render_ns":1500,"frame_mode":"low_latency","frequency_limit_enabled":true,"last_event":"lifecycle_completed","last_pixel_encode_ms":0.1851,"last_pixel_request_ms":0.188,"last_pixel_snapshot_ms":0.0029,"last_render_ms":5.2644,"latest_sequence":54,"limit_state":"frequency_limited","next_refresh_interval_ns":33333333,"observation_count":216,"paint_duration_ns":440900,"paint_lease_wait_ns":500,"paint_state_wait_ns":100,"pending_frames":0,"pixel_frame_count":275,"pixel_payload_bytes":716816,"pixel_payload_megabytes_per_second":83.64294901376336,"pixel_response_fps":116.68677737908105,"produced_frame_count":54,"publish_state_wait_ns":100,"queue_wait_ns":0,"ready_wait_ns":1500,"render_duration_ns":4807900,"render_finish_state_wait_ns":500,"render_lease_wait_ns":300,"render_state_wait_ns":100,"session_id":4,"successful_render_count":53,"target_interval_ns":33333333,"unix_ms":1786686083362,"websocket_buffered_bytes":0} diff --git a/render_2D/render_2D/axis/Abs_Axis.cpp b/render_2D/render_2D/axis/Abs_Axis.cpp index 9328488..930b195 100644 --- a/render_2D/render_2D/axis/Abs_Axis.cpp +++ b/render_2D/render_2D/axis/Abs_Axis.cpp @@ -5,7 +5,8 @@ namespace renderive { -Abs_Axis::Abs_Axis() : Renderable(true) {} +Abs_Axis::Abs_Axis() + : detail::Renderable(std::make_unique(), true) {} Abs_Axis::~Abs_Axis() = default; diff --git a/render_2D/render_2D/axis/Abs_Axis.h b/render_2D/render_2D/axis/Abs_Axis.h index 3314312..a6ccc22 100644 --- a/render_2D/render_2D/axis/Abs_Axis.h +++ b/render_2D/render_2D/axis/Abs_Axis.h @@ -1,23 +1,21 @@ #pragma once - -#include "../renderable/Renderable.h" +#include "../plottable/Plottable.h" #include "Axis_Types.h" - #include - namespace renderive { - -class LIB_DECL Abs_Axis : public Renderable { -public: +struct LIB_DECL Abs_Axis +: detail::Renderable { Abs_Axis(); ~Abs_Axis() override; - [[nodiscard]] virtual Axis_Transform transform() const = 0; [[nodiscard]] virtual Axis_Transform transform(const Render_State_View& state) const noexcept = 0; [[nodiscard]] int pixel_sample_count(Range range) const; [[nodiscard]] virtual double tick_step(Range range) const = 0; [[nodiscard]] int sub_tick_count(double major_step) const noexcept; [[nodiscard]] virtual std::string tick_label(double tick) const = 0; +protected: + struct State : next_State {}; + struct Impl : next_Impl {}; + using detail::Renderable::Renderable; }; - } // namespace renderive diff --git a/render_2D/render_2D/axis/Axis_Builder.h b/render_2D/render_2D/axis/Axis_Builder.h deleted file mode 100644 index 49b139c..0000000 --- a/render_2D/render_2D/axis/Axis_Builder.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once -#include "../renderable/Renderable.h" -#include "Axis_Types.h" -#include -#include -namespace renderive::detail { -template -class Axis_Renderable_Builder { -public: - using Self = Axis_Renderable_Builder; - Axis_Renderable_Builder(renderive_Owner parent, Orientation orientation, ::Scene_Base::Attach_Builder* attach_builder = nullptr) - : parent_(std::move(parent)), attach_builder_(attach_builder) { - properties_.orientation = orientation; - } - Self& set_x(int value) { properties_.x = value; return *this; } - Self& set_y(int value) { properties_.y = value; return *this; } - Self& set_orientation(Orientation value) { properties_.orientation = value; return *this; } - Self& set_pixel_length(std::size_t value) { properties_.pixel_length = value; return *this; } - Self& set_tick_length(int value) { properties_.tick_length = value; return *this; } - Self& set_sub_tick_length(int value) { properties_.sub_tick_length = value; return *this; } - Self& set_color(Color value) { properties_.color = value; return *this; } - Self& set_locale(Number_Locale value) { properties_.locale = value; return *this; } - Self& set_unit_text(std::string value) { properties_.unit_text = std::move(value); return *this; } - Self& set_unit_text_font(Font value) { properties_.unit_text_font = value; return *this; } - Self& set_unit_text_pen(Pen value) { properties_.unit_text_pen = value; return *this; } - Self& set_unit_text_background_brush(Brush value) { properties_.unit_text_background_brush = value; return *this; } - Self& set_label_rotation_degrees(int value) { properties_.label_rotation_degrees = value; return *this; } - Self& set_coord_range(Range value) requires requires(Properties properties) { properties.coordinates = value; } { - properties_.coordinates = value; - return *this; - } - Self& set_label_precision(int value) requires requires(Properties properties) { properties.precision = value; } { - properties_.precision = value; - return *this; - } - Self& set_use_wheel(bool value) requires requires(Properties properties) { properties.wheel = value; } { - properties_.wheel = value; - return *this; - } - Self& set_use_drag(bool value) requires requires(Properties properties) { properties.drag = value; } { - properties_.drag = value; - return *this; - } - Self& set_visible_time_point_count(int value) requires requires(Properties properties) { properties.visible_count = value; } { - properties_.visible_count = value; - return *this; - } - Self& set_tick_label_spacing_px(int value) requires requires(Properties properties) { properties.tick_label_spacing_px = value; } { - properties_.tick_label_spacing_px = value; - return *this; - } - Self& set_time_format(std::string value) requires requires(Properties properties) { properties.format = value; } { - properties_.format = std::move(value); - return *this; - } - Self& set_font(Font value) requires requires(Properties properties) { properties.unit_text_font = value; } { - properties_.unit_text_font = value; - return *this; - } - Self& set_newest_at_axis_start(bool value) requires requires(Properties properties) { properties.newest_at_start = value; } { - properties_.newest_at_start = value; - return *this; - } - renderive_Owner build() const { - if (!parent_) - return {}; - auto result = renderive_Owner::make(properties_); - if (attach_builder_) { - attach_builder_->attach(result); - attach_builder_->add_display_parent(result, parent_); - attach_builder_->add_dependency_parent(result, parent_); - } - return result; - } -private: - renderive_Owner parent_; - Properties properties_; - ::Scene_Base::Attach_Builder* attach_builder_{}; -}; -} diff --git a/render_2D/render_2D/axis/Axis_State_Strategy.hpp b/render_2D/render_2D/axis/Axis_State_Strategy.hpp index d201c34..6bafdb3 100644 --- a/render_2D/render_2D/axis/Axis_State_Strategy.hpp +++ b/render_2D/render_2D/axis/Axis_State_Strategy.hpp @@ -1,211 +1,388 @@ #pragma once - #include "Abs_Axis.h" #include "Axis_Format.h" #include "../render/Blend2D_Cache.h" - #include #include -#include - +#include +#include +#include #include #include +#include #include #include #include - namespace renderive::detail { - -template -requires std::is_base_of_v -class Axis_State_Strategy - : public Double_State_Strategy> { - using State_Observer = Observer_State; - using Base = Double_State_Strategy; - -public: - explicit Axis_State_Strategy(State state) - : Base(state, - With_Observer{State_Observer(Renderable_State_Observer(this))}) {} - - template Value> - Axis_State_Strategy& set(Value&& value) { - Base::template set(std::forward(value)); - using Member_Value = decltype(std::declval().*Member); - if constexpr (Paint_Only_Property_Value) - this->paint_changed(); - else - this->changed(); +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; } - - template - requires std::invocable - Axis_State_Strategy& update(Update&& value) { - Base::update(std::forward(value)); - this->changed(); - return *this; + Self& set_x(int value) { + return set<&Properties::x>(value); } - - [[nodiscard]] Axis_Transform transform() const final { - return Base::read([this](const State& state) { - return make_transform(state, coordinate_range(state)); - }); + Self& set_y(int value) { + return set<&Properties::y>(value); } - - [[nodiscard]] Axis_Transform transform(const Render_State_View& view) const noexcept final { - const auto& state = view.get(static_cast(*this)); - return make_transform(state, coordinate_range(state)); + Self& set_orientation(Orientation value) { + return set<&Properties::orientation>(value); } - - [[nodiscard]] double tick_step(Range range) const final { - return Base::read([this, range](const State& state) { - return calculate_tick_step(range, state); - }); + Self& set_pixel_length(std::size_t value) { + return set<&Properties::pixel_length>(value); } - - [[nodiscard]] std::string tick_label(double tick) const final { - return Base::read([this, tick](const State& state) { - return format_tick_label(tick, state); - }); + Self& set_tick_length(int value) { + return set<&Properties::tick_length>(value); } - -protected: - [[nodiscard]] virtual Range coordinate_range(const State& state) const noexcept = 0; - - [[nodiscard]] virtual double calculate_tick_step(Range range, const State&) const { - const double raw = range.size() / 5.0; - if (!(raw > 0.0) || !std::isfinite(raw)) - return 1.0; - const double scale = std::pow(10.0, std::floor(std::log10(raw))); - const double normalized = raw / scale; - const double nice = normalized <= 1.0 ? 1.0 - : normalized <= 2.0 ? 2.0 - : normalized <= 5.0 ? 5.0 - : 10.0; - return nice * scale; + Self& set_sub_tick_length(int value) { + return set<&Properties::sub_tick_length>(value); } - - [[nodiscard]] virtual std::string format_tick_label(double tick, const State& state) const { - int precision = 2; - if constexpr (requires { state.precision; }) - precision = state.precision.get(); - return localized_axis_number(tick, precision, state.locale); + 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: - static Axis_Transform make_transform(const State& state, Range coordinates) noexcept { - return { - coordinates, - state.orientation == Orientation::Horizontal ? static_cast(state.x) - : static_cast(state.y), - static_cast(state.pixel_length), - state.orientation + 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; + [[nodiscard]] Axis_Transform transform(const Properties& state) const noexcept { + return make_transform(state, coordinate_range(state)); + } + [[nodiscard]] double tick_step(Range range, const Properties& state) const { + return calculate_tick_step(range, state); + } + [[nodiscard]] std::string tick_label(double tick, + const Properties& state) const { + return format_tick_label(tick, state); + } + private: + struct Prepared_Line { + PointF first; + PointF second; }; - } - - struct Prepared_Line { - PointF first; - PointF second; - }; - struct Prepared_Label { - PointF position; - std::string text; - }; - struct Axis_Prepare_Buffer { - std::vector lines; - std::vector labels; - PointF unit_position; - double unit_width{}; - bool valid{}; - }; - void prepare_frame(const Prepare_Render_Context& context) final { - const auto& view = context.frame.render_state; - const auto& state = view.get(static_cast(*this)); - auto& output = prepare_buffer_; - output = {}; - if (state.pixel_length == 0) - return; - - const Range coordinates = coordinate_range(state); - const Axis_Transform axis = make_transform(state, coordinates); - const double step = calculate_tick_step(coordinates, state); - if (!(step > 0.0)) - return; - - const PointF first{static_cast(state.x), static_cast(state.y)}; - const PointF last = state.orientation == Orientation::Horizontal - ? PointF{first.x + state.pixel_length, first.y} - : PointF{first.x, first.y + state.pixel_length}; - output.lines.push_back({first, last}); - - const auto [low, high] = std::minmax(coordinates.origin, coordinates.target); - const double initial = std::ceil(low / step) * step; - int tick_index{}; - for (double tick = initial; tick <= high + step * 1e-6 && tick_index < 1000; - tick += step, ++tick_index) { - const double pixel = axis.coord_to_pixel(tick); - PointF tick_start; - PointF tick_end; - PointF label; - if (state.orientation == Orientation::Horizontal) { - tick_start = {pixel, static_cast(state.y)}; - tick_end = {pixel, static_cast(state.y + state.tick_length)}; - label = {pixel + 2.0, static_cast(state.y + state.tick_length + 2)}; - } else { - tick_start = {static_cast(state.x), pixel}; - tick_end = {static_cast(state.x + state.tick_length), pixel}; - label = {static_cast(state.x + state.tick_length + 2), pixel - 7.0}; - } - output.lines.push_back({tick_start, tick_end}); - output.labels.push_back({label, format_tick_label(tick, state)}); - const int subdivisions = std::max(0, this->sub_tick_count(step)); - for (int sub_index = 1; sub_index <= subdivisions; ++sub_index) { - const double sub_tick = tick + step * sub_index / (subdivisions + 1.0); - if (sub_tick >= high) - break; - const double sub_pixel = axis.coord_to_pixel(sub_tick); + struct Prepared_Label { + PointF position; + std::string text; + }; + struct Prepare_Buffer { + std::vector lines; + std::vector labels; + PointF unit_position; + double unit_width{}; + bool valid{}; + }; + void prepare_frame(const Prepare_Render_Context& context) final { + auto& axis = static_cast(this->owner()); + const auto& view = context.frame.render_state; + const auto& state = axis.template render_state(view); + auto& output = prepare_buffer; + output = {}; + if (state.pixel_length == 0) + return; + const Range coordinates = coordinate_range(state); + const Axis_Transform transform = make_transform(state, coordinates); + const double step = calculate_tick_step(coordinates, state); + if (!(step > 0.0)) + return; + const PointF first{ + static_cast(state.x), + static_cast(state.y) + }; + const PointF last = state.orientation == Orientation::Horizontal + ? PointF{ + first.x + state.pixel_length, + first.y + } + : PointF{ + first.x, + first.y + state.pixel_length + }; + output.lines.push_back({first, last}); + const auto [low, high] = + std::minmax(coordinates.origin, coordinates.target); + const double initial = std::ceil(low / step) * step; + int tick_index{}; + for (double tick = initial; + tick <= high + step * 1e-6 && tick_index < 1000; + tick += step, ++tick_index) { + const double pixel = transform.coord_to_pixel(tick); + PointF tick_start; + PointF tick_end; + PointF label; if (state.orientation == Orientation::Horizontal) { - output.lines.push_back({ - {sub_pixel, static_cast(state.y)}, - {sub_pixel, static_cast(state.y + state.sub_tick_length)}}); - } else { - output.lines.push_back({ - {static_cast(state.x), sub_pixel}, - {static_cast(state.x + state.sub_tick_length), sub_pixel}}); + tick_start = {pixel, static_cast(state.y)}; + tick_end = { + pixel, + static_cast(state.y + state.tick_length) + }; + label = { + pixel + 2.0, + static_cast(state.y + state.tick_length + 2) + }; + } + else { + tick_start = {static_cast(state.x), pixel}; + tick_end = { + static_cast(state.x + state.tick_length), pixel + }; + label = { + static_cast(state.x + state.tick_length + 2), + pixel - 7.0 + }; + } + output.lines.push_back({tick_start, tick_end}); + output.labels.push_back( + {label, format_tick_label(tick, state)}); + const int subdivisions = + std::max(0, axis.sub_tick_count(step)); + for (int sub_index = 1; sub_index <= subdivisions; + ++sub_index) { + const double sub_tick = + tick + step * sub_index / (subdivisions + 1.0); + if (sub_tick >= high) + break; + const double sub_pixel = + transform.coord_to_pixel(sub_tick); + if (state.orientation == Orientation::Horizontal) { + output.lines.push_back({ + {sub_pixel, static_cast(state.y)}, + { + sub_pixel, + static_cast(state.y + + state.sub_tick_length) + } + }); + } + else { + output.lines.push_back({ + {static_cast(state.x), sub_pixel}, + { + static_cast(state.x + + state.sub_tick_length), + sub_pixel + } + }); + } } } + if (!state.unit_text.empty()) { + output.unit_position = {last.x + 4.0, last.y + 4.0}; + output.unit_width = std::max( + 4.0, + state.unit_text.size() * state.unit_text_font.size * 0.65); + } + output.valid = true; } - if (!state.unit_text.empty()) { - output.unit_position = {last.x + 4.0, last.y + 4.0}; - output.unit_width = - std::max(4.0, state.unit_text.size() * state.unit_text_font.size * 0.65); + void paint(Painter& painter, + const Paint_Render_Context& context) final { + auto& axis = static_cast(this->owner()); + const auto& output = prepare_buffer; + if (!output.valid) + return; + const auto& view = context.frame.render_state; + const auto& state = axis.template render_state(view); + const Pen axis_pen{state.color, 1.0}; + for (const auto& line : output.lines) + painter.line(line.first, line.second, axis_pen); + for (const auto& label : output.labels) { + painter.text(label.position, label.text, state.unit_text_font, + state.unit_text_pen, + state.label_rotation_degrees); + } + if (!state.unit_text.empty()) { + painter.rect( + { + output.unit_position.x - 2.0, + output.unit_position.y - 2.0, + output.unit_width + 4.0, + state.unit_text_font.size * 1.5 + 4.0 + }, + Pen{.style = Line_Style::None}, + state.unit_text_background_brush); + painter.text(output.unit_position, state.unit_text, + state.unit_text_font, state.unit_text_pen); + } } - output.valid = true; + Prepare_Buffer prepare_buffer; + [[nodiscard]] virtual Range coordinate_range( + const Properties& state) const noexcept = 0; + [[nodiscard]] virtual double calculate_tick_step( + Range range, const Properties&) const { + const double raw = range.size() / 5.0; + if (!(raw > 0.0) || !std::isfinite(raw)) + return 1.0; + const double scale = + std::pow(10.0, std::floor(std::log10(raw))); + const double normalized = raw / scale; + const double nice = normalized <= 1.0 + ? 1.0 + : normalized <= 2.0 + ? 2.0 + : normalized <= 5.0 + ? 5.0 + : 10.0; + return nice * scale; + } + [[nodiscard]] virtual std::string format_tick_label( + double tick, const Properties& state) const { + int precision = 2; + if constexpr (requires { state.precision; }) + precision = state.precision.get(); + return localized_axis_number(tick, precision, state.locale); + } + static Axis_Transform make_transform( + const Properties& state, Range coordinates) noexcept { + return { + coordinates, + state.orientation == Orientation::Horizontal + ? static_cast(state.x) + : static_cast(state.y), + static_cast(state.pixel_length), + state.orientation + }; + } + }; + template + using next_Impl = renderable_inheritance::Impl_Node; +public: + template + Axis_State_Strategy& set(Value&& value) { + this->template set_state( + std::forward(value)); + return *this; } - void paint(Painter& painter, const Paint_Render_Context& context) final { - const auto& output = prepare_buffer_; - if (!output.valid) - return; - const auto& view = context.frame.render_state; - const auto& state = view.get(static_cast(*this)); - const Pen axis_pen{state.color, 1.0}; - for (const auto& line : output.lines) - painter.line(line.first, line.second, axis_pen); - for (const auto& label : output.labels) - painter.text(label.position, label.text, state.unit_text_font, - state.unit_text_pen, state.label_rotation_degrees); - if (!state.unit_text.empty()) { - painter.rect({output.unit_position.x - 2.0, output.unit_position.y - 2.0, - output.unit_width + 4.0, - state.unit_text_font.size * 1.5 + 4.0}, - Pen{.style = Line_Style::None}, state.unit_text_background_brush); - painter.text(output.unit_position, state.unit_text, state.unit_text_font, - state.unit_text_pen); - } + template + Axis_State_Strategy& update(Update&& value) { + this->template update_state( + std::forward(value)); + return *this; } - Axis_Prepare_Buffer prepare_buffer_; + template + [[nodiscard]] auto get() const { + return this->template get_state(); + } + template + [[nodiscard]] decltype(auto) read(Read&& value) const { + return this->template read_state( + std::forward(value)); + } + [[nodiscard]] Axis_Transform transform() const final { + using State = typename Layer::State; + return read([this](const State& state) { + return this->template d_func().transform(state); + }); + } + [[nodiscard]] Axis_Transform transform( + const Render_State_View& view) const noexcept final { + using State = typename Layer::State; + const auto& state = this->template render_state(view); + return this->template d_func().transform(state); + } + [[nodiscard]] double tick_step(Range range) const final { + using State = typename Layer::State; + return read([this, range](const State& state) { + return this->template d_func().tick_step(range, state); + }); + } + [[nodiscard]] std::string tick_label(double tick) const final { + using State = typename Layer::State; + return read([this, tick](const State& state) { + return this->template d_func().tick_label(tick, state); + }); + } +protected: + template + Axis_State_Strategy(With_Attached_Impl implementation, + State state) + : Render_Base(implementation, std::move(state)) {} }; - } // namespace renderive::detail diff --git a/render_2D/render_2D/axis/Frequency_Axis.cpp b/render_2D/render_2D/axis/Frequency_Axis.cpp index 0f285af..bc28aa9 100644 --- a/render_2D/render_2D/axis/Frequency_Axis.cpp +++ b/render_2D/render_2D/axis/Frequency_Axis.cpp @@ -1,22 +1,36 @@ #include "Frequency_Axis.h" #include "Axis_Format.h" +#include "Numeric_Axis_p.h" #include namespace renderive::detail { -Frequency_Axis_Control::Frequency_Axis_Control(const Axis_Properties& properties) - : Axis(properties) {} +struct Frequency_Axis::Impl + : next_Impl { +private: + [[nodiscard]] std::string format_tick_label( + double tick, const Axis_Properties& state) const override { + const double absolute = std::abs(tick); + if (absolute >= 1'000'000.0) + return localized_axis_number( + tick / 1'000'000.0, state.precision.get(), state.locale) + + " MHz"; + if (absolute >= 1'000.0) + return localized_axis_number( + tick / 1'000.0, state.precision.get(), state.locale) + + " kHz"; + return localized_axis_number(tick, state.precision.get(), state.locale) + + " Hz"; + } +}; -std::string Frequency_Axis_Control::format_tick_label(double tick, - const Axis_Properties& state) const { - const double absolute = std::abs(tick); - if (absolute >= 1'000'000.0) - return localized_axis_number(tick / 1'000'000.0, state.precision.get(), state.locale) + " MHz"; - if (absolute >= 1'000.0) - return localized_axis_number(tick / 1'000.0, state.precision.get(), state.locale) + " kHz"; - return localized_axis_number(tick, state.precision.get(), state.locale) + " Hz"; -} +static_assert( + std::constructible_from<::renderive::Frequency_Axis, + const ::renderive::Frequency_Axis::Properties&>); + +Frequency_Axis::Frequency_Axis(const State& state) + : Renderable(With_Attached_Impl{}, state) {} } // namespace renderive::detail diff --git a/render_2D/render_2D/axis/Frequency_Axis.h b/render_2D/render_2D/axis/Frequency_Axis.h index 8b09e78..e83e0d0 100644 --- a/render_2D/render_2D/axis/Frequency_Axis.h +++ b/render_2D/render_2D/axis/Frequency_Axis.h @@ -1,20 +1,17 @@ #pragma once - #include "Numeric_Axis.h" - namespace renderive { - namespace detail { - -class LIB_DECL Frequency_Axis_Control : public Axis { -public: - explicit Frequency_Axis_Control(const Axis_Properties& properties); +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: - [[nodiscard]] std::string format_tick_label(double tick, const Axis_Properties& state) const override; + struct Impl; + using Renderable::Renderable; }; - } // namespace detail - -using Frequency_Axis = Attach_Builder; - +using Frequency_Axis = renderable::attach; } // namespace renderive diff --git a/render_2D/render_2D/axis/Numeric_Axis.cpp b/render_2D/render_2D/axis/Numeric_Axis.cpp index 380405d..6cebf36 100644 --- a/render_2D/render_2D/axis/Numeric_Axis.cpp +++ b/render_2D/render_2D/axis/Numeric_Axis.cpp @@ -1,82 +1,80 @@ #include "Numeric_Axis.h" +#include "Numeric_Axis_p.h" + +#include namespace renderive::detail { -Axis_Control::Axis_Control(const Axis_Properties& properties) - : Axis_State_Strategy(properties) {} +static_assert(std::constructible_from<::renderive::Axis, + const ::renderive::Axis::Properties&>); -void Axis_Control::handle_event(const Event& event) { - if (event.type == Event_Type::Wheel && get<&Axis_Properties::wheel>()) { +Axis::Axis(const State& state) + : Axis_State_Strategy(With_Attached_Impl{}, state) {} + +void Axis::Impl::handle_event(const Event& event) { + auto& axis = static_cast(owner()); + if (event.type == Event_Type::Wheel && + axis.get<&Axis_Properties::wheel>()) { const auto& wheel = static_cast(event); - const Range range = get<&Axis_Properties::coordinates>(); - const Orientation orientation = get<&Axis_Base_Properties::orientation>(); + const Range range = axis.get<&Axis_Properties::coordinates>(); + const Orientation orientation = + axis.get<&Axis_Base_Properties::orientation>(); const double anchor_pixel = orientation == Orientation::Horizontal ? wheel.position.x : wheel.position.y; - const double anchor = transform().pixel_to_coord(anchor_pixel); + const double anchor = axis.transform().pixel_to_coord(anchor_pixel); const double factor = wheel.angle_delta_y >= 0.0 ? 0.9 : 1.1; const Range next{anchor + (range.origin - anchor) * factor, anchor + (range.target - anchor) * factor}; - set<&Axis_Properties::coordinates>(next); + axis.set<&Axis_Properties::coordinates>(next); event.accept(); return; } - if (!get<&Axis_Properties::drag>()) + if (!axis.get<&Axis_Properties::drag>()) return; if (event.type == Event_Type::Pointer_Press) { const auto& pointer = static_cast(event); if (pointer.button == Mouse_Button::Left) { - interaction_.update([&](Axis_Interaction_State& interaction) { - interaction.dragging = true; - interaction.last_pointer = pointer.position; - }); + std::lock_guard lock(interaction_mutex); + dragging = true; + last_pointer = pointer.position; event.accept(); } } else if (event.type == Event_Type::Pointer_Move) { const auto& pointer = static_cast(event); PointF previous; - bool dragging{}; - interaction_.update([&](Axis_Interaction_State& interaction) { - dragging = interaction.dragging; + { + std::lock_guard lock(interaction_mutex); if (!dragging) return; - previous = interaction.last_pointer; - interaction.last_pointer = pointer.position; - }); - if (!dragging) - return; - const Orientation orientation = get<&Axis_Base_Properties::orientation>(); + previous = last_pointer; + last_pointer = pointer.position; + } + const Orientation orientation = + axis.get<&Axis_Base_Properties::orientation>(); const double delta = orientation == Orientation::Horizontal ? pointer.position.x - previous.x : pointer.position.y - previous.y; - const Range range = get<&Axis_Properties::coordinates>(); - const std::size_t pixel_length = get<&Axis_Base_Properties::pixel_length>(); + const Range range = axis.get<&Axis_Properties::coordinates>(); + const std::size_t pixel_length = + axis.get<&Axis_Base_Properties::pixel_length>(); const double shift = pixel_length == 0 ? 0.0 : -delta * range.length() / static_cast(pixel_length); - set<&Axis_Properties::coordinates>(Range{range.origin + shift, range.target + shift}); + axis.set<&Axis_Properties::coordinates>( + Range{range.origin + shift, range.target + shift}); event.accept(); } else if (event.type == Event_Type::Pointer_Release) { - bool accepted{}; - interaction_.update([&](Axis_Interaction_State& interaction) { - accepted = interaction.dragging; - interaction.dragging = false; - }); + std::lock_guard lock(interaction_mutex); + const bool accepted = dragging; + dragging = false; if (accepted) event.accept(); } } -void Axis_Control::publish() { - Axis_State_Strategy::publish(); - interaction_.publish(); -} - -std::uint64_t Axis_Control::state_revision() const { - return Axis_State_Strategy::state_revision() + interaction_.state_revision(); -} - -Range Axis_Control::coordinate_range(const Axis_Properties& state) const noexcept { +Range Axis::Impl::coordinate_range( + const Axis_Properties& state) const noexcept { return state.coordinates.get(); } diff --git a/render_2D/render_2D/axis/Numeric_Axis.h b/render_2D/render_2D/axis/Numeric_Axis.h index 873a27f..8819292 100644 --- a/render_2D/render_2D/axis/Numeric_Axis.h +++ b/render_2D/render_2D/axis/Numeric_Axis.h @@ -1,56 +1,33 @@ #pragma once - #include "Axis_State_Strategy.hpp" -#include "Axis_Builder.h" - -#include #include - #include #include - namespace renderive { - struct Axis_Coordinate_Range_Validator { void operator()(const Range& value) const { if (!std::isfinite(value.origin) || !std::isfinite(value.target) || !(value.size() > 0.0)) throw std::invalid_argument("axis coordinate range must be finite and non-empty"); } }; - struct Axis_Properties : Axis_Base_Properties { Validated_Value coordinates{Range{0.0, 20.0}}; Range_Value precision{2}; bool wheel{}; bool drag{}; }; - namespace detail { - -struct Axis_Interaction_State { - bool dragging{}; - PointF last_pointer{}; -}; - -class LIB_DECL Axis_Control : public Axis_State_Strategy, public Event_Handler { -public: - explicit Axis_Control(const Axis_Properties& properties); - void handle_event(const Event& event) override; - - void publish() override; - [[nodiscard]] std::uint64_t state_revision() const override; - -private: - struct Interaction_Base {}; - using Interaction_State = Double_State_Strategy; +struct LIB_DECL Axis +: Axis_State_Strategy { + struct State : next_State, Axis_Properties { + State() = default; + State(const Axis_Properties& properties) : Axis_Properties(properties) {} + }; + explicit Axis(const State& state); protected: - [[nodiscard]] Range coordinate_range(const Axis_Properties& state) const noexcept override; -private: - Interaction_State interaction_; + struct Impl; + using Axis_State_Strategy::Axis_State_Strategy; }; - } // namespace detail - -using Axis = Attach_Builder; - +using Axis = renderable::attach; } // namespace renderive diff --git a/render_2D/render_2D/axis/Numeric_Axis_p.h b/render_2D/render_2D/axis/Numeric_Axis_p.h new file mode 100644 index 0000000..01a7a5a --- /dev/null +++ b/render_2D/render_2D/axis/Numeric_Axis_p.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Numeric_Axis.h" + +#include + +namespace renderive::detail { + +struct Axis::Impl + : next_Impl, + Renderable_Event_Handler { + +private: + void handle_event(const Event& event) override; + [[nodiscard]] Range coordinate_range( + const Axis_Properties& state) const noexcept override; + + std::mutex interaction_mutex; + bool dragging{}; + PointF last_pointer{}; +}; + +} // namespace renderive::detail diff --git a/render_2D/render_2D/axis/Time_Axis.cpp b/render_2D/render_2D/axis/Time_Axis.cpp index 88a0125..15a1de1 100644 --- a/render_2D/render_2D/axis/Time_Axis.cpp +++ b/render_2D/render_2D/axis/Time_Axis.cpp @@ -6,26 +6,61 @@ #include namespace renderive::detail { -namespace { +struct Time_Axis::Impl + : next_Impl { +private: + [[nodiscard]] Range coordinate_range( + const Axis_Base_Properties& properties) const noexcept override { + const auto& state = static_cast(properties); + const int latest = std::max(1, state.next_tick - 1); + const int earliest = + std::max(0, latest - state.visible_count.get() + 1); + return state.newest_at_start + ? Range{static_cast(latest), + static_cast(earliest)} + : Range{static_cast(earliest), + static_cast(latest)}; + } -Time_Axis_State time_state_from(const Time_Axis_Properties& properties) { - Time_Axis_State state; - static_cast(state) = properties; - return state; + [[nodiscard]] double calculate_tick_step( + Range range, const Axis_Base_Properties& properties) const override { + const auto& state = static_cast(properties); + const double available = static_cast(state.pixel_length); + const double label_width = + std::max(48.0, state.unit_text_font.size * 7.0); + const double label_count = std::max( + 1.0, + available / (label_width + state.tick_label_spacing_px.get())); + return std::max(1.0, std::ceil(range.size() / label_count)); + } + + [[nodiscard]] std::string format_tick_label( + double tick, const Axis_Base_Properties& properties) const override { + const auto& state = static_cast(properties); + const int target = static_cast(std::llround(tick)); + const auto iterator = std::find_if( + state.samples.begin(), state.samples.end(), + [target](const auto& value) { return value.first == target; }); + return iterator == state.samples.end() + ? std::string{} + : formatted_axis_time(iterator->second, state.format); + } +}; + +static_assert( + std::constructible_from<::renderive::Time_Axis, + const ::renderive::Time_Axis::Properties&>); + +Time_Axis::Time_Axis(const State& state) + : Axis_State_Strategy(With_Attached_Impl{}, state) {} + +std::size_t Time_Axis::time_point_count() const { + return read([](const State& state) { return state.samples.size(); }); } -} // namespace - -Time_Axis_Control::Time_Axis_Control(const Time_Axis_Properties& properties) - : Axis_State_Strategy(time_state_from(properties)) {} - -std::size_t Time_Axis_Control::time_point_count() const { - return read([](const Time_Axis_State& state) { return state.samples.size(); }); -} - -int Time_Axis_Control::append_time(Time_Of_Day time) { +int Time_Axis::append_time(Time_Of_Day time) { int tick{}; - update([&](Time_Axis_State& state) { + update([&](State& state) { tick = state.next_tick++; state.samples.emplace_back(tick, time); const auto limit = static_cast(std::max(512, state.visible_count.get() * 4)); @@ -35,36 +70,12 @@ int Time_Axis_Control::append_time(Time_Of_Day time) { return tick; } -Time_Of_Day Time_Axis_Control::tick_to_time(int tick) const { - return read([tick](const Time_Axis_State& state) { +Time_Of_Day Time_Axis::tick_to_time(int tick) const { + return read([tick](const State& state) { const auto iterator = std::find_if(state.samples.begin(), state.samples.end(), [tick](const auto& value) { return value.first == tick; }); return iterator == state.samples.end() ? Time_Of_Day{} : iterator->second; }); } -Range Time_Axis_Control::coordinate_range(const Time_Axis_State& state) const noexcept { - const int latest = std::max(1, state.next_tick - 1); - const int earliest = std::max(0, latest - state.visible_count.get() + 1); - return state.newest_at_start ? Range{static_cast(latest), static_cast(earliest)} - : Range{static_cast(earliest), static_cast(latest)}; -} - -double Time_Axis_Control::calculate_tick_step(Range range, const Time_Axis_State& state) const { - const double available = static_cast(state.pixel_length); - const double label_width = std::max(48.0, state.unit_text_font.size * 7.0); - const double label_count = - std::max(1.0, available / (label_width + state.tick_label_spacing_px.get())); - return std::max(1.0, std::ceil(range.size() / label_count)); -} - -std::string Time_Axis_Control::format_tick_label(double tick, const Time_Axis_State& state) const { - const int target = static_cast(std::llround(tick)); - const auto iterator = std::find_if(state.samples.begin(), state.samples.end(), - [target](const auto& value) { return value.first == target; }); - return iterator == state.samples.end() - ? std::string{} - : formatted_axis_time(iterator->second, state.format); -} - } // namespace renderive::detail diff --git a/render_2D/render_2D/axis/Time_Axis.h b/render_2D/render_2D/axis/Time_Axis.h index 919c50f..0deefef 100644 --- a/render_2D/render_2D/axis/Time_Axis.h +++ b/render_2D/render_2D/axis/Time_Axis.h @@ -1,45 +1,29 @@ #pragma once - #include "Axis_State_Strategy.hpp" -#include "Axis_Builder.h" - -#include #include #include #include #include - namespace renderive { - -struct Time_Axis_Properties : Axis_Base_Properties { - Range_Value::max()> visible_count{100}; - Range_Value::max()> tick_label_spacing_px{8}; - std::string format = "mm:ss.zzz"; - bool newest_at_start{}; -}; - namespace detail { - -struct Time_Axis_State : Time_Axis_Properties { - int next_tick{}; - std::deque> samples; -}; - -class LIB_DECL Time_Axis_Control : public Axis_State_Strategy { -public: - explicit Time_Axis_Control(const Time_Axis_Properties& properties); +struct LIB_DECL Time_Axis +: Axis_State_Strategy { + struct State : next_State, Axis_Base_Properties { + Range_Value::max()> visible_count{100}; + Range_Value::max()> tick_label_spacing_px{8}; + std::string format = "mm:ss.zzz"; + bool newest_at_start{}; + int next_tick{}; + std::deque> samples; + }; + explicit Time_Axis(const State& state); [[nodiscard]] std::size_t time_point_count() const; int append_time(Time_Of_Day time); [[nodiscard]] Time_Of_Day tick_to_time(int tick) const; - -private: - [[nodiscard]] Range coordinate_range(const Time_Axis_State& state) const noexcept override; - [[nodiscard]] double calculate_tick_step(Range range, const Time_Axis_State& state) const override; - [[nodiscard]] std::string format_tick_label(double tick, const Time_Axis_State& state) const override; +protected: + struct Impl; + using Axis_State_Strategy::Axis_State_Strategy; }; - } // namespace detail - -using Time_Axis = Attach_Builder; - +using Time_Axis = renderable::attach; } // namespace renderive diff --git a/render_2D/render_2D/base/Types.h b/render_2D/render_2D/base/Types.h index c5946f6..119b05c 100644 --- a/render_2D/render_2D/base/Types.h +++ b/render_2D/render_2D/base/Types.h @@ -1,5 +1,4 @@ #pragma once - #include #include #include @@ -12,54 +11,58 @@ #include #include #include - #ifndef LIB_DECL #define LIB_DECL #endif - namespace renderive { - struct Point { int x{}; int y{}; }; - struct PointF { double x{}; double y{}; }; - struct Size { int width{}; int height{}; - - [[nodiscard]] bool empty() const noexcept { return width <= 0 || height <= 0; } + [[nodiscard]] bool empty() const noexcept { + return width <= 0 || height <= 0; + } friend bool operator==(Size, Size) = default; }; - struct Rect { int x{}; int y{}; int width{}; int height{}; - - [[nodiscard]] bool empty() const noexcept { return width <= 0 || height <= 0; } - [[nodiscard]] int right() const noexcept { return x + width; } - [[nodiscard]] int bottom() const noexcept { return y + height; } + [[nodiscard]] bool empty() const noexcept { + return width <= 0 || height <= 0; + } + [[nodiscard]] int right() const noexcept { + return x + width; + } + [[nodiscard]] int bottom() const noexcept { + return y + height; + } [[nodiscard]] bool contains(Point point) const noexcept { return point.x >= x && point.x <= right() && point.y >= y && point.y <= bottom(); } }; - struct RectF { double x{}; double y{}; double width{}; double height{}; - - [[nodiscard]] bool empty() const noexcept { return width <= 0.0 || height <= 0.0; } - [[nodiscard]] double right() const noexcept { return x + width; } - [[nodiscard]] double bottom() const noexcept { return y + height; } + [[nodiscard]] bool empty() const noexcept { + return width <= 0.0 || height <= 0.0; + } + [[nodiscard]] double right() const noexcept { + return x + width; + } + [[nodiscard]] double bottom() const noexcept { + return y + height; + } [[nodiscard]] bool contains(PointF point) const noexcept { return point.x >= x && point.x <= right() && point.y >= y && point.y <= bottom(); } @@ -76,103 +79,100 @@ struct RectF { return value; } }; - enum class Orientation : std::uint8_t { Horizontal, Vertical }; - struct Range { double origin{}; double target{}; - - [[nodiscard]] double size() const noexcept { return std::abs(target - origin); } - [[nodiscard]] double length() const noexcept { return target - origin; } - [[nodiscard]] double center() const noexcept { return origin + length() * 0.5; } + [[nodiscard]] double size() const noexcept { + return std::abs(target - origin); + } + [[nodiscard]] double length() const noexcept { + return target - origin; + } + [[nodiscard]] double center() const noexcept { + return origin + length() * 0.5; + } [[nodiscard]] bool contains(double value) const noexcept { const auto [low, high] = std::minmax(origin, target); return value >= low - 1e-9 && value <= high + 1e-9; } friend bool operator==(Range, Range) = default; }; - struct Color { std::uint8_t r{}; std::uint8_t g{}; std::uint8_t b{}; std::uint8_t a{255}; - - static constexpr Color transparent() noexcept { return {0, 0, 0, 0}; } - static constexpr Color black() noexcept { return {0, 0, 0, 255}; } - static constexpr Color white() noexcept { return {255, 255, 255, 255}; } - static constexpr Color red() noexcept { return {255, 0, 0, 255}; } - static constexpr Color green() noexcept { return {0, 255, 0, 255}; } - static constexpr Color yellow() noexcept { return {255, 255, 0, 255}; } + static constexpr Color transparent() noexcept { + return {0, 0, 0, 0}; + } + static constexpr Color black() noexcept { + return {0, 0, 0, 255}; + } + static constexpr Color white() noexcept { + return {255, 255, 255, 255}; + } + static constexpr Color red() noexcept { + return {255, 0, 0, 255}; + } + static constexpr Color green() noexcept { + return {0, 255, 0, 255}; + } + static constexpr Color yellow() noexcept { + return {255, 255, 0, 255}; + } friend bool operator==(Color, Color) = default; }; - using Pixel = std::uint32_t; - constexpr Pixel premultiply(Color color) noexcept { const auto r = static_cast((color.r * color.a + 127) / 255); const auto g = static_cast((color.g * color.a + 127) / 255); const auto b = static_cast((color.b * color.a + 127) / 255); return (static_cast(color.a) << 24) | (r << 16) | (g << 8) | b; } - constexpr Pixel pack_rgba(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a = 255) noexcept { return premultiply(Color{r, g, b, a}); } - enum class Line_Style : std::uint8_t { None, Solid, Dash, Dot }; enum class Line_Cap : std::uint8_t { Butt, Square, Round }; enum class Line_Join : std::uint8_t { Miter, Bevel, Round }; - struct Pen { Color color = Color::white(); double width = 1.0; Line_Style style = Line_Style::Solid; Line_Cap cap = Line_Cap::Butt; Line_Join join = Line_Join::Miter; - [[nodiscard]] bool enabled() const noexcept { return style != Line_Style::None && width > 0.0 && color.a != 0; } friend bool operator==(const Pen&, const Pen&) = default; }; - enum class Brush_Style : std::uint8_t { None, Solid }; - struct Brush { Color color = Color::transparent(); Brush_Style style = Brush_Style::None; - [[nodiscard]] bool enabled() const noexcept { return style != Brush_Style::None && color.a != 0; } friend bool operator==(const Brush&, const Brush&) = default; }; - struct Font { double size = 12.0; int weight = 400; bool italic{}; - friend bool operator==(const Font&, const Font&) = default; }; - struct Number_Locale { char decimal_point = '.'; - friend bool operator==(Number_Locale, Number_Locale) = default; }; - struct Time_Of_Day { std::int64_t milliseconds = -1; - [[nodiscard]] bool valid() const noexcept { return milliseconds >= 0 && milliseconds < 24LL * 60LL * 60LL * 1000LL; } friend bool operator==(Time_Of_Day, Time_Of_Day) = default; }; - enum class Image_Interpolation_Mode : std::uint8_t { Nearest, Bilinear, Bicubic }; enum class Line_Interpolation_Mode : std::uint8_t { Nearest_Sample, @@ -183,21 +183,17 @@ enum class Line_Interpolation_Mode : std::uint8_t { Cubic_Value }; enum class Renderable_Cache_Mode : std::uint8_t { Direct, Local_Pixel }; - enum class Pixel_Format : std::uint8_t { Premultiplied_32 }; - struct Image_View { const std::byte* data{}; int width{}; int height{}; int stride{}; Pixel_Format format = Pixel_Format::Premultiplied_32; - [[nodiscard]] bool empty() const noexcept { return data == nullptr || width <= 0 || height <= 0; } }; - enum class Memory_Domain : std::uint8_t { Renderable, Input_Buffer, @@ -219,28 +215,25 @@ enum class Memory_Domain : std::uint8_t { Other, Count }; - LIB_DECL std::pmr::memory_resource* memory_resource() noexcept; LIB_DECL std::pmr::memory_resource* memory_resource(Memory_Domain domain) noexcept; - template std::shared_ptr make_shared(Memory_Domain domain, Args&&... args) { return std::allocate_shared(std::pmr::polymorphic_allocator(memory_resource(domain)), std::forward(args)...); } - -class LIB_DECL Color_Map { -public: +struct LIB_DECL Color_Map { Color_Map(); explicit Color_Map(std::vector colors); - - [[nodiscard]] const std::vector& colors() const noexcept { return colors_; } + [[nodiscard]] const std::vector& colors() const noexcept { + return colors_; + } void set_colors(std::vector colors); - [[nodiscard]] int size() const noexcept { return static_cast(colors_.size()); } + [[nodiscard]] int size() const noexcept { + return static_cast(colors_.size()); + } [[nodiscard]] Pixel at_normalized(double value) const noexcept; - private: std::vector colors_; }; - } // namespace renderive diff --git a/render_2D/render_2D/plottable/Afterglow.cpp b/render_2D/render_2D/plottable/Afterglow.cpp index 880ca6a..1c1cabc 100644 --- a/render_2D/render_2D/plottable/Afterglow.cpp +++ b/render_2D/render_2D/plottable/Afterglow.cpp @@ -4,6 +4,7 @@ #include "../render/Blend2D_Cache.h" #include "../renderable/Render_Partition.h" #include "../renderable/Renderable_p.h" +#include #include #include #include @@ -11,7 +12,7 @@ namespace renderive::detail { namespace { using Afterglow_History = Plottable_History_Real_Time_Data, std::deque>>; struct Afterglow_Prepare_Buffer { - Afterglow_Properties properties; + ::renderive::Afterglow::Properties properties; std::deque> history; Axis_Raster_Layout layout; std::vector intensity; @@ -24,7 +25,8 @@ struct Afterglow_Prepare_Buffer { bool valid{}; }; } -struct Afterglow_Control::Impl : Renderable::Impl { +struct Afterglow::Impl + : next_Impl, ::Render_Frame_Completion { Impl(renderive_Owner frequency, renderive_Owner power) : frequency_axis(std::move(frequency)), @@ -34,22 +36,36 @@ struct Afterglow_Control::Impl : Renderable::Impl { std::optional history; Adaptive_Render_Partitioner partitioner; Afterglow_Prepare_Buffer prepare_buffer; +protected: + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; + void build_prepare_graph(Renderable_Graph_Builder& builder) override; + void build_paint_graph(Renderable_Graph_Builder& builder) override; + void prepare_render_frame(const Render_State_View& state, int graph_partition_count); + void accumulate_partition(int partition_index); + void normalize_render_frame(); + void color_partition(int partition_index); + void paint_render_frame(Painter& painter); +private: + void render_frame_completed(std::uint64_t target_interval_ns) override; }; -Afterglow_Control::Afterglow_Control(const Afterglow_Properties& properties, renderive_Owner frequency_axis, renderive_Owner power_axis) - : Plottable_State(properties, std::make_unique( - std::move(frequency_axis), std::move(power_axis))) { +Afterglow::Afterglow(const State& state, + renderive_Owner frequency_axis, + renderive_Owner power_axis) + : Renderable(With_Attached_Impl{}, state, + std::move(frequency_axis), std::move(power_axis)) { d_func().history.emplace(*this); } -Afterglow_Control::~Afterglow_Control() = default; -std::size_t Afterglow_Control::history_count() const { +Afterglow::~Afterglow() = default; +std::size_t Afterglow::history_count() const { return d_func().history->size(); } -std::size_t Afterglow_Control::latest_spectrum_point_count() const { +std::size_t Afterglow::latest_spectrum_point_count() const { const auto history = d_func().history->snapshot(); return history.empty() ? 0 : history.back().size(); } -std::size_t Afterglow_Control::rendered_cell_count() const { - const auto state = properties(); +std::size_t Afterglow::rendered_cell_count() const { + const auto state = Renderable::state(); const auto history = d_func().history->snapshot(); if (history.empty()) return 0; @@ -59,34 +75,32 @@ std::size_t Afterglow_Control::rendered_cell_count() const { : std::max(1, static_cast(d_func().power_axis->transform().pixel_length)); return width > 0 && height > 0 ? static_cast(width) * static_cast(height) : 0; } -void Afterglow_Control::append_spectrum(std::span values) { - if (get<&Afterglow_Properties::frequency_point_size>() <= 0) - set<&Afterglow_Properties::frequency_point_size>(static_cast(values.size())); +void Afterglow::append_spectrum(std::span values) { + if (get_state() <= 0) + set_state( + static_cast(values.size())); d_func().history->update({values.begin(), values.end()}, 64); - render_graph_changed(); + d_func().render_graph_changed(); } -void Afterglow_Control::append_spectrum(std::pmr::vector&& values) { +void Afterglow::append_spectrum(std::pmr::vector&& values) { append_spectrum(std::span(values.data(), values.size())); } -void Afterglow_Control::publish() { - publish_properties(); -} - -void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { - const auto view = d_func().render_state_view(); - const auto state = properties(); - const auto& history = view.get(*d_func().history); - const int width = history.empty() - ? 0 - : std::min(state.frequency_point_size.get(), - static_cast(history.back().size())); +void Afterglow::Impl::build_prepare_graph(Renderable_Graph_Builder& builder) { + auto& control = static_cast(owner()); + const auto view = render_state_view(); + const auto state = control.state(); + const auto& published_history = view.get(*history); + const int width = published_history.empty() + ? 0 + : std::min(state.frequency_point_size.get(), + static_cast(published_history.back().size())); const int height = state.power_point_size.get() > 0 - ? state.power_point_size.get() - : std::max(1, static_cast(d_func().power_axis->transform(view).pixel_length)); + ? state.power_point_size.get() + : std::max(1, static_cast(power_axis->transform(view).pixel_length)); const std::size_t work_size = width > 0 && height > 0 - ? static_cast(width) * static_cast(height) - : 0; - const int partition_count = d_func().partitioner.graph_partition_count( + ? static_cast(width) * static_cast(height) + : 0; + const int partition_count = partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto prepare = add_prepare_task( @@ -95,10 +109,10 @@ void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { prepare_render_frame(context.frame.render_state, partition_count); if (context.metrics) { context.metrics->set(Node_Metric_Kind::input_count, - d_func().prepare_buffer.history.size()); + prepare_buffer.history.size()); context.metrics->set(Node_Metric_Kind::chunk_size, - d_func().prepare_buffer.work_size / - std::max(1, d_func().prepare_buffer.active_partitions)); + prepare_buffer.work_size / + std::max(1, prepare_buffer.active_partitions)); } }); std::vector accumulation; @@ -111,11 +125,11 @@ void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { accumulate_partition(index); if (context.metrics) { const auto range = render_partition_range( - static_cast(d_func().prepare_buffer.source_width), - index, d_func().prepare_buffer.active_partitions); + static_cast(prepare_buffer.source_width), + index, prepare_buffer.active_partitions); context.metrics->set(Node_Metric_Kind::prepared_cells, (range.last - range.first) * - d_func().prepare_buffer.source_height); + prepare_buffer.source_height); } }); builder.precede(prepare, task); @@ -125,33 +139,37 @@ void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { return; const auto normalize = builder.emplace( "normalize", "Normalize Afterglow", - [this](const Prepare_Render_Context&) { normalize_render_frame(); }); + [this](const Prepare_Render_Context&) { + normalize_render_frame(); + }); for (const auto task : accumulation) builder.precede(task, normalize); for (int index = 0; index < partition_count; ++index) { const auto task = builder.emplace( "color:" + std::to_string(index), "Afterglow Chunk " + std::to_string(index + 1) + " Color", - [this, index](const Prepare_Render_Context&) { color_partition(index); }); + [this, index](const Prepare_Render_Context&) { + color_partition(index); + }); builder.precede(normalize, task); } } - -void Afterglow_Control::build_paint_graph(Renderable_Graph_Builder& builder) { - const auto view = d_func().render_state_view(); - const auto state = properties(); - const auto& history = view.get(*d_func().history); - const int width = history.empty() - ? 0 - : std::min(state.frequency_point_size.get(), - static_cast(history.back().size())); +void Afterglow::Impl::build_paint_graph(Renderable_Graph_Builder& builder) { + auto& control = static_cast(owner()); + const auto view = render_state_view(); + const auto state = control.state(); + const auto& published_history = view.get(*history); + const int width = published_history.empty() + ? 0 + : std::min(state.frequency_point_size.get(), + static_cast(published_history.back().size())); const int height = state.power_point_size.get() > 0 - ? state.power_point_size.get() - : std::max(1, static_cast(d_func().power_axis->transform(view).pixel_length)); + ? state.power_point_size.get() + : std::max(1, static_cast(power_axis->transform(view).pixel_length)); const std::size_t work_size = width > 0 && height > 0 - ? static_cast(width) * static_cast(height) - : 0; - const int partition_count = d_func().partitioner.graph_partition_count( + ? static_cast(width) * static_cast(height) + : 0; + const int partition_count = partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto paint_image = add_paint_task( @@ -160,34 +178,35 @@ void Afterglow_Control::build_paint_graph(Renderable_Graph_Builder& builder) { paint_render_frame(painter); if (context.metrics) context.metrics->set(Node_Metric_Kind::pixel_count, - d_func().prepare_buffer.work_size); + prepare_buffer.work_size); }); if (partition_count == 0) { builder.precede(builder.find("prepare"), paint_image); - } else { + } + else { for (int index = 0; index < partition_count; ++index) builder.precede(builder.find("color:" + std::to_string(index)), paint_image); } } - -void Afterglow_Control::prepare_render_frame(const Render_State_View& view, - int graph_partition_count) { - const auto& state = render_properties(view); - const auto& history = view.get(*d_func().history); - auto& output = d_func().prepare_buffer; +void Afterglow::Impl::prepare_render_frame(const Render_State_View& view, + int graph_partition_count) { + auto& control = static_cast(owner()); + const auto& state = control.render_state(view); + const auto& published_history = view.get(*history); + auto& output = prepare_buffer; output = {}; output.properties = state; - output.history.assign(history.begin(), history.end()); - if (history.empty()) + output.history.assign(published_history.begin(), published_history.end()); + if (published_history.empty()) return; - const int width = std::min(state.frequency_point_size.get(), static_cast(history.back().size())); + const int width = std::min(state.frequency_point_size.get(), static_cast(published_history.back().size())); const int height = state.power_point_size.get() > 0 ? state.power_point_size.get() - : std::max(1, static_cast(d_func().power_axis->transform(view).pixel_length)); + : std::max(1, static_cast(power_axis->transform(view).pixel_length)); if (width <= 0 || height <= 0) return; - const auto layout = axis_raster_layout(d_func().frequency_axis->transform(view), - d_func().power_axis->transform(view), + const auto layout = axis_raster_layout(frequency_axis->transform(view), + power_axis->transform(view), state.frequency_range, state.power_range, width, height); if (!layout.valid()) @@ -198,13 +217,11 @@ void Afterglow_Control::prepare_render_frame(const Render_State_View& view, output.work_size = static_cast(width) * height; output.intensity.resize(output.work_size); output.pixels.resize(output.work_size); - output.active_partitions = d_func().partitioner.begin(graph_partition_count, - output.work_size); + output.active_partitions = partitioner.begin(graph_partition_count, output.work_size); output.valid = true; } - -void Afterglow_Control::accumulate_partition(int partition_index) { - auto& output = d_func().prepare_buffer; +void Afterglow::Impl::accumulate_partition(int partition_index) { + auto& output = prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& state = output.properties; @@ -231,16 +248,14 @@ void Afterglow_Control::accumulate_partition(int partition_index) { break; } } - -void Afterglow_Control::normalize_render_frame() { - auto& output = d_func().prepare_buffer; +void Afterglow::Impl::normalize_render_frame() { + auto& output = prepare_buffer; if (output.valid) output.maximum = std::max(1.0, *std::max_element(output.intensity.begin(), - output.intensity.end())); + output.intensity.end())); } - -void Afterglow_Control::color_partition(int partition_index) { - auto& output = d_func().prepare_buffer; +void Afterglow::Impl::color_partition(int partition_index) { + auto& output = prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& state = output.properties; @@ -250,40 +265,37 @@ void Afterglow_Control::color_partition(int partition_index) { const int y = static_cast(cell / static_cast(output.source_width)); const int x = static_cast(cell % static_cast(output.source_width)); output.pixels[output.layout.index(x, y, output.source_width, output.source_height)] = - state.color_map.at_normalized(output.intensity[cell] / output.maximum); + state.color_map.at_normalized(output.intensity[cell] / output.maximum); } } - -void Afterglow_Control::paint_render_frame(Painter& painter) { - const auto& output = d_func().prepare_buffer; +void Afterglow::Impl::paint_render_frame(Painter& painter) { + const auto& output = prepare_buffer; if (!output.valid) return; painter.heatmap(output.layout.target, output.layout.width, output.layout.height, output.pixels, Image_Interpolation_Mode::Bilinear); } - -void Afterglow_Control::render_frame_completed( +void Afterglow::Impl::render_frame_completed( std::uint64_t target_interval_ns) { - const auto& output = d_func().prepare_buffer; - if (!output.valid || !is_visible()) + auto& control = static_cast(owner()); + const auto& output = prepare_buffer; + if (!output.valid || !control.is_visible()) return; const auto& state = output.properties; - if (d_func().partitioner.finish( - state.partition_mode, output.active_partitions, target_interval_ns, - static_cast(Scene_Base::task_executor_worker_count()), - output.work_size, 4096)) + if (partitioner.finish( + state.partition_mode, output.active_partitions, target_interval_ns, + static_cast(Scene_Base::task_executor_worker_count()), + output.work_size, 4096)) render_graph_changed(); } - -void Afterglow_Control::prepare_frame(const Prepare_Render_Context& context) { +void Afterglow::Impl::prepare_frame(const Prepare_Render_Context& context) { prepare_render_frame(context.frame.render_state, 1); accumulate_partition(0); normalize_render_frame(); color_partition(0); } - -void Afterglow_Control::paint(Painter& painter, - const Paint_Render_Context&) { +void Afterglow::Impl::paint(Painter& painter, + const Paint_Render_Context&) { paint_render_frame(painter); } } diff --git a/render_2D/render_2D/plottable/Afterglow.h b/render_2D/render_2D/plottable/Afterglow.h index 620383a..61f2486 100644 --- a/render_2D/render_2D/plottable/Afterglow.h +++ b/render_2D/render_2D/plottable/Afterglow.h @@ -2,27 +2,15 @@ #include "Plottable.h" #include "../axis/Axis.h" #include "../renderable/Render_Partition.h" -#include #include #include namespace renderive { -struct Afterglow_Properties { - Range frequency_range{0.0, 10.0}; - Range power_range{0.0, 10.0}; - Nonnegative_Count frequency_point_size; - Nonnegative_Count power_point_size; - Render_Partition_Mode partition_mode{Render_Partition_Mode::Automatic}; - Positive_Count partition_count; - bool interpolate = true; - Unit_Interval attenuation_rate{0.2}; - Color_Map color_map; -}; namespace detail { -class LIB_DECL Afterglow_Control : public Plottable_State, - public ::Render_Frame_Completion { -public: - Afterglow_Control(const Afterglow_Properties& properties, renderive_Owner frequency_axis, renderive_Owner power_axis); - ~Afterglow_Control() override; +struct LIB_DECL Afterglow : Renderable { + struct State; + Afterglow(const State& state, renderive_Owner frequency_axis, + renderive_Owner power_axis); + ~Afterglow() override; [[nodiscard]] std::size_t history_count() const; [[nodiscard]] std::size_t latest_spectrum_point_count() const; [[nodiscard]] std::size_t rendered_cell_count() const; @@ -32,21 +20,20 @@ public: void append_spectrum(const Values& values) { append_spectrum(std::span(values.data(), values.size())); } -protected: - void prepare_frame(const Prepare_Render_Context& context) override; - void paint(Painter& painter, const Paint_Render_Context& context) override; - void build_prepare_graph(Renderable_Graph_Builder& builder) override; - void build_paint_graph(Renderable_Graph_Builder& builder) override; + struct State : next_State { + Range frequency_range{0.0, 10.0}; + Range power_range{0.0, 10.0}; + Nonnegative_Count frequency_point_size; + Nonnegative_Count power_point_size; + Render_Partition_Mode partition_mode{Render_Partition_Mode::Automatic}; + Positive_Count partition_count; + bool interpolate = true; + Unit_Interval attenuation_rate{0.2}; + Color_Map color_map; + }; private: struct Impl; - void prepare_render_frame(const Render_State_View& state, int graph_partition_count); - void accumulate_partition(int partition_index); - void normalize_render_frame(); - void color_partition(int partition_index); - void paint_render_frame(Painter& painter); - void render_frame_completed(std::uint64_t target_interval_ns) override; - void publish() override; }; } -using Afterglow = detail::Attach_Plottable; +using Afterglow = renderable::attach; } diff --git a/render_2D/render_2D/plottable/Constellation_Diagram.cpp b/render_2D/render_2D/plottable/Constellation_Diagram.cpp index 8425940..c1e77fe 100644 --- a/render_2D/render_2D/plottable/Constellation_Diagram.cpp +++ b/render_2D/render_2D/plottable/Constellation_Diagram.cpp @@ -18,7 +18,8 @@ struct Timed_Point { }; using Constellation_History = Plottable_History_Real_Time_Data>; } -struct Constellation_Diagram_Control::Impl : Renderable::Impl { +struct Constellation_Diagram::Impl + : next_Impl { struct Prepare_Buffer { std::vector anchors; std::vector points; @@ -29,44 +30,47 @@ struct Constellation_Diagram_Control::Impl : Renderable::Impl { renderive_Owner q_axis; std::optional points; Prepare_Buffer prepare_buffer; + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; }; -Constellation_Diagram_Control::Constellation_Diagram_Control(const Constellation_Diagram_Properties& properties, renderive_Owner i_axis, renderive_Owner q_axis) - : Plottable_State(properties, std::make_unique( - std::move(i_axis), std::move(q_axis))) { +Constellation_Diagram::Constellation_Diagram( + const State& state, renderive_Owner i_axis, + renderive_Owner q_axis) + : Renderable(With_Attached_Impl{}, state, + std::move(i_axis), std::move(q_axis)) { d_func().points.emplace(*this); } -Constellation_Diagram_Control::~Constellation_Diagram_Control() = default; -void Constellation_Diagram_Control::append_point(PointF point) { +Constellation_Diagram::~Constellation_Diagram() = default; +void Constellation_Diagram::append_point(PointF point) { const auto now = std::chrono::steady_clock::now(); - const int lifetime = get<&Constellation_Diagram_Properties::point_lifetime_ms>(); + const int lifetime = + get_state(); const auto cutoff = now - std::chrono::milliseconds(lifetime); const auto cutoff_ns = std::chrono::duration_cast(cutoff.time_since_epoch()).count(); d_func().points->update({point, now}); d_func().points->discard_before_time_ns(static_cast(cutoff_ns)); - changed(); + d_func().changed(); } -std::size_t Constellation_Diagram_Control::point_count() const { +std::size_t Constellation_Diagram::point_count() const { return d_func().points->size(); } -void Constellation_Diagram_Control::fit_square_to_axes() { - const auto state = properties(); +void Constellation_Diagram::fit_square_to_axes() { + const auto state = Renderable::state(); const double side = std::max(state.i_range.size(), state.q_range.size()); d_func().i_axis->set<&Axis_Properties::coordinates>( Range{state.i_range.center() - side * 0.5, state.i_range.center() + side * 0.5}); d_func().q_axis->set<&Axis_Properties::coordinates>( Range{state.q_range.center() + side * 0.5, state.q_range.center() - side * 0.5}); } -void Constellation_Diagram_Control::publish() { - publish_properties(); -} -void Constellation_Diagram_Control::prepare_frame( +void Constellation_Diagram::Impl::prepare_frame( const Prepare_Render_Context& context) { + auto& control = static_cast(owner()); const auto& view = context.frame.render_state; - const auto& state = render_properties(view); - const auto& points = view.get(*d_func().points); - const Axis_Transform x = d_func().i_axis->transform(view); - const Axis_Transform y = d_func().q_axis->transform(view); - auto& output = d_func().prepare_buffer; + const auto& state = control.render_state(view); + const auto& published_points = view.get(*points); + const Axis_Transform x = i_axis->transform(view); + const Axis_Transform y = q_axis->transform(view); + auto& output = prepare_buffer; output = {}; const int count = static_cast(state.type); const double radius = std::min(state.i_range.size(), state.q_range.size()) * 0.4; @@ -77,18 +81,19 @@ void Constellation_Diagram_Control::prepare_frame( output.anchors.push_back(mapped_point(x, point.x, y, point.y)); } const auto cutoff = std::chrono::steady_clock::now() - std::chrono::milliseconds(state.point_lifetime_ms.get()); - output.points.reserve(points.size()); - for (const auto& value : points) { + output.points.reserve(published_points.size()); + for (const auto& value : published_points) { if (value.time < cutoff) continue; output.points.push_back(mapped_point(x, value.point.x, y, value.point.y)); } } -void Constellation_Diagram_Control::paint(Painter& painter, - const Paint_Render_Context& context) { - const auto& output = d_func().prepare_buffer; +void Constellation_Diagram::Impl::paint(Painter& painter, + const Paint_Render_Context& context) { + auto& control = static_cast(owner()); + const auto& output = prepare_buffer; const auto& view = context.frame.render_state; - const auto& state = render_properties(view); + const auto& state = control.render_state(view); for (const PointF point : output.anchors) painter.circle(point, 3.0, Pen{state.anchor_color}, Brush{state.anchor_color, Brush_Style::Solid}); diff --git a/render_2D/render_2D/plottable/Constellation_Diagram.h b/render_2D/render_2D/plottable/Constellation_Diagram.h index 295131d..3aec681 100644 --- a/render_2D/render_2D/plottable/Constellation_Diagram.h +++ b/render_2D/render_2D/plottable/Constellation_Diagram.h @@ -7,30 +7,27 @@ enum class Constellation_Diagram_Type : std::uint8_t { Psk8 = 8, Psk16 = 16 }; -struct Constellation_Diagram_Properties { - Range i_range{0.0, 100.0}; - Range q_range{0.0, 100.0}; - Color point_color = Color::red(); - Color anchor_color = Color::yellow(); - Nonnegative_Count point_lifetime_ms{1000}; - Constellation_Diagram_Type type = Constellation_Diagram_Type::Psk8; - double phase_offset_radians{}; -}; namespace detail { -class LIB_DECL Constellation_Diagram_Control : public Plottable_State { -public: - Constellation_Diagram_Control(const Constellation_Diagram_Properties& properties, renderive_Owner i_axis, renderive_Owner q_axis); - ~Constellation_Diagram_Control() override; +struct LIB_DECL Constellation_Diagram : Renderable { + struct State; + Constellation_Diagram(const State& state, renderive_Owner i_axis, + renderive_Owner q_axis); + ~Constellation_Diagram() override; void append_point(PointF point); [[nodiscard]] std::size_t point_count() const; void fit_square_to_axes(); -protected: - void prepare_frame(const Prepare_Render_Context& context) override; - void paint(Painter& painter, const Paint_Render_Context& context) override; + struct State : next_State { + Range i_range{0.0, 100.0}; + Range q_range{0.0, 100.0}; + Color point_color = Color::red(); + Color anchor_color = Color::yellow(); + Nonnegative_Count point_lifetime_ms{1000}; + Constellation_Diagram_Type type = Constellation_Diagram_Type::Psk8; + double phase_offset_radians{}; + }; private: struct Impl; - void publish() override; }; -} -using Constellation_Diagram = detail::Attach_Plottable; -} +} // namespace detail +using Constellation_Diagram = renderable::attach; +} // namespace renderive diff --git a/render_2D/render_2D/plottable/Frequency_Trace.cpp b/render_2D/render_2D/plottable/Frequency_Trace.cpp index e2d07c3..fae588a 100644 --- a/render_2D/render_2D/plottable/Frequency_Trace.cpp +++ b/render_2D/render_2D/plottable/Frequency_Trace.cpp @@ -12,7 +12,8 @@ namespace detail { namespace { using Frequency_Trace_History = Plottable_History_Real_Time_Data, std::deque>>; } -struct Frequency_Trace_Control::Impl : Renderable::Impl { +struct Frequency_Trace::Impl + : next_Impl { struct Prepare_Buffer { std::vector points; }; @@ -22,52 +23,54 @@ struct Frequency_Trace_Control::Impl : Renderable::Impl { renderive_Owner value_axis; std::optional samples; Prepare_Buffer prepare_buffer; +protected: + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; }; -Frequency_Trace_Control::Frequency_Trace_Control(const Frequency_Trace_Properties& properties, renderive_Owner time_axis, renderive_Owner value_axis) - : Plottable_State(properties, std::make_unique( - std::move(time_axis), std::move(value_axis))) { +Frequency_Trace::Frequency_Trace(const State& state, + renderive_Owner time_axis, + renderive_Owner value_axis) + : Renderable(With_Attached_Impl{}, state, + std::move(time_axis), std::move(value_axis)) { d_func().samples.emplace(*this); } -Frequency_Trace_Control::~Frequency_Trace_Control() = default; -void Frequency_Trace_Control::append_sample(int tick, double value) { +Frequency_Trace::~Frequency_Trace() = default; +void Frequency_Trace::append_sample(int tick, double value) { auto& d = d_func(); - const int limit = std::max(2, d.time_axis->get<&Time_Axis_Properties::visible_count>()); + const int limit = std::max( + 2, d.time_axis->get<&::renderive::Time_Axis::Properties::visible_count>()); d.samples->update({tick, value}, static_cast(limit)); - changed(); + d_func().changed(); } -void Frequency_Trace_Control::append_sample(Time_Of_Day time, double value) { +void Frequency_Trace::append_sample(Time_Of_Day time, double value) { append_sample(d_func().time_axis->append_time(time), value); } -std::size_t Frequency_Trace_Control::sample_count() const { +std::size_t Frequency_Trace::sample_count() const { return d_func().samples->size(); } -std::size_t Frequency_Trace_Control::rendered_point_count() const { +std::size_t Frequency_Trace::rendered_point_count() const { const std::size_t count = sample_count(); return count >= 2 ? count : 0; } -void Frequency_Trace_Control::publish() { - publish_properties(); -} -void Frequency_Trace_Control::prepare_frame(const Prepare_Render_Context& context) { +void Frequency_Trace::Impl::prepare_frame(const Prepare_Render_Context& context) { const auto& view = context.frame.render_state; - const auto& state = render_properties(view); - auto& d = d_func(); - const auto& samples = view.get(*d.samples); - auto& output = d.prepare_buffer; + const auto& published_samples = view.get(*samples); + auto& output = prepare_buffer; output = {}; - if (samples.size() < 2) + if (published_samples.size() < 2) return; - const Axis_Transform x = d.time_axis->transform(view); - const Axis_Transform y = d.value_axis->transform(view); - output.points.reserve(samples.size()); - for (const auto& [tick, value] : samples) + const Axis_Transform x = time_axis->transform(view); + const Axis_Transform y = value_axis->transform(view); + output.points.reserve(published_samples.size()); + for (const auto& [tick, value] : published_samples) output.points.push_back(mapped_point(x, tick, y, value)); } -void Frequency_Trace_Control::paint(Painter& painter, - const Paint_Render_Context& context) { +void Frequency_Trace::Impl::paint(Painter& painter, + const Paint_Render_Context& context) { + auto& control = static_cast(owner()); const auto& view = context.frame.render_state; - painter.polyline(d_func().prepare_buffer.points, - render_properties(view).pen); + painter.polyline(prepare_buffer.points, + control.render_state(view).pen); } } } diff --git a/render_2D/render_2D/plottable/Frequency_Trace.h b/render_2D/render_2D/plottable/Frequency_Trace.h index 57aa327..65aa7e2 100644 --- a/render_2D/render_2D/plottable/Frequency_Trace.h +++ b/render_2D/render_2D/plottable/Frequency_Trace.h @@ -2,25 +2,22 @@ #include "Plottable.h" #include "../axis/Axis.h" namespace renderive { -struct Frequency_Trace_Properties { - Pen pen{Color::yellow()}; -}; namespace detail { -class LIB_DECL Frequency_Trace_Control : public Plottable_State { -public: - Frequency_Trace_Control(const Frequency_Trace_Properties& properties, renderive_Owner time_axis, renderive_Owner value_axis); - ~Frequency_Trace_Control() override; +struct LIB_DECL Frequency_Trace : Renderable { + struct State; + Frequency_Trace(const State& state, renderive_Owner time_axis, + renderive_Owner value_axis); + ~Frequency_Trace() override; void append_sample(int tick, double value); void append_sample(Time_Of_Day time, double value); [[nodiscard]] std::size_t sample_count() const; [[nodiscard]] std::size_t rendered_point_count() const; -protected: - void prepare_frame(const Prepare_Render_Context& context) override; - void paint(Painter& painter, const Paint_Render_Context& context) override; + struct State : next_State { + Pen pen{Color::yellow()}; + }; private: struct Impl; - void publish() override; }; } -using Frequency_Trace = detail::Attach_Plottable; +using Frequency_Trace = renderable::attach; } diff --git a/render_2D/render_2D/plottable/Performance_Overlay.h b/render_2D/render_2D/plottable/Performance_Overlay.h index b589079..528e936 100644 --- a/render_2D/render_2D/plottable/Performance_Overlay.h +++ b/render_2D/render_2D/plottable/Performance_Overlay.h @@ -1,21 +1,16 @@ #pragma once - #include "../base/Types.h" #include "../scene/Scene.h" - #include #include #include #include - namespace renderive { - struct Performance_Log_Options { bool enabled{}; std::string directory; std::string session_name = "renderive"; }; - struct Performance_Overlay_Style { Font font; int top_margin = 4; @@ -27,12 +22,10 @@ struct Performance_Overlay_Style { Color label_color{190, 190, 190}; Color value_color{235, 218, 130}; }; - struct Performance_Overlay_Options { Performance_Overlay_Style style; Performance_Log_Options log; }; - struct Performance_Display_Snapshot { Performance_Overlay_Style style; Size viewport_size; @@ -40,16 +33,12 @@ struct Performance_Display_Snapshot { std::vector lines; std::uint64_t frame_count{}; }; - class LIB_DECL Performance_Overlay { -public: Performance_Overlay(); explicit Performance_Overlay(Performance_Overlay_Options options); ~Performance_Overlay(); - [[nodiscard]] bool enabled() const noexcept; [[nodiscard]] std::shared_ptr display_snapshot() const; - private: template friend std::shared_ptr attach_performance_overlay(Basic_Scene2D&); @@ -59,10 +48,8 @@ private: friend void set_performance_plot_name(Basic_Scene2D&, std::string); template friend void set_performance_overlay_enabled(Basic_Scene2D&, bool); - struct Impl; std::unique_ptr impl_; - void set_enabled(bool enabled); void set_plot_name(std::string name); void set_options(Performance_Overlay_Options options); @@ -72,7 +59,6 @@ private: std::size_t renderable_count, Size viewport); }; - template std::shared_ptr attach_performance_overlay(Basic_Scene2D& scene) { if (auto existing = scene.performance_overlay()) @@ -110,5 +96,4 @@ void set_performance_overlay_enabled(Basic_Scene2D& scene, bool e overlay->set_enabled(enabled); scene.notify_model_dirty(); } - } // namespace renderive diff --git a/render_2D/render_2D/plottable/Plottable.h b/render_2D/render_2D/plottable/Plottable.h index 5a37b76..00e9770 100644 --- a/render_2D/render_2D/plottable/Plottable.h +++ b/render_2D/render_2D/plottable/Plottable.h @@ -1,18 +1,16 @@ #pragma once #include "../renderable/Renderable_Builder.h" -#include +#include "../renderable/Renderable_p.h" #include -#include +#include #include #include #include #include namespace renderive { class Abs_Axis; - template -class Clamped_Property { -public: +struct Clamped_Property { using Value = Value_Type; Clamped_Property() : value(Default) {} Clamped_Property(Value_Type value) : value(std::clamp(value, Minimum, Maximum)) {} @@ -31,8 +29,7 @@ private: }; using Nonnegative_Count = Clamped_Property::max(), 0>; using Positive_Count = Clamped_Property::max(), 1>; -class Unit_Interval { -public: +struct Unit_Interval { using Value = double; Unit_Interval() = default; Unit_Interval(double value) : value(std::isfinite(value) ? std::clamp(value, 0.0, 1.0) : 0.0) {} @@ -51,80 +48,108 @@ private: }; namespace detail { class Paint_Overlay {}; +template +struct Renderable : renderable::render_base { + using Render_Base = renderable::render_base; +protected: + struct State + : Render_Base::template next_State {}; -template -class Plottable_State - : public Double_State_Strategy> { -public: - using Properties = Properties_Type; + template + using next_State = + renderable_inheritance::State_Node; + + template + using next_Impl = + renderable_inheritance::Impl_Node; + template + using Business_Builder = Renderable_Builder; using State_Observer = Observer_State; - using Base = Double_State_Strategy; - explicit Plottable_State(const Properties& properties) - : Base(properties, - With_Observer{ - State_Observer(Renderable_State_Observer(this))}) {} - template - Plottable_State(const Properties& properties, - std::unique_ptr implementation) - : Base(properties, - With_Observer{ - State_Observer(Renderable_State_Observer(this))}, - std::move(implementation), true) {} - template Value> - Plottable_State& set(Value&& value) { - Base::template set(std::forward(value)); - if constexpr (requires { &Properties::partition_count; &Properties::partition_mode; }) { + 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)...), + true) {} + using Render_Base::Render_Base; + template Value> + void set_state(Value&& value) { + state_storage().template set( + std::forward(value)); + if constexpr (requires { + &State_Type::partition_count; + &State_Type::partition_mode; + }) { if constexpr (std::same_as) { - if constexpr (Member == &Properties::partition_count) { - this->render_graph_changed(); - return *this; + decltype(&State_Type::partition_count)>) { + if constexpr (Member == &State_Type::partition_count) { + this->d_func().render_graph_changed(); + return; } } if constexpr (std::same_as) { - if constexpr (Member == &Properties::partition_mode) { - this->render_graph_changed(); - return *this; + decltype(&State_Type::partition_mode)>) { + if constexpr (Member == &State_Type::partition_mode) { + this->d_func().render_graph_changed(); + return; } } } - using Member_Value = decltype(std::declval().*Member); + using Member_Value = decltype(std::declval().*Member); if constexpr (Paint_Only_Property_Value) - this->paint_changed(); + this->d_func().paint_changed(); else - this->changed(); - return *this; + this->d_func().changed(); } - template - auto get() const { - return Base::template get(); + template + auto get_state() const { + return state_storage().template get(); } -protected: - Properties properties() const { - return Base::read([](const Properties& value) { return value; }); + template + requires std::invocable + void update_state(Update&& update) { + state_storage().update(std::forward(update)); + this->d_func().changed(); } - template - auto property_value() const { - return Base::read([](const Properties& value) { return value.*Member; }); + template + requires std::invocable + decltype(auto) read_state(Read&& read) const { + return state_storage().read(std::forward(read)); } - const Properties& render_properties(const Render_State_View& state) const noexcept { - return state.get(static_cast(*this)); + template + State_Type state() const { + return state_storage().read( + [](const State_Type& value) { + return value; + }); } - void publish_properties() { - Base::publish(); + template + const State_Type& render_state( + const Render_State_View& state_view) const noexcept { + return state_view.get(state_storage()); } private: - std::uint64_t state_revision() const override { - return Base::state_revision(); + template + using State_Storage = + Double_State_Storage; + template + [[nodiscard]] State_Storage& state_storage() noexcept { + return dynamic_cast&>( + this->template d_func<::renderive::Renderable::Impl>()); + } + template + [[nodiscard]] const State_Storage& state_storage() const noexcept { + return dynamic_cast&>( + this->template d_func<::renderive::Renderable::Impl>()); } - using Base::read; - using Base::update; }; -template -using Attach_Plottable = Attach_Builder; - } } 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 2b7da03..3cc1a91 100644 --- a/render_2D/render_2D/plottable/Plottable_Real_Time_Data.h +++ b/render_2D/render_2D/plottable/Plottable_Real_Time_Data.h @@ -6,15 +6,15 @@ #include namespace renderive::detail { using Plottable_Real_Time_Data_Observer = Observer_State; -inline Plottable_Real_Time_Data_Observer observe_real_time_data(Renderable& renderable) { +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)); } template -class Plottable_History_Real_Time_Data - : public History_Real_Time_Data { +struct Plottable_History_Real_Time_Data +: History_Real_Time_Data { using Base = History_Real_Time_Data; -public: - explicit Plottable_History_Real_Time_Data(Renderable& owner) + explicit Plottable_History_Real_Time_Data(::renderive::Renderable& owner) : Base(observe_real_time_data(owner)), binding_(this->bind_renderable(owner)) {} private: Real_Time_Data_Binding binding_; diff --git a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp index 7374e9f..95aa32f 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp @@ -10,18 +10,29 @@ namespace renderive { namespace detail { namespace { -struct Selection_Interaction_Base {}; struct Selection_Interaction { bool selecting{}; PointF selection_start{}; PointF selection_current{}; }; -using Selection_Interaction_State = Double_State_Strategy; +using Selection_Interaction_State = Double_State_Storage; RectF axis_content_rect(const Axis_Transform& first, const Axis_Transform& second) { return mapped_rect(first, second, first.coordinate_range, second.coordinate_range); } } -struct Selection_Rectangle_Overlay_Control::Impl : Renderable::Impl { +struct Selection_Rectangle_Overlay::Impl + : next_Impl, + public Paint_Overlay, + Renderable_Event_Handler { + struct Observer : next_Observer { + + static void handle(Impl& impl, + const Renderable_Event_View& observation) { + if (observation.event == Renderable_Observer_Event::Published) + impl.interaction.publish(); + } + }; + struct Prepared_Region { RectF pixels; std::string label; @@ -41,30 +52,39 @@ struct Selection_Rectangle_Overlay_Control::Impl : Renderable::Impl { std::vector>> regions; Selection_Interaction_State interaction; Prepare_Buffer prepare_buffer; +protected: + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; +private: + void handle_event(const Event& event) override; }; -Selection_Rectangle_Overlay_Control::Selection_Rectangle_Overlay_Control(const Selection_Rectangle_Overlay_Properties& properties, renderive_Owner horizontal_axis, renderive_Owner vertical_axis) - : Plottable_State(properties, std::make_unique( - std::move(horizontal_axis), std::move(vertical_axis))) { +Selection_Rectangle_Overlay::Selection_Rectangle_Overlay( + const State& state, renderive_Owner horizontal_axis, + renderive_Owner vertical_axis) + : Renderable(With_Attached_Impl{}, state, + std::move(horizontal_axis), std::move(vertical_axis)) { d_func().regions.emplace(*this); } -Selection_Rectangle_Overlay_Control::~Selection_Rectangle_Overlay_Control() = default; -std::vector Selection_Rectangle_Overlay_Control::selected_regions() const { +Selection_Rectangle_Overlay::~Selection_Rectangle_Overlay() = default; +std::vector Selection_Rectangle_Overlay::selected_regions() const { return d_func().regions->snapshot(); } -void Selection_Rectangle_Overlay_Control::clear_selected_regions() { +void Selection_Rectangle_Overlay::clear_selected_regions() { d_func().regions->clear(); d_func().interaction.update([](Selection_Interaction& interaction) { interaction.selecting = false; }); - changed(); + d_func().changed(); } -void Selection_Rectangle_Overlay_Control::handle_event(const Event& event) { - const RectF content = axis_content_rect(d_func().horizontal_axis->transform(), d_func().vertical_axis->transform()); - if(event.type == Event_Type::Pointer_Press) { +void Selection_Rectangle_Overlay::Impl::handle_event(const Event& event) { + auto& control = static_cast(owner()); + const RectF content = + axis_content_rect(horizontal_axis->transform(), vertical_axis->transform()); + if (event.type == Event_Type::Pointer_Press) { const auto& pointer = static_cast(event); - if(pointer.button != Mouse_Button::Left || !content.contains(pointer.position)) + if (pointer.button != Mouse_Button::Left || !content.contains(pointer.position)) return; - d_func().interaction.update([&](Selection_Interaction& interaction) { + interaction.update([&](Selection_Interaction& interaction) { interaction.selecting = true; interaction.selection_start = interaction.selection_current = pointer.position; }); @@ -72,61 +92,60 @@ void Selection_Rectangle_Overlay_Control::handle_event(const Event& event) { changed(); return; } - if(event.type == Event_Type::Pointer_Move) { + if (event.type == Event_Type::Pointer_Move) { const auto& pointer = static_cast(event); bool selecting{}; - d_func().interaction.update([&](Selection_Interaction& interaction) { + interaction.update([&](Selection_Interaction& interaction) { selecting = interaction.selecting; - if(selecting) + if (selecting) interaction.selection_current = pointer.position; }); - if(!selecting) + if (!selecting) return; event.accept(); changed(); return; } - if(event.type != Event_Type::Pointer_Release) + if (event.type != Event_Type::Pointer_Release) return; const auto& pointer = static_cast(event); PointF start; bool selecting{}; - d_func().interaction.update([&](Selection_Interaction& interaction) { + interaction.update([&](Selection_Interaction& interaction) { selecting = interaction.selecting; - if(!selecting) + if (!selecting) return; interaction.selecting = false; start = interaction.selection_start; interaction.selection_current = pointer.position; }); - if(!selecting) + if (!selecting) return; - const Axis_Transform horizontal = d_func().horizontal_axis->transform(); - const Axis_Transform vertical = d_func().vertical_axis->transform(); + const Axis_Transform horizontal = horizontal_axis->transform(); + const Axis_Transform vertical = vertical_axis->transform(); const double first_start = horizontal.point_to_coord(start); const double second_start = vertical.point_to_coord(start); - const RectF region{first_start, second_start, - horizontal.point_to_coord(pointer.position) - first_start, - vertical.point_to_coord(pointer.position) - second_start}; - if(std::abs(region.width) > 1e-9 && std::abs(region.height) > 1e-9) - d_func().regions->update(region.normalized()); + const RectF region{ + first_start, second_start, + horizontal.point_to_coord(pointer.position) - first_start, + vertical.point_to_coord(pointer.position) - second_start + }; + if (std::abs(region.width) > 1e-9 && std::abs(region.height) > 1e-9) + regions->update(region.normalized()); event.accept(); changed(); } -void Selection_Rectangle_Overlay_Control::publish() { - publish_properties(); - d_func().interaction.publish(); -} -void Selection_Rectangle_Overlay_Control::prepare_frame( +void Selection_Rectangle_Overlay::Impl::prepare_frame( const Prepare_Render_Context& context) { + auto& control = static_cast(owner()); const auto& view = context.frame.render_state; - const auto& state = render_properties(view); - const auto& interaction = view.get(d_func().interaction); - const auto horizontal = d_func().horizontal_axis->transform(view); - const auto vertical = d_func().vertical_axis->transform(view); - auto& output = d_func().prepare_buffer; + const auto& state = control.render_state(view); + const auto& published_interaction = view.get(interaction); + const auto horizontal = horizontal_axis->transform(view); + const auto vertical = vertical_axis->transform(view); + auto& output = prepare_buffer; output = {}; - for (const RectF& region : view.get(*d_func().regions)) { + for (const RectF& region : view.get(*regions)) { const RectF pixels = mapped_rect(horizontal, vertical, {region.x, region.right()}, {region.y, region.bottom()}); @@ -134,18 +153,20 @@ void Selection_Rectangle_Overlay_Control::prepare_frame( text << region.width << " x " << region.height; output.regions.push_back({pixels, text.str()}); } - output.selecting = interaction.selecting; + output.selecting = published_interaction.selecting; output.active_selection = { - interaction.selection_start.x, - interaction.selection_start.y, - interaction.selection_current.x - interaction.selection_start.x, - interaction.selection_current.y - interaction.selection_start.y}; + published_interaction.selection_start.x, + published_interaction.selection_start.y, + published_interaction.selection_current.x - published_interaction.selection_start.x, + published_interaction.selection_current.y - published_interaction.selection_start.y + }; } -void Selection_Rectangle_Overlay_Control::paint( +void Selection_Rectangle_Overlay::Impl::paint( Painter& painter, const Paint_Render_Context& context) { - const auto& output = d_func().prepare_buffer; + auto& control = static_cast(owner()); + const auto& output = prepare_buffer; const auto& view = context.frame.render_state; - const auto& state = render_properties(view); + const auto& state = control.render_state(view); for (const auto& region : output.regions) { painter.rect(region.pixels, state.selection_border_pen, state.selection_brush); diff --git a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.h b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.h index 8b38160..ee61dcc 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.h +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.h @@ -2,27 +2,27 @@ #include "Plottable.h" #include "../axis/Axis.h" namespace renderive { -struct Selection_Rectangle_Overlay_Properties { - Font label_font; - Pen label_pen{Color::white()}; - Brush selection_brush{Color{0, 0, 255, 50}, Brush_Style::Solid}; - Pen selection_border_pen{Color::white(), 1.0, Line_Style::Dash}; -}; namespace detail { -class LIB_DECL Selection_Rectangle_Overlay_Control : public Plottable_State, public Paint_Overlay, public Event_Handler { -public: - Selection_Rectangle_Overlay_Control(const Selection_Rectangle_Overlay_Properties& properties, renderive_Owner horizontal_axis, renderive_Owner vertical_axis); - ~Selection_Rectangle_Overlay_Control() override; +struct LIB_DECL Selection_Rectangle_Overlay + : Renderable { + struct State; + + Selection_Rectangle_Overlay( + const State& state, renderive_Owner horizontal_axis, + renderive_Owner vertical_axis); + ~Selection_Rectangle_Overlay() override; [[nodiscard]] std::vector selected_regions() const; void clear_selected_regions(); - void handle_event(const Event& event) override; -protected: - void prepare_frame(const Prepare_Render_Context& context) override; - void paint(Painter& painter, const Paint_Render_Context& context) override; + struct State : next_State { + Font label_font; + Pen label_pen{Color::white()}; + Brush selection_brush{Color{0, 0, 255, 50}, Brush_Style::Solid}; + Pen selection_border_pen{Color::white(), 1.0, Line_Style::Dash}; + }; private: struct Impl; - void publish() override; }; } -using Selection_Rectangle_Overlay = detail::Attach_Plottable; +using Selection_Rectangle_Overlay = +renderable::attach; } diff --git a/render_2D/render_2D/plottable/Spectrum.cpp b/render_2D/render_2D/plottable/Spectrum.cpp index 1970c57..0184ac7 100644 --- a/render_2D/render_2D/plottable/Spectrum.cpp +++ b/render_2D/render_2D/plottable/Spectrum.cpp @@ -3,19 +3,19 @@ #include "../render/Blend2D_Cache.h" #include "../renderable/Render_Partition.h" #include "../renderable/Renderable_p.h" +#include #include #include #include #include namespace renderive::detail { namespace { -struct Spectrum_Interaction_Base {}; struct Spectrum_Interaction { std::vector markers; int selected_marker = -1; Hover_Tooltip_Runtime tooltip; }; -using Spectrum_Interaction_State = Double_State_Strategy; +using Spectrum_Interaction_State = Double_State_Storage; struct Prepared_Curve { std::vector points; std::vector fill; @@ -41,7 +41,7 @@ struct Spectrum_Extreme_Buffer { bool maximum{}; }; struct Spectrum_Prepare_Buffer { - Spectrum_Properties properties; + ::renderive::Spectrum::Properties properties; const Spectrum_Frame* frame{}; Spectrum_Interaction interaction; Axis_Transform frequency_axis; @@ -57,13 +57,11 @@ struct Spectrum_Prepare_Buffer { std::size_t work_size{}; bool valid{}; }; - struct Curve_Partition { std::span values; Range domain; Range clip_domain; }; - Curve_Partition curve_partition(std::span values, Range domain, int partition_index, int partition_count) { if (values.size() < 2 || partition_count <= 0) @@ -76,7 +74,7 @@ Curve_Partition curve_partition(std::span values, Range domain, const std::size_t last = std::min(segment_count, core.last + 1); const auto coordinate = [domain, segment_count](std::size_t index) { return domain.origin + domain.length() * static_cast(index) / - static_cast(segment_count); + static_cast(segment_count); }; return { values.subspan(first, last - first + 1), @@ -91,17 +89,18 @@ Prepared_Curve prepare_curve(std::span values, Range domain, Prepared_Curve result; result.points = curve_points(values, domain, frequency_axis, power_axis, visible_only, interpolation); - if(result.points.size() < 2) + if (result.points.size() < 2) return result; result.fill.reserve(result.points.size() + 2); PointF first_baseline = result.points.front(); PointF last_baseline = result.points.back(); const double baseline = - power_axis.coord_to_pixel(power_axis.coordinate_range.target); + power_axis.coord_to_pixel(power_axis.coordinate_range.target); if (power_axis.orientation == Orientation::Horizontal) { first_baseline.x = baseline; last_baseline.x = baseline; - } else { + } + else { first_baseline.y = baseline; last_baseline.y = baseline; } @@ -112,15 +111,15 @@ Prepared_Curve prepare_curve(std::span values, Range domain, } void paint_curve(Painter& painter, const Prepared_Curve& curve, const Pen& pen, const Brush& brush) { - if(curve.points.size() < 2) + if (curve.points.size() < 2) return; - if(brush.enabled() && !curve.fill.empty()) + if (brush.enabled() && !curve.fill.empty()) painter.polygon(curve.fill, Pen{.style = Line_Style::None}, brush); painter.polyline(curve.points, pen); } -double spectrum_power_at(const Spectrum_Properties& properties, const Spectrum_Frame& frame, double frequency, bool& ok) { +double spectrum_power_at(const ::renderive::Spectrum::Properties& properties, const Spectrum_Frame& frame, double frequency, bool& ok) { ok = false; - if(frame.samples.empty() || !properties.frequency_range.contains(frequency) || properties.frequency_range.length() == 0.0) + if (frame.samples.empty() || !properties.frequency_range.contains(frequency) || properties.frequency_range.length() == 0.0) return 0.0; const double normalized = (frequency - properties.frequency_range.origin) / properties.frequency_range.length(); const double position = std::clamp(normalized, 0.0, 1.0) * static_cast(frame.samples.size() - 1); @@ -131,7 +130,22 @@ double spectrum_power_at(const Spectrum_Properties& properties, const Spectrum_F return frame.samples[lower] * (1.0 - fraction) + frame.samples[upper] * fraction; } } -struct Spectrum_Control::Impl : Renderable::Impl { +struct Spectrum::Impl + : next_Impl, + Spectrum_Real_Time_Data_Strategy, + Renderable_Event_Handler, + ::Render_Frame_Completion { + struct Observer : next_Observer { + + static void handle(Impl& impl, + const Renderable_Event_View& observation) { + if (observation.event != Renderable_Observer_Event::Published) + return; + impl.Spectrum_Real_Time_Data_Strategy::publish(); + impl.interaction.publish(); + } + }; + Impl(renderive_Owner frequency, renderive_Owner power) : frequency_axis(std::move(frequency)), @@ -141,18 +155,34 @@ struct Spectrum_Control::Impl : Renderable::Impl { Spectrum_Interaction_State interaction; Adaptive_Render_Partitioner partitioner; Spectrum_Prepare_Buffer prepare_buffer; + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; + void build_prepare_graph(Renderable_Graph_Builder& builder) override; + void build_paint_graph(Renderable_Graph_Builder& builder) override; + void prepare_render_frame(const Render_State_View& state, int graph_partition_count); + void prepare_partition(int partition_index); + void paint_partition(Painter& painter, int partition_index, + const Render_State_View& state); + void paint_background(Painter& painter, const Render_State_View& state); + void paint_overlay(Painter& painter, const Render_State_View& state); + void handle_event(const Event& event) override; + void render_frame_completed(std::uint64_t target_interval_ns) override; + [[nodiscard]] std::uint64_t dependent_state_revision() const noexcept { + return revision(); + } }; -Spectrum_Control::Spectrum_Control(const Spectrum_Properties& properties, renderive_Owner frequency_axis, renderive_Owner power_axis) - : Spectrum_Real_Time_Data_Strategy( - std::in_place, - properties, - std::make_unique(std::move(frequency_axis), - std::move(power_axis))) {} -Spectrum_Control::~Spectrum_Control() = default; -void Spectrum_Control::update_samples(std::span values) { - if(get<&Spectrum_Properties::frequency_point_size>() <= 0) - set<&Spectrum_Properties::frequency_point_size>(static_cast(values.size())); - const auto& published = render_buffer_value(); +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)) {} +Spectrum::~Spectrum() = default; +void Spectrum::update_samples(std::span values) { + if (get_state() <= 0) + set_state( + static_cast(values.size())); + const auto& published = + d_func().render_buffer_value(); const bool shape_changed = published.samples.size() != values.size(); Spectrum_Frame next; next.samples.assign(values.begin(), values.end()); @@ -170,148 +200,154 @@ void Spectrum_Control::update_samples(std::span values) { for (std::size_t index = 0; index < values.size(); ++index) next.minima[index] = std::min(published.minima[index], values[index]); } - Spectrum_Real_Time_Data_Strategy::write(std::move(next)); + d_func().write(std::move(next)); if (shape_changed) - render_graph_changed(); + d_func().render_graph_changed(); else - changed(); + d_func().changed(); } -void Spectrum_Control::update_samples(std::pmr::vector&& values) { +void Spectrum::update_samples(std::pmr::vector&& values) { update_samples(std::span(values.data(), values.size())); } -std::size_t Spectrum_Control::sample_count() const { - return render_buffer_value().samples.size(); +std::size_t Spectrum::sample_count() const { + return d_func().cache_buffer_value().samples.size(); } -std::size_t Spectrum_Control::rendered_point_count() const { - const auto state = properties(); - return curve_points(render_buffer_value().samples, +std::size_t Spectrum::rendered_point_count() const { + const auto state = Renderable::state(); + return curve_points(d_func().cache_buffer_value().samples, state.frequency_range, d_func().frequency_axis->transform(), d_func().power_axis->transform(), state.visible_range_only, state.interpolation_mode).size(); } -double Spectrum_Control::power_at(double frequency, bool& ok) const { - const auto state = properties(); +double Spectrum::power_at(double frequency, bool& ok) const { + const auto state = Renderable::state(); return spectrum_power_at( - state, render_buffer_value(), frequency, ok); + state, d_func().cache_buffer_value(), + frequency, ok); } -void Spectrum_Control::add_custom_marker(double frequency) { +void Spectrum::add_custom_marker(double frequency) { add_custom_line_marker(frequency); } -void Spectrum_Control::add_custom_line_marker(double frequency) { - d_func().interaction.update([frequency](Spectrum_Interaction& interaction) { interaction.markers.push_back(frequency); }); - changed(); +void Spectrum::add_custom_line_marker(double frequency) { + d_func().interaction.update([frequency](Spectrum_Interaction& interaction) { + interaction.markers.push_back(frequency); + }); + d_func().changed(); } -void Spectrum_Control::remove_custom_marker(double frequency) { +void Spectrum::remove_custom_marker(double frequency) { bool removed{}; d_func().interaction.update([&](Spectrum_Interaction& interaction) { - if(interaction.markers.empty()) + if (interaction.markers.empty()) return; - auto closest = std::min_element(interaction.markers.begin(), interaction.markers.end(), [frequency](double left, double right) { return std::abs(left - frequency) < std::abs(right - frequency); }); + auto closest = std::min_element(interaction.markers.begin(), interaction.markers.end(), [frequency](double left, double right) { + return std::abs(left - frequency) < std::abs(right - frequency); + }); const int removed_index = static_cast(std::distance(interaction.markers.begin(), closest)); interaction.markers.erase(closest); - if(interaction.selected_marker == removed_index) + if (interaction.selected_marker == removed_index) interaction.selected_marker = -1; - else if(interaction.selected_marker > removed_index) + else if (interaction.selected_marker > removed_index) --interaction.selected_marker; removed = true; }); - if(removed) - changed(); + if (removed) + d_func().changed(); } -void Spectrum_Control::remove_selected_marker() { +void Spectrum::remove_selected_marker() { bool removed{}; d_func().interaction.update([&](Spectrum_Interaction& interaction) { - if(interaction.selected_marker < 0 || interaction.selected_marker >= static_cast(interaction.markers.size())) + if (interaction.selected_marker < 0 || interaction.selected_marker >= static_cast(interaction.markers.size())) return; interaction.markers.erase(interaction.markers.begin() + interaction.selected_marker); interaction.selected_marker = -1; removed = true; }); - if(removed) - changed(); + if (removed) + d_func().changed(); } -void Spectrum_Control::clear_custom_markers() { +void Spectrum::clear_custom_markers() { d_func().interaction.update([](Spectrum_Interaction& interaction) { interaction.markers.clear(); interaction.selected_marker = -1; }); - changed(); + d_func().changed(); } -int Spectrum_Control::selectable_line_marker_count() const { - return d_func().interaction.read([](const Spectrum_Interaction& interaction) { return static_cast(interaction.markers.size()); }); +int Spectrum::selectable_line_marker_count() const { + return d_func().interaction.read([](const Spectrum_Interaction& interaction) { + return static_cast(interaction.markers.size()); + }); } -int Spectrum_Control::selected_marker_index() const { +int Spectrum::selected_marker_index() const { return d_func().interaction.get<&Spectrum_Interaction::selected_marker>(); } -void Spectrum_Control::set_selected_marker_index(int index) { - d_func().interaction.update([index](Spectrum_Interaction& interaction) { interaction.selected_marker = index >= 0 && index < static_cast(interaction.markers.size()) ? index : -1; }); - changed(); +void Spectrum::set_selected_marker_index(int index) { + d_func().interaction.update([index](Spectrum_Interaction& interaction) { + interaction.selected_marker = index >= 0 && index < static_cast(interaction.markers.size()) ? index : -1; + }); + d_func().changed(); } -void Spectrum_Control::select_next_marker() { +void Spectrum::select_next_marker() { d_func().interaction.update([](Spectrum_Interaction& interaction) { - if(interaction.markers.empty()) + if (interaction.markers.empty()) interaction.selected_marker = -1; else interaction.selected_marker = (interaction.selected_marker + 1) % static_cast(interaction.markers.size()); }); - changed(); + d_func().changed(); } -void Spectrum_Control::select_previous_marker() { +void Spectrum::select_previous_marker() { d_func().interaction.update([](Spectrum_Interaction& interaction) { - if(interaction.markers.empty()) + if (interaction.markers.empty()) interaction.selected_marker = -1; else interaction.selected_marker = (interaction.selected_marker <= 0 ? static_cast(interaction.markers.size()) : interaction.selected_marker) - 1; }); - changed(); + d_func().changed(); } -void Spectrum_Control::clear_marker_selection() { +void Spectrum::clear_marker_selection() { set_selected_marker_index(-1); } -double Spectrum_Control::marker_frequency(int index) const { - return d_func().interaction.read([index](const Spectrum_Interaction& interaction) { return index >= 0 && index < static_cast(interaction.markers.size()) ? interaction.markers[index] : 0.0; }); +double Spectrum::marker_frequency(int index) const { + return d_func().interaction.read([index](const Spectrum_Interaction& interaction) { + return index >= 0 && index < static_cast(interaction.markers.size()) ? interaction.markers[index] : 0.0; + }); } -void Spectrum_Control::set_marker_frequency(int index, double frequency) { +void Spectrum::set_marker_frequency(int index, double frequency) { bool updated{}; d_func().interaction.update([&](Spectrum_Interaction& interaction) { - if(index < 0 || index >= static_cast(interaction.markers.size())) + if (index < 0 || index >= static_cast(interaction.markers.size())) return; interaction.markers[index] = frequency; updated = true; }); - if(updated) - changed(); + if (updated) + d_func().changed(); } -void Spectrum_Control::set_current_marker_frequency(double frequency) { +void Spectrum::set_current_marker_frequency(double frequency) { const int index = selected_marker_index(); - if(index >= 0) + if (index >= 0) set_marker_frequency(index, frequency); } -void Spectrum_Control::handle_event(const Event& event) { +void Spectrum::Impl::handle_event(const Event& event) { + auto& control = static_cast(owner()); bool updated{}; - d_func().interaction.update([&](Spectrum_Interaction& interaction) { updated = update_hover_tooltip(interaction.tooltip, event); }); - if(updated) + interaction.update([&](Spectrum_Interaction& interaction) { + updated = update_hover_tooltip(interaction.tooltip, event); + }); + if (updated) changed(); } -void Spectrum_Control::publish() { - publish_properties(); - Spectrum_Real_Time_Data_Strategy::publish(); - d_func().interaction.publish(); -} - -std::uint64_t Spectrum_Control::state_revision() const { - return Plottable_State::Base::state_revision() + - Spectrum_Real_Time_Data_Strategy::revision(); -} - -void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { - const auto state = properties(); - const auto& published_frame = render_buffer_value(); +void Spectrum::Impl::build_prepare_graph( + Renderable_Graph_Builder& builder) { + auto& control = static_cast(owner()); + const auto state = control.state(); + const auto& published_frame = + render_buffer_value(); const std::size_t work_size = published_frame.samples.size() > 1 - ? published_frame.samples.size() - 1 - : 0; - const int partition_count = d_func().partitioner.graph_partition_count( + ? published_frame.samples.size() - 1 + : 0; + const int partition_count = partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 128); const auto prepare = add_prepare_task( @@ -320,10 +356,10 @@ void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { prepare_render_frame(context.frame.render_state, partition_count); if (context.metrics) { context.metrics->set(Node_Metric_Kind::input_count, - d_func().prepare_buffer.frame->samples.size()); + prepare_buffer.frame->samples.size()); context.metrics->set(Node_Metric_Kind::chunk_size, - d_func().prepare_buffer.work_size / - std::max(1, d_func().prepare_buffer.active_partitions)); + prepare_buffer.work_size / + std::max(1, prepare_buffer.active_partitions)); } }); for (int index = 0; index < partition_count; ++index) { @@ -334,8 +370,8 @@ void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { prepare_partition(index); if (context.metrics) { const auto range = render_partition_range( - d_func().prepare_buffer.work_size, index, - d_func().prepare_buffer.active_partitions); + prepare_buffer.work_size, index, + prepare_buffer.active_partitions); context.metrics->set(Node_Metric_Kind::prepared_cells, range.last - range.first); } @@ -343,14 +379,16 @@ void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { builder.precede(prepare, partition); } } - -void Spectrum_Control::build_paint_graph(Renderable_Graph_Builder& builder) { - const auto state = properties(); - const auto& published_frame = render_buffer_value(); +void Spectrum::Impl::build_paint_graph( + Renderable_Graph_Builder& builder) { + auto& control = static_cast(owner()); + const auto state = control.state(); + const auto& published_frame = + render_buffer_value(); const std::size_t work_size = published_frame.samples.size() > 1 - ? published_frame.samples.size() - 1 - : 0; - const int partition_count = d_func().partitioner.graph_partition_count( + ? published_frame.samples.size() - 1 + : 0; + const int partition_count = partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 128); const auto paint = add_paint_task( @@ -363,7 +401,7 @@ void Spectrum_Control::build_paint_graph(Renderable_Graph_Builder& builder) { paint_overlay(painter, context.frame.render_state); if (context.metrics) context.metrics->set(Node_Metric_Kind::primitive_count, - d_func().prepare_buffer.work_size); + prepare_buffer.work_size); }); if (partition_count == 0) builder.precede(builder.find("prepare"), paint); @@ -372,23 +410,24 @@ void Spectrum_Control::build_paint_graph(Renderable_Graph_Builder& builder) { builder.precede(builder.find("chunk_prepare:" + std::to_string(index)), paint); } - -void Spectrum_Control::prepare_render_frame(const Render_State_View& view, - int graph_partition_count) { - const auto& published_frame = render_buffer_value(); - auto& output = d_func().prepare_buffer; +void Spectrum::Impl::prepare_render_frame( + const Render_State_View& view, int graph_partition_count) { + auto& control = static_cast(owner()); + const auto& published_frame = + render_buffer_value(); + auto& output = prepare_buffer; output = {}; - output.properties = render_properties(view); + output.properties = control.render_state(view); output.frame = &published_frame; - output.interaction = view.get(d_func().interaction); - output.frequency_axis = d_func().frequency_axis->transform(view); - output.power_axis = d_func().power_axis->transform(view); + output.interaction = view.get(interaction); + output.frequency_axis = frequency_axis->transform(view); + output.power_axis = power_axis->transform(view); output.valid = axes_are_orthogonal(output.frequency_axis, output.power_axis); output.work_size = published_frame.samples.size() > 1 - ? published_frame.samples.size() - 1 - : 0; - output.active_partitions = d_func().partitioner.begin(graph_partition_count, - output.work_size); + ? published_frame.samples.size() - 1 + : 0; + output.active_partitions = partitioner.begin(graph_partition_count, + output.work_size); output.partitions.resize(static_cast(graph_partition_count)); if (!output.valid) return; @@ -409,7 +448,8 @@ void Spectrum_Control::prepare_render_frame(const Render_State_View& view, output.power_axis, output.power_axis.coordinate_range.origin), mapped_point(output.frequency_axis, state.center_frequency, output.power_axis, output.power_axis.coordinate_range.target), - Spectrum_Marker_Style::middle}); + Spectrum_Marker_Style::middle + }); for (std::size_t index = 0; index < output.interaction.markers.size(); ++index) { const double frequency = output.interaction.markers[index]; output.markers.push_back({ @@ -419,24 +459,26 @@ void Spectrum_Control::prepare_render_frame(const Render_State_View& view, output.power_axis.coordinate_range.target), static_cast(index) == output.interaction.selected_marker ? Spectrum_Marker_Style::selected - : Spectrum_Marker_Style::marker}); + : Spectrum_Marker_Style::marker + }); } if (!output.frame->samples.empty() && (state.max_marker_visible || state.use_min_marker)) { const auto prepare_extreme = [&](bool maximum) { const auto iterator = maximum - ? std::max_element(output.frame->samples.begin(), output.frame->samples.end()) - : std::min_element(output.frame->samples.begin(), output.frame->samples.end()); + ? std::max_element(output.frame->samples.begin(), output.frame->samples.end()) + : std::min_element(output.frame->samples.begin(), output.frame->samples.end()); const std::size_t index = static_cast( std::distance(output.frame->samples.begin(), iterator)); const double denominator = output.frame->samples.size() > 1 - ? static_cast(output.frame->samples.size() - 1) - : 1.0; + ? static_cast(output.frame->samples.size() - 1) + : 1.0; const double frequency = state.frequency_range.origin + - state.frequency_range.length() * static_cast(index) / denominator; + state.frequency_range.length() * static_cast(index) / denominator; output.extremes.push_back({ mapped_point(output.frequency_axis, frequency, output.power_axis, *iterator), - maximum}); + maximum + }); }; if (state.max_marker_visible) prepare_extreme(true); @@ -453,15 +495,16 @@ void Spectrum_Control::prepare_render_frame(const Render_State_View& view, std::ostringstream text; text << std::fixed << std::setprecision(2) << frequency << " Hz " << power; output.tooltip_text = text.str(); - output.tooltip_box = {output.interaction.tooltip.position.x + 8.0, - output.interaction.tooltip.position.y + 8.0, - 170.0, 24.0}; + output.tooltip_box = { + output.interaction.tooltip.position.x + 8.0, + output.interaction.tooltip.position.y + 8.0, + 170.0, 24.0 + }; } } } - -void Spectrum_Control::prepare_partition(int partition_index) { - auto& output = d_func().prepare_buffer; +void Spectrum::Impl::prepare_partition(int partition_index) { + auto& output = prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& state = output.properties; @@ -492,41 +535,41 @@ void Spectrum_Control::prepare_partition(int partition_index) { current.values, current.domain, output.frequency_axis, output.power_axis, state.visible_range_only, state.interpolation_mode); } - -void Spectrum_Control::paint_partition(Painter& painter, int partition_index, - const Render_State_View& view) { - const auto& output = d_func().prepare_buffer; +void Spectrum::Impl::paint_partition( + Painter& painter, int partition_index, const Render_State_View& view) { + auto& control = static_cast(owner()); + const auto& output = prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& partition = output.partitions[static_cast(partition_index)]; - const auto& state = render_properties(view); + const auto& state = control.render_state(view); const auto clip = painter.scoped_clip(partition.clip); paint_curve(painter, partition.maximum, state.max_pen, state.max_brush); paint_curve(painter, partition.minimum, state.min_pen, state.min_brush); paint_curve(painter, partition.current, state.current_pen, state.current_brush); } - -void Spectrum_Control::paint_background(Painter& painter, - const Render_State_View& view) { - const auto& output = d_func().prepare_buffer; +void Spectrum::Impl::paint_background( + Painter& painter, const Render_State_View& view) { + auto& control = static_cast(owner()); + const auto& output = prepare_buffer; if (!output.valid || output.sweep_region.empty()) return; painter.rect(output.sweep_region, Pen{.style = Line_Style::None}, - render_properties(view).sweep_region_brush); + control.render_state(view).sweep_region_brush); } - -void Spectrum_Control::paint_overlay(Painter& painter, - const Render_State_View& view) { - const auto& output = d_func().prepare_buffer; +void Spectrum::Impl::paint_overlay( + Painter& painter, const Render_State_View& view) { + auto& control = static_cast(owner()); + const auto& output = prepare_buffer; if (!output.valid) return; - const auto& paint_state = render_properties(view); + const auto& paint_state = control.render_state(view); for (const auto& marker : output.markers) { const Pen& pen = marker.style == Spectrum_Marker_Style::middle - ? paint_state.middle_frequency_pen - : marker.style == Spectrum_Marker_Style::selected - ? paint_state.selected_marker_pen - : paint_state.marker_pen; + ? paint_state.middle_frequency_pen + : marker.style == Spectrum_Marker_Style::selected + ? paint_state.selected_marker_pen + : paint_state.marker_pen; if (pen.enabled()) painter.line(marker.first, marker.second, pen); } @@ -543,27 +586,26 @@ void Spectrum_Control::paint_overlay(Painter& painter, paint_state.tooltip_text_pen); } } - -void Spectrum_Control::render_frame_completed( +void Spectrum::Impl::render_frame_completed( std::uint64_t target_interval_ns) { - const auto& output = d_func().prepare_buffer; - if (!output.valid || !is_visible()) + auto& control = static_cast(owner()); + const auto& output = prepare_buffer; + if (!output.valid || !control.is_visible()) return; const auto& state = output.properties; - if (d_func().partitioner.finish( - state.partition_mode, output.active_partitions, target_interval_ns, - static_cast(Scene_Base::task_executor_worker_count()), - output.work_size, 128)) + if (partitioner.finish( + state.partition_mode, output.active_partitions, target_interval_ns, + static_cast(Scene_Base::task_executor_worker_count()), + output.work_size, 128)) render_graph_changed(); } - -void Spectrum_Control::prepare_frame(const Prepare_Render_Context& context) { +void Spectrum::Impl::prepare_frame( + const Prepare_Render_Context& context) { prepare_render_frame(context.frame.render_state, 1); prepare_partition(0); } - -void Spectrum_Control::paint(Painter& painter, - const Paint_Render_Context& context) { +void Spectrum::Impl::paint( + Painter& painter, const Paint_Render_Context& context) { paint_background(painter, context.frame.render_state); paint_partition(painter, 0, context.frame.render_state); paint_overlay(painter, context.frame.render_state); diff --git a/render_2D/render_2D/plottable/Spectrum.h b/render_2D/render_2D/plottable/Spectrum.h index fa0f642..23b1c62 100644 --- a/render_2D/render_2D/plottable/Spectrum.h +++ b/render_2D/render_2D/plottable/Spectrum.h @@ -4,36 +4,10 @@ #include "../axis/Axis.h" #include "../renderable/Render_Partition.h" #include -#include #include #include #include namespace renderive { -struct Spectrum_Properties : Hover_Tooltip_Properties { - Nonnegative_Count frequency_point_size; - Render_Partition_Mode partition_mode{Render_Partition_Mode::Automatic}; - Positive_Count partition_count; - Range frequency_range{}; - double center_frequency = 50.0; - Range sweep_frequency_range{40.0, 60.0}; - bool max_hold_visible{}; - bool min_hold_visible{}; - bool max_marker_visible{}; - bool use_min_marker{}; - bool sweep_region_visible{}; - bool visible_range_only{true}; - Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Value; - Brush max_brush; - Brush current_brush; - Brush min_brush; - Pen max_pen{Color::red()}; - Pen current_pen{Color::green()}; - Pen min_pen{Color::white()}; - Pen selected_marker_pen{Color{0, 0, 139, 255}, 2.0}; - Pen marker_pen{Color::red()}; - Pen middle_frequency_pen{Color::red()}; - Brush sweep_region_brush{Color{255, 255, 0, 100}, Brush_Style::Solid}; -}; namespace detail { struct Spectrum_Frame { std::vector samples; @@ -41,31 +15,51 @@ struct Spectrum_Frame { std::vector minima; }; struct Spectrum_Frame_Tag {}; -using Spectrum_Buffer_Layout = - ::Double_Buffer_Layout< - ::Buffered_Data>; -class Spectrum_Real_Time_Data_Strategy - : public ::Multi_Double_Buffer_Strategy< - Plottable_State, Spectrum_Buffer_Layout, - std::mutex> { - using Base = - ::Multi_Double_Buffer_Strategy< - Plottable_State, Spectrum_Buffer_Layout, - std::mutex>; -protected: +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; using Base::Base; + using Base::cache_buffer_value; using Base::publish; using Base::render_buffer_value; using Base::revision; using Base::write; }; - -class LIB_DECL Spectrum_Control : public Spectrum_Real_Time_Data_Strategy, - public Event_Handler, - public ::Render_Frame_Completion { -public: - Spectrum_Control(const Spectrum_Properties& properties, renderive_Owner frequency_axis, renderive_Owner power_axis); - ~Spectrum_Control() override; +struct LIB_DECL Spectrum : Renderable { + struct State : next_State, Hover_Tooltip_Properties { + Nonnegative_Count frequency_point_size; + Render_Partition_Mode partition_mode{Render_Partition_Mode::Automatic}; + Positive_Count partition_count; + Range frequency_range{}; + double center_frequency = 50.0; + Range sweep_frequency_range{40.0, 60.0}; + bool max_hold_visible{}; + bool min_hold_visible{}; + bool max_marker_visible{}; + bool use_min_marker{}; + bool sweep_region_visible{}; + bool visible_range_only{true}; + Line_Interpolation_Mode interpolation_mode = + Line_Interpolation_Mode::Linear_Value; + Brush max_brush; + Brush current_brush; + Brush min_brush; + Pen max_pen{Color::red()}; + Pen current_pen{Color::green()}; + Pen min_pen{Color::white()}; + Pen selected_marker_pen{Color{0, 0, 139, 255}, 2.0}; + Pen marker_pen{Color::red()}; + Pen middle_frequency_pen{Color::red()}; + Brush sweep_region_brush{ + Color{255, 255, 0, 100}, + Brush_Style::Solid + }; + }; + Spectrum(const State& state, + renderive_Owner frequency_axis, + renderive_Owner power_axis); + ~Spectrum() override; void update_samples(std::span values); void update_samples(std::pmr::vector&& values); template @@ -89,24 +83,9 @@ public: [[nodiscard]] double marker_frequency(int index) const; void set_marker_frequency(int index, double frequency); void set_current_marker_frequency(double frequency); - void handle_event(const Event& event) override; -protected: - void prepare_frame(const Prepare_Render_Context& context) override; - void paint(Painter& painter, const Paint_Render_Context& context) override; - void build_prepare_graph(Renderable_Graph_Builder& builder) override; - void build_paint_graph(Renderable_Graph_Builder& builder) override; private: struct Impl; - void prepare_render_frame(const Render_State_View& state, int graph_partition_count); - void prepare_partition(int partition_index); - void paint_partition(Painter& painter, int partition_index, - const Render_State_View& state); - void paint_background(Painter& painter, const Render_State_View& state); - void paint_overlay(Painter& painter, const Render_State_View& state); - void render_frame_completed(std::uint64_t target_interval_ns) override; - void publish() override; - [[nodiscard]] std::uint64_t state_revision() const override; }; } -using Spectrum = detail::Attach_Plottable; +using Spectrum = renderable::attach; } diff --git a/render_2D/render_2D/plottable/Sweep_Spectrum.cpp b/render_2D/render_2D/plottable/Sweep_Spectrum.cpp index 9de3eb5..8ae5448 100644 --- a/render_2D/render_2D/plottable/Sweep_Spectrum.cpp +++ b/render_2D/render_2D/plottable/Sweep_Spectrum.cpp @@ -13,12 +13,27 @@ namespace { using Sweep_Spectrum_History = Plottable_History_Real_Time_Data, std::deque>>; std::vector flatten(const std::deque>& blocks) { std::vector values; - for(const auto& block : blocks) + for (const auto& block : blocks) values.insert(values.end(), block.begin(), block.end()); return values; } } -struct Sweep_Spectrum_Control::Impl : Renderable::Impl { +struct Sweep_Spectrum::Impl + : next_Impl { + struct Observer : next_Observer { + + static void handle(Impl& impl, + const Renderable_Event_View& observation) { + if (observation.event != Renderable_Observer_Event::Published) + return; + auto& control = + static_cast(impl.owner()); + const int limit = + control.get_state(); + impl.blocks->retain_latest(static_cast(limit)); + } + }; + struct Prepare_Buffer { std::vector points; PointF current_first; @@ -31,69 +46,71 @@ struct Sweep_Spectrum_Control::Impl : Renderable::Impl { renderive_Owner power_axis; std::optional blocks; Prepare_Buffer prepare_buffer; + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; }; -Sweep_Spectrum_Control::Sweep_Spectrum_Control(const Sweep_Spectrum_Properties& properties, renderive_Owner frequency_axis, renderive_Owner power_axis) - : Plottable_State(properties, std::make_unique( - std::move(frequency_axis), std::move(power_axis))) { +Sweep_Spectrum::Sweep_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)) { d_func().blocks.emplace(*this); } -Sweep_Spectrum_Control::~Sweep_Spectrum_Control() = default; -void Sweep_Spectrum_Control::append_block(std::span values) { - if(get<&Sweep_Spectrum_Properties::bins_per_block>() <= 0) - set<&Sweep_Spectrum_Properties::bins_per_block>(static_cast(values.size())); - const int limit = get<&Sweep_Spectrum_Properties::block_count>(); +Sweep_Spectrum::~Sweep_Spectrum() = default; +void Sweep_Spectrum::append_block(std::span values) { + if (get_state() <= 0) + set_state( + static_cast(values.size())); + const int limit = get_state(); d_func().blocks->update({values.begin(), values.end()}, static_cast(limit)); - changed(); + d_func().changed(); } -void Sweep_Spectrum_Control::append_block(std::pmr::vector&& values) { +void Sweep_Spectrum::append_block(std::pmr::vector&& values) { append_block(std::span(values.data(), values.size())); } -std::size_t Sweep_Spectrum_Control::stored_block_count() const { +std::size_t Sweep_Spectrum::stored_block_count() const { return d_func().blocks->size(); } -std::size_t Sweep_Spectrum_Control::stored_point_count() const { +std::size_t Sweep_Spectrum::stored_point_count() const { const auto blocks = d_func().blocks->snapshot(); std::size_t count{}; - for(const auto& block : blocks) + for (const auto& block : blocks) count += block.size(); return count; } -std::size_t Sweep_Spectrum_Control::rendered_point_count() const { - const auto state = properties(); +std::size_t Sweep_Spectrum::rendered_point_count() const { + const auto state = Renderable::state(); const auto values = flatten(d_func().blocks->snapshot()); return curve_points(values, state.frequency_range, d_func().frequency_axis->transform(), d_func().power_axis->transform(), state.visible_range_only, state.interpolation_mode).size(); } -void Sweep_Spectrum_Control::publish() { - publish_properties(); - const int limit = get<&Sweep_Spectrum_Properties::block_count>(); - d_func().blocks->retain_latest(static_cast(limit)); -} -void Sweep_Spectrum_Control::prepare_frame(const Prepare_Render_Context& context) { +void Sweep_Spectrum::Impl::prepare_frame(const Prepare_Render_Context& context) { + auto& control = static_cast(owner()); const auto& view = context.frame.render_state; - const auto& state = render_properties(view); - const auto& blocks = view.get(*d_func().blocks); - const auto values = flatten(blocks); - auto& output = d_func().prepare_buffer; + const auto& state = control.render_state(view); + const auto& published_blocks = view.get(*blocks); + const auto values = flatten(published_blocks); + auto& output = prepare_buffer; output = {}; if (values.size() < 2) return; - const Axis_Transform x = d_func().frequency_axis->transform(view); - const Axis_Transform y = d_func().power_axis->transform(view); + const Axis_Transform x = frequency_axis->transform(view); + const Axis_Transform y = power_axis->transform(view); output.points = curve_points(values, state.frequency_range, x, y, state.visible_range_only, state.interpolation_mode); - const double completed = std::min(1.0, static_cast(blocks.size()) / state.block_count.get()); + const double completed = std::min(1.0, static_cast(published_blocks.size()) / state.block_count.get()); const double frequency = state.frequency_range.origin + state.frequency_range.length() * completed; output.current_first = mapped_point(x, frequency, y, y.coordinate_range.origin); output.current_second = mapped_point(x, frequency, y, y.coordinate_range.target); output.valid = true; } -void Sweep_Spectrum_Control::paint(Painter& painter, - const Paint_Render_Context& context) { - const auto& output = d_func().prepare_buffer; +void Sweep_Spectrum::Impl::paint(Painter& painter, + const Paint_Render_Context& context) { + auto& control = static_cast(owner()); + const auto& output = prepare_buffer; if (!output.valid) return; const auto& view = context.frame.render_state; - const auto& state = render_properties(view); + const auto& state = control.render_state(view); painter.polyline(output.points, state.pen); painter.line(output.current_first, output.current_second, state.current_frequency_pen); diff --git a/render_2D/render_2D/plottable/Sweep_Spectrum.h b/render_2D/render_2D/plottable/Sweep_Spectrum.h index a1d5ac6..0d338c5 100644 --- a/render_2D/render_2D/plottable/Sweep_Spectrum.h +++ b/render_2D/render_2D/plottable/Sweep_Spectrum.h @@ -4,20 +4,12 @@ #include #include namespace renderive { -struct Sweep_Spectrum_Properties { - Range frequency_range{}; - Nonnegative_Count bins_per_block; - Positive_Count block_count; - Pen pen{Color::yellow()}; - Pen current_frequency_pen{Color::red(), 2.0}; - bool visible_range_only{true}; - Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Value; -}; namespace detail { -class LIB_DECL Sweep_Spectrum_Control : public Plottable_State { -public: - Sweep_Spectrum_Control(const Sweep_Spectrum_Properties& properties, renderive_Owner frequency_axis, renderive_Owner power_axis); - ~Sweep_Spectrum_Control() override; +struct LIB_DECL Sweep_Spectrum : Renderable { + struct State; + Sweep_Spectrum(const State& state, renderive_Owner frequency_axis, + renderive_Owner power_axis); + ~Sweep_Spectrum() override; void append_block(std::span values); void append_block(std::pmr::vector&& values); template @@ -27,13 +19,19 @@ public: [[nodiscard]] std::size_t stored_block_count() const; [[nodiscard]] std::size_t stored_point_count() const; [[nodiscard]] std::size_t rendered_point_count() const; -protected: - void prepare_frame(const Prepare_Render_Context& context) override; - void paint(Painter& painter, const Paint_Render_Context& context) override; + struct State : next_State { + Range frequency_range{}; + Nonnegative_Count bins_per_block; + Positive_Count block_count; + Pen pen{Color::yellow()}; + Pen current_frequency_pen{Color::red(), 2.0}; + bool visible_range_only{true}; + Line_Interpolation_Mode interpolation_mode = + Line_Interpolation_Mode::Linear_Value; + }; private: struct Impl; - void publish() override; }; } -using Sweep_Spectrum = detail::Attach_Plottable; +using Sweep_Spectrum = renderable::attach; } diff --git a/render_2D/render_2D/plottable/Waterfall.cpp b/render_2D/render_2D/plottable/Waterfall.cpp index c0d3e06..95ba6e8 100644 --- a/render_2D/render_2D/plottable/Waterfall.cpp +++ b/render_2D/render_2D/plottable/Waterfall.cpp @@ -3,6 +3,7 @@ #include "Plottable_Real_Time_Data.h" #include "../renderable/Render_Partition.h" #include "../renderable/Renderable_p.h" +#include #include #include #include @@ -14,14 +15,13 @@ struct Waterfall_Row { int tick{}; std::vector values; }; -struct Waterfall_Interaction_Base {}; struct Waterfall_Interaction { Hover_Tooltip_Runtime tooltip; }; using Waterfall_History = Plottable_History_Real_Time_Data>; -using Waterfall_Interaction_State = Double_State_Strategy; +using Waterfall_Interaction_State = Double_State_Storage; struct Waterfall_Prepare_Buffer { - Waterfall_Properties properties; + ::renderive::Waterfall::Properties properties; std::deque rows; Waterfall_Interaction interaction; Axis_Raster_Layout layout; @@ -36,7 +36,7 @@ struct Waterfall_Prepare_Buffer { std::size_t work_size{}; bool valid{}; }; -std::size_t waterfall_work_size(const Waterfall_Properties& state, +std::size_t waterfall_work_size(const ::renderive::Waterfall::Properties& state, const std::deque& rows, const Axis_Transform& frequency_axis) { if (rows.empty()) @@ -54,11 +54,23 @@ std::size_t waterfall_work_size(const Waterfall_Properties& state, source_width, state.visible_range_only); return columns - ? static_cast(columns->last - columns->first + 1) * rows.size() - : 0; + ? static_cast(columns->last - columns->first + 1) * rows.size() + : 0; } } -struct Waterfall_Control::Impl : Renderable::Impl { +struct Waterfall::Impl + : next_Impl, + Renderable_Event_Handler, + ::Render_Frame_Completion { + struct Observer : next_Observer { + + static void handle(Impl& impl, + const Renderable_Event_View& observation) { + if (observation.event == Renderable_Observer_Event::Published) + impl.interaction.publish(); + } + }; + Impl(renderive_Owner frequency, renderive_Owner time) : frequency_axis(std::move(frequency)), time_axis(std::move(time)) {} @@ -68,47 +80,64 @@ struct Waterfall_Control::Impl : Renderable::Impl { Waterfall_Interaction_State interaction; Adaptive_Render_Partitioner partitioner; Waterfall_Prepare_Buffer prepare_buffer; + void prepare_render_frame(const Render_State_View& state, int graph_partition_count); + void paint_render_frame(Painter& painter, const Render_State_View& state); + void render_partition(int partition_index); +protected: + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; + void build_prepare_graph(Renderable_Graph_Builder& builder) override; + void build_paint_graph(Renderable_Graph_Builder& builder) override; +private: + void handle_event(const Event& event) override; + void render_frame_completed(std::uint64_t target_interval_ns) override; }; -Waterfall_Control::Waterfall_Control(const Waterfall_Properties& properties, renderive_Owner frequency_axis, renderive_Owner time_axis) - : Plottable_State(properties, std::make_unique( - std::move(frequency_axis), std::move(time_axis))) { +Waterfall::Waterfall(const State& state, + renderive_Owner frequency_axis, + renderive_Owner time_axis) + : Renderable(With_Attached_Impl{}, state, + std::move(frequency_axis), std::move(time_axis)) { d_func().rows.emplace(*this); } -Waterfall_Control::~Waterfall_Control() = default; -void Waterfall_Control::append_row(int tick, std::span values) { +Waterfall::~Waterfall() = default; +void Waterfall::append_row(int tick, std::span values) { const std::size_t limit = static_cast( - std::max(2, d_func().time_axis->get<&Time_Axis_Properties::visible_count>())); - if(get<&Waterfall_Properties::frequency_bin_count>() <= 0) - set<&Waterfall_Properties::frequency_bin_count>(static_cast(values.size())); + std::max(2, d_func().time_axis->get< + &::renderive::Time_Axis::Properties::visible_count>())); + if (get_state() <= 0) + set_state( + static_cast(values.size())); d_func().rows->update({tick, {values.begin(), values.end()}}, limit); - render_graph_changed(); + d_func().render_graph_changed(); } -void Waterfall_Control::append_row(int tick, std::pmr::vector&& values) { +void Waterfall::append_row(int tick, std::pmr::vector&& values) { append_row(tick, std::span(values.data(), values.size())); } -void Waterfall_Control::append_row(Time_Of_Day time, std::span values) { +void Waterfall::append_row(Time_Of_Day time, std::span values) { append_row(d_func().time_axis->append_time(time), values); } -void Waterfall_Control::append_row(Time_Of_Day time, std::pmr::vector&& values) { +void Waterfall::append_row(Time_Of_Day time, std::pmr::vector&& values) { append_row(time, std::span(values.data(), values.size())); } -std::size_t Waterfall_Control::row_count() const { +std::size_t Waterfall::row_count() const { return d_func().rows->size(); } -std::size_t Waterfall_Control::stored_point_count() const { +std::size_t Waterfall::stored_point_count() const { const auto rows = d_func().rows->snapshot(); std::size_t count{}; - for(const auto& row : rows) + for (const auto& row : rows) count += row.values.size(); return count; } -std::size_t Waterfall_Control::rendered_cell_count() const { - const auto state = properties(); +std::size_t Waterfall::rendered_cell_count() const { + const auto state = Renderable::state(); const auto rows = d_func().rows->snapshot(); - if(rows.empty()) + if (rows.empty()) return 0; - const int source_width = std::min(state.frequency_bin_count.get(), static_cast(std::min_element(rows.begin(), rows.end(), [](const auto& left, const auto& right) { return left.values.size() < right.values.size(); })->values.size())); - if(source_width <= 0) + const int source_width = std::min(state.frequency_bin_count.get(), static_cast(std::min_element(rows.begin(), rows.end(), [](const auto& left, const auto& right) { + return left.values.size() < right.values.size(); + })->values.size())); + if (source_width <= 0) return 0; const auto columns = frequency_columns( state.frequency_range, @@ -117,24 +146,23 @@ std::size_t Waterfall_Control::rendered_cell_count() const { state.visible_range_only); return columns ? static_cast(columns->last - columns->first + 1) * rows.size() : 0; } -void Waterfall_Control::handle_event(const Event& event) { +void Waterfall::Impl::handle_event(const Event& event) { + auto& control = static_cast(owner()); bool updated{}; - d_func().interaction.update([&](Waterfall_Interaction& interaction) { updated = update_hover_tooltip(interaction.tooltip, event); }); - if(updated) + interaction.update([&](Waterfall_Interaction& interaction) { + updated = update_hover_tooltip(interaction.tooltip, event); + }); + if (updated) changed(); } -void Waterfall_Control::publish() { - publish_properties(); - d_func().interaction.publish(); -} - -void Waterfall_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { - const auto view = d_func().render_state_view(); - const auto state = properties(); - const auto& rows = view.get(*d_func().rows); +void Waterfall::Impl::build_prepare_graph(Renderable_Graph_Builder& builder) { + auto& control = static_cast(owner()); + const auto view = render_state_view(); + const auto state = control.state(); + const auto& published_rows = view.get(*rows); const std::size_t work_size = waterfall_work_size( - state, rows, d_func().frequency_axis->transform(view)); - const int partition_count = d_func().partitioner.graph_partition_count( + state, published_rows, frequency_axis->transform(view)); + const int partition_count = partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto prepare = add_prepare_task( @@ -143,10 +171,10 @@ void Waterfall_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { prepare_render_frame(context.frame.render_state, partition_count); if (context.metrics) { context.metrics->set(Node_Metric_Kind::input_count, - d_func().prepare_buffer.rows.size()); + prepare_buffer.rows.size()); context.metrics->set(Node_Metric_Kind::chunk_size, - d_func().prepare_buffer.work_size / - std::max(1, d_func().prepare_buffer.active_partitions)); + prepare_buffer.work_size / + std::max(1, prepare_buffer.active_partitions)); } }); std::vector partitions; @@ -159,8 +187,8 @@ void Waterfall_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { render_partition(index); if (context.metrics) { const auto range = render_partition_range( - d_func().prepare_buffer.work_size, index, - d_func().prepare_buffer.active_partitions); + prepare_buffer.work_size, index, + prepare_buffer.active_partitions); context.metrics->set(Node_Metric_Kind::prepared_cells, range.last - range.first); } @@ -169,87 +197,94 @@ void Waterfall_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { partitions.push_back(partition); } } -void Waterfall_Control::build_paint_graph(Renderable_Graph_Builder& builder) { +void Waterfall::Impl::build_paint_graph(Renderable_Graph_Builder& builder) { + auto& control = static_cast(owner()); const auto paint_image = add_paint_task( builder, "paint", "Paint Waterfall", [this](Painter& painter, const Paint_Render_Context& context) { paint_render_frame(painter, context.frame.render_state); if (context.metrics) context.metrics->set(Node_Metric_Kind::pixel_count, - d_func().prepare_buffer.work_size); + prepare_buffer.work_size); }); - const auto view = d_func().render_state_view(); - const auto state = properties(); - const auto& rows = view.get(*d_func().rows); + const auto view = render_state_view(); + const auto state = control.state(); + const auto& published_rows = view.get(*rows); const std::size_t work_size = waterfall_work_size( - state, rows, d_func().frequency_axis->transform(view)); - const int count = d_func().partitioner.graph_partition_count( + state, published_rows, frequency_axis->transform(view)); + const int count = partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); if (count == 0) { builder.precede(builder.find("prepare"), paint_image); - } else { + } + else { for (int index = 0; index < count; ++index) builder.precede(builder.find("chunk_prepare:" + std::to_string(index)), paint_image); } } - -void Waterfall_Control::prepare_render_frame(const Render_State_View& view, - int graph_partition_count) { - const auto& state = render_properties(view); - const auto& rows = view.get(*d_func().rows); - auto& output = d_func().prepare_buffer; +void Waterfall::Impl::prepare_render_frame(const Render_State_View& view, + int graph_partition_count) { + auto& control = static_cast(owner()); + const auto& state = control.render_state(view); + const auto& published_rows = view.get(*rows); + auto& output = prepare_buffer; output = {}; output.properties = state; - output.rows.assign(rows.begin(), rows.end()); - output.interaction = view.get(d_func().interaction); - if (rows.empty()) + output.rows.assign(published_rows.begin(), published_rows.end()); + output.interaction = view.get(interaction); + if (published_rows.empty()) return; - const int source_width = std::min(state.frequency_bin_count.get(), static_cast(std::min_element(rows.begin(), rows.end(), [](const auto& left, const auto& right) { return left.values.size() < right.values.size(); })->values.size())); - const int height = static_cast(rows.size()); + const int source_width = std::min(state.frequency_bin_count.get(), static_cast(std::min_element(published_rows.begin(), published_rows.end(), [](const auto& left, const auto& right) { + return left.values.size() < right.values.size(); + })->values.size())); + const int height = static_cast(published_rows.size()); if (source_width <= 0 || height <= 0) return; - const Axis_Transform frequency_axis = d_func().frequency_axis->transform(view); - const Axis_Transform time_axis = d_func().time_axis->transform(view); - const auto columns = frequency_columns(state.frequency_range, frequency_axis.coordinate_range, + const Axis_Transform published_frequency_axis = frequency_axis->transform(view); + const Axis_Transform published_time_axis = time_axis->transform(view); + const auto columns = frequency_columns(state.frequency_range, published_frequency_axis.coordinate_range, source_width, state.visible_range_only); if (!columns) return; const int width = columns->last - columns->first + 1; - const Range time_range = rows.size() == 1 - ? time_axis.coordinate_range - : Range{static_cast(rows.front().tick), - static_cast(rows.back().tick)}; - const auto layout = axis_raster_layout(frequency_axis, time_axis, columns->range, + const Range time_range = published_rows.size() == 1 + ? published_time_axis.coordinate_range + : Range{ + static_cast(published_rows.front().tick), + static_cast(published_rows.back().tick) + }; + const auto layout = axis_raster_layout(published_frequency_axis, published_time_axis, columns->range, time_range, width, height); if (!layout.valid()) return; output.layout = layout; output.columns = *columns; - output.frequency_axis = frequency_axis; + output.frequency_axis = published_frequency_axis; output.source_width = width; output.source_height = height; output.work_size = static_cast(width) * height; output.pixels.resize(output.work_size); - output.active_partitions = d_func().partitioner.begin(graph_partition_count, - output.work_size); + output.active_partitions = partitioner.begin(graph_partition_count, + output.work_size); if (state.tooltip_enabled && output.interaction.tooltip.active && output.layout.target.contains(output.interaction.tooltip.position)) { - const double frequency = frequency_axis.point_to_coord( + const double frequency = published_frequency_axis.point_to_coord( output.interaction.tooltip.position); std::ostringstream text; text << std::fixed << std::setprecision(2) << frequency << " Hz"; output.tooltip_text = text.str(); - output.tooltip_box = {output.interaction.tooltip.position.x + 8.0, - output.interaction.tooltip.position.y + 8.0, - 110.0, 24.0}; + output.tooltip_box = { + output.interaction.tooltip.position.x + 8.0, + output.interaction.tooltip.position.y + 8.0, + 110.0, 24.0 + }; } output.valid = true; } - -void Waterfall_Control::render_partition(int partition_index) { - auto& output = d_func().prepare_buffer; +void Waterfall::Impl::render_partition(int partition_index) { + auto& output = prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& state = output.properties; @@ -261,18 +296,18 @@ void Waterfall_Control::render_partition(int partition_index) { const int x = static_cast(cell % static_cast(output.source_width)); const auto& row = rows[static_cast(y)].values; output.pixels[output.layout.index(x, y, output.source_width, output.source_height)] = - state.color_map.at_normalized(normalized_value( - row[static_cast(output.columns.first) + static_cast(x)], - state.power_range)); + state.color_map.at_normalized(normalized_value( + row[static_cast(output.columns.first) + static_cast(x)], + state.power_range)); } } - -void Waterfall_Control::paint_render_frame(Painter& painter, - const Render_State_View& view) { - const auto& output = d_func().prepare_buffer; +void Waterfall::Impl::paint_render_frame(Painter& painter, + const Render_State_View& view) { + auto& control = static_cast(owner()); + const auto& output = prepare_buffer; if (!output.valid) return; - const auto& paint_state = render_properties(view); + const auto& paint_state = control.render_state(view); painter.heatmap(output.layout.target, output.layout.width, output.layout.height, output.pixels, paint_state.interpolation_mode); if (!output.tooltip_text.empty()) { @@ -283,26 +318,25 @@ void Waterfall_Control::paint_render_frame(Painter& painter, paint_state.tooltip_text_pen); } } - -void Waterfall_Control::render_frame_completed( +void Waterfall::Impl::render_frame_completed( std::uint64_t target_interval_ns) { - const auto& output = d_func().prepare_buffer; - if (!output.valid || !is_visible()) + auto& control = static_cast(owner()); + const auto& output = prepare_buffer; + if (!output.valid || !control.is_visible()) return; const auto& state = output.properties; - if (d_func().partitioner.finish( - state.partition_mode, output.active_partitions, target_interval_ns, - static_cast(Scene_Base::task_executor_worker_count()), - output.work_size, 4096)) + if (partitioner.finish( + state.partition_mode, output.active_partitions, target_interval_ns, + static_cast(Scene_Base::task_executor_worker_count()), + output.work_size, 4096)) render_graph_changed(); } - -void Waterfall_Control::prepare_frame(const Prepare_Render_Context& context) { +void Waterfall::Impl::prepare_frame(const Prepare_Render_Context& context) { prepare_render_frame(context.frame.render_state, 1); render_partition(0); } -void Waterfall_Control::paint(Painter& painter, - const Paint_Render_Context& context) { +void Waterfall::Impl::paint(Painter& painter, + const Paint_Render_Context& context) { paint_render_frame(painter, context.frame.render_state); } } diff --git a/render_2D/render_2D/plottable/Waterfall.h b/render_2D/render_2D/plottable/Waterfall.h index 37ce577..76596ad 100644 --- a/render_2D/render_2D/plottable/Waterfall.h +++ b/render_2D/render_2D/plottable/Waterfall.h @@ -3,27 +3,16 @@ #include "Plottable.h" #include "../axis/Axis.h" #include "../renderable/Render_Partition.h" -#include #include #include namespace renderive { -struct Waterfall_Properties : Hover_Tooltip_Properties { - Range frequency_range{0.0, 10.0}; - Range power_range{0.0, 10.0}; - Nonnegative_Count frequency_bin_count; - Render_Partition_Mode partition_mode{Render_Partition_Mode::Automatic}; - Positive_Count partition_count; - bool visible_range_only{true}; - Image_Interpolation_Mode interpolation_mode = Image_Interpolation_Mode::Nearest; - Color_Map color_map; -}; namespace detail { -class LIB_DECL Waterfall_Control : public Plottable_State, - public Event_Handler, - public ::Render_Frame_Completion { -public: - Waterfall_Control(const Waterfall_Properties& properties, renderive_Owner frequency_axis, renderive_Owner time_axis); - ~Waterfall_Control() override; +struct LIB_DECL Waterfall : Renderable { + struct State; + Waterfall(const State& state, + renderive_Owner frequency_axis, + renderive_Owner time_axis); + ~Waterfall() override; void append_row(int tick, std::span values); void append_row(int tick, std::pmr::vector&& values); void append_row(Time_Of_Day time, std::span values); @@ -39,20 +28,20 @@ public: [[nodiscard]] std::size_t row_count() const; [[nodiscard]] std::size_t stored_point_count() const; [[nodiscard]] std::size_t rendered_cell_count() const; - void handle_event(const Event& event) override; -protected: - void prepare_frame(const Prepare_Render_Context& context) override; - void paint(Painter& painter, const Paint_Render_Context& context) override; - void build_prepare_graph(Renderable_Graph_Builder& builder) override; - void build_paint_graph(Renderable_Graph_Builder& builder) override; + struct State : next_State, Hover_Tooltip_Properties { + Range frequency_range{0.0, 10.0}; + Range power_range{0.0, 10.0}; + Nonnegative_Count frequency_bin_count; + Render_Partition_Mode partition_mode{Render_Partition_Mode::Automatic}; + Positive_Count partition_count; + bool visible_range_only{true}; + Image_Interpolation_Mode interpolation_mode = + Image_Interpolation_Mode::Nearest; + Color_Map color_map; + }; private: struct Impl; - void prepare_render_frame(const Render_State_View& state, int graph_partition_count); - void paint_render_frame(Painter& painter, const Render_State_View& state); - void render_frame_completed(std::uint64_t target_interval_ns) override; - void render_partition(int partition_index); - void publish() override; }; } -using Waterfall = detail::Attach_Plottable; +using Waterfall = renderable::attach; } diff --git a/render_2D/render_2D/render/Blend2D_Cache.h b/render_2D/render_2D/render/Blend2D_Cache.h index 404e3b5..b3e4de1 100644 --- a/render_2D/render_2D/render/Blend2D_Cache.h +++ b/render_2D/render_2D/render/Blend2D_Cache.h @@ -1,35 +1,24 @@ #pragma once - #include "../base/Types.h" - #include #include - #include #include - namespace renderive::detail { - -class Blend2D_Color_Cache final : public ::Color_Cache { -public: +struct Blend2D_Color_Cache final : ::Color_Cache { Blend2D_Color_Cache() = default; explicit Blend2D_Color_Cache(std::pmr::memory_resource&) {} - void clear() override; void composite(const ::Color_Cache& source) override; void ensure_size(Size size); [[nodiscard]] Size size() const noexcept; [[nodiscard]] Image_View view() const noexcept; - private: friend class Painter; BLImage image_; }; - -class Painter { -public: - class Clip_Scope { - public: +struct Painter { + struct Clip_Scope { Clip_Scope(Painter& painter, RectF rect); ~Clip_Scope(); Clip_Scope(const Clip_Scope&) = delete; @@ -41,8 +30,9 @@ public: ~Painter(); Painter(const Painter&) = delete; Painter& operator=(const Painter&) = delete; - - [[nodiscard]] explicit operator bool() const noexcept { return active_; } + [[nodiscard]] explicit operator bool() const noexcept { + return active_; + } [[nodiscard]] Clip_Scope scoped_clip(RectF rect); void composite(const Blend2D_Color_Cache& source); void line(PointF first, PointF second, const Pen& pen); @@ -57,14 +47,11 @@ public: int height, std::span pixels, Image_Interpolation_Mode interpolation); - private: void apply_pen(const Pen& pen); static BLRgba rgba(Color color) noexcept; static BLFont make_font(const Font& font); - BLContext context_; bool active_{}; }; - } // namespace renderive::detail diff --git a/render_2D/render_2D/renderable/Render_Partition.h b/render_2D/render_2D/renderable/Render_Partition.h index a24a2e8..532753b 100644 --- a/render_2D/render_2D/renderable/Render_Partition.h +++ b/render_2D/render_2D/renderable/Render_Partition.h @@ -1,48 +1,41 @@ #pragma once - #include #include #include #include #include - namespace renderive { - enum class Render_Partition_Mode : std::uint8_t { Automatic, Fixed }; - namespace detail { - struct Render_Partition_Range { std::size_t first{}; std::size_t last{}; }; - inline Render_Partition_Range render_partition_range(std::size_t size, int index, int count) noexcept { if (size == 0 || count <= 0 || index < 0 || index >= count) return {}; - return {size * static_cast(index) / static_cast(count), - size * static_cast(index + 1) / static_cast(count)}; + return { + size * static_cast(index) / static_cast(count), + size * static_cast(index + 1) / static_cast(count) + }; } - -class Adaptive_Render_Partitioner { -public: +struct Adaptive_Render_Partitioner { [[nodiscard]] int graph_partition_count(Render_Partition_Mode mode, int configured_count, - int worker_count, - std::size_t work_size, - std::size_t minimum_partition_size) noexcept { + int worker_count, + std::size_t work_size, + std::size_t minimum_partition_size) noexcept { if (work_size == 0) return 0; const int useful = static_cast(std::min( static_cast(std::numeric_limits::max()), work_size)); if (mode == Render_Partition_Mode::Fixed) return std::clamp(configured_count, 1, useful); - const std::size_t granularity = std::max(1, minimum_partition_size); const int work_limit = static_cast(std::min( static_cast(std::numeric_limits::max()), @@ -51,7 +44,6 @@ public: automatic_count_ = std::clamp(automatic_count_, 1, automatic_limit); return automatic_count_; } - int begin(int graph_partition_count, std::size_t work_size) noexcept { started_at_ = Clock::now(); if (work_size == 0 || graph_partition_count <= 0) @@ -61,7 +53,6 @@ public: work_size)); return std::clamp(graph_partition_count, 1, useful); } - [[nodiscard]] bool finish(Render_Partition_Mode mode, int active_count, std::uint64_t frame_interval_ns, int worker_count, std::size_t work_size, @@ -77,29 +68,29 @@ public: const auto elapsed = Clock::now() - started_at_; const auto elapsed_ns = std::chrono::duration_cast(elapsed); const auto bottleneck_threshold = frame_interval_ns == 0 - ? std::chrono::duration_cast( - std::chrono::microseconds(1500)) - : std::max(std::chrono::duration_cast( - std::chrono::microseconds(750)), - std::chrono::nanoseconds(frame_interval_ns / 4)); + ? std::chrono::duration_cast( + std::chrono::microseconds(1500)) + : std::max(std::chrono::duration_cast( + std::chrono::microseconds(750)), + std::chrono::nanoseconds(frame_interval_ns / 4)); const int previous = automatic_count_; const auto estimated_serial_cost = elapsed_ns * std::max(1, active_count); if (elapsed_ns > bottleneck_threshold && active_count < automatic_limit) { automatic_count_ = std::min(automatic_limit, active_count * 2); - } else if (active_count > 1 && - estimated_serial_cost < bottleneck_threshold * 3 / 5) { + } + else if (active_count > 1 && + estimated_serial_cost < bottleneck_threshold * 3 / 5) { automatic_count_ = 1; - } else { + } + else { automatic_count_ = std::clamp(active_count, 1, automatic_limit); } return automatic_count_ != previous; } - private: using Clock = std::chrono::steady_clock; Clock::time_point started_at_{}; int automatic_count_{1}; }; - } // namespace detail } // namespace renderive diff --git a/render_2D/render_2D/renderable/Renderable.cpp b/render_2D/render_2D/renderable/Renderable.cpp index 87ba45b..e02ce37 100644 --- a/render_2D/render_2D/renderable/Renderable.cpp +++ b/render_2D/render_2D/renderable/Renderable.cpp @@ -1,6 +1,5 @@ #include "Renderable.h" #include "Renderable_p.h" -#include #include "../render/Blend2D_Cache.h" @@ -9,8 +8,7 @@ namespace renderive { Renderable::Renderable(bool cache_enabled) - : ::Renderable_Base(std::make_unique(), - {.cache_enabled = cache_enabled}) {} + : Root(std::make_unique(), {.cache_enabled = cache_enabled}) {} Renderable::~Renderable() = default; @@ -43,91 +41,90 @@ Renderable_Observation Renderable::observation() const noexcept { return d.observation; } -void Renderable::observe_state(Renderable_Observer_Event event, - std::uint64_t time_ns, - std::uint64_t cache_update_count, - std::uint64_t publish_count) noexcept { - auto& d = d_func(); - std::lock_guard lock(d.observation_mutex); - d.observation.event = event; - d.observation.event_time_ns = time_ns; - d.observation.cache_update_count = cache_update_count; - d.observation.publish_count = publish_count; -} - -void Renderable::build_prepare_graph(Renderable_Graph_Builder& builder) { - add_prepare_task(builder, "prepare", "Prepare", - [this](const Prepare_Render_Context& context) { - d_func().execute_prepare(context); - }); -} - -void Renderable::build_paint_graph(Renderable_Graph_Builder& builder) { - const auto paint_task = add_paint_task(builder, "paint", "Paint", - [this](detail::Painter& painter, const Paint_Render_Context& context) { - paint(painter, context); - }); - builder.precede(builder.find("prepare"), paint_task); -} - -Renderable_Graph_Builder::Task Renderable::add_prepare_task( - Renderable_Graph_Builder& builder, - std::string logical_key, - std::string name, - Prepare_Task_Function function) { - return builder.emplace(std::move(logical_key), std::move(name), - std::move(function)); -} - -Renderable_Graph_Builder::Task Renderable::add_paint_task( - Renderable_Graph_Builder& builder, - std::string logical_key, - std::string name, - Paint_Task_Function function) { - return builder.emplace(std::move(logical_key), std::move(name), - [function = std::move(function)](const Paint_Render_Context& context) { - if (!context.renderable.visible) - return; - auto* cache = dynamic_cast(&context.color_cache); - if (!cache) - return; - detail::Painter painter(*cache, {context.frame.viewport.width, - context.frame.viewport.height}); - if (painter) - function(painter, context); - }); -} - -void Renderable::changed() noexcept { - d_func().invalidate_prepare(); - d_func().notify_scene_model_dirty(); -} - -void Renderable::paint_changed() noexcept { - d_func().invalidate_paint(); - d_func().notify_scene_model_dirty(); -} - -void Renderable::render_graph_changed() { - d_func().rebuild_render_graph(); +void Renderable::dispatch_event(const Event& event) { + if (auto* handler = + dynamic_cast(&d_func())) + handler->handle_event(event); } Renderable& Renderable::Impl::renderable() noexcept { return static_cast(owner()); } +void Renderable::Impl::prepare_frame(const Prepare_Render_Context&) {} + +void Renderable::Impl::paint(detail::Painter&, + const Paint_Render_Context&) {} + void Renderable::Impl::build_prepare_graph( Renderable_Graph_Builder& builder) { - renderable().build_prepare_graph(builder); + add_prepare_task(builder, "prepare", "Prepare", + [this](const Prepare_Render_Context& context) { + execute_prepare(context); + }); } void Renderable::Impl::build_paint_graph( Renderable_Graph_Builder& builder) { - renderable().build_paint_graph(builder); + const auto paint_task = add_paint_task( + builder, "paint", "Paint", + [this](detail::Painter& painter, + const Paint_Render_Context& context) { + paint(painter, context); + }); + builder.precede(builder.find("prepare"), paint_task); } void Renderable::Impl::prepare(const Prepare_Render_Context& context) { - renderable().prepare_frame(context); + prepare_frame(context); +} + +Renderable_Graph_Builder::Task Renderable::Impl::add_prepare_task( + Renderable_Graph_Builder& builder, std::string logical_key, + std::string name, Prepare_Task_Function function) { + return builder.emplace(std::move(logical_key), std::move(name), + std::move(function)); +} + +Renderable_Graph_Builder::Task Renderable::Impl::add_paint_task( + Renderable_Graph_Builder& builder, std::string logical_key, + std::string name, Paint_Task_Function function) { + return builder.emplace( + std::move(logical_key), std::move(name), + [function = std::move(function)]( + const Paint_Render_Context& context) { + if (!context.renderable.visible) + return; + auto* cache = dynamic_cast( + &context.color_cache); + if (!cache) + return; + detail::Painter painter( + *cache, {context.frame.viewport.width, + context.frame.viewport.height}); + if (painter) + function(painter, context); + }); +} + +void Renderable::Impl::observe_state( + Renderable_Observer_Event event, std::uint64_t time_ns, + std::uint64_t cache_update_count, + std::uint64_t publish_count) noexcept { + std::lock_guard lock(observation_mutex); + observation.event = event; + observation.event_time_ns = time_ns; + observation.cache_update_count = cache_update_count; + observation.publish_count = publish_count; +} + +void Renderable::Impl::observe_data( + std::uint64_t revision, std::size_t item_count, + std::size_t auxiliary_item_count) noexcept { + std::lock_guard lock(observation_mutex); + observation.data_revision = revision; + observation.item_count = item_count; + observation.auxiliary_item_count = auxiliary_item_count; } } // namespace renderive diff --git a/render_2D/render_2D/renderable/Renderable.h b/render_2D/render_2D/renderable/Renderable.h index 9fe8b2c..a755c66 100644 --- a/render_2D/render_2D/renderable/Renderable.h +++ b/render_2D/render_2D/renderable/Renderable.h @@ -1,133 +1,82 @@ #pragma once - #include "../base/Types.h" #include "../event/Event.h" - +#include +#include #include -#include -#include - -#include #include -#include +#include +#include #include -#include #include #include - +#include namespace renderive { - -class Renderable; +struct Renderable; namespace detail { -class Painter; -class Renderable_State_Observer; +struct Painter; +struct Renderable_State_Observer; } - -class Event_Handler { -public: - virtual ~Event_Handler() = default; - virtual void handle_event(const Event& event) = 0; -}; - -enum class Renderable_Observer_Event { +enum struct Renderable_Observer_Event { None, Cache_Updated, Published }; - 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{}; + std::size_t auxiliary_item_count{}; }; - -class LIB_DECL Renderable : public ::Renderable_Base { -public: +struct LIB_DECL Renderable +: renderable::render_base { explicit Renderable(bool cache_enabled = true); ~Renderable() override; - [[nodiscard]] Renderable_Cache_Mode get_cache_mode() const noexcept; void set_cache_mode(Renderable_Cache_Mode mode); - [[nodiscard]] std::string object_name() const; void set_object_name(std::string name); [[nodiscard]] Renderable_Observation observation() const noexcept; - + void dispatch_event(const Event& event); protected: - class Impl; - + struct Impl; + template + explicit Renderable(With_Attached_Impl, + bool cache_enabled = true) + : render_base( + std::make_unique(), + {.cache_enabled = cache_enabled}) {} template explicit Renderable(std::unique_ptr implementation, bool cache_enabled) - : ::Renderable_Base( - std::move(implementation), - {.cache_enabled = cache_enabled}) {} - - using Prepare_Task_Function = - std::function; - using Paint_Task_Function = - std::function; - - virtual void prepare_frame(const Prepare_Render_Context& context) = 0; - virtual void paint(detail::Painter& painter, - const Paint_Render_Context& context) = 0; - virtual void build_prepare_graph(Renderable_Graph_Builder& builder); - virtual void build_paint_graph(Renderable_Graph_Builder& builder); - Renderable_Graph_Builder::Task add_prepare_task(Renderable_Graph_Builder& builder, - std::string logical_key, - std::string name, - Prepare_Task_Function function); - Renderable_Graph_Builder::Task add_paint_task(Renderable_Graph_Builder& builder, - std::string logical_key, - std::string name, - Paint_Task_Function function); - void changed() noexcept; - void paint_changed() noexcept; - void render_graph_changed(); - + : render_base( + std::move(implementation), + {.cache_enabled = cache_enabled}) {} private: - void observe_state(Renderable_Observer_Event event, std::uint64_t time_ns, - std::uint64_t cache_update_count, - std::uint64_t publish_count) noexcept; + template + void report_state_observation(const Observation& observation); friend class detail::Renderable_State_Observer; }; - namespace detail { - -class Renderable_State_Observer { -public: +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 { - const bool cache_updated = - observation.event == decltype(observation.event)::cache_updated; - renderable_->observe_state( - cache_updated ? Renderable_Observer_Event::Cache_Updated - : Renderable_Observer_Event::Published, - observation.time_ns, - observation.cache_update_count, - observation.publish_count - ); + renderable_->report_state_observation(observation); } - private: Renderable* renderable_; }; - } // namespace detail - namespace detail { - template inline constexpr bool Paint_Only_Property_Value = - std::same_as, Color> || - std::same_as, Pen> || - std::same_as, Brush>; - +std::same_as, Color> || +std::same_as, Pen> || +std::same_as, Brush>; } // namespace detail - } // namespace renderive diff --git a/render_2D/render_2D/renderable/Renderable_Builder.h b/render_2D/render_2D/renderable/Renderable_Builder.h index d0ad6b0..9287328 100644 --- a/render_2D/render_2D/renderable/Renderable_Builder.h +++ b/render_2D/render_2D/renderable/Renderable_Builder.h @@ -2,6 +2,7 @@ #include "Renderable.h" #include #include +#include #include #include #include @@ -12,7 +13,7 @@ class Abs_Axis; namespace detail { class Paint_Overlay; template -requires std::derived_from && std::derived_from + requires std::derived_from && std::derived_from void attach_renderable_dependency(::Scene_Base::Attach_Builder& builder, const renderive_Owner& renderable, const renderive_Owner& axis) { if (!axis) return; @@ -25,7 +26,7 @@ void attach_renderable_dependency(::Scene_Base::Attach_Builder& builder, const r template void attach_renderable_dependency(::Scene_Base::Attach_Builder&, const renderive_Owner&, const Value&) {} template -requires std::derived_from + requires std::derived_from void attach_renderable(::Scene_Base::Attach_Builder& builder, const renderive_Owner& renderable, const renderive_Owner& parent, const Args&... args) { builder.attach(renderable); builder.add_display_parent(renderable, parent); @@ -34,8 +35,7 @@ void attach_renderable(::Scene_Base::Attach_Builder& builder, const renderive_Ow } } template Validator_Type = No_Property_Validator> -class Renderable_Builder { -public: +struct Renderable_Builder { using Product = Product_Type; using Properties = Properties_Type; using Validator = Validator_Type; @@ -58,15 +58,18 @@ public: return properties; } template - requires std::derived_from && std::constructible_from + requires std::derived_from && std::constructible_from renderive_Owner build(const renderive_Owner& parent, Args&&... args) const { if (!parent || !(valid_argument(args) && ...)) return {}; validator(properties); - auto result = renderive_Owner::make(properties, args...); - if (attach_builder_) - detail::attach_renderable(*attach_builder_, result, parent, args...); - return result; + return inheritance::initialize( + renderive_Owner::make(properties, args...), + [&](renderive_Owner& result) { + if (attach_builder_) + detail::attach_renderable(*attach_builder_, result, + parent, args...); + }); } private: template diff --git a/render_2D/render_2D/renderable/Renderable_p.h b/render_2D/render_2D/renderable/Renderable_p.h index ff5101c..846cab1 100644 --- a/render_2D/render_2D/renderable/Renderable_p.h +++ b/render_2D/render_2D/renderable/Renderable_p.h @@ -1,28 +1,106 @@ #pragma once - #include "Renderable.h" - +#include +#include +#include #include #include - -#include - +#include namespace renderive { - -class Renderable::Impl : public ::Renderable_Base::Impl { -public: +namespace detail { +struct Renderable_Event_Handler { + virtual ~Renderable_Event_Handler() = default; + virtual void handle_event(const Event& event) = 0; +}; +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; + } +}; +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_event_view( + const Payload& payload, std::uint64_t time_ns) noexcept { + return { + Renderable_Observer_Event::None, time_ns, 0, 0, + &typeid(payload), &payload + }; +} +} +struct Renderable::Impl +: renderable_inheritance::Impl_Root { + struct Observer : renderable_inheritance::Observer_Root { + static void handle( + Impl& impl, + const detail::Renderable_Event_View& value) noexcept { + impl.observe_state(value.event, value.time_ns, + value.cache_update_count, + value.publish_count); + } + }; + virtual ~Impl() = default; +protected: + template + void report_observation(const Payload& payload, + std::uint64_t time_ns = 0) { + dispatch(detail::renderable_event_view(payload, time_ns)); + } + void observe_data(std::uint64_t revision, std::size_t item_count, + std::size_t auxiliary_item_count = 0) noexcept; + using Prepare_Task_Function = + std::function; + using Paint_Task_Function = + std::function; + virtual void prepare_frame(const Prepare_Render_Context& context); + virtual void paint(detail::Painter& painter, + const Paint_Render_Context& context); + void build_prepare_graph(Renderable_Graph_Builder& builder) override; + void build_paint_graph(Renderable_Graph_Builder& builder) override; + void prepare(const Prepare_Render_Context& context) override; + Renderable_Graph_Builder::Task add_prepare_task( + Renderable_Graph_Builder& builder, std::string logical_key, + std::string name, Prepare_Task_Function function); + Renderable_Graph_Builder::Task add_paint_task( + Renderable_Graph_Builder& builder, std::string logical_key, + std::string name, Paint_Task_Function function); +private: + [[nodiscard]] Renderable& renderable() noexcept; + void observe_state(Renderable_Observer_Event event, std::uint64_t time_ns, + std::uint64_t cache_update_count, + std::uint64_t publish_count) noexcept; mutable std::mutex metadata_mutex; std::string object_name; mutable std::mutex observation_mutex; Renderable_Observation observation; - -protected: - void build_prepare_graph(Renderable_Graph_Builder& builder) override; - void build_paint_graph(Renderable_Graph_Builder& builder) override; - void prepare(const Prepare_Render_Context& context) override; - -private: - [[nodiscard]] Renderable& renderable() noexcept; + friend class Renderable; + friend class detail::Renderable_State_Observer; }; - +template +void Renderable::report_state_observation(const Observation& observation) { + d_func().dispatch(detail::renderable_event_view(observation)); +} } // namespace renderive diff --git a/render_2D/render_2D/scene/Scene.cpp b/render_2D/render_2D/scene/Scene.cpp index dd449a0..8b0f50f 100644 --- a/render_2D/render_2D/scene/Scene.cpp +++ b/render_2D/render_2D/scene/Scene.cpp @@ -3,12 +3,8 @@ #include "../renderable/Renderable.h" namespace renderive::detail { namespace { -class Renderable_Group final : public Renderable { -public: +struct Renderable_Group final : Renderable { using Renderable::Renderable; -private: - void prepare_frame(const Prepare_Render_Context&) override {} - void paint(Painter&, const Paint_Render_Context&) override {} }; } renderive_Owner make_renderable_group(bool cache_enabled) { diff --git a/render_2D/render_2D/scene/Scene.h b/render_2D/render_2D/scene/Scene.h index 5123e72..857080a 100644 --- a/render_2D/render_2D/scene/Scene.h +++ b/render_2D/render_2D/scene/Scene.h @@ -25,7 +25,6 @@ namespace renderive { class Performance_Overlay; class Renderable; -class Event_Handler; enum class Frame_Control_Mode : std::uint8_t { Manual, Low_Latency, @@ -76,7 +75,6 @@ struct Frame_Observer_Snapshot { std::uint64_t end_to_end_ns{}; }; class Presentation_Sink { -public: virtual ~Presentation_Sink() = default; virtual void request_present(Rect dirty_rect) = 0; }; @@ -128,7 +126,8 @@ struct Frame_Observer { data->render_state_wait_ns = timing.render_state_wait_ns; data->render_finish_state_wait_ns = timing.render_finish_state_wait_ns; } - } else if (observation.statistics.sequence != 0) { + } + else if (observation.statistics.sequence != 0) { data->paint_duration_ns = observation.statistics.paint_duration_ns; data->render_duration_ns = observation.statistics.render_duration_ns; } @@ -142,14 +141,16 @@ struct Frame_Observer { data->dropped_frame_count = observation.state.replaced_prepared_frame_count + observation.state.discarded_prepared_frame_count; data->failed_operation_count = observation.state.failed_refresh_count; data->pending_frame_count = observation.state.pending_frame ? 1U : 0U; - } else if constexpr (requires { observation.state.limit_state; }) { + } + else if constexpr (requires { observation.state.limit_state; }) { data->produced_frame_count = observation.statistics.counters.published_frame_count; data->consumed_frame_count = observation.state.completed_lifecycle_count; data->dropped_frame_count = observation.statistics.counters.abandoned_frame_count + observation.statistics.counters.manually_discarded_frame_count; data->failed_operation_count = observation.statistics.counters.swap_failure_count; const auto retired = observation.state.completed_lifecycle_count + observation.statistics.counters.abandoned_frame_count + observation.statistics.counters.manually_discarded_frame_count; data->pending_frame_count = observation.statistics.counters.published_frame_count > retired ? 1U : 0U; - } else { + } + else { data->produced_frame_count = observation.state.enqueued_frame_count; data->consumed_frame_count = observation.state.rendered_frame_count; data->failed_operation_count = observation.state.empty_acquire_count; @@ -198,7 +199,6 @@ class Basic_Scene2D final : public ::Scene2D_Context; using Scene_State_Strategy = typename Kernel_Scene::Scene_State_Strategy; static constexpr Frame_Control_Mode Mode = detail::frame_control_mode(); -public: Basic_Scene2D() : Basic_Scene2D(std::make_shared()) {} ~Basic_Scene2D() override = default; Basic_Scene2D(const Basic_Scene2D&) = delete; @@ -251,8 +251,7 @@ public: auto base = renderive_const_owner_cast<::Renderable_Base>(*iterator); if (auto renderable = renderive_dynamic_owner_cast(base)) { if (renderable->is_visible()) { - if (auto handler = renderive_dynamic_owner_cast(base)) - handler->handle_event(event); + renderable->dispatch_event(event); } if (event.is_accepted()) break; @@ -305,7 +304,8 @@ public: } if (!render_prepared_frame()) return false; - } catch (...) { + } + catch (...) { this->notify_model_dirty(); throw; } @@ -344,7 +344,8 @@ public: void set_max_render_fps(double fps) { if constexpr (Mode != Frame_Control_Mode::Low_Latency) { throw std::logic_error("maximum render FPS is only available in low-latency mode"); - } else { + } + else { if (!std::isfinite(fps) || fps <= 0.0) throw std::invalid_argument("maximum render FPS must be finite and positive"); this->frame_control.set_frequency_hz(fps); @@ -354,7 +355,8 @@ public: void clear_max_render_fps() { if constexpr (Mode != Frame_Control_Mode::Low_Latency) { throw std::logic_error("maximum render FPS is only available in low-latency mode"); - } else { + } + else { this->frame_control.clear_frequency_limit(); this->notify_model_dirty(); } diff --git a/render_3D/render_3D/Point_Visual.cpp b/render_3D/render_3D/Point_Visual.cpp index f07944b..05847fc 100644 --- a/render_3D/render_3D/Point_Visual.cpp +++ b/render_3D/render_3D/Point_Visual.cpp @@ -2,7 +2,11 @@ #include "detail/Point_Core.h" +#include +#include + #include +#include #include #include @@ -30,75 +34,95 @@ void validate(const std::vector& points) { } // 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); + update_points(std::move(points)); + } + + void update_points(std::vector points) { + validate(points); + write( + std::make_shared>(std::move(points))); + } + + void edit_points(const std::function&)>& edit) { + if (!edit) + throw std::invalid_argument("Point_Visual point edit is empty"); + const auto& published = points(); + auto next = published ? *published : std::vector{}; + edit(next); + update_points(std::move(next)); + } + + [[nodiscard]] const detail::Point_Payload& points() const noexcept { + return render_buffer_value(); + } + + [[nodiscard]] std::size_t point_count() const noexcept { + return points() ? points()->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) - : detail::Point_Visual_Strategy(std::in_place, initial) { - validate(initial); - validate(points); - detail::Point_Visual_Strategy::write( - std::make_shared>(std::move(points))); -} + : impl_(std::make_unique(std::move(points), initial)) {} Point_Visual::~Point_Visual() = default; void Point_Visual::update_points(std::vector points) { - validate(points); - detail::Point_Visual_Strategy::write( - std::make_shared>(std::move(points))); + impl_->update_points(std::move(points)); } void Point_Visual::edit_points( const std::function&)>& edit) { - if (!edit) - throw std::invalid_argument("Point_Visual point edit is empty"); - const auto& published = - detail::Point_Visual_Strategy::render_buffer_value< - detail::Point_Payload_Tag>(); - auto points = published ? *published : std::vector{}; - edit(points); - update_points(std::move(points)); + impl_->edit_points(edit); } std::size_t Point_Visual::point_count() const { - const auto& points = - detail::Point_Visual_Strategy::render_buffer_value< - detail::Point_Payload_Tag>(); - return points ? points->size() : 0U; + return impl_->point_count(); } std::uint64_t Point_Visual::data_revision() const { - return detail::Point_Visual_Strategy::revision(); + return impl_->revision(); } void Point_Visual::configure(Point_State state) { - validate(state); - detail::Point_State_Strategy::update( - [state = std::move(state)](Point_State& target) mutable { - target = std::move(state); - }); + impl_->configure(std::move(state)); } detail::Published_Point detail::Point_State_Access::publish(Point_Visual& visual) { - return visual.publish_frame(); -} - -detail::Published_Point Point_Visual::publish_frame() { - publish(); - return { - detail::Point_State_Strategy::render_state_value(), - detail::Point_Visual_Strategy::render_buffer_value< - detail::Point_Payload_Tag>(), - detail::Point_State_Strategy::state_revision(), - detail::Point_Visual_Strategy::revision()}; -} - -void Point_Visual::publish() { - detail::Point_State_Strategy::publish(); - detail::Point_Visual_Strategy::publish(); -} - -std::uint64_t Point_Visual::state_revision() const { - return detail::Point_State_Strategy::state_revision() + - detail::Point_Visual_Strategy::revision(); + return visual.impl_->publish_frame(); } } // namespace renderive::render_3d diff --git a/render_3D/render_3D/Point_Visual.h b/render_3D/render_3D/Point_Visual.h index 99b7ac4..97a8a25 100644 --- a/render_3D/render_3D/Point_Visual.h +++ b/render_3D/render_3D/Point_Visual.h @@ -1,13 +1,11 @@ #pragma once #include +#include #include #include #include -#include #include #include -#include -#include namespace renderive::render_3d { struct Vec3 { float x{}; @@ -48,53 +46,32 @@ struct Point_Style { Point_Aspect aspect{Point_Aspect::Filled}; bool operator==(const Point_Style&) const = default; }; -struct Point_State { - Point_Style style; - Matrix4 transform; - bool visible{true}; - bool depth_test{true}; - bool operator==(const Point_State&) const = default; -}; namespace detail { -struct Point_State_Base {}; -struct Point_Payload_Tag {}; -using Point_Payload = std::shared_ptr>; -using Point_State_Strategy = - ::Double_State_Strategy; -using Point_Buffer_Layout = - ::Double_Buffer_Layout< - ::Buffered_Data>; -class Point_Visual_Strategy - : public ::Multi_Double_Buffer_Strategy< - Point_State_Strategy, Point_Buffer_Layout, std::mutex> { - using Base = ::Multi_Double_Buffer_Strategy< - Point_State_Strategy, Point_Buffer_Layout, std::mutex>; -protected: - using Base::Base; - using Base::publish; - using Base::render_buffer_value; - using Base::revision; - using Base::write; -}; struct Published_Point; class Point_State_Access; } // Thread-safe logical point visual. Datoviz resources deliberately do not live here. -class Point_Visual final : private detail::Point_Visual_Strategy, - Non_Copyable { -public: - explicit Point_Visual(std::vector points = {}, Point_State initial = {}); - ~Point_Visual(); +struct Point_Visual final : Non_Copyable { + struct State { + Point_Style style; + Matrix4 transform; + bool visible{true}; + bool depth_test{true}; + bool operator==(const State&) const = default; + }; + explicit Point_Visual(std::vector points = {}, State initial = {}); + ~Point_Visual(); 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(Point_State state); + void configure(State state); private: + struct Impl; + std::unique_ptr impl_; friend class detail::Point_State_Access; - [[nodiscard]] detail::Published_Point publish_frame(); - void publish() override; - [[nodiscard]] std::uint64_t state_revision() const override; }; + +using Point_State = Point_Visual::State; } // 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 859db9e..cda859b 100644 --- a/render_3D/render_3D/detail/Point_Core.h +++ b/render_3D/render_3D/detail/Point_Core.h @@ -3,7 +3,7 @@ #include "render_3D/Point_Scene.h" #include -#include +#include #include #include @@ -35,11 +35,9 @@ struct Scene_State { bool operator==(const Scene_State&) const = default; }; -struct Scene_State_Base {}; - class Scene_State_Buffer final - : public ::Double_State_Strategy { - using Base = ::Double_State_Strategy; + : public ::Double_State_Storage { + using Base = ::Double_State_Storage; public: struct Published { diff --git a/render_3D/tests/Point_State_Tests.cpp b/render_3D/tests/Point_State_Tests.cpp index 29cb430..1ea3e26 100644 --- a/render_3D/tests/Point_State_Tests.cpp +++ b/render_3D/tests/Point_State_Tests.cpp @@ -31,6 +31,7 @@ TEST(PointState, UsesKernelDoubleStateAtFrameBoundary) { TEST(PointState, PublishesImmutableBulkPayloadsThroughDoubleBuffer) { Point_Visual visual; constexpr int update_count = 500; + constexpr std::uint64_t initial_data_revision = 1; std::atomic done{}; std::thread writer([&] { @@ -57,7 +58,8 @@ TEST(PointState, PublishesImmutableBulkPayloadsThroughDoubleBuffer) { const auto published = detail::Point_State_Access::publish(visual); EXPECT_GT(published.data_revision, 0U); - EXPECT_LE(published.data_revision, update_count); + EXPECT_LE(published.data_revision, + initial_data_revision + update_count); ASSERT_FALSE(published.data->empty()); EXPECT_EQ(published.data->front().position.x, static_cast(update_count)); } diff --git a/renderive_package/web_server/app/Gallery_Plot_Session.cpp b/renderive_package/web_server/app/Gallery_Plot_Session.cpp index 81ae9f1..f2026dd 100644 --- a/renderive_package/web_server/app/Gallery_Plot_Session.cpp +++ b/renderive_package/web_server/app/Gallery_Plot_Session.cpp @@ -1018,7 +1018,7 @@ private: return; } if (case_id_ == "spectrum" || case_id_ == "selection_overlay") { - Spectrum_Properties properties; + Spectrum::Properties properties; properties.frequency_range = {88'000'000.0, 108'000'000.0}; properties.frequency_point_size = 512; properties.center_frequency = 98'000'000.0; @@ -1041,7 +1041,7 @@ private: data_node_, frequency_domain_axis_, value_axis_); primary_ = spectrum_; if (case_id_ == "selection_overlay") { - Selection_Rectangle_Overlay_Properties overlay; + Selection_Rectangle_Overlay::Properties overlay; overlay.label_font = {12.0, 500, false}; overlay.selection_brush = {color_from_hex("#173b66", 70), Brush_Style::Solid}; overlay.selection_border_pen = {color_from_hex("#7dd3fc"), 1.0, Line_Style::Dash}; @@ -1052,7 +1052,7 @@ private: return; } if (case_id_ == "waterfall") { - Waterfall_Properties properties; + Waterfall::Properties properties; properties.frequency_range = {88'000'000.0, 108'000'000.0}; properties.power_range = {-120.0, -20.0}; properties.frequency_bin_count = 256; @@ -1062,7 +1062,7 @@ private: return; } if (case_id_ == "afterglow") { - Afterglow_Properties properties; + Afterglow::Properties properties; properties.frequency_range = {88'000'000.0, 108'000'000.0}; properties.power_range = {-120.0, -20.0}; properties.frequency_point_size = 192; @@ -1074,7 +1074,7 @@ private: return; } if (case_id_ == "sweep_spectrum") { - Sweep_Spectrum_Properties properties; + Sweep_Spectrum::Properties properties; properties.frequency_range = {0.0, 300.0}; properties.bins_per_block = 64; properties.block_count = 8; @@ -1086,7 +1086,7 @@ private: return; } if (case_id_ == "frequency_trace") { - Frequency_Trace_Properties properties; + Frequency_Trace::Properties properties; properties.pen = {color_from_hex("#35e6b2"), 2.0, Line_Style::Solid, Line_Cap::Round, Line_Join::Round}; trace_ = Gallery_Frequency_Trace::Builder{properties, &attach}.build( @@ -1095,7 +1095,7 @@ private: return; } if (case_id_ == "constellation") { - Constellation_Diagram_Properties properties; + Constellation_Diagram::Properties properties; properties.i_range = {-1.25, 1.25}; properties.q_range = {-1.25, 1.25}; properties.point_color = color_from_hex("#49e6c3"); diff --git a/renderive_package/web_server/app/Gallery_Renderable_Types.h b/renderive_package/web_server/app/Gallery_Renderable_Types.h index 2d075f7..c4ea256 100644 --- a/renderive_package/web_server/app/Gallery_Renderable_Types.h +++ b/renderive_package/web_server/app/Gallery_Renderable_Types.h @@ -46,7 +46,7 @@ public: private: template auto adminive_read() const { - return this->template property_value(); + return this->template get(); } template void adminive_write(Value&& value) { diff --git a/renderive_package/web_server/app/Gallery_Renderables.h b/renderive_package/web_server/app/Gallery_Renderables.h index 419fb6f..b50b08a 100644 --- a/renderive_package/web_server/app/Gallery_Renderables.h +++ b/renderive_package/web_server/app/Gallery_Renderables.h @@ -366,7 +366,7 @@ struct Type_Descriptor { } \ } -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum_Properties, "spectrum", "实时频谱", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum::Properties, "spectrum", "实时频谱", property_number("frequency_point_size", "频率点数"), property_select("partition_mode", "任务分区模式") .enum_label("自动瓶颈优化") @@ -405,7 +405,7 @@ RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum_Properties, "spectrum", property_object("tooltip_text_pen", "提示框文字画笔"), property_object("tooltip_background_brush", "提示框背景")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall_Properties, "waterfall", "频谱瀑布", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall::Properties, "waterfall", "频谱瀑布", property_object("frequency_range", "频率范围"), property_object("power_range", "功率范围"), property_number("frequency_bin_count", "频率格数"), @@ -429,7 +429,7 @@ RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall_Properties, "waterfall property_object("tooltip_text_pen", "提示框文字画笔"), property_object("tooltip_background_brush", "提示框背景")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow_Properties, "afterglow", "余辉频谱", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow::Properties, "afterglow", "余辉频谱", property_object("frequency_range", "频率范围"), property_object("power_range", "功率范围"), property_number("frequency_point_size", "频率点数"), @@ -447,7 +447,7 @@ RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow_Properties, "afterglow .enum_label("火焰") .enum_label("灰度")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Sweep_Spectrum, Sweep_Spectrum_Properties, "sweep_spectrum", "扫频频谱", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Sweep_Spectrum, Sweep_Spectrum::Properties, "sweep_spectrum", "扫频频谱", property_object("frequency_range", "频率范围"), property_number("bins_per_block", "每块频率格数"), property_number("block_count", "扫频块数"), @@ -462,16 +462,16 @@ RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Sweep_Spectrum, Sweep_Spectrum_Properties, .enum_label("右阶梯") .enum_label("三次插值")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Frequency_Trace, Frequency_Trace_Properties, "frequency_trace", "频率轨迹", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Frequency_Trace, Frequency_Trace::Properties, "frequency_trace", "频率轨迹", property_object("pen", "轨迹画笔")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Selection_Rectangle_Overlay, Selection_Rectangle_Overlay_Properties, "selection_overlay", "框选区域", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Selection_Rectangle_Overlay, Selection_Rectangle_Overlay::Properties, "selection_overlay", "框选区域", property_object("label_font", "标签字体"), property_object("label_pen", "标签画笔"), property_object("selection_brush", "选区画刷"), property_object("selection_border_pen", "选区边框画笔")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Constellation_Diagram, Constellation_Diagram_Properties, "constellation", "星座图", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Constellation_Diagram, Constellation_Diagram::Properties, "constellation", "星座图", property_object("i_range", "I 轴范围"), property_object("q_range", "Q 轴范围"), property_color("point_color", "点颜色"), diff --git a/web_server/app/Gallery_Plot_Session.cpp b/web_server/app/Gallery_Plot_Session.cpp index b194e38..cd8e1bd 100644 --- a/web_server/app/Gallery_Plot_Session.cpp +++ b/web_server/app/Gallery_Plot_Session.cpp @@ -1032,7 +1032,7 @@ private: return; } if (case_id_ == "spectrum" || case_id_ == "selection_overlay") { - Spectrum_Properties properties; + Spectrum::Properties properties; properties.frequency_range = {88'000'000.0, 108'000'000.0}; properties.frequency_point_size = 512; properties.center_frequency = 98'000'000.0; @@ -1055,7 +1055,7 @@ private: data_node_, frequency_domain_axis_, value_axis_); primary_ = spectrum_; if (case_id_ == "selection_overlay") { - Selection_Rectangle_Overlay_Properties overlay; + Selection_Rectangle_Overlay::Properties overlay; overlay.label_font = {12.0, 500, false}; overlay.selection_brush = {color_from_hex("#173b66", 70), Brush_Style::Solid}; overlay.selection_border_pen = {color_from_hex("#7dd3fc"), 1.0, Line_Style::Dash}; @@ -1066,7 +1066,7 @@ private: return; } if (case_id_ == "waterfall") { - Waterfall_Properties properties; + Waterfall::Properties properties; properties.frequency_range = {88'000'000.0, 108'000'000.0}; properties.power_range = {-120.0, -20.0}; properties.frequency_bin_count = 256; @@ -1076,7 +1076,7 @@ private: return; } if (case_id_ == "afterglow") { - Afterglow_Properties properties; + Afterglow::Properties properties; properties.frequency_range = {88'000'000.0, 108'000'000.0}; properties.power_range = {-120.0, -20.0}; properties.frequency_point_size = 192; @@ -1088,7 +1088,7 @@ private: return; } if (case_id_ == "sweep_spectrum") { - Sweep_Spectrum_Properties properties; + Sweep_Spectrum::Properties properties; properties.frequency_range = {0.0, 300.0}; properties.bins_per_block = 64; properties.block_count = 8; @@ -1100,7 +1100,7 @@ private: return; } if (case_id_ == "frequency_trace") { - Frequency_Trace_Properties properties; + Frequency_Trace::Properties properties; properties.pen = {color_from_hex("#35e6b2"), 2.0, Line_Style::Solid, Line_Cap::Round, Line_Join::Round}; trace_ = Gallery_Frequency_Trace::Builder{properties, &attach}.build( @@ -1109,7 +1109,7 @@ private: return; } if (case_id_ == "constellation") { - Constellation_Diagram_Properties properties; + Constellation_Diagram::Properties properties; properties.i_range = {-1.25, 1.25}; properties.q_range = {-1.25, 1.25}; properties.point_color = color_from_hex("#49e6c3"); diff --git a/web_server/app/Gallery_Renderable_Types.h b/web_server/app/Gallery_Renderable_Types.h index 2d075f7..c60db71 100644 --- a/web_server/app/Gallery_Renderable_Types.h +++ b/web_server/app/Gallery_Renderable_Types.h @@ -46,7 +46,7 @@ public: private: template auto adminive_read() const { - return this->template property_value(); + return this->template get(); } template void adminive_write(Value&& value) { @@ -131,14 +131,16 @@ 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 = + detail::Axis_Renderable_Builder; }; class Gallery_Time_Axis final : public Gallery_Axis_Base { public: using Gallery_Axis_Base::Gallery_Axis_Base; - using Builder = detail::Axis_Renderable_Builder; + using Builder = + detail::Axis_Renderable_Builder; }; } // namespace renderive::web diff --git a/web_server/app/Gallery_Renderables.h b/web_server/app/Gallery_Renderables.h index 419fb6f..b50b08a 100644 --- a/web_server/app/Gallery_Renderables.h +++ b/web_server/app/Gallery_Renderables.h @@ -366,7 +366,7 @@ struct Type_Descriptor { } \ } -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum_Properties, "spectrum", "实时频谱", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum::Properties, "spectrum", "实时频谱", property_number("frequency_point_size", "频率点数"), property_select("partition_mode", "任务分区模式") .enum_label("自动瓶颈优化") @@ -405,7 +405,7 @@ RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum_Properties, "spectrum", property_object("tooltip_text_pen", "提示框文字画笔"), property_object("tooltip_background_brush", "提示框背景")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall_Properties, "waterfall", "频谱瀑布", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall::Properties, "waterfall", "频谱瀑布", property_object("frequency_range", "频率范围"), property_object("power_range", "功率范围"), property_number("frequency_bin_count", "频率格数"), @@ -429,7 +429,7 @@ RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall_Properties, "waterfall property_object("tooltip_text_pen", "提示框文字画笔"), property_object("tooltip_background_brush", "提示框背景")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow_Properties, "afterglow", "余辉频谱", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow::Properties, "afterglow", "余辉频谱", property_object("frequency_range", "频率范围"), property_object("power_range", "功率范围"), property_number("frequency_point_size", "频率点数"), @@ -447,7 +447,7 @@ RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow_Properties, "afterglow .enum_label("火焰") .enum_label("灰度")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Sweep_Spectrum, Sweep_Spectrum_Properties, "sweep_spectrum", "扫频频谱", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Sweep_Spectrum, Sweep_Spectrum::Properties, "sweep_spectrum", "扫频频谱", property_object("frequency_range", "频率范围"), property_number("bins_per_block", "每块频率格数"), property_number("block_count", "扫频块数"), @@ -462,16 +462,16 @@ RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Sweep_Spectrum, Sweep_Spectrum_Properties, .enum_label("右阶梯") .enum_label("三次插值")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Frequency_Trace, Frequency_Trace_Properties, "frequency_trace", "频率轨迹", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Frequency_Trace, Frequency_Trace::Properties, "frequency_trace", "频率轨迹", property_object("pen", "轨迹画笔")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Selection_Rectangle_Overlay, Selection_Rectangle_Overlay_Properties, "selection_overlay", "框选区域", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Selection_Rectangle_Overlay, Selection_Rectangle_Overlay::Properties, "selection_overlay", "框选区域", property_object("label_font", "标签字体"), property_object("label_pen", "标签画笔"), property_object("selection_brush", "选区画刷"), property_object("selection_border_pen", "选区边框画笔")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Constellation_Diagram, Constellation_Diagram_Properties, "constellation", "星座图", +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Constellation_Diagram, Constellation_Diagram::Properties, "constellation", "星座图", property_object("i_range", "I 轴范围"), property_object("q_range", "Q 轴范围"), property_color("point_color", "点颜色"),