diff --git a/kernel/src/kernel/double_buffer/Dependency_Graph_Storage.hpp b/kernel/src/kernel/double_buffer/Dependency_Graph_Storage.hpp index 4736b39..1d1b8ea 100644 --- a/kernel/src/kernel/double_buffer/Dependency_Graph_Storage.hpp +++ b/kernel/src/kernel/double_buffer/Dependency_Graph_Storage.hpp @@ -238,7 +238,7 @@ struct Root::Builder { using Prop = typename Object::Prop; using Buffer_Build = detail::Buffer_Build_Storage; using Dependency_Graph_Build = detail::Dependency_Graph_Build_Storage; - using Final_Builder = typename Object::Builder; + using Final_Builder = typename Object::template Builder; std::unique_ptr object; /* 尚未挂接最终 Private 的对象外壳。 */ Prop prop{}; /* build() 前暂存的发布属性写入面。 */ Buffer_Build buffer_storage; /* build() 前暂存的普通 Buffer 初值。 */ @@ -368,7 +368,8 @@ struct Root::Builder { std::expected, Dependency_Graph_Error> build() { auto validate_result = validate(); if (!validate_result) return std::unexpected(validate_result.error()); - auto private_data = std::make_unique(object->memory_resource()); + auto private_data = std::make_unique>( + object.get(), object->memory_resource()); *private_data->pending = prop; *private_data->current = prop; buffer_storage.commit(private_data->buffer_storage); @@ -376,6 +377,7 @@ struct Root::Builder { object->d = private_data.release(); // d 已指向最终 Private 后,由 Private 继承链按最终 Object 类型安装公开薄壳所需的 CRTP 分派。 static_cast(*object->d).bind_private_crtp(object.get()); + object->template bind_object_crtp(); return std::move(object); } std::expected validate() const { @@ -387,11 +389,13 @@ protected: [[nodiscard]] static Private_Access private_access(Target* target) noexcept { return Private_Access{static_cast(*target->d)}; } - /* Builder 内部分派读取目标已提交 Prop;不经过 Impl 对外的 pending 读取接口。 */ + /* Builder 内部读取目标已提交 Prop;不经过对象的外部 pending 读取接口。 */ template [[nodiscard]] static const auto& current_prop(const Target* target) noexcept { - const auto& private_data = static_cast(*target->d); - return private_data.template current_prop(); + using Layer = detail::Prop_Value; + const auto* prop = static_cast( + target->d->runtime_current_prop_data()); + return static_cast(*prop); } }; } diff --git a/kernel/src/kernel/double_buffer/mechanism.hpp b/kernel/src/kernel/double_buffer/mechanism.hpp index 1bef015..765ed20 100644 --- a/kernel/src/kernel/double_buffer/mechanism.hpp +++ b/kernel/src/kernel/double_buffer/mechanism.hpp @@ -698,6 +698,11 @@ template struct Dependency_Graph_Storage; template struct Dependency_Graph_Build_Storage; +template +struct Attached_Private; +template +struct Public_Access; +struct Internal_Access; } enum struct Dependency_Graph_Error { self_dependency, @@ -709,18 +714,22 @@ struct Root { struct Private { using Tag_Type = Root; virtual ~Private() = default; + [[nodiscard]] virtual const void* runtime_current_prop_data() const noexcept = 0; + [[nodiscard]] virtual void* runtime_pending_prop_data() noexcept = 0; + [[nodiscard]] virtual const void* runtime_current_state_data() const noexcept = 0; + [[nodiscard]] virtual void* runtime_pending_state_data() noexcept = 0; /* CRTP 默认:Builder 挂接最终 Private 后按基类到派生类绑定最终对象类型;Root 层不处理。 */ template void bind_private_crtp(Object*) {} protected: - /* 内部实现按目标类型读取已提交 Prop;不经过 Impl 对外的 pending 读取接口。 */ + /* 内部实现按目标类型读取已提交 Prop;不经过对象的外部 pending 读取接口。 */ template - requires requires(const typename Target::Private& private_data) { - private_data.template current_prop(); - } + requires detail::Prop_Tag_In> [[nodiscard]] static const auto& read_current_prop(const Target* target) noexcept { - const auto& private_data = static_cast(*target->d); - return private_data.template current_prop(); + using Layer = detail::Prop_Value; + const auto* prop = static_cast( + target->d->runtime_current_prop_data()); + return static_cast(*prop); } }; using Buffers = std::tuple<>; @@ -735,6 +744,11 @@ struct Root { bool operator==(const State&) const = default; }; private: + template + friend struct detail::Attached_Private; + template + friend struct detail::Public_Access; + friend struct detail::Internal_Access; using Call = void (*)(Root*); Call advance_call{}; detail::Pmr_Resource pmr_resource; @@ -814,8 +828,7 @@ concept Object_Root = requires { template concept Attached = Object_Core && requires { typename T::Attached_Object; - requires Object_Root; - requires std::derived_from; + requires std::same_as; }; namespace detail { template diff --git a/kernel/src/kernel/double_buffer/model.hpp b/kernel/src/kernel/double_buffer/model.hpp index 0a71a9b..ca1ef2f 100644 --- a/kernel/src/kernel/double_buffer/model.hpp +++ b/kernel/src/kernel/double_buffer/model.hpp @@ -26,41 +26,119 @@ concept Object = requires { }; namespace detail { template -using Impl_Buffers = decltype(std::tuple_cat( +using Def_Buffers = decltype(std::tuple_cat( std::declval(), std::declval>()... )); template -using Impl_Dependency_Graph_Types = decltype(std::tuple_cat( +using Def_Dependency_Graph_Types = decltype(std::tuple_cat( std::declval(), std::declval>()... )); template -using Impl_Mpmc_Triple_Buffers = decltype(std::tuple_cat( +using Def_Mpmc_Triple_Buffers = decltype(std::tuple_cat( std::declval(), std::declval>()... )); template -using Impl_States = decltype(std::tuple_cat( +using Def_States = decltype(std::tuple_cat( std::declval(), std::declval>>() )); template -concept Impl_Mechanisms = Object_Root && (Mechanism_Type && ...) && requires { - requires Buffer_List>; - requires Dependency_Graph_List>; - requires Mpmc_Triple_Buffer_List>; - requires State_List>; +concept Def_Mechanisms = Object_Root && (Mechanism_Type && ...) && requires { + requires Buffer_List>; + requires Dependency_Graph_List>; + requires Mpmc_Triple_Buffer_List>; + requires State_List>; }; template concept Process_Callback_For = requires(Private& private_data, Object_T* object, Callback&& callback) { private_data.process(object, std::forward(callback)); }; +template +struct Public_Access { +private: + [[nodiscard]] auto& private_data() noexcept { + return static_cast&>(*static_cast(this)->d); + } + [[nodiscard]] const auto& private_data() const noexcept { + return static_cast&>(*static_cast(this)->d); + } +public: + template Tag> + decltype(auto) pending_buffer() { return private_data().template pending_buffer(); } + template void submit_stream(Input&& input) { private_data().template submit_stream(std::forward(input)); } + template void exchange_stream() { private_data().template exchange_stream(); } + template void accumulate_stream(std::size_t capacity) { private_data().template accumulate_stream(capacity); } + template void accumulate_stream(Predicate&& retain) { private_data().template accumulate_stream(std::forward(retain)); } + template decltype(auto) access_rendering_stream(Callback&& callback) { return private_data().template access_rendering_stream(std::forward(callback)); } + template decltype(auto) access_query_stream(Callback&& callback) const { return private_data().template access_query_stream(std::forward(callback)); } + template Tag> + decltype(auto) current_buffer() const { return private_data().template current_buffer(); } + template Tag, typename Value> + void initialize_buffer(Value&& value) { private_data().template initialize_buffer(std::forward(value)); } + template [[nodiscard]] auto pending_dependency_graph() const { return private_data().template pending_dependency_graph(); } + template [[nodiscard]] auto current_dependency_graph() const { return private_data().template current_dependency_graph(); } + template void access_pending_dependency_graph(Callback&& callback) { private_data().template access_pending_dependency_graph(std::forward(callback)); } + template auto edit_dependency_graph(Callback&& callback) { return private_data().template edit_dependency_graph(std::forward(callback)); } + [[nodiscard]] auto validate_dependency_graph() const { return private_data().validate_dependency_graph(); } + template auto for_each_dependency_graph_topological(Callback&& callback) const { return private_data().template for_each_dependency_graph_topological(std::forward(callback)); } + template void for_each_current_dependency_graph(Callback&& callback) const { private_data().for_each_current_dependency_graph(std::forward(callback)); } + template Obj& set(Value&& value) { return private_data().template set(std::forward(value)); } + template [[nodiscard]] auto get() const { return private_data().template get(); } + template [[nodiscard]] const auto& read_prop() const noexcept { return private_data().template read_prop(); } + template void update_prop(Callback&& callback) { private_data().template update_prop(std::forward(callback)); } + template void set_state_callback(Callback&& callback) { private_data().template set_state_callback(std::forward(callback)); } + template void clear_state_callback() { private_data().template clear_state_callback(); } + template void access_state(Callback&& callback) const { private_data().template access_state(std::forward(callback)); } + template [[nodiscard]] const auto& read_state() const noexcept { return private_data().template read_state(); } + template Value> + void update_state(Value&& value) { private_data().template update_state(std::forward(value)); } + template + requires (sizeof...(Members) > 0) && + (detail::State_Member && ...) && + std::invocable> + void update_state(Callback&& callback) { private_data().template update_state(std::forward(callback)); } + template void process(Callback&& callback) { private_data().process(std::forward(callback)); } + void advance() { private_data().advance(); } + template void publish_state() { private_data().template publish_state(); } + template void publish_state(Callback&& callback) { private_data().template publish_state(std::forward(callback)); } + template void advance(Callback&& callback) { private_data().advance(std::forward(callback)); } +}; } -// Impl 在编译期把 Buffer/Dependency_Graph/State 三类机制叠加到继承链;每一层只声明自己的机制,最终类型汇总完整能力。 +// Def 在编译期把 Buffer/Dependency_Graph/State 三类机制叠加到继承链;最终具体对象汇总完整能力。 template requires - detail::Impl_Mechanisms -struct Def : Base { + detail::Def_Mechanisms +struct Def : Base, detail::Public_Access { + using Access = detail::Public_Access; + using Access::access_pending_dependency_graph; + using Access::access_query_stream; + using Access::access_rendering_stream; + using Access::access_state; + using Access::accumulate_stream; + using Access::advance; + using Access::clear_state_callback; + using Access::current_buffer; + using Access::current_dependency_graph; + using Access::edit_dependency_graph; + using Access::exchange_stream; + using Access::for_each_current_dependency_graph; + using Access::for_each_dependency_graph_topological; + using Access::get; + using Access::initialize_buffer; + using Access::pending_buffer; + using Access::pending_dependency_graph; + using Access::process; + using Access::publish_state; + using Access::read_prop; + using Access::read_state; + using Access::set; + using Access::set_state_callback; + using Access::submit_stream; + using Access::update_prop; + using Access::update_state; + using Access::validate_dependency_graph; using This_Object = Self; using Prev_Object = Base; using Base_Tag = Self; @@ -69,10 +147,11 @@ struct Def : Base { using Prev_Builder = typename Base::template Builder; using Prev_State = State_Type; using Base_Private = typename Base::Private; - using Buffers = detail::Impl_Buffers; - using Dependency_Graph_Types = detail::Impl_Dependency_Graph_Types; - using Mpmc_Triple_Buffers = detail::Impl_Mpmc_Triple_Buffers; - using States = detail::Impl_States; + using Buffers = detail::Def_Buffers; + using Dependency_Graph_Types = detail::Def_Dependency_Graph_Types; + using Mpmc_Triple_Buffers = detail::Def_Mpmc_Triple_Buffers; + using States = detail::Def_States; + using Attached_Object = Self; struct Private : Base_Private { using Tag_Type = Base_Tag; using Prev_Private = Base_Private; @@ -119,8 +198,184 @@ struct Def : Base { } }; // Attach_Object 是对象运行时唯一数据入口:Prop 向外发布,State 向内提交,普通 Buffer 只推进,Dependency_Graph 负责依赖图同步。 -template -struct Impl : Obj { +namespace detail { +template +struct Renderable_Runtime_Private : Base {}; + +template + requires requires { typename Base::Renderable_Runtime_Interface; } +struct Renderable_Runtime_Private : Base { +private: + [[nodiscard]] Attached_Private& runtime() noexcept { + return static_cast&>(*this); + } + [[nodiscard]] const Attached_Private& runtime() const noexcept { + return static_cast&>(*this); + } +public: + [[nodiscard]] std::string_view business_name() const override { + static const std::string value = [] { + std::string name = typeid(Obj).name(); + for (const std::string_view prefix : {"struct ", "class "}) + if (name.starts_with(prefix)) name.erase(0, prefix.size()); + if (const auto separator = name.rfind("::"); separator != std::string::npos) + name.erase(0, separator + 2); + return name; + }(); + return value; + } + [[nodiscard]] bool prepare_predicate(Root*, bool dirty) override { + if constexpr (requires { typename Base::No_Prepare; }) return false; + else if constexpr (requires { this->should_prepare(runtime().object, *runtime().state.current, dirty); }) + return this->should_prepare(runtime().object, *runtime().state.current, dirty); + else return false; + } + [[nodiscard]] bool prepare_rebuild_predicate(Root*) override { + if constexpr (requires { this->should_rebuild_prepare_graph(runtime().object, *runtime().current); }) + return this->should_rebuild_prepare_graph(runtime().object, *runtime().current); + else return false; + } + [[nodiscard]] bool has_prepare_graph() const override { + return requires(Renderable_Runtime_Private& value, Obj* object) { + static_cast(value).build_prepare_graph(object, *value.runtime().current); + }; + } + [[nodiscard]] bool has_prepare_run() const override { + return !requires { typename Base::No_Prepare; } && + requires(Renderable_Runtime_Private& value, Obj* object) { value.prepare_data(object); }; + } + void run_prepare(Root*) override { + if constexpr (!requires { typename Base::No_Prepare; } && + requires { this->prepare_data(runtime().object); }) { + this->prepare_data(runtime().object); + auto callback = [&](auto& value) { + if constexpr (requires { value.after_prepare_data(runtime().object); }) + value.after_prepare_data(runtime().object); + }; + Obj::template walk_private(runtime().object, callback); + } + } + [[nodiscard]] typename Base::Task_Graph_Type build_prepare_graph(Root*) override { + using Result = typename Base::Task_Graph_Type; + if constexpr (requires { static_cast(*this).build_prepare_graph(runtime().object, *runtime().current); }) + return static_cast(*this).build_prepare_graph(runtime().object, *runtime().current); + else return Result{}; + } + [[nodiscard]] bool paint_predicate(Root*, bool dirty) override { + if constexpr (requires { this->should_paint(runtime().object, *runtime().state.current, dirty); }) + return this->should_paint(runtime().object, *runtime().state.current, dirty); + else return false; + } + [[nodiscard]] bool paint_rebuild_predicate(Root*) override { + if constexpr (requires { this->should_rebuild_paint_graph(runtime().object, *runtime().current); }) + return this->should_rebuild_paint_graph(runtime().object, *runtime().current); + else return false; + } + [[nodiscard]] bool has_paint_graph() const override { + return requires(Renderable_Runtime_Private& value, Obj* object) { + static_cast(value).build_paint_graph(object, *value.runtime().current); + }; + } + [[nodiscard]] bool has_paint_run() const override { + return requires(Renderable_Runtime_Private& value, Obj* object) { value.paint(object); }; + } + void run_paint(Root*) override { + if constexpr (requires { this->paint(runtime().object); }) this->paint(runtime().object); + } + [[nodiscard]] typename Base::Task_Graph_Type build_paint_graph(Root*) override { + using Result = typename Base::Task_Graph_Type; + if constexpr (requires { static_cast(*this).build_paint_graph(runtime().object, *runtime().current); }) + return static_cast(*this).build_paint_graph(runtime().object, *runtime().current); + else return Result{}; + } + [[nodiscard]] void* pending_renderable_state(Root*) override { + return runtime().state.pending; + } + void publish_renderable_state(Root*) override { + runtime().template publish_state_snapshot(); + } +}; + +template > +struct Axis_Runtime_Private : Base {}; + +template + requires requires { typename Base::Axis_Runtime_Interface; } +struct Axis_Runtime_Private : Base { +private: + [[nodiscard]] Attached_Private& runtime() noexcept { + return static_cast&>(*this); + } + [[nodiscard]] const Attached_Private& runtime() const noexcept { + return static_cast&>(*this); + } +public: + [[nodiscard]] typename Base::Axis_Range_Type runtime_coordinate_range() const override { + return static_cast(*this).coordinate_range(runtime().object); + } + [[nodiscard]] typename Base::Axis_Pixel_Position_Type runtime_coordinate_to_pixel( + typename Base::Axis_Coordinate_Type coordinate) const override { + return static_cast(*this).coordinate_to_pixel(runtime().object, coordinate); + } + [[nodiscard]] typename Base::Axis_Coordinate_Type runtime_pixel_to_coordinate( + typename Base::Axis_Pixel_Position_Type pixel) const override { + return static_cast(*this).pixel_to_coordinate(runtime().object, pixel); + } + [[nodiscard]] typename Base::Axis_Coordinate_Type runtime_point_to_coordinate( + typename Base::Axis_Point_Type point) const override { + return static_cast(*this).point_to_coordinate(runtime().object, point); + } + [[nodiscard]] typename Base::Axis_Pixel_Sample_Count_Type runtime_pixel_sample_count( + typename Base::Axis_Range_Type coordinate_range) const override { + return static_cast(*this).pixel_sample_count(runtime().object, coordinate_range); + } + [[nodiscard]] typename Base::Axis_Coordinate_Type runtime_tick_step( + typename Base::Axis_Range_Type coordinate_range) const override { + return static_cast(*this).tick_step(runtime().object, coordinate_range); + } + [[nodiscard]] std::string runtime_tick_label( + typename Base::Axis_Coordinate_Type tick) const override { + return static_cast(*this).tick_label(runtime().object, tick); + } + [[nodiscard]] typename Base::Axis_Tick_Count_Type runtime_sub_tick_count( + typename Base::Axis_Coordinate_Type major_step) const override { + return static_cast(*this).sub_tick_count(runtime().object, major_step); + } +}; + +template > +struct Runtime_Private_Base : Base {}; + +template + requires requires { typename Base::Time_Axis_Runtime_Interface; } +struct Runtime_Private_Base : Base { +private: + [[nodiscard]] Attached_Private& runtime() noexcept { + return static_cast&>(*this); + } + [[nodiscard]] const Attached_Private& runtime() const noexcept { + return static_cast&>(*this); + } +public: + [[nodiscard]] std::size_t runtime_time_point_count() const override { + return static_cast(*this).time_point_count(runtime().object); + } + typename Base::Axis_Time_Tick_Type runtime_append_time( + typename Base::Time_Of_Day_Type time) override { + return static_cast(*this).append_time(runtime().object, time); + } + [[nodiscard]] typename Base::Time_Of_Day_Type runtime_tick_to_time( + typename Base::Axis_Time_Tick_Type tick) const override { + return static_cast(*this).tick_to_time(runtime().object, tick); + } +}; + +template +struct Attached_Private : Runtime_Private_Base, Commit_Double_Buffer { + static_assert(Object_Root); + static_assert(Lockable); using Prop = typename Obj::Prop; using State = typename Obj::State; using Buffers = typename Obj::Buffers; @@ -128,12 +383,8 @@ struct Impl : Obj { using Mpmc_Triple_Buffers = typename Obj::Mpmc_Triple_Buffers; using States = typename Obj::States; /* CRTP 最终 Builder:所有基类流式接口都返回本类型,确保 build() 分派到最派生业务 Builder。 */ - struct Builder : Obj::template Builder { - using Base = typename Obj::template Builder; - using Base::Base; - }; - using Attached_Object = Obj; - struct Private : Obj::Private, Commit_Double_Buffer { + using Private = Attached_Private; + Obj* object; using Allocator = std::pmr::polymorphic_allocator; mutable Lock advance_lock; /* 仅保护全部双缓冲在 advance/publish 边界的交换。 */ Commit_Double_Buffer state; @@ -141,17 +392,31 @@ struct Impl : Obj { detail::Buffer_Storage buffer_storage; detail::Dependency_Graph_Storage dependency_graph_storage; detail::Mpmc_Triple_Buffer_Storage_Set mpmc_triple_buffer_storage; - explicit Private(std::pmr::memory_resource* resource) : Commit_Double_Buffer(std::allocator_arg, Allocator{resource}), + explicit Attached_Private(Obj* object_value, std::pmr::memory_resource* resource) + : Commit_Double_Buffer(std::allocator_arg, Allocator{resource}), state(std::allocator_arg, Allocator{resource}), buffer_storage(std::allocator_arg, Allocator{resource}), - dependency_graph_storage(std::allocator_arg, Allocator{resource}) {} + dependency_graph_storage(std::allocator_arg, Allocator{resource}), + object(object_value) {} + [[nodiscard]] const void* runtime_current_prop_data() const noexcept override { + return this->current; + } + [[nodiscard]] void* runtime_pending_prop_data() noexcept override { + return this->pending; + } + [[nodiscard]] const void* runtime_current_state_data() const noexcept override { + return state.current; + } + [[nodiscard]] void* runtime_pending_state_data() noexcept override { + return state.pending; + } template requires detail::Prop_Tag_In> [[nodiscard]] const auto& current_prop() const noexcept { using Layer = detail::Prop_Value; return static_cast(*this->current); } template Tag> - void publish_state() { + void publish_state_snapshot() { using Layer = detail::State_Value; state_callbacks.template publish([&] { std::lock_guard guard(advance_lock); @@ -159,19 +424,12 @@ struct Impl : Obj { return Layer{static_cast(*state.current)}; }); } - }; private: - friend struct Root::Builder; - template requires std::constructible_from - explicit Impl(Args&&... args) : Obj(std::forward(args)...) { - this->template bind_object_crtp(); - } - friend struct Root; [[nodiscard]] Private& data() noexcept { - return static_cast(*this->d); + return *this; } [[nodiscard]] const Private& data() const noexcept { - return static_cast(*this->d); + return *this; } template void walk_private(Callback& callback) { @@ -184,71 +442,71 @@ private: template void before_prop_set(Member Owner::* member) { Prop_Access props{*data().pending}; - auto callback = [&](auto& private_data) { private_data.before_prop_set(this, member, props); }; - walk_private(callback); + auto callback = [&](auto& private_data) { private_data.before_prop_set(object, member, props); }; + Obj::template walk_private(object, callback); } template void after_prop_set(Member Owner::* member) { Prop_Access props{*data().pending}; - auto callback = [&](auto& private_data) { private_data.after_prop_set(this, member, props); }; - walk_private(callback); + auto callback = [&](auto& private_data) { private_data.after_prop_set(object, member, props); }; + Obj::template walk_private(object, callback); } template void emit_prop_dependencies() { - this->emit_dependency_source(detail::dependency_id>()); + object->emit_dependency_source(detail::dependency_id>()); using Prop_Tag = detail::Prop_Member_Tag; - this->emit_dependency_source(detail::dependency_id>()); + object->emit_dependency_source(detail::dependency_id>()); } template void before_state_set(Member Owner::* member) { State_Access pending_states{*data().state.pending}; auto callback = [&](auto& private_data) { - private_data.before_state_set(this, member, pending_states); + private_data.before_state_set(object, member, pending_states); }; - walk_private(callback); + Obj::template walk_private(object, callback); } template void after_state_set(Member Owner::* member) { State_Access pending_states{*data().state.pending}; auto callback = [&](auto& private_data) { - private_data.after_state_set(this, member, pending_states); + private_data.after_state_set(object, member, pending_states); }; - walk_private(callback); + Obj::template walk_private(object, callback); } /* 同时发布字段级键和字段所属 State Tag 的状态层级键。 */ template void emit_state_dependencies() { - this->emit_dependency_source(detail::dependency_id>()); + object->emit_dependency_source(detail::dependency_id>()); using State_Tag = detail::State_Member_Tag; - this->emit_dependency_source(detail::dependency_id>()); + object->emit_dependency_source(detail::dependency_id>()); } void before_advance() { State_Access pending_states{*data().state.pending}; State_Access current_states{std::as_const(*data().state.current)}; auto callback = [&](auto& private_data) { private_data.before_advance( - this, + object, data().pending, pending_states, data().current, current_states ); }; - walk_private(callback); + Obj::template walk_private(object, callback); } void after_advance() { State_Access pending_states{*data().state.pending}; State_Access current_states{*data().state.current}; auto callback = [&](auto& private_data) { private_data.after_advance( - this, + object, data().pending, pending_states, data().current, current_states ); }; - walk_private(callback); + Obj::template walk_private(object, callback); } void exchange() { // Prop 与 State 均从外部 pending 提交到内部 current,交换后 pending 同步为最新编辑基线。 @@ -260,7 +518,7 @@ private: public: template Tag> auto& pending_buffer() { - this->emit_dependency_source(detail::dependency_id>()); + object->emit_dependency_source(detail::dependency_id>()); return *data().buffer_storage.template get().pending; } template Tag, typename Input> @@ -334,14 +592,14 @@ public: data().dependency_graph_storage.for_each_current(std::forward(callback)); } template requires detail::Prop_Member && requires(Prop& prop, Value&& value) { prop.*Member = std::forward(value); } - Impl& set(Value&& value) { + Obj& set(Value&& value) { before_prop_set(Member); data().pending->*Member = std::forward(value); after_prop_set(Member); emit_prop_dependencies(); - return *this; + return *object; } - template requires detail::Prop_Dependency_Source + template requires detail::Prop_Dependency_Source [[nodiscard]] auto get() const { return data().pending->*Member; } @@ -398,10 +656,10 @@ public: (after_state_set(Members), ...); (emit_state_dependencies(), ...); } - template Callback> + template Callback> void process(Callback&& callback) { advance(); - data().process(this, std::forward(callback)); + static_cast(*this).process(object, std::forward(callback)); } void advance() { before_advance(); @@ -415,7 +673,7 @@ public: } template Tag> void publish_state() { - data().template publish_state(); + data().template publish_state_snapshot(); } template Tag, auto... Members, typename Callback> requires (sizeof...(Members) > 0) && @@ -435,4 +693,167 @@ public: std::invoke(std::forward(callback), std::as_const(*data().state.current)); } }; +struct Internal_Access { +private: + template + [[nodiscard]] static Attached_Private& runtime(Object* object) noexcept { + return static_cast&>(*object->d); + } + template + [[nodiscard]] static const Attached_Private& runtime(const Object* object) noexcept { + return static_cast&>(*object->d); + } +public: + template + [[nodiscard]] static typename Tag::Private& layer(Object* object) noexcept { + return static_cast(*object->d); + } + template + [[nodiscard]] static const typename Tag::Private& layer(const Object* object) noexcept { + return static_cast(*object->d); + } + template + [[nodiscard]] static const typename Object::Prop& current_prop(const Object* object) noexcept { + return *static_cast(object->d->runtime_current_prop_data()); + } + template + requires detail::Prop_Tag_In> + [[nodiscard]] static const auto& current_prop_layer(const Object* object) noexcept { + using Layer = detail::Prop_Value; + return static_cast(current_prop(object)); + } + template + [[nodiscard]] static typename Object::Prop& pending_prop(Object* object) noexcept { + return *static_cast(object->d->runtime_pending_prop_data()); + } + template + [[nodiscard]] static typename Object::State& pending_state(Object* object) noexcept { + return *static_cast(object->d->runtime_pending_state_data()); + } + template + [[nodiscard]] static const typename Object::State& current_state(const Object* object) noexcept { + return *static_cast(object->d->runtime_current_state_data()); + } + template + requires detail::Buffer_Tag_In + [[nodiscard]] static decltype(auto) current_buffer(const Object* object) { + return runtime(object).template current_buffer(); + } + template + requires detail::Buffer_Tag_In + [[nodiscard]] static decltype(auto) pending_buffer(Object* object) { + return runtime(object).template pending_buffer(); + } + template + static void exchange_stream(Object* object) { + runtime(object).template exchange_stream(); + } + template + static void accumulate_stream(Object* object, std::size_t capacity) { + runtime(object).template accumulate_stream(capacity); + } + template + requires (!std::convertible_to) + static void accumulate_stream(Object* object, Predicate&& retain) { + runtime(object).template accumulate_stream(std::forward(retain)); + } + template + static decltype(auto) access_rendering_stream(Object* object, Callback&& callback) { + return runtime(object).template access_rendering_stream(std::forward(callback)); + } + template + static decltype(auto) access_query_stream(const Object* object, Callback&& callback) { + return runtime(object).template access_query_stream(std::forward(callback)); + } + template + static void submit_stream(Object* object, Input&& input) { + runtime(object).template submit_stream(std::forward(input)); + } + template + static void update_prop(Object* object, Callback&& callback) { + runtime(object).template update_prop(std::forward(callback)); + } + template + static void set(Object* object, Value&& value) { + runtime(object).template set(std::forward(value)); + } + template Value> + static void update_state(Object* object, Value&& value) { + runtime(object).template update_state(std::forward(value)); + } + template + requires (sizeof...(Members) > 0) && + (detail::State_Member && ...) && + std::invocable> + static void update_state(Object* object, Callback&& callback) { + runtime(object).template update_state(std::forward(callback)); + } + template + [[nodiscard]] static auto current_dependency_graph(const Object* object) { + return runtime(object).template current_dependency_graph(); + } + template + [[nodiscard]] static auto pending_dependency_graph(const Object* object) { + return runtime(object).template pending_dependency_graph(); + } + template + static void access_pending_dependency_graph(Object* object, Callback&& callback) { + runtime(object).template access_pending_dependency_graph( + std::forward(callback)); + } + template + static auto edit_dependency_graph(Object* object, Callback&& callback) { + return runtime(object).template edit_dependency_graph( + std::forward(callback)); + } + template + static void for_each_current_dependency_graph(const Object* object, Callback&& callback) { + runtime(object).for_each_current_dependency_graph(std::forward(callback)); + } + template + static void mark_dirty(Object* object) { + object->mark_dirty_id( + detail::dependency_id(), + detail::dependency_id>()); + } + template + [[nodiscard]] static bool dirty(const Object* object) { + return std::find(object->dirty_tags.begin(), object->dirty_tags.end(), + detail::dependency_id()) != object->dirty_tags.end(); + } + template + [[nodiscard]] static bool take_dirty(Object* object) { + const auto current = std::find(object->dirty_tags.begin(), object->dirty_tags.end(), + detail::dependency_id()); + if (current == object->dirty_tags.end()) return false; + object->dirty_tags.erase(current); + return true; + } + template + static void advance(Object* object) { + runtime(object).advance(); + } + template + static void publish_state(Object* object) { + runtime(object).template publish_state(); + } + template + static void access_state(const Object* object, Callback&& callback) { + runtime(object).template access_state(std::forward(callback)); + } + template + static void publish_state(Object* object, Callback&& callback) { + runtime(object).template publish_state(std::forward(callback)); + } + template + [[nodiscard]] static typename Object::Private& get(Object* object) noexcept { + return static_cast(*object->d); + } + template + [[nodiscard]] static const typename Object::Private& get(const Object* object) noexcept { + return static_cast(*object->d); + } +}; +} } diff --git a/kernel/src/kernel/render_common.hpp b/kernel/src/kernel/render_common.hpp index b94291f..a45b581 100644 --- a/kernel/src/kernel/render_common.hpp +++ b/kernel/src/kernel/render_common.hpp @@ -15,7 +15,6 @@ namespace aethera { using double_buffer::Pinned; using double_buffer::Def; -using double_buffer::Impl; using double_buffer::State_Type; using double_buffer::State_Access; using double_buffer::Prop_Access; diff --git a/kernel/src/kernel/renderable.hpp b/kernel/src/kernel/renderable.hpp index 43c49d2..5459fb0 100644 --- a/kernel/src/kernel/renderable.hpp +++ b/kernel/src/kernel/renderable.hpp @@ -64,7 +64,7 @@ concept Renderable_Object = Attached && std::derived_from && r } && (No_Prepare_Renderable || Prepare_Data_Renderable || Prepare_Graph_Renderable) && (Paint_Data_Renderable || Paint_Graph_Renderable); /* * Renderable 定义 Scene 可调度对象的公共机制。 - * 用户继续派生该定义,在派生类型的 Private 中提供 Prepare/Paint 定制点,并最终使用 Impl 创建可运行实例。 + * 用户继续派生该定义,在最终具体类型的 Private 中提供 Prepare/Paint 定制点并直接创建该具体对象。 */ struct Renderable : Def { /* Renderable 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */ diff --git a/kernel/src/kernel/renderable.ipp b/kernel/src/kernel/renderable.ipp index 53c26b8..50f07ca 100644 --- a/kernel/src/kernel/renderable.ipp +++ b/kernel/src/kernel/renderable.ipp @@ -4,40 +4,34 @@ #include namespace aethera { struct Renderable::Private : Prev_Private { + using Renderable_Runtime_Interface = void; + using Task_Graph_Type = Task_Graph; + using Renderable_State_Tag = Renderable::Base_Tag; /* * 派生 Renderable 的 Private 必须继承 Prev_Private,并为 Prepare/Paint 各提供一种执行能力。 * Prepare 在编译期按能力选择:存在 build_prepare_graph(...) 时使用子图模式,否则使用 prepare_data(...);两者同时存在时子图优先。 * Paint 在编译期按能力选择:存在 build_paint_graph(...) 时使用子图模式,否则使用 paint(...);两者同时存在时子图优先。 * should_rebuild_prepare_graph(...) 和 should_rebuild_paint_graph(...) 只决定已选中子图模式后的重建时机,不负责选择模式。 */ - using Run_Predicate = bool (*)(Root*, bool); - using Rebuild_Predicate = bool (*)(Root*); - using Stage_Run = void (*)(Root*); - using Graph_Builder = Task_Graph (*)(Root*); - using State_Get = State* (*)(Root*); - using State_Notify = void (*)(Root*); using Event_Run = void (*)(Root*, const Event&); using Event_Routing_Distance_Run = std::optional (*)(const Root*, const Event&); using Color_Cache_Visitor = void (*)(void*, const Color_Cache&); using Color_Cache_Visit = void (*)(Root*, void*, Color_Cache_Visitor); - struct Stage_Dispatch { - Run_Predicate predicate; /* 判断该阶段本次是否执行。 */ - Rebuild_Predicate rebuild_predicate; /* 判断已有阶段子图是否重建。 */ - Stage_Run run; /* 数据模式执行入口;子图模式为空。 */ - Graph_Builder builder; /* 子图模式构建入口;数据模式为空。 */ - }; - struct State_Dispatch { - State_Get current; /* 读取已经发布的 Renderable 状态。 */ - State_Get pending; /* 写入本阶段诊断,由完成节点统一交换发布。 */ - State_Notify publish; /* 阶段完成后交换 State 双缓冲并发布稳定 current。 */ - }; - struct Dispatch { - Stage_Dispatch prepare; /* Prepare 阶段分派。 */ - Stage_Dispatch paint; /* Paint 阶段分派。 */ - State_Dispatch state; /* Renderable 状态访问与发布分派。 */ - std::string_view business_name; /* 最终 Renderable 类型的诊断业务名。 */ - }; - const Dispatch* dispatch{}; /* 绑定最终对象类型后指向其静态分派表。 */ + [[nodiscard]] virtual std::string_view business_name() const = 0; + [[nodiscard]] virtual bool prepare_predicate(Root* object, bool dirty) = 0; + [[nodiscard]] virtual bool prepare_rebuild_predicate(Root* object) = 0; + [[nodiscard]] virtual bool has_prepare_graph() const = 0; + [[nodiscard]] virtual bool has_prepare_run() const = 0; + virtual void run_prepare(Root* object) = 0; + [[nodiscard]] virtual Task_Graph build_prepare_graph(Root* object) = 0; + [[nodiscard]] virtual bool paint_predicate(Root* object, bool dirty) = 0; + [[nodiscard]] virtual bool paint_rebuild_predicate(Root* object) = 0; + [[nodiscard]] virtual bool has_paint_graph() const = 0; + [[nodiscard]] virtual bool has_paint_run() const = 0; + virtual void run_paint(Root* object) = 0; + [[nodiscard]] virtual Task_Graph build_paint_graph(Root* object) = 0; + [[nodiscard]] virtual void* pending_renderable_state(Root* object) = 0; + virtual void publish_renderable_state(Root* object) = 0; Event_Run event_run{}; /* 最终 Private 具备事件能力时的无虚函数入口。 */ Event_Routing_Distance_Run event_routing_distance_run{}; /* 可选事件候选距离;Scene 路由规则按需查询。 */ Color_Cache_Visit color_cache_visit{}; /* 最终对象存在 Color_Cache Buffer 时访问本轮写入结果。 */ @@ -64,7 +58,7 @@ struct Renderable::Private : Prev_Private { }; template void Renderable::run_prepare_data(Object* object) { - auto& private_data = static_cast(*object->d); + auto& private_data = double_buffer::detail::Internal_Access::get(object); private_data.prepare_data(object); auto callback = [&](auto& value) { if constexpr (requires { value.after_prepare_data(object); }) value.after_prepare_data(object); @@ -87,24 +81,30 @@ inline void Renderable::Private::after_prepare_data(Attached auto*) {} template void Renderable::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); - if constexpr (!No_Prepare_Renderable) object->template mark_dirty(); + if constexpr (!No_Prepare_Renderable) + double_buffer::detail::Internal_Access::mark_dirty(object); auto& data = static_cast(*this); if constexpr (Event_Renderable) { data.event_run = [](Root* root, const Event& event) { auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); + auto& private_data = double_buffer::detail::Internal_Access::get(value); private_data.handle_event(value, event); }; } - if constexpr (requires { object->template pending_buffer(); }) { - using Cache = std::remove_reference_ttemplate pending_buffer())>; + if constexpr (requires { + double_buffer::detail::Internal_Access::pending_buffer(object); + }) { + using Cache = std::remove_reference_t(object))>; if constexpr (std::derived_from) { data.color_cache_visit = [](Root* root, void* context, Private::Color_Cache_Visitor visitor) { auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - const auto& render_state = static_cast(*private_data.state.current); - if (render_state.paint_executed) visitor(context, value->template pending_buffer()); - else visitor(context, value->template current_buffer()); + const auto& render_state = static_cast( + double_buffer::detail::Internal_Access::current_state(value)); + if (render_state.paint_executed) + visitor(context, double_buffer::detail::Internal_Access::pending_buffer(value)); + else + visitor(context, double_buffer::detail::Internal_Access::current_buffer(value)); }; } } @@ -112,106 +112,6 @@ void Renderable::Private::bind_private_crtp(Object* object) { inline void Renderable::bind_dependency_graph_object(Attached auto* object) { using Object = std::remove_pointer_t; static_assert(Renderable_Object); - auto& data = static_cast(*object->d); - static const std::string business_name = [] { - std::string name = typeid(typename Object::Attached_Object).name(); - for (const std::string_view prefix : {"struct ", "struct "}) if (name.starts_with(prefix)) name.erase(0, prefix.size()); - if (const auto separator = name.rfind("::"); separator != std::string::npos) name.erase(0, separator + 2); - return name; - }(); - static const Private::Dispatch dispatch{ - { - [](Root* root, bool dirty) { - auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - if constexpr (No_Prepare_Renderable) return false; - return private_data.should_prepare(value, *private_data.state.current, dirty); - }, - [](Root* root) { - auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - return private_data.should_rebuild_prepare_graph(value, *private_data.current); - }, - []() -> Private::Stage_Run { - if constexpr (Prepare_Data_Renderable && !No_Prepare_Renderable) { - return [](Root* root) { - auto* value = static_cast(root); - run_prepare_data(value); - }; - } - else { - return nullptr; - } - }(), - []() -> Private::Graph_Builder { - if constexpr (Prepare_Graph_Renderable) { - return [](Root* root) { - auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - return private_data.build_prepare_graph(value, *private_data.current); - }; - } - else { - return nullptr; - } - }() - }, - { - [](Root* root, bool dirty) { - auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - return private_data.should_paint(value, *private_data.state.current, dirty); - }, - [](Root* root) { - auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - return private_data.should_rebuild_paint_graph(value, *private_data.current); - }, - []() -> Private::Stage_Run { - if constexpr (Paint_Data_Renderable) { - return [](Root* root) { - auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - private_data.paint(value); - }; - } - else { - return nullptr; - } - }(), - []() -> Private::Graph_Builder { - if constexpr (Paint_Graph_Renderable) { - return [](Root* root) { - auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - return private_data.build_paint_graph(value, *private_data.current); - }; - } - else { - return nullptr; - } - }() - }, - { - [](Root* root) { - auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - return static_cast(private_data.state.current); - }, - [](Root* root) { - auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - return static_cast(private_data.state.pending); - }, - [](Root* root) { - auto* value = static_cast(root); - auto& private_data = static_cast(*value->d); - private_data.template publish_state(); - } - }, - business_name - }; - data.dispatch = &dispatch; - object->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(object); } } diff --git a/kernel/src/kernel/scene.hpp b/kernel/src/kernel/scene.hpp index 8ea50a6..3bf7d86 100644 --- a/kernel/src/kernel/scene.hpp +++ b/kernel/src/kernel/scene.hpp @@ -9,7 +9,7 @@ struct Scene_Event_Stream_Tag {}; /* Scene 状态标签,用于访问和订阅 Scene::State。 */ /* * Scene 只汇总跨渲染后端共有的 Prepare 数据依赖图并构建 Taskflow。 - * 用户最终通过 Impl 创建可使用实例;编辑 Dependency_Graph 后调用 advance() 提交结构变化,再调用 process(...) 执行当前场景。 + * 最终具体 Scene 直接持有其 Private;编辑 Dependency_Graph 后提交结构变化,再执行当前场景。 */ struct Scene : Def, diff --git a/kernel/src/kernel/scene.ipp b/kernel/src/kernel/scene.ipp index d156b47..cb3f9a5 100644 --- a/kernel/src/kernel/scene.ipp +++ b/kernel/src/kernel/scene.ipp @@ -24,7 +24,7 @@ struct Scene::Private : Prev_Private { State_Access pending_states, const Prop* current_prop, State_Access current_states); - /* Impl CRTP 入口:同步执行当前总 Taskflow;派生 Private 只有在替换完整 Scene 处理语义时才应覆盖。 */ + /* Private CRTP 入口:执行当前总 Taskflow;派生 Private 只有在替换完整 Scene 处理语义时才应覆盖。 */ template void process(Object* object, Callback&& callback) requires std::invocable; /* 派生渲染 Scene 使用:把调用方外部帧贯穿公共 Prepare 阶段并追加诊断标记。 */ @@ -42,8 +42,8 @@ void Scene::Private::bind_private_crtp(Object* object) { } template Scene::Event_Batch Scene::Private::take_events(Object* object) { - object->template exchange_stream(); - return object->template access_rendering_stream( + double_buffer::detail::Internal_Access::exchange_stream(object); + return double_buffer::detail::Internal_Access::access_rendering_stream(object, [&](std::span events) { Event_Batch result{object->memory_resource()}; result.reserve(events.size()); @@ -64,8 +64,8 @@ void Scene::Private::process(Object* object, Callback&& callback) requires std:: } template void Scene::Private::process(Object* object, Render_Frame* frame, Callback&& callback) requires std::invocable { - auto& private_data = static_cast(*this); - auto& state = static_cast(*private_data.state.pending); + auto& state = static_cast( + double_buffer::detail::Internal_Access::pending_state(object)); state.taskflow_execution_time_ns = 0; if (frame) frame->mark(Frame_Trace_Marker::prepare_started); if (runtime->taskflow && !runtime->taskflow->empty()) @@ -73,12 +73,12 @@ void Scene::Private::process(Object* object, Render_Frame* frame, Callback&& cal ? detail::run_taskflow(*runtime->taskflow, *frame, "scene.prepare") : detail::run_taskflow(*runtime->taskflow); if (frame) frame->mark(Frame_Trace_Marker::prepare_finished); - object->template current_dependency_graph().for_each_bound( + double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each_bound( [](Renderable* renderable, Renderable::Private& data) { - data.dispatch->state.publish(renderable); + data.publish_renderable_state(renderable); }); state.event_statistics = event_statistics.state(); - private_data.template publish_state(); + double_buffer::detail::Internal_Access::publish_state(object); Result result; std::invoke(std::forward(callback), std::as_const(result)); } @@ -93,7 +93,7 @@ void Scene::Private::after_advance(Object* object, scene_state.taskflow_rebuilt = false; std::pmr::unordered_set advanced_objects{resource}; advanced_objects.insert(object); - object->for_each_current_dependency_graph( + double_buffer::detail::Internal_Access::for_each_current_dependency_graph(object, [&](const Dependency_Graph& dependency_graph) { dependency_graph.for_each( [&](const Dependency_Graph::Node& node) { @@ -104,9 +104,10 @@ void Scene::Private::after_advance(Object* object, ); } ); - auto prepare_dependencies = object->template current_dependency_graph(); + auto prepare_dependencies = + double_buffer::detail::Internal_Access::current_dependency_graph(object); bool taskflow_dirty = !runtime->taskflow; - object->template access_pending_dependency_graph( + double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, [&](auto& prepare_state) { taskflow_dirty = taskflow_dirty || prepare_state.dirty(); }); if (!taskflow_dirty) return; if (!runtime->taskflow) runtime->taskflow = std::make_unique("scene.prepare"); @@ -133,28 +134,27 @@ void Scene::Private::after_advance(Object* object, Root* root = renderable; auto* data = prepare_dependencies.private_data(root); if (!data) continue; - auto* dispatch = data->dispatch; - const auto task_prefix = std::string(dispatch->business_name) + ".prepare"; - auto prepare_if = taskflow.add_condition(task_prefix + ".condition", [data, dispatch, root] { - auto& state = *dispatch->state.pending(root); + const auto task_prefix = std::string(data->business_name()) + ".prepare"; + auto prepare_if = taskflow.add_condition(task_prefix + ".condition", [data, root] { + auto& state = *static_cast(data->pending_renderable_state(root)); state.prepare_graph_rebuilt = false; state.prepare_execution_time_ns = 0; - if (dispatch->prepare.builder) { - bool rebuild = dispatch->prepare.rebuild_predicate(root); + if (data->has_prepare_graph()) { + bool rebuild = data->prepare_rebuild_predicate(root); if (!data->prepare_graph_built || rebuild) { - *data->prepare_graph = dispatch->prepare.builder(root); + *data->prepare_graph = data->build_prepare_graph(root); data->prepare_graph_built = true; state.prepare_graph_rebuilt = true; - root->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(root); } state.prepare_task_count = data->prepare_graph->size(); } else { - state.prepare_task_count = dispatch->prepare.run ? 1 : 0; + state.prepare_task_count = data->has_prepare_run() ? 1 : 0; } - bool dirty = root->template dirty(); + bool dirty = double_buffer::detail::Internal_Access::dirty(root); state.prepare_dirty = dirty; - state.prepare_executed = dispatch->prepare.predicate(root, dirty); + state.prepare_executed = data->prepare_predicate(root, dirty); if (state.prepare_executed) { state.prepare_execution_time_ns = static_cast( std::chrono::duration_cast( @@ -163,19 +163,19 @@ void Scene::Private::after_advance(Object* object, return state.prepare_executed ? 0 : 1; }); Task_Node prepare_run; - if (dispatch->prepare.builder) { + if (data->has_prepare_graph()) { if (!data->prepare_graph) data->prepare_graph = std::make_unique(task_prefix + ".graph"); prepare_run = taskflow.compose(task_prefix + ".graph", *data->prepare_graph); } else { - prepare_run = taskflow.add(task_prefix + ".data", [dispatch, root] { - if (dispatch->prepare.run) dispatch->prepare.run(root); + prepare_run = taskflow.add(task_prefix + ".data", [data, root] { + if (data->has_prepare_run()) data->run_prepare(root); }); } - auto prepare_done = taskflow.add(task_prefix + ".complete", [dispatch, root] { - auto& state = *dispatch->state.pending(root); + auto prepare_done = taskflow.add(task_prefix + ".complete", [data, root] { + auto& state = *static_cast(data->pending_renderable_state(root)); if (state.prepare_executed) { - root->template take_dirty(); + (void)double_buffer::detail::Internal_Access::take_dirty(root); const auto finished = static_cast( std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count()); @@ -232,7 +232,7 @@ void Scene::Private::after_advance(Object* object, scene_state.taskflow_max_successors = std::max( scene_state.taskflow_max_successors, node.successors.size()); } - object->template access_pending_dependency_graph( + double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, [](auto& prepare_state) { if (prepare_state.dirty()) prepare_state.take_dirty(); }); scene_state.taskflow_rebuilt = true; } diff --git a/kernel/src/test/Dependency_Graph_Test.cpp b/kernel/src/test/Dependency_Graph_Test.cpp index d2d50cd..c5226c6 100644 --- a/kernel/src/test/Dependency_Graph_Test.cpp +++ b/kernel/src/test/Dependency_Graph_Test.cpp @@ -11,7 +11,7 @@ struct Node_Object : double_buffer::Def { }; struct Private : Prev_Private {}; }; -using Node = double_buffer::Impl; +using Node = Node_Object; struct Graph_Object : double_buffer::Def> { struct Prop : Prev_Prop {}; struct State : Prev_State { @@ -20,19 +20,19 @@ struct Graph_Object : double_buffer::Def; +using Graph = Graph_Object; inline auto& Graph_Object::data_for_test() { - return static_cast(*d); + return static_cast&>(*d); } template std::unique_ptr build_object() { - auto result = typename Object_Type::Builder{}.build(); + auto result = typename Object_Type::template Builder{}.build(); if (!result) std::terminate(); return std::move(result).value(); } -static_assert(!double_buffer::detail::Dependency_Object); +static_assert(double_buffer::detail::Dependency_Object); static_assert(double_buffer::detail::Dependency_Object); -static_assert(!double_buffer::detail::State_Dependency_Source); +static_assert(double_buffer::detail::State_Dependency_Source); static_assert(double_buffer::detail::State_Layer_Dependency_Source); static_assert(double_buffer::detail::Prop_Layer_Dependency_Source); } diff --git a/kernel/src/test/object_test.cpp b/kernel/src/test/object_test.cpp index eb0430d..9b993f2 100644 --- a/kernel/src/test/object_test.cpp +++ b/kernel/src/test/object_test.cpp @@ -11,7 +11,7 @@ struct Stream_Object : double_buffer::Def< struct State : Prev_State { bool operator==(const State&) const = default; }; struct Private : Prev_Private {}; }; -using Stream = double_buffer::Impl; +using Stream = Stream_Object; struct Test_Object : double_buffer::Def> { struct Prop : Prev_Prop { int first{}; @@ -25,9 +25,9 @@ struct Test_Object : double_buffer::Def; +using Object = Test_Object; inline auto& Test_Object::data_for_test() { - return static_cast(*d); + return static_cast&>(*d); } struct Derived_Object : double_buffer::Def { struct Prop : Prev_Prop {}; @@ -37,7 +37,7 @@ struct Derived_Object : double_buffer::Def { }; struct Private : Prev_Private {}; }; -using Derived = double_buffer::Impl; +using Derived = Derived_Object; struct Lifetime_Object : double_buffer::Def { struct Prop : Prev_Prop {}; struct State : Prev_State { @@ -50,14 +50,13 @@ struct Lifetime_Object : double_buffer::Def; +using Lifetime = Lifetime_Object; template std::unique_ptr build_object() { - auto result = typename Object_Type::Builder{}.build(); + auto result = typename Object_Type::template Builder{}.build(); if (!result) std::terminate(); return std::move(result).value(); } -static_assert(!std::constructible_from); } TEST(object_lifetime, root_destroys_attached_private_through_virtual_base) { Lifetime_Object::Private::destruction_count = 0; @@ -68,7 +67,7 @@ TEST(object_lifetime, root_destroys_attached_private_through_virtual_base) { EXPECT_EQ(Lifetime_Object::Private::destruction_count, 1); } TEST(object_builder, build_attaches_private_and_commits_staged_values) { - Object::Builder builder; + Object::Builder builder; builder.set(&Test_Object::Prop::first, 29).set(31); auto result = builder.build(); ASSERT_TRUE(result.has_value()); @@ -248,7 +247,12 @@ TEST(base_tag, prop_state_and_private_domains_are_independently_addressable) { double_buffer::Prop_Access props{prop}; props.get().first = 53; EXPECT_EQ(props.get().first, 53); - Derived_Object::Private private_data; + struct Test_Private final : Derived_Object::Private { + [[nodiscard]] const void* runtime_current_prop_data() const noexcept override { return nullptr; } + [[nodiscard]] void* runtime_pending_prop_data() noexcept override { return nullptr; } + [[nodiscard]] const void* runtime_current_state_data() const noexcept override { return nullptr; } + [[nodiscard]] void* runtime_pending_state_data() noexcept override { return nullptr; } + } private_data; double_buffer::Private_Access private_layers{private_data}; EXPECT_EQ(&private_layers.get(), static_cast(&private_data)); EXPECT_EQ(&private_layers.get(), static_cast(&private_data)); diff --git a/kernel/src/test/render_test.cpp b/kernel/src/test/render_test.cpp index 56ca012..8c7b5ae 100644 --- a/kernel/src/test/render_test.cpp +++ b/kernel/src/test/render_test.cpp @@ -45,9 +45,9 @@ struct Graph_Renderable : double_buffer::Def; -using Graph = double_buffer::Impl; -using Scene = double_buffer::Impl; +using Direct = Direct_Renderable; +using Graph = Graph_Renderable; +using Scene = aethera::Scene; inline Direct_Renderable::Private& Direct_Renderable::data_for_test() { return static_cast(*d); } @@ -66,8 +66,10 @@ struct Dependency_Renderable : double_buffer::Def::Private&>(*this); - if (std::exchange(publish_revision, false)) object->template update_state<&State::revision>(private_data.state.pending->revision + 1); + using Object = std::remove_pointer_t; + auto& private_data = static_cast&>(*this); + if (std::exchange(publish_revision, false)) + private_data.template update_state<&State::revision>(private_data.state.pending->revision + 1); } void paint(double_buffer::Attached auto*) { ++paint_calls; @@ -75,13 +77,13 @@ struct Dependency_Renderable : double_buffer::Def; +using Dependency = Dependency_Renderable; inline Dependency_Renderable::Private& Dependency_Renderable::data_for_test() { return static_cast(*d); } template std::unique_ptr build_object() { - auto result = typename Object_Type::Builder{}.build(); + auto result = typename Object_Type::template Builder{}.build(); if (!result) std::terminate(); return std::move(result).value(); } diff --git a/render_2D/render_2D/axis/Abs_Axis.cpp b/render_2D/render_2D/axis/Abs_Axis.cpp index 138db61..6dcf1ef 100644 --- a/render_2D/render_2D/axis/Abs_Axis.cpp +++ b/render_2D/render_2D/axis/Abs_Axis.cpp @@ -7,30 +7,31 @@ namespace aethera::render_2d { bool Abs_Axis::State::operator==(const State&) const = default; bool Abs_Axis::Prop::operator==(const Prop&) const = default; -Axis_Range Abs_Axis::Private::coordinate_range(const Root* object) const { - return dispatch->coordinate_range(object); +Axis_Range Abs_Axis::coordinate_range() const { + return double_buffer::detail::Internal_Access::layer(this).runtime_coordinate_range(); } -double Abs_Axis::Private::coordinate_to_pixel(const Root* object, double coordinate) const { - return dispatch->coordinate_to_pixel(object, coordinate); +Axis_Pixel_Position Abs_Axis::coordinate_to_pixel(Axis_Coordinate coordinate) const { + return double_buffer::detail::Internal_Access::layer(this).runtime_coordinate_to_pixel(coordinate); } -double Abs_Axis::Private::pixel_to_coordinate(const Root* object, double pixel) const { - return dispatch->pixel_to_coordinate(object, pixel); +Axis_Coordinate Abs_Axis::pixel_to_coordinate(Axis_Pixel_Position pixel) const { + return double_buffer::detail::Internal_Access::layer(this).runtime_pixel_to_coordinate(pixel); } -double Abs_Axis::Private::point_to_coordinate(const Root* object, Point_F point) const { - return dispatch->point_to_coordinate(object, point); +Axis_Coordinate Abs_Axis::point_to_coordinate(Point_F point) const { + return double_buffer::detail::Internal_Access::layer(this).runtime_point_to_coordinate(point); } -int Abs_Axis::Private::pixel_sample_count(const Root* object, Axis_Range coordinate_range) const { - return dispatch->pixel_sample_count(object, coordinate_range); +Axis_Pixel_Sample_Count Abs_Axis::pixel_sample_count(Axis_Range coordinate_range) const { + return double_buffer::detail::Internal_Access::layer(this).runtime_pixel_sample_count(coordinate_range); } -double Abs_Axis::Private::tick_step(const Root* object, Axis_Range coordinate_range) const { - return dispatch->tick_step(object, coordinate_range); +Axis_Coordinate Abs_Axis::tick_step(Axis_Range coordinate_range) const { + return double_buffer::detail::Internal_Access::layer(this).runtime_tick_step(coordinate_range); } -std::string Abs_Axis::Private::tick_label(const Root* object, double tick) const { - return dispatch->tick_label(object, tick); +std::string Abs_Axis::tick_label(Axis_Coordinate tick) const { + return double_buffer::detail::Internal_Access::layer(this).runtime_tick_label(tick); } -int Abs_Axis::Private::sub_tick_count(const Root* object, double major_step) const { - return dispatch->sub_tick_count(object, major_step); +Axis_Tick_Count Abs_Axis::sub_tick_count(Axis_Coordinate major_step) const { + return double_buffer::detail::Internal_Access::layer(this).runtime_sub_tick_count(major_step); } + double Abs_Axis::Private::nice_tick_step(Axis_Range coordinate_range) { if (!(coordinate_range.size() > 0.0) || !std::isfinite(coordinate_range.origin) || !std::isfinite(coordinate_range.target)) return 1.0; @@ -47,28 +48,4 @@ std::string Abs_Axis::Private::localized_number(double value, int precision, Num if (locale.decimal_point != '.') std::replace(result.begin(), result.end(), '.', locale.decimal_point); return result; } -Axis_Range Abs_Axis::coordinate_range() const { - return static_cast(*d).coordinate_range(this); -} -double Abs_Axis::coordinate_to_pixel(double coordinate) const { - return static_cast(*d).coordinate_to_pixel(this, coordinate); -} -double Abs_Axis::pixel_to_coordinate(double pixel) const { - return static_cast(*d).pixel_to_coordinate(this, pixel); -} -double Abs_Axis::point_to_coordinate(Point_F point) const { - return static_cast(*d).point_to_coordinate(this, point); -} -int Abs_Axis::pixel_sample_count(Axis_Range coordinate_range) const { - return static_cast(*d).pixel_sample_count(this, coordinate_range); -} -double Abs_Axis::tick_step(Axis_Range coordinate_range) const { - return static_cast(*d).tick_step(this, coordinate_range); -} -std::string Abs_Axis::tick_label(double tick) const { - return static_cast(*d).tick_label(this, tick); -} -int Abs_Axis::sub_tick_count(double major_step) const { - return static_cast(*d).sub_tick_count(this, major_step); -} } diff --git a/render_2D/render_2D/axis/Abs_Axis.hpp b/render_2D/render_2D/axis/Abs_Axis.hpp index eafe766..3d2507a 100644 --- a/render_2D/render_2D/axis/Abs_Axis.hpp +++ b/render_2D/render_2D/axis/Abs_Axis.hpp @@ -18,8 +18,14 @@ concept Axis_Object = Renderable_Object && std::derived_from && { private_data.tick_label(object, tick) } -> std::same_as; { private_data.sub_tick_count(object, tick) } -> std::same_as; }; -/* 所有二维坐标轴共享的定义层;最终通过 Impl 创建运行时对象。 */ +/* 所有二维坐标轴共享的定义层;最终具体坐标轴直接持有其 Private 运行时实现。 */ struct Abs_Axis : Def { + using Axis_Range_Type = Axis_Range; + using Axis_Coordinate_Type = Axis_Coordinate; + using Axis_Pixel_Position_Type = Axis_Pixel_Position; + using Axis_Point_Type = Point_F; + using Axis_Pixel_Sample_Count_Type = Axis_Pixel_Sample_Count; + using Axis_Tick_Count_Type = Axis_Tick_Count; struct Prop : Prev_Prop { Point_F position{}; /* 坐标轴起点在画布中的二维像素位置。 */ Axis_Pixel_Length pixel_length{}; /* 从坐标起点到终点的轴向像素跨度;为 0 时反算返回坐标起点。 */ @@ -40,22 +46,22 @@ struct Abs_Axis : Def { }; /* 完整声明及派生轴 CRTP 能力契约见 Abs_Axis.ipp。 */ struct Private; - /* 返回由最终轴 Prop 计算得到的当前有向坐标区间。 */ [[nodiscard]] Axis_Range coordinate_range() const; - /* 将坐标值映射到当前轴向像素位置。 */ [[nodiscard]] Axis_Pixel_Position coordinate_to_pixel(Axis_Coordinate coordinate) const; - /* 将当前轴向像素位置反算为坐标值。 */ [[nodiscard]] Axis_Coordinate pixel_to_coordinate(Axis_Pixel_Position pixel) const; - /* 按轴方向从二维像素点取值并反算为坐标。 */ [[nodiscard]] Axis_Coordinate point_to_coordinate(Point_F point) const; - /* 返回给定坐标区间覆盖的整数像素样本数量。 */ [[nodiscard]] Axis_Pixel_Sample_Count pixel_sample_count(Axis_Range coordinate_range) const; - /* 返回给定坐标区间使用的主刻度步长。 */ [[nodiscard]] Axis_Coordinate tick_step(Axis_Range coordinate_range) const; - /* 返回指定主刻度的显示文本。 */ [[nodiscard]] std::string tick_label(Axis_Coordinate tick) const; - /* 返回相邻主刻度之间的次刻度数量。 */ [[nodiscard]] Axis_Tick_Count sub_tick_count(Axis_Coordinate major_step) const; + /* 返回由最终轴 Prop 计算得到的当前有向坐标区间。 */ + /* 将坐标值映射到当前轴向像素位置。 */ + /* 将当前轴向像素位置反算为坐标值。 */ + /* 按轴方向从二维像素点取值并反算为坐标。 */ + /* 返回给定坐标区间覆盖的整数像素样本数量。 */ + /* 返回给定坐标区间使用的主刻度步长。 */ + /* 返回指定主刻度的显示文本。 */ + /* 返回相邻主刻度之间的次刻度数量。 */ }; } #include "Abs_Axis.ipp" diff --git a/render_2D/render_2D/axis/Abs_Axis.ipp b/render_2D/render_2D/axis/Abs_Axis.ipp index 205c20f..1011f6a 100644 --- a/render_2D/render_2D/axis/Abs_Axis.ipp +++ b/render_2D/render_2D/axis/Abs_Axis.ipp @@ -4,6 +4,21 @@ #include namespace aethera::render_2d { struct Abs_Axis::Private : Prev_Private { + using Axis_Runtime_Interface = void; + using Axis_Range_Type = Axis_Range; + using Axis_Coordinate_Type = Axis_Coordinate; + using Axis_Pixel_Position_Type = Axis_Pixel_Position; + using Axis_Point_Type = Point_F; + using Axis_Pixel_Sample_Count_Type = Axis_Pixel_Sample_Count; + using Axis_Tick_Count_Type = Axis_Tick_Count; + [[nodiscard]] virtual Axis_Range runtime_coordinate_range() const = 0; + [[nodiscard]] virtual Axis_Pixel_Position runtime_coordinate_to_pixel(Axis_Coordinate coordinate) const = 0; + [[nodiscard]] virtual Axis_Coordinate runtime_pixel_to_coordinate(Axis_Pixel_Position pixel) const = 0; + [[nodiscard]] virtual Axis_Coordinate runtime_point_to_coordinate(Point_F point) const = 0; + [[nodiscard]] virtual Axis_Pixel_Sample_Count runtime_pixel_sample_count(Axis_Range coordinate_range) const = 0; + [[nodiscard]] virtual Axis_Coordinate runtime_tick_step(Axis_Range coordinate_range) const = 0; + [[nodiscard]] virtual std::string runtime_tick_label(Axis_Coordinate tick) const = 0; + [[nodiscard]] virtual Axis_Tick_Count runtime_sub_tick_count(Axis_Coordinate major_step) const = 0; /* * 派生轴 Private 必须继承 Prev_Private,并提供以下 CRTP 能力: * Axis_Range coordinate_range(const T* object) const:从最终轴当前 State 返回权威坐标区间。 @@ -12,23 +27,6 @@ struct Abs_Axis::Private : Prev_Private { * int sub_tick_count(const T* object, double major_step) const:返回次刻度数量;默认固定返回 4。 * 坐标映射直接读取 Abs_Axis::Base_Tag 状态并调用最终 Private 的 coordinate_range(...),不保存变换快照。 */ - using Coordinate_Range_Call = Axis_Range (*)(const Root*); - using Scalar_Call = double (*)(const Root*, double); - using Point_Call = double (*)(const Root*, Point_F); - using Range_Count_Call = int (*)(const Root*, Axis_Range); - using Range_Scalar_Call = double (*)(const Root*, Axis_Range); - using Count_Call = int (*)(const Root*, double); - using Label_Call = std::string (*)(const Root*, double); - struct Dispatch { - Coordinate_Range_Call coordinate_range; /* 读取最终轴权威坐标区间。 */ - Scalar_Call coordinate_to_pixel; /* 坐标到像素的最终类型分派。 */ - Scalar_Call pixel_to_coordinate; /* 像素到坐标的最终类型分派。 */ - Point_Call point_to_coordinate; /* 二维点到坐标的最终类型分派。 */ - Range_Count_Call pixel_sample_count; /* 像素样本数量的最终类型分派。 */ - Range_Scalar_Call tick_step; /* 主刻度步长的最终类型分派。 */ - Label_Call tick_label; /* 主刻度标签的最终类型分派。 */ - Count_Call sub_tick_count; /* 次刻度数量的最终类型分派。 */ - }; struct Prepared_Line { Point_F first{}; /* 线段起点,单位为画布像素。 */ Point_F second{}; /* 线段终点,单位为画布像素。 */ @@ -44,18 +42,11 @@ struct Abs_Axis::Private : Prev_Private { double unit_width{}; /* 单位文本背景的估算宽度,单位为像素。 */ bool valid{}; /* 本轮 Prepare 是否生成了可绘制内容。 */ }; - const Dispatch* dispatch{}; /* Builder 绑定最终轴类型后指向静态分派表。 */ Prepared_Axis prepared{}; /* 由当前 State 推导、仅供紧随其后的 Paint 消费。 */ - [[nodiscard]] Axis_Range coordinate_range(const Root* object) const; - [[nodiscard]] double coordinate_to_pixel(const Root* object, double coordinate) const; - [[nodiscard]] double pixel_to_coordinate(const Root* object, double pixel) const; - [[nodiscard]] double point_to_coordinate(const Root* object, Point_F point) const; - [[nodiscard]] int pixel_sample_count(const Root* object, Axis_Range coordinate_range) const; - [[nodiscard]] double tick_step(const Root* object, Axis_Range coordinate_range) const; - [[nodiscard]] std::string tick_label(const Root* object, double tick) const; - [[nodiscard]] int sub_tick_count(const Root* object, double major_step) const; - template - [[nodiscard]] static const Dispatch& dispatch_for(); + [[nodiscard]] double coordinate_to_pixel(const Attached auto* object, double coordinate) const; + [[nodiscard]] double pixel_to_coordinate(const Attached auto* object, double pixel) const; + [[nodiscard]] double point_to_coordinate(const Attached auto* object, Point_F point) const; + [[nodiscard]] int pixel_sample_count(const Attached auto* object, Axis_Range coordinate_range) const; /* 供派生轴复用的 1/2/5 十进制主刻度算法。 */ [[nodiscard]] static double nice_tick_step(Axis_Range coordinate_range); /* 供派生轴复用的定点数值标签格式化算法。 */ @@ -75,95 +66,52 @@ struct Abs_Axis::Private : Prev_Private { template void bind_private_crtp(Object* object); }; -template -const Abs_Axis::Private::Dispatch& Abs_Axis::Private::dispatch_for() { - static const Dispatch result{ - [](const Root* root) { - auto* object = static_cast(root); - const auto& private_data = static_cast(*object->d); - return private_data.coordinate_range(object); - }, - [](const Root* root, double coordinate) { - auto* object = static_cast(root); - const auto& private_data = static_cast(*object->d); - const auto& axis_state = static_cast(*private_data.current); - const Axis_Range range = private_data.coordinate_range(object); - const double coordinate_length = range.length(); - const double pixel_origin = axis_state.orientation == Axis_Orientation::horizontal - ? axis_state.position.x - : axis_state.position.y; - if (coordinate_length == 0.0) return pixel_origin; - return pixel_origin + (coordinate - range.origin) / coordinate_length * axis_state.pixel_length; - }, - [](const Root* root, double pixel) { - auto* object = static_cast(root); - const auto& private_data = static_cast(*object->d); - const auto& axis_state = static_cast(*private_data.current); - const Axis_Range range = private_data.coordinate_range(object); - if (axis_state.pixel_length == 0.0) return range.origin; - const double pixel_origin = axis_state.orientation == Axis_Orientation::horizontal - ? axis_state.position.x - : axis_state.position.y; - return range.origin + (pixel - pixel_origin) / axis_state.pixel_length * range.length(); - }, - [](const Root* root, Point_F point) { - auto* object = static_cast(root); - const auto& private_data = static_cast(*object->d); - const auto& axis_state = static_cast(*private_data.current); - const double pixel = axis_state.orientation == Axis_Orientation::horizontal ? point.x : point.y; - const Axis_Range range = private_data.coordinate_range(object); - if (axis_state.pixel_length == 0.0) return range.origin; - const double pixel_origin = axis_state.orientation == Axis_Orientation::horizontal - ? axis_state.position.x - : axis_state.position.y; - return range.origin + (pixel - pixel_origin) / axis_state.pixel_length * range.length(); - }, - [](const Root* root, Axis_Range coordinate_range) { - auto* object = static_cast(root); - const auto& private_data = static_cast(*object->d); - const auto& axis_state = static_cast(*private_data.current); - const Axis_Range range = private_data.coordinate_range(object); - const auto map = [&](double coordinate) { - const double pixel_origin = axis_state.orientation == Axis_Orientation::horizontal - ? axis_state.position.x - : axis_state.position.y; - if (range.length() == 0.0) return pixel_origin; - return pixel_origin + (coordinate - range.origin) / range.length() * axis_state.pixel_length; - }; - const double first_pixel = map(coordinate_range.origin); - const double last_pixel = map(coordinate_range.target); - return std::max(0, static_cast(std::abs(last_pixel - first_pixel)) + 1); - }, - [](const Root* root, Axis_Range coordinate_range) { - auto* object = static_cast(root); - const auto& private_data = static_cast(*object->d); - return private_data.tick_step(object, coordinate_range); - }, - [](const Root* root, double tick) { - auto* object = static_cast(root); - const auto& private_data = static_cast(*object->d); - return private_data.tick_label(object, tick); - }, - [](const Root* root, double major_step) { - auto* object = static_cast(root); - const auto& private_data = static_cast(*object->d); - return private_data.sub_tick_count(object, major_step); - } - }; - return result; +inline double Abs_Axis::Private::coordinate_to_pixel(const Attached auto* object, double coordinate) const { + using Object = std::remove_cv_t>; + const auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& axis_prop = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); + const Axis_Range range = private_data.coordinate_range(object); + const double pixel_origin = axis_prop.orientation == Axis_Orientation::horizontal + ? axis_prop.position.x + : axis_prop.position.y; + if (range.length() == 0.0) return pixel_origin; + return pixel_origin + (coordinate - range.origin) / range.length() * axis_prop.pixel_length; +} +inline double Abs_Axis::Private::pixel_to_coordinate(const Attached auto* object, double pixel) const { + using Object = std::remove_cv_t>; + const auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& axis_prop = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); + const Axis_Range range = private_data.coordinate_range(object); + if (axis_prop.pixel_length == 0.0) return range.origin; + const double pixel_origin = axis_prop.orientation == Axis_Orientation::horizontal + ? axis_prop.position.x + : axis_prop.position.y; + return range.origin + (pixel - pixel_origin) / axis_prop.pixel_length * range.length(); +} +inline double Abs_Axis::Private::point_to_coordinate(const Attached auto* object, Point_F point) const { + using Object = std::remove_cv_t>; + const auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& axis_prop = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); + return private_data.pixel_to_coordinate( + object, axis_prop.orientation == Axis_Orientation::horizontal ? point.x : point.y); +} +inline int Abs_Axis::Private::pixel_sample_count(const Attached auto* object, + Axis_Range coordinate_range) const { + const double first_pixel = coordinate_to_pixel(object, coordinate_range.origin); + const double last_pixel = coordinate_to_pixel(object, coordinate_range.target); + return std::max(0, static_cast(std::abs(last_pixel - first_pixel)) + 1); } template void Abs_Axis::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); - dispatch = &Private::dispatch_for(); this->event_routing_distance_run = [](const Root* root, const Event& event) -> std::optional { if (event.type != Event_Type::wheel && event.type != Event_Type::pointer_press && event.type != Event_Type::pointer_move && event.type != Event_Type::pointer_release) return std::nullopt; const auto* pointer = dynamic_cast(&event); if (!pointer) return std::nullopt; const auto* axis = static_cast(root); - const auto& private_data = static_cast(*axis->d); - const auto& layout = static_cast(*private_data.current); + const auto& private_data = double_buffer::detail::Internal_Access::get(axis); + const auto& layout = static_cast(double_buffer::detail::Internal_Access::current_prop(axis)); const Point_F first = layout.position; const Point_F second = layout.orientation == Axis_Orientation::horizontal ? Point_F{first.x + layout.pixel_length, first.y} @@ -182,8 +130,8 @@ void Abs_Axis::Private::bind_private_crtp(Object* object) { } inline Rect_F Abs_Axis::Private::event_region(const Attached auto* object, Size) const { using Object = std::remove_cv_t>; - const auto& private_data = static_cast(*this); - const auto& layout = static_cast(*private_data.current); + const auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& layout = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); const double margin = std::max(8.0, std::abs(layout.tick_length) + layout.unit_text_font.size * 1.5); const Point_F second = layout.orientation == Axis_Orientation::horizontal ? Point_F{layout.position.x + layout.pixel_length, layout.position.y} @@ -194,8 +142,8 @@ inline Rect_F Abs_Axis::Private::event_region(const Attached auto* object, Size) } inline void Abs_Axis::Private::prepare_data(Attached auto* object) { using Object = std::remove_pointer_t; - auto& private_data = static_cast(*this); - const auto& state = static_cast(*private_data.current); + auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); auto& output = prepared; output = {}; if (state.pixel_length == 0.0) return; @@ -250,12 +198,12 @@ inline void Abs_Axis::Private::prepare_data(Attached auto* object) { output.unit_width = std::max(4.0, state.unit_text.size() * state.unit_text_font.size * 0.65); } output.valid = true; - object->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(object); } inline void Abs_Axis::Private::paint(Attached auto* object) { using Object = std::remove_pointer_t; - auto& private_data = static_cast(*this); - const auto& state = static_cast(*private_data.current); + auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); auto& cache = private_data.paint_surface(); if (!prepared.valid) return; detail::Painter painter(cache, cache.size()); @@ -274,7 +222,7 @@ inline void Abs_Axis::Private::paint(Attached auto* object) { } template void Abs_Axis::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { - object->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(object); } inline int Abs_Axis::Private::sub_tick_count(const Attached auto*, double) const { return 4; diff --git a/render_2D/render_2D/axis/Frequency_Axis.ipp b/render_2D/render_2D/axis/Frequency_Axis.ipp index ea58a6f..798dbbf 100644 --- a/render_2D/render_2D/axis/Frequency_Axis.ipp +++ b/render_2D/render_2D/axis/Frequency_Axis.ipp @@ -6,8 +6,8 @@ struct Frequency_Axis::Private : Prev_Private { }; inline std::string Frequency_Axis::Private::tick_label(const Attached auto* object, double tick) const { using Object = std::remove_cv_t>; - const auto& final_private = static_cast(*this); - const auto& state = static_cast(*final_private.current); + const auto& state = static_cast( + double_buffer::detail::Internal_Access::current_prop(object)); const double absolute = std::abs(tick); if (absolute >= 1'000'000.0) return localized_number(tick / 1'000'000.0, state.precision, state.locale) + " MHz"; if (absolute >= 1'000.0) return localized_number(tick / 1'000.0, state.precision, state.locale) + " kHz"; diff --git a/render_2D/render_2D/axis/Numeric_Axis.ipp b/render_2D/render_2D/axis/Numeric_Axis.ipp index 4cba27d..d16ac16 100644 --- a/render_2D/render_2D/axis/Numeric_Axis.ipp +++ b/render_2D/render_2D/axis/Numeric_Axis.ipp @@ -19,23 +19,24 @@ struct Numeric_Axis::Private : Prev_Private { }; inline Axis_Range Numeric_Axis::Private::coordinate_range(const Attached auto* object) const { using Object = std::remove_cv_t>; - const auto& private_data = static_cast(*this); - return static_cast(*private_data.current).coordinate_range; + const auto& private_data = double_buffer::detail::Internal_Access::get(object); + return static_cast(double_buffer::detail::Internal_Access::current_prop(object)).coordinate_range; } inline double Numeric_Axis::Private::tick_step(const Attached auto*, Axis_Range coordinate_range) const { return nice_tick_step(coordinate_range); } inline std::string Numeric_Axis::Private::tick_label(const Attached auto* object, double tick) const { using Object = std::remove_cv_t>; - const auto& private_data = static_cast(*this); - const auto& state = static_cast(*private_data.current); + const auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); return localized_number(tick, state.precision, state.locale); } inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Event& event) { using Object = std::remove_pointer_t; - auto& private_data = static_cast(*this); - const auto& numeric_state = static_cast(*private_data.current); - const auto& axis_state = static_cast(*private_data.current); + auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& current_prop = double_buffer::detail::Internal_Access::current_prop(object); + const auto& numeric_state = static_cast(current_prop); + const auto& axis_state = static_cast(current_prop); const auto* pointer = dynamic_cast(&event); if (event.type == Event_Type::wheel && numeric_state.wheel_enabled) { const auto* wheel = dynamic_cast(&event); @@ -45,11 +46,11 @@ inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Eve const double anchor = pixel_to_coordinate(object, anchor_pixel); const double factor = wheel->angle_delta_y_value() >= 0.0 ? 0.9 : 1.1; const Axis_Range range = numeric_state.coordinate_range; - object->template set<&Prop::coordinate_range>(Axis_Range{ + double_buffer::detail::Internal_Access::set<&Prop::coordinate_range>(object, Axis_Range{ anchor + (range.origin - anchor) * factor, anchor + (range.target - anchor) * factor }); - object->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(object); event.accept(); return; } @@ -69,11 +70,11 @@ inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Eve ? current.x - previous.x : current.y - previous.y; const double shift = axis_state.pixel_length == 0.0 ? 0.0 : -delta * numeric_state.coordinate_range.length() / axis_state.pixel_length; - object->template set<&Prop::coordinate_range>(Axis_Range{ + double_buffer::detail::Internal_Access::set<&Prop::coordinate_range>(object, Axis_Range{ numeric_state.coordinate_range.origin + shift, numeric_state.coordinate_range.target + shift }); - object->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(object); event.accept(); return; } @@ -84,10 +85,10 @@ inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Eve } } template -void Numeric_Axis::Private::after_prop_set(Object*, Member Owner::* member, Prop_Access props) { - const auto& private_data = static_cast(*this); +void Numeric_Axis::Private::after_prop_set(Object* object, Member Owner::* member, Prop_Access props) { + const auto& private_data = double_buffer::detail::Internal_Access::get(object); auto& pending = props.template get(); - const auto& current = static_cast(*private_data.pending); + const auto& current = static_cast(double_buffer::detail::Internal_Access::pending_prop(object)); if constexpr (std::same_as && std::same_as) { if (member != &Prop::coordinate_range) return; const Axis_Range range = pending.coordinate_range; diff --git a/render_2D/render_2D/axis/Time_Axis.cpp b/render_2D/render_2D/axis/Time_Axis.cpp index 312421b..478639e 100644 --- a/render_2D/render_2D/axis/Time_Axis.cpp +++ b/render_2D/render_2D/axis/Time_Axis.cpp @@ -5,15 +5,16 @@ namespace aethera::render_2d { bool Time_Axis::State::operator==(const State&) const = default; bool Time_Axis::Prop::operator==(const Prop&) const = default; -std::size_t Time_Axis::Private::time_point_count(const Root* object) const { - return time_dispatch->time_point_count(object); +std::size_t Time_Axis::time_point_count() const { + return double_buffer::detail::Internal_Access::layer(this).runtime_time_point_count(); } -int Time_Axis::Private::append_time(Root* object, Time_Of_Day time) { - return time_dispatch->append_time(object, time); +Axis_Time_Tick Time_Axis::append_time(Time_Of_Day time) { + return double_buffer::detail::Internal_Access::layer(this).runtime_append_time(time); } -Time_Of_Day Time_Axis::Private::tick_to_time(const Root* object, int tick) const { - return time_dispatch->tick_to_time(object, tick); +Time_Of_Day Time_Axis::tick_to_time(Axis_Time_Tick tick) const { + return double_buffer::detail::Internal_Access::layer(this).runtime_tick_to_time(tick); } + std::string Time_Axis::Private::formatted_time(Time_Of_Day time, std::string_view format) { const auto digits = [](int value, int width) { std::ostringstream stream; @@ -50,13 +51,4 @@ std::string Time_Axis::Private::formatted_time(Time_Of_Day time, std::string_vie } return result; } -std::size_t Time_Axis::time_point_count() const { - return static_cast(*d).time_point_count(this); -} -int Time_Axis::append_time(Time_Of_Day time) { - return static_cast(*d).append_time(this, time); -} -Time_Of_Day Time_Axis::tick_to_time(int tick) const { - return static_cast(*d).tick_to_time(this, tick); -} } diff --git a/render_2D/render_2D/axis/Time_Axis.hpp b/render_2D/render_2D/axis/Time_Axis.hpp index b47f0df..0000df4 100644 --- a/render_2D/render_2D/axis/Time_Axis.hpp +++ b/render_2D/render_2D/axis/Time_Axis.hpp @@ -10,6 +10,9 @@ struct Time_Axis_Stream_Tag {}; struct Time_Axis : Def>> { + using Time_Axis_Capability = void; + using Axis_Time_Tick_Type = Axis_Time_Tick; + using Time_Of_Day_Type = Time_Of_Day; struct Prop : Prev_Prop { Axis_Visible_Count visible_count{100}; /* 当前坐标区间最多覆盖的样本数量;小于 2 时按 2 计算。 */ Axis_Pixel_Length tick_label_spacing_px{8.0}; /* 相邻标签之间预留的像素间距;负值按 0 计算。 */ @@ -25,12 +28,12 @@ struct Time_Axis : Def namespace aethera::render_2d { struct Time_Axis::Private : Prev_Private { + using Time_Axis_Runtime_Interface = void; + using Axis_Time_Tick_Type = Axis_Time_Tick; + using Time_Of_Day_Type = Time_Of_Day; + [[nodiscard]] virtual std::size_t runtime_time_point_count() const = 0; + virtual Axis_Time_Tick runtime_append_time(Time_Of_Day time) = 0; + [[nodiscard]] virtual Time_Of_Day runtime_tick_to_time(Axis_Time_Tick tick) const = 0; /* CRTP 实现:用固定数量的半开槽位窗口定位 tick;样本不足时保留空槽,禁止把已有样本拉伸铺满整轴。 */ [[nodiscard]] Axis_Range coordinate_range(const Attached auto* object) const; /* CRTP 实现:根据像素长度、估算标签宽度和间距计算至少为 1 的整数刻度步长。 */ [[nodiscard]] double tick_step(const Attached auto* object, Axis_Range coordinate_range) const; /* CRTP 实现:在当前样本中查找 tick,并按 State::format 生成时间标签。 */ [[nodiscard]] std::string tick_label(const Attached auto* object, double tick) const; - using Count_Call = std::size_t (*)(const Root*); - using Append_Call = int (*)(Root*, Time_Of_Day); - using Lookup_Call = Time_Of_Day (*)(const Root*, int); - struct Time_Dispatch { - Count_Call time_point_count; /* 读取当前样本数量的最终类型分派。 */ - Append_Call append_time; /* 追加提交侧时间样本的最终类型分派。 */ - Lookup_Call tick_to_time; /* 查询当前 tick 时间的最终类型分派。 */ - }; - const Time_Dispatch* time_dispatch{}; /* Builder 绑定最终时间轴类型后指向静态分派表。 */ std::atomic next_tick{}; std::span> active_samples{}; /* 多生产者分配 tick 的唯一权威来源。 */ - [[nodiscard]] std::size_t time_point_count(const Root* object) const; - int append_time(Root* object, Time_Of_Day time); - [[nodiscard]] Time_Of_Day tick_to_time(const Root* object, int tick) const; - template - [[nodiscard]] static const Time_Dispatch& time_dispatch_for(); + [[nodiscard]] std::size_t time_point_count(const Attached auto* object) const; + int append_time(Attached auto* object, Time_Of_Day time); + [[nodiscard]] Time_Of_Day tick_to_time(const Attached auto* object, int tick) const; /* 将一天内时间按支持的占位符格式化;未知字符原样保留。 */ [[nodiscard]] static std::string formatted_time(Time_Of_Day time, std::string_view format); /* CRTP 覆盖:时间样本写入后使本轴的刻度布局失效;样本仍只保存在 Time_Axis::State。 */ @@ -39,8 +34,8 @@ struct Time_Axis::Private : Prev_Private { }; inline Axis_Range Time_Axis::Private::coordinate_range(const Attached auto* object) const { using Object = std::remove_cv_t>; - const auto& private_data = static_cast(*this); - const auto& prop = static_cast(*private_data.current); + const auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& prop = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); const int visible_count = std::max(2, prop.visible_count); const int latest = std::max(0, next_tick.load(std::memory_order_relaxed) - 1); if (prop.newest_at_start) return {static_cast(latest) + 0.5, static_cast(latest - visible_count) + 0.5}; @@ -48,17 +43,18 @@ inline Axis_Range Time_Axis::Private::coordinate_range(const Attached auto* obje } inline double Time_Axis::Private::tick_step(const Attached auto* object, Axis_Range coordinate_range) const { using Object = std::remove_cv_t>; - const auto& private_data = static_cast(*this); - const auto& time_state = static_cast(*private_data.current); - const auto& axis_state = static_cast(*private_data.current); + const auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& current_prop = double_buffer::detail::Internal_Access::current_prop(object); + const auto& time_state = static_cast(current_prop); + const auto& axis_state = static_cast(current_prop); const double label_width = std::max(76.0, time_state.estimated_label_width_px); const double label_count = std::max(1.0, std::abs(axis_state.pixel_length) / (label_width + std::max(0.0, time_state.tick_label_spacing_px))); return std::max(1.0, std::ceil(coordinate_range.size() / label_count)); } inline std::string Time_Axis::Private::tick_label(const Attached auto* object, double tick) const { using Object = std::remove_cv_t>; - const auto& private_data = static_cast(*this); - const auto& prop = static_cast(*private_data.current); + const auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& prop = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); const int target = static_cast(std::llround(tick)); const auto find_label = [&](std::span> samples) { const auto current = std::find_if(samples.begin(), samples.end(), [target](const auto& sample) { @@ -67,52 +63,43 @@ inline std::string Time_Axis::Private::tick_label(const Attached auto* object, d return current == samples.end() ? std::string{} : formatted_time(current->second, prop.format); }; if (!active_samples.empty()) return find_label(active_samples); - return object->template access_query_stream(find_label); + return double_buffer::detail::Internal_Access::access_query_stream(object, find_label); } -template -const Time_Axis::Private::Time_Dispatch& Time_Axis::Private::time_dispatch_for() { - static const Time_Dispatch result{ - [](const Root* root) { - auto* object = static_cast(root); - return object->template access_query_stream( - [](std::span> samples) { return samples.size(); }); - }, - [](Root* root, Time_Of_Day time) { - auto* object = static_cast(root); - auto& data = static_cast(*object->d); - const int tick = data.next_tick.fetch_add(1, std::memory_order_relaxed); - object->template submit_stream(std::pair{tick, time}); - object->template mark_dirty(); - return tick; - }, - [](const Root* root, int tick) { - auto* object = static_cast(root); - return object->template access_query_stream( - [tick](std::span> samples) { - const auto current = std::find_if(samples.begin(), samples.end(), [tick](const auto& sample) { - return sample.first == tick; - }); - return current == samples.end() ? Time_Of_Day{} : current->second; - }); - } - }; - return result; +inline std::size_t Time_Axis::Private::time_point_count(const Attached auto* object) const { + return double_buffer::detail::Internal_Access::access_query_stream(object, + [](std::span> samples) { + return samples.size(); + }); +} +inline int Time_Axis::Private::append_time(Attached auto* object, Time_Of_Day time) { + const int tick = next_tick.fetch_add(1, std::memory_order_relaxed); + double_buffer::detail::Internal_Access::submit_stream(object, std::pair{tick, time}); + double_buffer::detail::Internal_Access::mark_dirty(object); + return tick; +} +inline Time_Of_Day Time_Axis::Private::tick_to_time(const Attached auto* object, int tick) const { + return double_buffer::detail::Internal_Access::access_query_stream(object, + [tick](std::span> samples) { + const auto current = std::find_if(samples.begin(), samples.end(), [tick](const auto& sample) { + return sample.first == tick; + }); + return current == samples.end() ? Time_Of_Day{} : current->second; + }); } template void Time_Axis::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); - time_dispatch = &Private::time_dispatch_for(); } inline void Time_Axis::Private::prepare_data(Attached auto* object) { using Object = std::remove_pointer_t; - object->template exchange_stream(); - auto& private_data = static_cast(*this); - const auto& prop = static_cast(*private_data.current); - object->template accumulate_stream( + double_buffer::detail::Internal_Access::exchange_stream(object); + auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& prop = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); + double_buffer::detail::Internal_Access::accumulate_stream(object, static_cast(std::max(2, prop.visible_count))); - auto& state = static_cast(*private_data.state.pending); + auto& state = static_cast(double_buffer::detail::Internal_Access::pending_state(object)); state.next_tick = next_tick.load(std::memory_order_relaxed); - object->template access_rendering_stream( + double_buffer::detail::Internal_Access::access_rendering_stream(object, [&](std::span> samples) { active_samples = samples; Prev_Private::prepare_data(object); @@ -124,6 +111,7 @@ inline void Time_Axis::Private::paint(Attached auto* object) { } template void Time_Axis::Private::after_state_set(Object* object, Member Owner::*, State_Access) { - if constexpr (std::same_as) object->template mark_dirty(); + if constexpr (std::same_as) + double_buffer::detail::Internal_Access::mark_dirty(object); } } diff --git a/render_2D/render_2D/base/Renderable_2D.ipp b/render_2D/render_2D/base/Renderable_2D.ipp index ca70438..4431dd4 100644 --- a/render_2D/render_2D/base/Renderable_2D.ipp +++ b/render_2D/render_2D/base/Renderable_2D.ipp @@ -33,25 +33,26 @@ inline Rect_F Renderable_2D::Private::event_region(const Attached auto*, Size vi template void Renderable_2D::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { - if constexpr (std::same_as) object->template mark_dirty(); + if constexpr (std::same_as) + double_buffer::detail::Internal_Access::mark_dirty(object); } template void Renderable_2D::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); - object->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(object); this->event_region_run = [](const Root* root, Size viewport) { const auto* value = static_cast(root); - const auto& private_data = static_cast(*value->d); + const auto& private_data = double_buffer::detail::Internal_Access::get(value); return private_data.event_region(value, viewport); }; this->pending_cache = [](Root* root) { - return &static_cast(root)->template pending_buffer(); + return &double_buffer::detail::Internal_Access::pending_buffer( + static_cast(root)); }; this->cache_enabled = [](const Root* root) { const auto* value = static_cast(root); - const auto& private_data = static_cast(*value->d); const Renderable_2D::Prop& prop = - private_data.template current_prop(); + double_buffer::detail::Internal_Access::current_prop_layer(value); return prop.cache_enabled; }; } diff --git a/render_2D/render_2D/plottable/Afterglow.hpp b/render_2D/render_2D/plottable/Afterglow.hpp index eb9d817..7dddf5e 100644 --- a/render_2D/render_2D/plottable/Afterglow.hpp +++ b/render_2D/render_2D/plottable/Afterglow.hpp @@ -13,7 +13,7 @@ struct Afterglow_Stream_Tag {}; struct Afterglow : Def>>> { - using Scene_Object = Impl; using Frequency_Object = Impl; using Power_Object = Impl; + using Scene_Object = Render_Scene_2D; using Frequency_Object = Frequency_Axis; using Power_Object = Numeric_Axis; struct Prop : Prev_Prop { std::size_t frequency_point_size{}; /* 栅格频率列数;零值使用最新频谱尺寸。 */ std::size_t power_point_size{}; /* 栅格功率行数;零值使用默认尺寸。 */ diff --git a/render_2D/render_2D/plottable/Afterglow.ipp b/render_2D/render_2D/plottable/Afterglow.ipp index 725d12a..0e280a9 100644 --- a/render_2D/render_2D/plottable/Afterglow.ipp +++ b/render_2D/render_2D/plottable/Afterglow.ipp @@ -20,25 +20,17 @@ struct Afterglow::Private : Prev_Private { Plot_Partition_Grid graph_partition_grid{}; /* Prepare/Paint 子图当前固化的 framebuffer 列×行网格。 */ void bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value); /* CRTP 覆盖:构建累加、归一化和着色三阶段 Prepare 子图。 */ - template - [[nodiscard]] Task_Graph build_prepare_graph(Object* object, const Prop& state); + [[nodiscard]] Task_Graph build_prepare_graph(Afterglow* object, const Prop& state); /* CRTP 覆盖:构建消费色块矩阵的 Paint 子图。 */ - template - [[nodiscard]] Task_Graph build_paint_graph(Object* object, const Prop& state); + [[nodiscard]] Task_Graph build_paint_graph(Afterglow* object, const Prop& state); /* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */ - template - [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const Prop& state); - template - [[nodiscard]] bool should_rebuild_paint_graph(Object* object, const Prop& state); - template - void prepare_frame(Object* object); - template - void accumulate_partition(Object* object, Plot_Partition_Count index); + [[nodiscard]] bool should_rebuild_prepare_graph(Afterglow* object, const Prop& state); + [[nodiscard]] bool should_rebuild_paint_graph(Afterglow* object, const Prop& state); + void prepare_frame(Afterglow* object); + void accumulate_partition(Afterglow* object, Plot_Partition_Count index); void normalize_frame(); - template - void color_partition(Object* object, Plot_Partition_Count index); - template - void paint_partition(Object* object, Plot_Partition_Count index); + void color_partition(Afterglow* object, Plot_Partition_Count index); + void paint_partition(Afterglow* object, Plot_Partition_Count index); /* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */ template void after_prop_set(Object* object, Member Owner::* member, Prop_Access states); @@ -57,15 +49,15 @@ std::expected, Dependency_Graph_Error> Afterglow::Builde auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto plot = std::move(result).value(); - auto& private_data = static_cast(*plot->d); + auto& private_data = detail::Internal_Access::layer(plot.get()); private_data.bind_sources(frequency_axis, power_axis); private_data.scene_attach = [object = plot.get()](Root* root) -> std::expected { auto* scene = static_cast(root); - auto& data = static_cast(*object->d); + auto& data = detail::Internal_Access::layer(object); data.scene = scene; auto* frequency_axis = data.frequency_axis; auto* power_axis = data.power_axis; - return scene->template edit_dependency_graph([&](auto& prepare, auto& paint, auto& cache) { + return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, frequency_axis); prepare.add_dependency(object, power_axis); @@ -84,18 +76,15 @@ std::expected, Dependency_Graph_Error> Afterglow::Builde }; return plot; } -template -bool Afterglow::Private::should_rebuild_prepare_graph(Object*, +inline bool Afterglow::Private::should_rebuild_prepare_graph(Afterglow*, const Prop& state) { return graph_partition_grid != state.partition_grid; } -template -bool Afterglow::Private::should_rebuild_paint_graph(Object*, const Prop&) { +inline bool Afterglow::Private::should_rebuild_paint_graph(Afterglow*, const Prop&) { return !paint_graph || paint_graph->size() != detail::raster_partition_count(graph_partition_grid); } -template -Task_Graph Afterglow::Private::build_prepare_graph(Object* object, const Prop& state) { +inline Task_Graph Afterglow::Private::build_prepare_graph(Afterglow* object, const Prop& state) { graph_partition_grid = state.partition_grid; const Plot_Partition_Count partition_count = detail::raster_partition_count(graph_partition_grid); @@ -120,8 +109,7 @@ Task_Graph Afterglow::Private::build_prepare_graph(Object* object, const Prop& s } return graph; } -template -Task_Graph Afterglow::Private::build_paint_graph(Object* object, const Prop&) { +inline Task_Graph Afterglow::Private::build_paint_graph(Afterglow* object, const Prop&) { Task_Graph graph{"afterglow.paint"}; const Plot_Partition_Count partition_count = detail::raster_partition_count(graph_partition_grid); @@ -131,16 +119,15 @@ Task_Graph Afterglow::Private::build_paint_graph(Object* object, const Prop&) { }); return graph; } -template -void Afterglow::Private::prepare_frame(Object* object) { +inline void Afterglow::Private::prepare_frame(Afterglow* object) { const auto& state = this->template read_current_prop(object); - object->template exchange_stream(); + detail::Internal_Access::exchange_stream(object); prepared.source_spectra.clear(); - object->template access_rendering_stream( + detail::Internal_Access::access_rendering_stream(object, [&](std::span>> spectra) { prepared.source_spectra.assign(spectra.begin(), spectra.end()); }); - object->template accumulate_stream(std::size_t{64}); + detail::Internal_Access::accumulate_stream(object, std::size_t{64}); const auto& frequency_layout = this->template read_current_prop(frequency_axis); const auto& power_layout = this->template read_current_prop(power_axis); const std::size_t available = prepared.source_spectra.empty() ? 0 : prepared.source_spectra.back()->size(); @@ -167,8 +154,7 @@ void Afterglow::Private::prepare_frame(Object* object) { } prepared.valid = true; } -template -void Afterglow::Private::accumulate_partition(Object* object, Plot_Partition_Count index) { +inline void Afterglow::Private::accumulate_partition(Afterglow* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = this->template read_current_prop(object); const int columns = prepared.layout.first_horizontal ? prepared.layout.width : prepared.layout.height; @@ -216,8 +202,7 @@ void Afterglow::Private::accumulate_partition(Object* object, Plot_Partition_Cou inline void Afterglow::Private::normalize_frame() { if (prepared.valid && !prepared.intensity.empty()) prepared.maximum = std::max(1.0, *std::max_element(prepared.intensity.begin(), prepared.intensity.end())); } -template -void Afterglow::Private::color_partition(Object* object, Plot_Partition_Count index) { +inline void Afterglow::Private::color_partition(Afterglow* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = this->template read_current_prop(object); const int columns = prepared.layout.first_horizontal ? prepared.layout.width : prepared.layout.height; @@ -235,8 +220,7 @@ void Afterglow::Private::color_partition(Object* object, Plot_Partition_Count in prepared.intensity[cell] / prepared.maximum)); } } -template -void Afterglow::Private::paint_partition(Object*, Plot_Partition_Count index) { +inline void Afterglow::Private::paint_partition(Afterglow*, Plot_Partition_Count index) { if (!prepared.valid || index >= detail::raster_partition_count(graph_partition_grid)) return; @@ -251,7 +235,7 @@ void Afterglow::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) { - object->template mark_dirty(); + detail::Internal_Access::mark_dirty(object); } } inline void Afterglow::Private::bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) { diff --git a/render_2D/render_2D/plottable/Constellation_Diagram.hpp b/render_2D/render_2D/plottable/Constellation_Diagram.hpp index 18530fb..125d3b8 100644 --- a/render_2D/render_2D/plottable/Constellation_Diagram.hpp +++ b/render_2D/render_2D/plottable/Constellation_Diagram.hpp @@ -17,8 +17,8 @@ struct Constellation_Stream_Tag {}; struct Constellation_Diagram : Def> { - using Scene_Object = Impl; - using Axis_Object = Impl; + using Scene_Object = Render_Scene_2D; + using Axis_Object = Numeric_Axis; struct Prop : Prev_Prop { Plot_Partition_Count partition_count{1}; /* 接收点 Paint 子图的显式分块数;零值禁用接收点任务。 */ Plot_Duration_Milliseconds point_lifetime_ms{1000}; /* 接收点保留时间,单位为毫秒。 */ diff --git a/render_2D/render_2D/plottable/Constellation_Diagram.ipp b/render_2D/render_2D/plottable/Constellation_Diagram.ipp index a2c780f..beda509 100644 --- a/render_2D/render_2D/plottable/Constellation_Diagram.ipp +++ b/render_2D/render_2D/plottable/Constellation_Diagram.ipp @@ -16,19 +16,14 @@ struct Constellation_Diagram::Private : Prev_Private { Prepared prepared{}; /* 当前权威状态推导出的 Paint 输入。 */ void bind_sources(Axis_Object* i_axis_value, Axis_Object* q_axis_value); /* CRTP 覆盖:从当前点集和轴状态准备画布坐标。 */ - template - void prepare_data(Object* object); + void prepare_data(Constellation_Diagram* object); /* CRTP 覆盖:构建锚点前驱与并行接收点 Paint 子图。 */ - template - [[nodiscard]] Task_Graph build_paint_graph(Object* object, + [[nodiscard]] Task_Graph build_paint_graph(Constellation_Diagram* object, const Prop& state); - template - [[nodiscard]] bool should_rebuild_paint_graph(Object* object, + [[nodiscard]] bool should_rebuild_paint_graph(Constellation_Diagram* object, const Prop& state); - template - void paint_anchors(Object* object); - template - void paint_partition(Object* object, Plot_Partition_Count index); + void paint_anchors(Constellation_Diagram* object); + void paint_partition(Constellation_Diagram* object, Plot_Partition_Count index); [[nodiscard]] static Rect_F point_region( std::span points, double radius); /* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */ @@ -46,15 +41,15 @@ std::expected, Dependency_Graph_Error> Constellation_Dia auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto plot = std::move(result).value(); - auto& private_data = static_cast(*plot->d); + auto& private_data = detail::Internal_Access::layer(plot.get()); private_data.bind_sources(i_axis, q_axis); private_data.scene_attach = [object = plot.get()](Root* root) -> std::expected { auto* scene = static_cast(root); - auto& data = static_cast(*object->d); + auto& data = detail::Internal_Access::layer(object); data.scene = scene; auto* i_axis = data.i_axis; auto* q_axis = data.q_axis; - return scene->template edit_dependency_graph([&](auto& prepare, auto& paint, auto& cache) { + return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, i_axis); prepare.add_dependency(object, q_axis); @@ -73,12 +68,11 @@ std::expected, Dependency_Graph_Error> Constellation_Dia }; return plot; } -template -void Constellation_Diagram::Private::prepare_data(Object* object) { +inline void Constellation_Diagram::Private::prepare_data(Constellation_Diagram* object) { const auto& state = this->template read_current_prop(object); - object->template exchange_stream(); + detail::Internal_Access::exchange_stream(object); const auto current = monotonic_milliseconds(); - object->template accumulate_stream( + detail::Internal_Access::accumulate_stream(object, [&](const Constellation_Point& point) { return current - point.submitted_at_ms <= state.point_lifetime_ms; }); @@ -89,7 +83,7 @@ void Constellation_Diagram::Private::prepare_data(Object* object) { if (prepared.canvas.empty() || i_layout.orientation == q_layout.orientation) { return; } - object->template access_rendering_stream( + detail::Internal_Access::access_rendering_stream(object, [&](std::span points) { for (const auto& value : points) if (current - value.submitted_at_ms <= state.point_lifetime_ms) @@ -105,11 +99,10 @@ void Constellation_Diagram::Private::prepare_data(Object* object) { prepared.anchors.push_back(detail::map_plot_point(i_axis, center_i + std::cos(angle) * radius, q_axis, center_q + std::sin(angle) * radius, i_layout.orientation)); } prepared.valid = true; - object->template mark_dirty(); + detail::Internal_Access::mark_dirty(object); } -template -Task_Graph Constellation_Diagram::Private::build_paint_graph( - Object* object, const Prop& state) { +inline Task_Graph Constellation_Diagram::Private::build_paint_graph( + Constellation_Diagram* object, const Prop& state) { Task_Graph graph{"constellation.paint"}; auto anchors = graph.add("anchors", [this, object] { paint_anchors(object); @@ -123,9 +116,8 @@ Task_Graph Constellation_Diagram::Private::build_paint_graph( } return graph; } -template -bool Constellation_Diagram::Private::should_rebuild_paint_graph( - Object*, const Prop& state) { +inline bool Constellation_Diagram::Private::should_rebuild_paint_graph( + Constellation_Diagram*, const Prop& state) { return !paint_graph || paint_graph->size() != state.partition_count + 1; } inline Rect_F Constellation_Diagram::Private::point_region( @@ -149,8 +141,7 @@ inline Rect_F Constellation_Diagram::Private::point_region( bottom - top + padding * 2.0 }; } -template -void Constellation_Diagram::Private::paint_anchors(Object* object) { +inline void Constellation_Diagram::Private::paint_anchors(Constellation_Diagram* object) { const auto& state = this->template read_current_prop(object); if (!prepared.valid || prepared.anchors.empty()) return; detail::Painter painter(this->paint_surface(), prepared.canvas, @@ -162,9 +153,8 @@ void Constellation_Diagram::Private::paint_anchors(Object* object) { */ painter.solid_circles(prepared.anchors, 4.5, state.anchor_color); } -template -void Constellation_Diagram::Private::paint_partition( - Object* object, Plot_Partition_Count index) { +inline void Constellation_Diagram::Private::paint_partition( + Constellation_Diagram* object, Plot_Partition_Count index) { const auto& state = this->template read_current_prop(object); if (!prepared.valid || state.partition_count == 0 || @@ -193,11 +183,11 @@ void Constellation_Diagram::Private::after_prop_set( if constexpr (std::same_as) { if constexpr (std::same_as) { if (member == &Prop::partition_count) { - object->template mark_dirty(); + detail::Internal_Access::mark_dirty(object); return; } } - object->template mark_dirty(); + detail::Internal_Access::mark_dirty(object); } } inline void Constellation_Diagram::Private::bind_sources(Axis_Object* i_axis_value, Axis_Object* q_axis_value) { diff --git a/render_2D/render_2D/plottable/Frequency_Trace.hpp b/render_2D/render_2D/plottable/Frequency_Trace.hpp index 6575b05..e609685 100644 --- a/render_2D/render_2D/plottable/Frequency_Trace.hpp +++ b/render_2D/render_2D/plottable/Frequency_Trace.hpp @@ -15,9 +15,9 @@ struct Frequency_Trace_Sample { struct Frequency_Trace_Stream_Tag {}; struct Frequency_Trace : Def> { - using Scene_Object = Impl; - using Time_Object = Impl; - using Value_Object = Impl; + using Scene_Object = Render_Scene_2D; + using Time_Object = Time_Axis; + using Value_Object = Numeric_Axis; struct Prop : Prev_Prop { Plot_Partition_Count partition_count{1}; /* 曲线 Prepare/Paint 子图的显式分块数;零值禁用曲线分块任务。 */ Pen pen{Color::yellow()}; /* 频率轨迹折线样式。 */ diff --git a/render_2D/render_2D/plottable/Frequency_Trace.ipp b/render_2D/render_2D/plottable/Frequency_Trace.ipp index 097cbe3..0744238 100644 --- a/render_2D/render_2D/plottable/Frequency_Trace.ipp +++ b/render_2D/render_2D/plottable/Frequency_Trace.ipp @@ -15,15 +15,15 @@ struct Frequency_Trace::Private : Prev_Private { Plot_Partition_Count graph_partition_count{}; /* 当前 Prepare 子图固化的分块数。 */ void bind_sources(Time_Object* time_axis_value, Value_Object* value_axis_value); /* CRTP 覆盖:按当前样本规模构建分块 Prepare 子图。 */ - template [[nodiscard]] Task_Graph build_prepare_graph(Object* object, const Prop& state); + [[nodiscard]] Task_Graph build_prepare_graph(Frequency_Trace* object, const Prop& state); /* CRTP 覆盖:构建消费已准备曲线的 Paint 子图。 */ - template [[nodiscard]] Task_Graph build_paint_graph(Object* object, const Prop& state); + [[nodiscard]] Task_Graph build_paint_graph(Frequency_Trace* object, const Prop& state); /* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */ - template [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const Prop& state); - template [[nodiscard]] bool should_rebuild_paint_graph(Object* object, const Prop& state); - template void prepare_frame(Object* object, Plot_Partition_Count partition_count); - template void prepare_partition(Object* object, Plot_Partition_Count partition_index); - template void paint_partition(Object* object, Plot_Partition_Count partition_index); + [[nodiscard]] bool should_rebuild_prepare_graph(Frequency_Trace* object, const Prop& state); + [[nodiscard]] bool should_rebuild_paint_graph(Frequency_Trace* object, const Prop& state); + void prepare_frame(Frequency_Trace* object, Plot_Partition_Count partition_count); + void prepare_partition(Frequency_Trace* object, Plot_Partition_Count partition_index); + void paint_partition(Frequency_Trace* object, Plot_Partition_Count partition_index); /* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */ template void after_prop_set(Object* object, Member Owner::* member, Prop_Access pending_states); }; @@ -36,52 +36,55 @@ Frequency_Trace::Builder::Builder( } template std::expected, Dependency_Graph_Error> Frequency_Trace::Builder::build() { - auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto trace = std::move(result).value(); auto& private_data = static_cast(*trace->d); private_data.bind_sources(time_axis, value_axis); - private_data.scene_attach = [object = trace.get()](Root* root) -> std::expected { auto* scene = static_cast(root); auto& data = static_cast(*object->d); data.scene = scene; auto* time_axis = data.time_axis; auto* value_axis = data.value_axis; return scene->template edit_dependency_graph([&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, time_axis); prepare.add_dependency(object, value_axis); paint.add_dependency(time_axis, object); paint.add_dependency(value_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, time_axis); cache.template add_prop_dependency<&Time_Axis::Prop::visible_count>(object, time_axis); cache.template add_prop_dependency<&Time_Axis::Prop::newest_at_start>(object, time_axis); cache.template add_dependency<&Time_Axis::State::next_tick>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, value_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, value_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, value_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, value_axis); }); }; return trace; + auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto trace = std::move(result).value(); auto& private_data = detail::Internal_Access::get(trace.get()); private_data.bind_sources(time_axis, value_axis); + private_data.scene_attach = [object = trace.get()](Root* root) -> std::expected { auto* scene = static_cast(root); auto& data = detail::Internal_Access::get(object); data.scene = scene; auto* time_axis = data.time_axis; auto* value_axis = data.value_axis; return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, time_axis); prepare.add_dependency(object, value_axis); paint.add_dependency(time_axis, object); paint.add_dependency(value_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, time_axis); cache.template add_prop_dependency<&Time_Axis::Prop::visible_count>(object, time_axis); cache.template add_prop_dependency<&Time_Axis::Prop::newest_at_start>(object, time_axis); cache.template add_dependency<&Time_Axis::State::next_tick>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, value_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, value_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, value_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, value_axis); }); }; return trace; } -template -bool Frequency_Trace::Private::should_rebuild_prepare_graph(Object*, const Prop& state) { return graph_partition_count != state.partition_count; } -template -bool Frequency_Trace::Private::should_rebuild_paint_graph(Object*, const Prop&) { return !paint_graph || paint_graph->size() != prepared.partitions.size(); } -template -Task_Graph Frequency_Trace::Private::build_prepare_graph(Object* object, const Prop& state) { +inline bool Frequency_Trace::Private::should_rebuild_prepare_graph(Frequency_Trace*, const Prop& state) { return graph_partition_count != state.partition_count; } +inline bool Frequency_Trace::Private::should_rebuild_paint_graph(Frequency_Trace*, const Prop&) { return !paint_graph || paint_graph->size() != prepared.partitions.size(); } +inline Task_Graph Frequency_Trace::Private::build_prepare_graph(Frequency_Trace* object, const Prop& state) { graph_partition_count = state.partition_count; Task_Graph graph{"frequency_trace.prepare"}; auto begin = graph.add("frame", [this, object] { prepare_frame(object, graph_partition_count); }); for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.add("partition", [this, object, index] { prepare_partition(object, index); }); begin.precede(task); } return graph; } -template -Task_Graph Frequency_Trace::Private::build_paint_graph(Object* object, const Prop&) { +inline Task_Graph Frequency_Trace::Private::build_paint_graph(Frequency_Trace* object, const Prop&) { Task_Graph graph{"frequency_trace.paint"}; for (Plot_Partition_Count index = 0; index < prepared.partitions.size(); ++index) graph.add("partition", [this, object, index] { paint_partition(object, index); }); return graph; } -template -void Frequency_Trace::Private::prepare_frame(Object* object, Plot_Partition_Count partition_count) { - const auto& state = this->template read_current_prop(object); const auto& time_layout = this->template read_current_prop(time_axis); const auto& value_layout = this->template read_current_prop(value_axis); - const auto visible_count = static_cast(std::max(2, this->template read_current_prop(time_axis).visible_count)); - object->template exchange_stream(); - object->template accumulate_stream(visible_count); - prepared = {}; prepared.partitions.resize(partition_count); prepared.canvas = this->template read_current_prop(scene).viewport; +inline void Frequency_Trace::Private::prepare_frame(Frequency_Trace* object, Plot_Partition_Count partition_count) { + const auto& state = static_cast(detail::Internal_Access::current_prop(object)); + const auto& time_layout = static_cast(detail::Internal_Access::current_prop(time_axis)); + const auto& value_layout = static_cast(detail::Internal_Access::current_prop(value_axis)); + const auto visible_count = static_cast(std::max( + 2, static_cast(detail::Internal_Access::current_prop(time_axis)).visible_count)); + detail::Internal_Access::exchange_stream(object); + detail::Internal_Access::accumulate_stream(object, visible_count); + prepared = {}; + prepared.partitions.resize(partition_count); + prepared.canvas = static_cast(detail::Internal_Access::current_prop(scene)).viewport; if (prepared.canvas.empty() || time_layout.orientation == value_layout.orientation) return; - object->template access_rendering_stream([&](std::span samples) { + detail::Internal_Access::access_rendering_stream(object, [&](std::span samples) { prepared.samples.reserve(samples.size()); - for (const auto& sample : samples) prepared.samples.push_back({static_cast(sample.tick), sample.value}); + for (const auto& sample : samples) + prepared.samples.push_back({static_cast(sample.tick), sample.value}); }); - if (prepared.samples.empty()) return; prepared.valid = !prepared.samples.empty(); } -template -void Frequency_Trace::Private::prepare_partition(Object* object, Plot_Partition_Count partition_index) { - if (!prepared.valid) return; const auto& state = this->template read_current_prop(object); const auto& time_layout = this->template read_current_prop(time_axis); const auto& value_layout = this->template read_current_prop(value_axis); const auto& value_state = this->template read_current_prop(value_axis); +inline void Frequency_Trace::Private::prepare_partition(Frequency_Trace* object, Plot_Partition_Count partition_index) { + if (!prepared.valid) return; + const auto& state = static_cast(detail::Internal_Access::current_prop(object)); + const auto& time_layout = static_cast(detail::Internal_Access::current_prop(time_axis)); + const auto& value_layout = static_cast(detail::Internal_Access::current_prop(value_axis)); + const auto& value_state = static_cast(detail::Internal_Access::current_prop(value_axis)); const Axis_Range domain{prepared.samples.front().coordinate, prepared.samples.back().coordinate}; const auto range = detail::curve_partition_range(prepared.samples.size(), partition_index, prepared.partitions.size(), domain); - prepared.partitions[partition_index] = detail::prepare_curve(std::span(prepared.samples).subspan(range.first_sample, range.sample_count), true, time_axis->coordinate_range(), value_state.coordinate_range, time_axis, value_axis, time_layout.orientation, value_layout.orientation); + const Axis_Range visible_range = detail::Internal_Access::layer(time_axis).runtime_coordinate_range(); + prepared.partitions[partition_index] = detail::prepare_curve(std::span(prepared.samples).subspan(range.first_sample, range.sample_count), true, visible_range, value_state.coordinate_range, time_axis, value_axis, time_layout.orientation, value_layout.orientation); } -template -void Frequency_Trace::Private::paint_partition(Object* object, Plot_Partition_Count partition_index) { +inline void Frequency_Trace::Private::paint_partition(Frequency_Trace* object, Plot_Partition_Count partition_index) { if (!prepared.valid || partition_index >= prepared.partitions.size()) return; - const auto& state = this->template read_current_prop(object); + const auto& state = static_cast(detail::Internal_Access::current_prop(object)); const auto& curve = prepared.partitions[partition_index]; const Rect_F region = detail::curve_paint_region(curve, state.pen.width + 2.0); if (region.empty()) return; @@ -92,7 +95,7 @@ template void Frequency_Trace::Private::after_prop_set( Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) { - object->template mark_dirty(); + detail::Internal_Access::mark_dirty(object); } } inline void Frequency_Trace::Private::bind_sources(Time_Object* time_axis_value, Value_Object* value_axis_value) { time_axis = time_axis_value; value_axis = value_axis_value; } diff --git a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp index 8f41fe7..cb85bf0 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp @@ -10,9 +10,12 @@ Axis_Rectangle Selection_Rectangle_Overlay::Private::axis_rectangle(Axis_Point f const auto [vertical_low, vertical_high] = std::minmax(first.vertical, second.vertical); return {{horizontal_low, horizontal_high}, {vertical_low, vertical_high}}; } -std::string Selection_Rectangle_Overlay::Private::range_label(const Abs_Axis* axis, Axis_Range range) { - const auto coordinate_label = [axis](Axis_Coordinate coordinate) { - auto label = axis->tick_label(coordinate); +std::string Selection_Rectangle_Overlay::Private::range_label(const Axis_Source& axis, Axis_Range range) { + const auto coordinate_label = [&axis](Axis_Coordinate coordinate) { + auto label = std::visit([coordinate](const auto* value) { + return double_buffer::detail::Internal_Access::layer(value) + .runtime_tick_label(coordinate); + }, axis); if (!label.empty()) return label; std::ostringstream stream; stream << std::fixed << std::setprecision(3) << coordinate; @@ -20,4 +23,32 @@ std::string Selection_Rectangle_Overlay::Private::range_label(const Abs_Axis* ax }; return coordinate_label(range.origin) + " .. " + coordinate_label(range.target); } +Axis_Range Selection_Rectangle_Overlay::Private::coordinate_range(const Axis_Source& axis) { + return std::visit([](const auto* value) { + return double_buffer::detail::Internal_Access::layer(value) + .runtime_coordinate_range(); + }, axis); +} +double Selection_Rectangle_Overlay::Private::coordinate_to_pixel(const Axis_Source& axis, double coordinate) { + return std::visit([coordinate](const auto* value) { + return double_buffer::detail::Internal_Access::layer(value) + .runtime_coordinate_to_pixel(coordinate); + }, axis); +} +double Selection_Rectangle_Overlay::Private::point_to_coordinate(const Axis_Source& axis, Point_F point) { + return std::visit([point](const auto* value) { + return double_buffer::detail::Internal_Access::layer(value) + .runtime_point_to_coordinate(point); + }, axis); +} +Rect_F Selection_Rectangle_Overlay::Private::map_rect(const Axis_Source& horizontal, + Axis_Range horizontal_range, + const Axis_Source& vertical, + Axis_Range vertical_range) { + return std::visit([&](const auto* horizontal_value, const auto* vertical_value) { + return detail::map_plot_rect(horizontal_value, horizontal_range, + vertical_value, vertical_range, + Axis_Orientation::horizontal); + }, horizontal, vertical); +} } diff --git a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.hpp b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.hpp index e8bbb50..3d1c550 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.hpp +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.hpp @@ -7,9 +7,13 @@ #include #include #include +#include namespace aethera::render_2d { struct Selection_Rectangle_Overlay : Def { - using Scene_Object = Impl; + using Scene_Object = Render_Scene_2D; + using Numeric_Axis_Object = Numeric_Axis; + using Frequency_Axis_Object = Frequency_Axis; + using Time_Axis_Object = Time_Axis; struct Prop : Prev_Prop { Font label_font{}; /* 选择范围标签使用的字体。 */ Pen label_pen{Color::white()}; /* 选择范围标签的文字样式。 */ diff --git a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp index b0f52b7..f44d09d 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp @@ -6,22 +6,29 @@ namespace aethera::render_2d { struct Selection_Rectangle_Overlay::Private : Prev_Private { using No_Prepare = void; + using Axis_Source = std::variant; Scene_Object* scene{}; /* 不拥有的所属 Scene。 */ - Abs_Axis* horizontal_axis{}; /* 不拥有的水平坐标轴。 */ - Abs_Axis* vertical_axis{}; /* 不拥有的垂直坐标轴。 */ + Axis_Source horizontal_axis{}; /* 保留最终类型的水平坐标轴。 */ + Axis_Source vertical_axis{}; /* 保留最终类型的垂直坐标轴。 */ Axis_Point drag_origin{}; /* 当前拖动起点的两轴数据坐标。 */ Axis_Point drag_current{}; /* 当前拖动终点的两轴数据坐标。 */ Point_F press_position{}; /* 仅用于区分点击与拖动的按下像素位置。 */ bool dragging{}; /* 是否正在构造尚未提交的选择矩形。 */ static constexpr double minimum_drag_distance_pixels{3.0}; /* 允许提交和绘制选择框的最小像素距离。 */ - void bind_sources(Abs_Axis* horizontal_axis_value, Abs_Axis* vertical_axis_value); + template + void bind_sources(Horizontal_Axis* horizontal_axis_value, Vertical_Axis* vertical_axis_value); [[nodiscard]] static Axis_Rectangle axis_rectangle(Axis_Point first, Axis_Point second); - [[nodiscard]] static std::string range_label(const Abs_Axis* axis, Axis_Range range); + [[nodiscard]] static std::string range_label(const Axis_Source& axis, Axis_Range range); + [[nodiscard]] static Axis_Range coordinate_range(const Axis_Source& axis); + [[nodiscard]] static double coordinate_to_pixel(const Axis_Source& axis, double coordinate); + [[nodiscard]] static double point_to_coordinate(const Axis_Source& axis, Point_F point); + [[nodiscard]] static Rect_F map_rect(const Axis_Source& horizontal, Axis_Range horizontal_range, + const Axis_Source& vertical, Axis_Range vertical_range); [[nodiscard]] bool has_visible_drag() const; /* CRTP 覆盖:无需 Prepare 子图,直接绘制选择矩形。 */ - template void paint(Object* object); + void paint(Selection_Rectangle_Overlay* object); /* CRTP 覆盖:处理拖拽并更新权威选择区域状态。 */ - template void handle_event(Object* object, const Event& event); + void handle_event(Selection_Rectangle_Overlay* object, const Event& event); /* CRTP 覆盖:空闲时只在两轴围成的图域接收事件,拖动期间扩展到完整 viewport。 */ [[nodiscard]] Rect_F event_region(const Attached auto* object, Size viewport) const; /* CRTP 覆盖:本类状态写入后只标记 Paint 数据失效。 */ @@ -31,15 +38,22 @@ struct Selection_Rectangle_Overlay::Private : Prev_Private { template template Selection_Rectangle_Overlay::Builder::Builder(Horizontal_Axis* horizontal_axis, Vertical_Axis* vertical_axis) : Base() { + static_assert((std::same_as || + std::same_as || + std::same_as) && + (std::same_as || + std::same_as || + std::same_as), + "selection rectangle axes must preserve one of the supported final axis types"); if (!horizontal_axis || !vertical_axis) throw std::invalid_argument("selection rectangle requires two axes"); initialize = [horizontal_axis, vertical_axis](Object* overlay) { - auto& private_data = static_cast(*overlay->d); + auto& private_data = detail::Internal_Access::layer(overlay); private_data.bind_sources(horizontal_axis, vertical_axis); private_data.scene_attach = [object = overlay, horizontal_axis, vertical_axis](Root* root) -> std::expected { auto* scene = static_cast(root); - auto& data = static_cast(*object->d); + auto& data = detail::Internal_Access::layer(object); data.scene = scene; - return scene->template edit_dependency_graph([&](auto& prepare, auto& paint, auto& cache) { + return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, horizontal_axis); prepare.add_dependency(object, vertical_axis); paint.add_dependency(horizontal_axis, object); paint.add_dependency(vertical_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); const auto bind_axis = [&](Axis* axis) { cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, axis); if constexpr (std::derived_from) { cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::precision>(object, axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::locale>(object, axis); } else if constexpr (std::derived_from) { cache.template add_prop_dependency<&Time_Axis::Prop::visible_count>(object, axis); cache.template add_prop_dependency<&Time_Axis::Prop::newest_at_start>(object, axis); cache.template add_prop_dependency<&Time_Axis::Prop::format>(object, axis); cache.template add_dependency<&Time_Axis::State::next_tick>(object, axis); } }; bind_axis(horizontal_axis); bind_axis(vertical_axis); @@ -55,18 +69,17 @@ std::expected, Dependency_Graph_Error> Selection_Rectang initialize(overlay.get()); return overlay; } -template -void Selection_Rectangle_Overlay::Private::paint(Object* object) { - auto& data = static_cast(*this); - const auto& state = static_cast(*data.current); +inline void Selection_Rectangle_Overlay::Private::paint(Selection_Rectangle_Overlay* object) { + auto& data = double_buffer::detail::Internal_Access::get(object); + const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); const Size canvas = this->template read_current_prop(scene).viewport; auto& cache = data.paint_surface(); if (canvas.empty()) return; detail::Painter painter(cache, canvas); - const auto plot_region = detail::map_plot_rect(horizontal_axis, horizontal_axis->coordinate_range(), vertical_axis, vertical_axis->coordinate_range(), Axis_Orientation::horizontal); + const auto plot_region = map_rect(horizontal_axis, coordinate_range(horizontal_axis), vertical_axis, coordinate_range(vertical_axis)); auto clip = painter.scoped_clip(plot_region); const auto paint_region = [&](const Axis_Rectangle& region) { - const auto rect = detail::map_plot_rect(horizontal_axis, region.horizontal, vertical_axis, region.vertical, Axis_Orientation::horizontal); + const auto rect = map_rect(horizontal_axis, region.horizontal, vertical_axis, region.vertical); painter.rect(rect, state.selection_border_pen, state.selection_brush); const Point_F label_position{rect.x + 4.0, rect.y + 3.0}; painter.text(label_position, "X " + range_label(horizontal_axis, region.horizontal), state.label_font, state.label_pen); @@ -76,38 +89,43 @@ void Selection_Rectangle_Overlay::Private::paint(Object* object) { if (dragging && has_visible_drag()) paint_region(axis_rectangle(drag_origin, drag_current)); } inline bool Selection_Rectangle_Overlay::Private::has_visible_drag() const { - const Point_F current_position{horizontal_axis->coordinate_to_pixel(drag_current.horizontal), vertical_axis->coordinate_to_pixel(drag_current.vertical)}; + const Point_F current_position{coordinate_to_pixel(horizontal_axis, drag_current.horizontal), coordinate_to_pixel(vertical_axis, drag_current.vertical)}; return std::hypot(current_position.x - press_position.x, current_position.y - press_position.y) >= minimum_drag_distance_pixels; } -template -void Selection_Rectangle_Overlay::Private::handle_event(Object* object, const Event& event) { +inline void Selection_Rectangle_Overlay::Private::handle_event(Selection_Rectangle_Overlay* object, const Event& event) { const auto* pointer = dynamic_cast(&event); if (!pointer) return; const Point_F point{pointer->position_x(), pointer->position_y()}; - const Axis_Point axis_point{horizontal_axis->point_to_coordinate(point), vertical_axis->point_to_coordinate(point)}; + const Axis_Point axis_point{point_to_coordinate(horizontal_axis, point), point_to_coordinate(vertical_axis, point)}; if (event.type == Event_Type::pointer_press && pointer->pointer_button() == Mouse_Button::left) { const auto modifiers = static_cast>(pointer->keyboard_modifiers()); const auto control = static_cast>(Keyboard_Modifier::control); - if ((modifiers & control) == 0) object->template set<&Prop::selected_regions>(std::vector{}); - dragging = true; drag_origin = axis_point; drag_current = axis_point; press_position = point; object->template mark_dirty(); event.accept(); return; + if ((modifiers & control) == 0) detail::Internal_Access::set<&Prop::selected_regions>(object, std::vector{}); + dragging = true; drag_origin = axis_point; drag_current = axis_point; press_position = point; detail::Internal_Access::mark_dirty(object); event.accept(); return; } - if (event.type == Event_Type::pointer_move && dragging) { drag_current = axis_point; object->template mark_dirty(); event.accept(); return; } + if (event.type == Event_Type::pointer_move && dragging) { drag_current = axis_point; detail::Internal_Access::mark_dirty(object); event.accept(); return; } if (event.type != Event_Type::pointer_release || !dragging) return; dragging = false; drag_current = axis_point; if (std::hypot(point.x - press_position.x, point.y - press_position.y) < minimum_drag_distance_pixels) { - object->template mark_dirty(); event.accept(); return; + detail::Internal_Access::mark_dirty(object); event.accept(); return; } const Axis_Rectangle selected = axis_rectangle(drag_origin, drag_current); - object->template update_prop<&Prop::selected_regions>([=](Prop_Access props) { props.template get().selected_regions.push_back(selected); }); + detail::Internal_Access::update_prop<&Prop::selected_regions>(object, [=](Prop_Access props) { + props.template get().selected_regions.push_back(selected); + }); event.accept(); } inline Rect_F Selection_Rectangle_Overlay::Private::event_region(const Attached auto*, Size viewport) const { if (dragging) return {0.0, 0.0, static_cast(viewport.width), static_cast(viewport.height)}; - return detail::map_plot_rect(horizontal_axis, horizontal_axis->coordinate_range(), vertical_axis, vertical_axis->coordinate_range(), Axis_Orientation::horizontal); + return map_rect(horizontal_axis, coordinate_range(horizontal_axis), vertical_axis, coordinate_range(vertical_axis)); } template -void Selection_Rectangle_Overlay::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) object->template mark_dirty(); } +void Selection_Rectangle_Overlay::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) detail::Internal_Access::mark_dirty(object); } template void Selection_Rectangle_Overlay::Private::before_advance(Object*, Prop_Type*, State_Access pending_states, const Prop_Type* current_prop, State_Access) { pending_states.template get().selected_region_count = static_cast(*current_prop).selected_regions.size(); } -inline void Selection_Rectangle_Overlay::Private::bind_sources(Abs_Axis* horizontal_axis_value, Abs_Axis* vertical_axis_value) { horizontal_axis = horizontal_axis_value; vertical_axis = vertical_axis_value; } +template +void Selection_Rectangle_Overlay::Private::bind_sources(Horizontal_Axis* horizontal_axis_value, Vertical_Axis* vertical_axis_value) { + horizontal_axis = horizontal_axis_value; + vertical_axis = vertical_axis_value; +} } diff --git a/render_2D/render_2D/plottable/Spectrum.hpp b/render_2D/render_2D/plottable/Spectrum.hpp index dd08d3b..fd5252d 100644 --- a/render_2D/render_2D/plottable/Spectrum.hpp +++ b/render_2D/render_2D/plottable/Spectrum.hpp @@ -20,9 +20,9 @@ struct Spectrum_Frame { /* 使用频率轴和功率轴分块准备、绘制当前值、保持曲线及频率标记。 */ struct Spectrum : Def> { - using Scene_Object = Impl; - using Frequency_Object = Impl; - using Power_Object = Impl; + using Scene_Object = Render_Scene_2D; + using Frequency_Object = Frequency_Axis; + using Power_Object = Numeric_Axis; struct Prop : Prev_Prop { Spectrum_Frequency center_frequency{50.0}; /* 中心频率标记位置,单位为 Hz。 */ Plot_Partition_Count partition_count{1}; /* 曲线 Prepare/Paint 子图的显式分块数;零值禁用曲线分块任务。 */ diff --git a/render_2D/render_2D/plottable/Spectrum.ipp b/render_2D/render_2D/plottable/Spectrum.ipp index 6b97031..8187159 100644 --- a/render_2D/render_2D/plottable/Spectrum.ipp +++ b/render_2D/render_2D/plottable/Spectrum.ipp @@ -43,26 +43,17 @@ struct Spectrum::Private : Prev_Private { template void after_prop_set(Object* object, Member Owner::* member, Prop_Access pending_states); /* CRTP 子图能力:按显式 partition_count 构建并行 Prepare 图。 */ - template - [[nodiscard]] Task_Graph build_prepare_graph(Object* object, const Prop& state); + [[nodiscard]] Task_Graph build_prepare_graph(Spectrum* object, const Prop& state); /* CRTP 子图能力:构建背景、分块曲线和覆盖标记的 Paint 图。 */ - template - [[nodiscard]] Task_Graph build_paint_graph(Object* object, const Prop& state); + [[nodiscard]] Task_Graph build_paint_graph(Spectrum* object, const Prop& state); /* CRTP 覆盖:样本规模或分块配置改变时重建 Prepare 子图。 */ - template - [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const Prop& state); - template - [[nodiscard]] bool should_rebuild_paint_graph(Object* object, const Prop& state); - template - void prepare_frame(Object* object, std::size_t partition_count); - template - void prepare_partition(Object* object, std::size_t partition_index); - template - void begin_paint(Object* object); - template - void paint_partition(Object* object, std::size_t partition_index); - template - void paint_overlays(Object* object); + [[nodiscard]] bool should_rebuild_prepare_graph(Spectrum* object, const Prop& state); + [[nodiscard]] bool should_rebuild_paint_graph(Spectrum* object, const Prop& state); + void prepare_frame(Spectrum* object, std::size_t partition_count); + void prepare_partition(Spectrum* object, std::size_t partition_index); + void begin_paint(Spectrum* object); + void paint_partition(Spectrum* object, std::size_t partition_index); + void paint_overlays(Spectrum* object); }; template Spectrum::Builder::Builder( @@ -78,15 +69,15 @@ std::expected, Dependency_Graph_Error> Spectrum::Builder auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto spectrum = std::move(result).value(); - auto& private_data = static_cast(*spectrum->d); + auto& private_data = detail::Internal_Access::layer(spectrum.get()); private_data.bind_render_sources(frequency_axis, power_axis); private_data.scene_attach = [object = spectrum.get()](Root* root) -> std::expected { auto* scene = static_cast(root); - auto& data = static_cast(*object->d); + auto& data = detail::Internal_Access::layer(object); data.scene = scene; auto* frequency_axis = data.frequency_axis; auto* power_axis = data.power_axis; - return scene->template edit_dependency_graph([&](auto& prepare, auto& paint, auto& cache) { + return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, frequency_axis); prepare.add_dependency(object, power_axis); @@ -105,16 +96,13 @@ std::expected, Dependency_Graph_Error> Spectrum::Builder }; return spectrum; } -template -bool Spectrum::Private::should_rebuild_prepare_graph(Object*, const Prop& state) { +inline bool Spectrum::Private::should_rebuild_prepare_graph(Spectrum*, const Prop& state) { return prepare_graph_partition_count != state.partition_count; } -template -bool Spectrum::Private::should_rebuild_paint_graph(Object*, const Prop&) { +inline bool Spectrum::Private::should_rebuild_paint_graph(Spectrum*, const Prop&) { return !paint_graph || paint_graph->size() != prepared.partitions.size() + 2; } -template -Task_Graph Spectrum::Private::build_prepare_graph(Object* object, const Prop& state) { +inline Task_Graph Spectrum::Private::build_prepare_graph(Spectrum* object, const Prop& state) { const Plot_Partition_Count partition_count = state.partition_count; prepare_graph_partition_count = partition_count; Task_Graph graph{"spectrum.prepare"}; @@ -129,8 +117,7 @@ Task_Graph Spectrum::Private::build_prepare_graph(Object* object, const Prop& st } return graph; } -template -Task_Graph Spectrum::Private::build_paint_graph(Object* object, const Prop&) { +inline Task_Graph Spectrum::Private::build_paint_graph(Spectrum* object, const Prop&) { Task_Graph graph{"spectrum.paint"}; auto begin = graph.add("begin", [this, object] { begin_paint(object); @@ -148,11 +135,10 @@ Task_Graph Spectrum::Private::build_paint_graph(Object* object, const Prop&) { if (prepared.partitions.empty()) begin.precede(overlays); return graph; } -template -void Spectrum::Private::prepare_frame(Object* object, std::size_t partition_count) { - auto& private_data = static_cast(*this); - const auto& state = static_cast(*private_data.current); - const auto& frame = object->template current_buffer(); +inline void Spectrum::Private::prepare_frame(Spectrum* object, std::size_t partition_count) { + auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); + const auto& frame = double_buffer::detail::Internal_Access::current_buffer(object); if (maxima.size() != frame.samples.size()) { maxima = frame.samples; minima = frame.samples; @@ -192,12 +178,11 @@ void Spectrum::Private::prepare_frame(Object* object, std::size_t partition_coun } prepared.valid = true; } -template -void Spectrum::Private::prepare_partition(Object* object, std::size_t partition_index) { +inline void Spectrum::Private::prepare_partition(Spectrum* object, std::size_t partition_index) { if (!prepared.valid || partition_index >= prepared.partitions.size()) return; - auto& private_data = static_cast(*this); - const auto& state = static_cast(*private_data.current); - const auto& frame = object->template current_buffer(); + auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); + const auto& frame = double_buffer::detail::Internal_Access::current_buffer(object); if (frame.samples.empty()) return; const auto& frequency_layout = this->template read_current_prop(frequency_axis); const auto& power_layout = this->template read_current_prop(power_axis); @@ -210,12 +195,11 @@ void Spectrum::Private::prepare_partition(Object* object, std::size_t partition_ if (state.max_hold_visible && maxima.size() == frame.samples.size()) partition.maximum = detail::prepare_curve(std::span(maxima).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation); if (state.min_hold_visible && minima.size() == frame.samples.size()) partition.minimum = detail::prepare_curve(std::span(minima).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation); } -template -void Spectrum::Private::begin_paint(Object* object) { - auto& private_data = static_cast(*this); - const auto& state = static_cast(*private_data.current); - auto& published = static_cast(*private_data.state.pending); - const auto& frame = object->template current_buffer(); +inline void Spectrum::Private::begin_paint(Spectrum* object) { + auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); + auto& published = static_cast(double_buffer::detail::Internal_Access::pending_state(object)); + const auto& frame = double_buffer::detail::Internal_Access::current_buffer(object); published.sample_count = frame.samples.size(); published.rendered_point_count = 0; for (const auto& partition : prepared.partitions) published.rendered_point_count += partition.current.points.size(); @@ -227,12 +211,11 @@ void Spectrum::Private::begin_paint(Object* object) { painter.rect(prepared.sweep_region, Pen{.style = Line_Style::none}, state.sweep_region_brush); } -template -void Spectrum::Private::paint_partition(Object* object, +inline void Spectrum::Private::paint_partition(Spectrum* object, std::size_t partition_index) { if (!prepared.valid || partition_index >= prepared.partitions.size()) return; - auto& private_data = static_cast(*this); - const auto& state = static_cast(*private_data.current); + auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); const auto& partition = prepared.partitions[partition_index]; if (partition.clip.empty()) return; const Rect_F clip = partition.clip.normalized(); @@ -249,11 +232,10 @@ void Spectrum::Private::paint_partition(Object* object, detail::paint_curve(painter, partition.current, state.current_pen, state.current_brush); } -template -void Spectrum::Private::paint_overlays(Object* object) { +inline void Spectrum::Private::paint_overlays(Spectrum* object) { if (!prepared.valid) return; - auto& private_data = static_cast(*this); - const auto& state = static_cast(*private_data.current); + auto& private_data = double_buffer::detail::Internal_Access::get(object); + const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); detail::Painter painter(private_data.paint_surface(), prepared.canvas_size); for (const auto& marker : prepared.markers) { const Pen& pen = marker.style == Marker_Style::middle ? state.middle_frequency_pen : marker.style == Marker_Style::selected ? state.selected_marker_pen : state.marker_pen; @@ -268,7 +250,7 @@ template void Spectrum::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) { - object->template mark_dirty(); + detail::Internal_Access::mark_dirty(object); } } inline void Spectrum::Private::bind_render_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) { diff --git a/render_2D/render_2D/plottable/Sweep_Spectrum.hpp b/render_2D/render_2D/plottable/Sweep_Spectrum.hpp index 75b859a..7d189dc 100644 --- a/render_2D/render_2D/plottable/Sweep_Spectrum.hpp +++ b/render_2D/render_2D/plottable/Sweep_Spectrum.hpp @@ -21,9 +21,9 @@ struct Sweep_Spectrum_Block { struct Sweep_Spectrum : Def>> { - using Scene_Object = Impl; - using Frequency_Object = Impl; - using Power_Object = Impl; + using Scene_Object = Render_Scene_2D; + using Frequency_Object = Frequency_Axis; + using Power_Object = Numeric_Axis; struct Prop : Prev_Prop { std::size_t bins_per_block{}; /* 每个扫频块期望的功率点数;零值接受首块尺寸。 */ std::size_t block_count{1}; /* 一个完整扫频周期包含的块数;零值按 1 处理。 */ diff --git a/render_2D/render_2D/plottable/Sweep_Spectrum.ipp b/render_2D/render_2D/plottable/Sweep_Spectrum.ipp index 74e96e9..2851222 100644 --- a/render_2D/render_2D/plottable/Sweep_Spectrum.ipp +++ b/render_2D/render_2D/plottable/Sweep_Spectrum.ipp @@ -27,16 +27,16 @@ struct Sweep_Spectrum::Private : Prev_Private { Plot_Partition_Count graph_partition_count{}; /* 当前 Prepare 子图分块数。 */ void bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value); /* CRTP 覆盖:按扫描点规模构建分块 Prepare 子图。 */ - template [[nodiscard]] Task_Graph build_prepare_graph(Object* object, const Prop& state); + [[nodiscard]] Task_Graph build_prepare_graph(Sweep_Spectrum* object, const Prop& state); /* CRTP 覆盖:构建消费曲线分块的 Paint 子图。 */ - template [[nodiscard]] Task_Graph build_paint_graph(Object* object, const Prop& state); + [[nodiscard]] Task_Graph build_paint_graph(Sweep_Spectrum* object, const Prop& state); /* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */ - template [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const Prop& state); - template [[nodiscard]] bool should_rebuild_paint_graph(Object* object, const Prop& state); - template void prepare_frame(Object* object); - template void prepare_partition(Object* object, Plot_Partition_Count index); - template void paint_partition(Object* object, Plot_Partition_Count index); - template void paint_marker(Object* object); + [[nodiscard]] bool should_rebuild_prepare_graph(Sweep_Spectrum* object, const Prop& state); + [[nodiscard]] bool should_rebuild_paint_graph(Sweep_Spectrum* object, const Prop& state); + void prepare_frame(Sweep_Spectrum* object); + void prepare_partition(Sweep_Spectrum* object, Plot_Partition_Count index); + void paint_partition(Sweep_Spectrum* object, Plot_Partition_Count index); + void paint_marker(Sweep_Spectrum* object); [[nodiscard]] std::size_t stored_point_count() const; template void after_prop_set(Object* object, Member Owner::* member, Prop_Access states); }; @@ -51,17 +51,13 @@ Sweep_Spectrum::Builder::Builder( } template std::expected, Dependency_Graph_Error> Sweep_Spectrum::Builder::build() { - auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto sweep = std::move(result).value(); auto& private_data = static_cast(*sweep->d); private_data.bind_sources(frequency_axis, power_axis); - private_data.scene_attach = [object = sweep.get()](Root* root) -> std::expected { auto* scene = static_cast(root); auto& data = static_cast(*object->d); data.scene = scene; auto* frequency_axis = data.frequency_axis; auto* power_axis = data.power_axis; return scene->template edit_dependency_graph([&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, frequency_axis); prepare.add_dependency(object, power_axis); paint.add_dependency(frequency_axis, object); paint.add_dependency(power_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, frequency_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, power_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, power_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, power_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, power_axis); }); }; return sweep; + auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto sweep = std::move(result).value(); auto& private_data = detail::Internal_Access::get(sweep.get()); private_data.bind_sources(frequency_axis, power_axis); + private_data.scene_attach = [object = sweep.get()](Root* root) -> std::expected { auto* scene = static_cast(root); auto& data = detail::Internal_Access::get(object); data.scene = scene; auto* frequency_axis = data.frequency_axis; auto* power_axis = data.power_axis; return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, frequency_axis); prepare.add_dependency(object, power_axis); paint.add_dependency(frequency_axis, object); paint.add_dependency(power_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, frequency_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, power_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, power_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, power_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, power_axis); }); }; return sweep; } -template -bool Sweep_Spectrum::Private::should_rebuild_prepare_graph(Object*, const Prop& state) { return graph_partition_count != state.partition_count; } -template -bool Sweep_Spectrum::Private::should_rebuild_paint_graph(Object*, const Prop&) { return !paint_graph || paint_graph->size() != prepared.partitions.size() + 1; } -template -Task_Graph Sweep_Spectrum::Private::build_prepare_graph(Object* object, const Prop& state) { graph_partition_count = state.partition_count; Task_Graph graph{"sweep_spectrum.prepare"}; auto begin = graph.add("frame", [this, object] { prepare_frame(object); }); for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.add("partition", [this, object, index] { prepare_partition(object, index); }); begin.precede(task); } return graph; } -template -Task_Graph Sweep_Spectrum::Private::build_paint_graph(Object* object, const Prop&) { +inline bool Sweep_Spectrum::Private::should_rebuild_prepare_graph(Sweep_Spectrum*, const Prop& state) { return graph_partition_count != state.partition_count; } +inline bool Sweep_Spectrum::Private::should_rebuild_paint_graph(Sweep_Spectrum*, const Prop&) { return !paint_graph || paint_graph->size() != prepared.partitions.size() + 1; } +inline Task_Graph Sweep_Spectrum::Private::build_prepare_graph(Sweep_Spectrum* object, const Prop& state) { graph_partition_count = state.partition_count; Task_Graph graph{"sweep_spectrum.prepare"}; auto begin = graph.add("frame", [this, object] { prepare_frame(object); }); for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.add("partition", [this, object, index] { prepare_partition(object, index); }); begin.precede(task); } return graph; } +inline Task_Graph Sweep_Spectrum::Private::build_paint_graph(Sweep_Spectrum* object, const Prop&) { Task_Graph graph{"sweep_spectrum.paint"}; auto marker = graph.add("marker", [this, object] { paint_marker(object); }); for (Plot_Partition_Count index = 0; index < prepared.partitions.size(); ++index) { @@ -70,8 +66,7 @@ Task_Graph Sweep_Spectrum::Private::build_paint_graph(Object* object, const Prop } return graph; } -template -void Sweep_Spectrum::Private::prepare_frame(Object* object) { +inline void Sweep_Spectrum::Private::prepare_frame(Sweep_Spectrum* object) { const auto& state = this->template read_current_prop(object); const auto& frequency_layout = this->template read_current_prop(frequency_axis); const auto& power_layout = this->template read_current_prop(power_axis); const auto& power_state = this->template read_current_prop(power_axis); prepared = {}; prepared.canvas = this->template read_current_prop(scene).viewport; const std::size_t block_count = std::max(1, state.block_count); if (sweep_blocks.size() != block_count) { @@ -79,9 +74,9 @@ void Sweep_Spectrum::Private::prepare_frame(Object* object) { latest_block_index = 0; has_latest_block = false; } - object->template exchange_stream(); + detail::Internal_Access::exchange_stream(object); /* 当前 rendering 队列就是本次输入组;组内所有合法块依次覆盖持久槽位。 */ - object->template access_rendering_stream([&](std::span> blocks) { + detail::Internal_Access::access_rendering_stream(object, [&](std::span> blocks) { for (const auto& block : blocks) { if (!block || block->index >= block_count) continue; sweep_blocks[block->index] = block; @@ -107,13 +102,11 @@ void Sweep_Spectrum::Private::prepare_frame(Object* object) { prepared.domain = {state.frequency_range.origin, domain_target}; prepared.marker_first = detail::map_plot_point(frequency_axis, latest_frequency, power_axis, power_state.coordinate_range.origin, frequency_layout.orientation); prepared.marker_second = detail::map_plot_point(frequency_axis, latest_frequency, power_axis, power_state.coordinate_range.target, frequency_layout.orientation); prepared.valid = true; } -template -void Sweep_Spectrum::Private::prepare_partition(Object* object, Plot_Partition_Count index) { +inline void Sweep_Spectrum::Private::prepare_partition(Sweep_Spectrum* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = this->template read_current_prop(object); const auto& frequency_layout = this->template read_current_prop(frequency_axis); const auto& power_layout = this->template read_current_prop(power_axis); const auto& frequency_state = this->template read_current_prop(frequency_axis); const auto& power_state = this->template read_current_prop(power_axis); const auto range = detail::curve_partition_range(prepared.values.size(), index, prepared.partitions.size(), prepared.domain); prepared.partitions[index] = detail::prepare_curve(std::span(prepared.values).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation); } -template -void Sweep_Spectrum::Private::paint_partition(Object* object, Plot_Partition_Count index) { +inline void Sweep_Spectrum::Private::paint_partition(Sweep_Spectrum* object, Plot_Partition_Count index) { if (!prepared.valid || index >= prepared.partitions.size()) return; const auto& state = this->template read_current_prop(object); const auto& curve = prepared.partitions[index]; @@ -122,8 +115,7 @@ void Sweep_Spectrum::Private::paint_partition(Object* object, Plot_Partition_Cou detail::Painter painter(this->paint_surface(), prepared.canvas, region); detail::paint_curve(painter, curve, state.pen); } -template -void Sweep_Spectrum::Private::paint_marker(Object* object) { +inline void Sweep_Spectrum::Private::paint_marker(Sweep_Spectrum* object) { if (!prepared.valid) return; const auto& state = this->template read_current_prop(object); const double padding = state.current_frequency_pen.width + 2.0; @@ -140,7 +132,7 @@ template void Sweep_Spectrum::Private::after_prop_set( Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) { - object->template mark_dirty(); + detail::Internal_Access::mark_dirty(object); } } inline void Sweep_Spectrum::Private::bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) { frequency_axis = frequency_axis_value; power_axis = power_axis_value; } diff --git a/render_2D/render_2D/plottable/Waterfall.hpp b/render_2D/render_2D/plottable/Waterfall.hpp index cefbf55..b5df90b 100644 --- a/render_2D/render_2D/plottable/Waterfall.hpp +++ b/render_2D/render_2D/plottable/Waterfall.hpp @@ -18,9 +18,9 @@ struct Waterfall_Row { struct Waterfall_Stream_Tag {}; struct Waterfall : Def>> { - using Scene_Object = Impl; - using Frequency_Object = Impl; - using Time_Object = Impl; + using Scene_Object = Render_Scene_2D; + using Frequency_Object = Frequency_Axis; + using Time_Object = Time_Axis; struct Prop : Prev_Prop { bool tooltip_enabled{true}; /* 是否响应指针位置显示提示。 */ Font tooltip_font{}; /* 提示文字字体。 */ diff --git a/render_2D/render_2D/plottable/Waterfall.ipp b/render_2D/render_2D/plottable/Waterfall.ipp index bfa748a..99d7723 100644 --- a/render_2D/render_2D/plottable/Waterfall.ipp +++ b/render_2D/render_2D/plottable/Waterfall.ipp @@ -26,27 +26,18 @@ struct Waterfall::Private : Prev_Private { Plot_Partition_Grid graph_partition_grid{}; /* Prepare/Paint 子图当前固化的 framebuffer 列×行网格。 */ void bind_sources(Frequency_Object* frequency_axis_value, Time_Object* time_axis_value); /* CRTP 覆盖:按当前色块工作量构建分块 Prepare 子图。 */ - template - [[nodiscard]] Task_Graph build_prepare_graph(Object* object, const Prop& state); + [[nodiscard]] Task_Graph build_prepare_graph(Waterfall* object, const Prop& state); /* CRTP 覆盖:构建消费色块矩阵和提示信息的 Paint 子图。 */ - template - [[nodiscard]] Task_Graph build_paint_graph(Object* object, const Prop& state); + [[nodiscard]] Task_Graph build_paint_graph(Waterfall* object, const Prop& state); /* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */ - template - [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const Prop& state); - template - [[nodiscard]] bool should_rebuild_paint_graph(Object* object, const Prop& state); - template - void prepare_frame(Object* object); - template - void prepare_partition(Object* object, Plot_Partition_Count index); - template - void paint_partition(Object* object, Plot_Partition_Count index); - template - void paint_tooltip(Object* object); + [[nodiscard]] bool should_rebuild_prepare_graph(Waterfall* object, const Prop& state); + [[nodiscard]] bool should_rebuild_paint_graph(Waterfall* object, const Prop& state); + void prepare_frame(Waterfall* object); + void prepare_partition(Waterfall* object, Plot_Partition_Count index); + void paint_partition(Waterfall* object, Plot_Partition_Count index); + void paint_tooltip(Waterfall* object); /* CRTP 覆盖:更新 hover 位置并请求重绘。 */ - template - void handle_event(Object* object, const Event& event); + void handle_event(Waterfall* object, const Event& event); /* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */ template void after_prop_set(Object* object, Member Owner::* member, Prop_Access states); @@ -64,15 +55,15 @@ std::expected, Dependency_Graph_Error> Waterfall::Builde auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto plot = std::move(result).value(); - auto& private_data = static_cast(*plot->d); + auto& private_data = detail::Internal_Access::layer(plot.get()); private_data.bind_sources(frequency_axis, time_axis); private_data.scene_attach = [object = plot.get()](Root* root) -> std::expected { auto* scene = static_cast(root); - auto& data = static_cast(*object->d); + auto& data = detail::Internal_Access::layer(object); data.scene = scene; auto* frequency_axis = data.frequency_axis; auto* time_axis = data.time_axis; - return scene->template edit_dependency_graph([&](auto& prepare, auto& paint, auto& cache) { + return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, frequency_axis); prepare.add_dependency(object, time_axis); @@ -93,17 +84,14 @@ std::expected, Dependency_Graph_Error> Waterfall::Builde }; return plot; } -template -bool Waterfall::Private::should_rebuild_prepare_graph(Object*, const Prop& state) { +inline bool Waterfall::Private::should_rebuild_prepare_graph(Waterfall*, const Prop& state) { return graph_partition_grid != state.partition_grid; } -template -bool Waterfall::Private::should_rebuild_paint_graph(Object*, const Prop&) { +inline bool Waterfall::Private::should_rebuild_paint_graph(Waterfall*, const Prop&) { return !paint_graph || paint_graph->size() != detail::raster_partition_count(graph_partition_grid) + 1; } -template -Task_Graph Waterfall::Private::build_prepare_graph(Object* object, +inline Task_Graph Waterfall::Private::build_prepare_graph(Waterfall* object, const Prop& state) { graph_partition_grid = state.partition_grid; const Plot_Partition_Count partition_count = @@ -120,8 +108,7 @@ Task_Graph Waterfall::Private::build_prepare_graph(Object* object, } return graph; } -template -Task_Graph Waterfall::Private::build_paint_graph(Object* object, const Prop&) { +inline Task_Graph Waterfall::Private::build_paint_graph(Waterfall* object, const Prop&) { Task_Graph graph{"waterfall.paint"}; auto tooltip_task = graph.add("tooltip", [this, object] { paint_tooltip(object); @@ -136,16 +123,15 @@ Task_Graph Waterfall::Private::build_paint_graph(Object* object, const Prop&) { } return graph; } -template -void Waterfall::Private::prepare_frame(Object* object) { +inline void Waterfall::Private::prepare_frame(Waterfall* object) { const auto& state = this->template read_current_prop(object); - object->template exchange_stream(); + detail::Internal_Access::exchange_stream(object); const auto row_limit = static_cast(std::max( 2, this->template read_current_prop(time_axis).visible_count)); - object->template accumulate_stream(row_limit); + detail::Internal_Access::accumulate_stream(object, row_limit); prepared = {}; std::vector> source_rows; - object->template access_rendering_stream( + detail::Internal_Access::access_rendering_stream(object, [&](std::span> rows) { source_rows.assign(rows.begin(), rows.end()); }); @@ -160,12 +146,15 @@ void Waterfall::Private::prepare_frame(Object* object) { }); const std::size_t available = (*shortest)->values.size(); const int source_columns = static_cast(state.frequency_bin_count ? std::min(state.frequency_bin_count, available) : available); - const auto selection = detail::raster_axis_selection(state.frequency_range, frequency_axis->coordinate_range(), source_columns, state.visible_range_only); + const auto selection = detail::raster_axis_selection( + state.frequency_range, + detail::Internal_Access::layer(frequency_axis).runtime_coordinate_range(), + source_columns, state.visible_range_only); if (!selection) { return; } const int time_slots = std::max(2, this->template read_current_prop(time_axis).visible_count); - const Axis_Range time_range = time_axis->coordinate_range(); + const Axis_Range time_range = detail::Internal_Access::layer(time_axis).runtime_coordinate_range(); prepared.layout = detail::raster_layout(frequency_axis, selection->range, selection->count(), time_axis, time_range, time_slots, frequency_layout.orientation, time_layout.orientation); if (prepared.canvas.empty() || !prepared.layout.valid()) { return; @@ -190,14 +179,16 @@ void Waterfall::Private::prepare_frame(Object* object) { } if (state.tooltip_enabled && tooltip.active && prepared.layout.target.contains(tooltip.position)) { std::ostringstream text; - text << std::fixed << std::setprecision(2) << frequency_axis->point_to_coordinate(tooltip.position) << " Hz"; + text << std::fixed << std::setprecision(2) + << detail::Internal_Access::layer(frequency_axis) + .runtime_point_to_coordinate(tooltip.position) + << " Hz"; prepared.tooltip_text = text.str(); prepared.tooltip_box = {tooltip.position.x + 8.0, tooltip.position.y + 8.0, 110.0, 24.0}; } prepared.valid = true; } -template -void Waterfall::Private::prepare_partition(Object* object, Plot_Partition_Count index) { +inline void Waterfall::Private::prepare_partition(Waterfall* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = this->template read_current_prop(object); const detail::Raster_Partition partition = detail::raster_partition( @@ -222,8 +213,7 @@ void Waterfall::Private::prepare_partition(Object* object, Plot_Partition_Count } } } -template -void Waterfall::Private::paint_partition(Object* object, +inline void Waterfall::Private::paint_partition(Waterfall* object, Plot_Partition_Count index) { const auto& state = this->template read_current_prop(object); if (!prepared.valid || index >= @@ -235,8 +225,7 @@ void Waterfall::Private::paint_partition(Object* object, detail::Painter painter(this->paint_surface(), prepared.canvas, region); detail::paint_raster(painter, prepared.layout, prepared.pixels, state.interpolation_mode); } -template -void Waterfall::Private::paint_tooltip(Object* object) { +inline void Waterfall::Private::paint_tooltip(Waterfall* object) { if (!prepared.valid || prepared.tooltip_text.empty()) return; const auto& state = this->template read_current_prop(object); detail::Painter painter(this->paint_surface(), prepared.canvas, @@ -248,16 +237,16 @@ void Waterfall::Private::paint_tooltip(Object* object) { prepared.tooltip_text, state.tooltip_font, state.tooltip_text_pen); } -template -void Waterfall::Private::handle_event(Object* object, const Event& event) { - if (detail::update_hover_tooltip(tooltip, event)) object->template mark_dirty(); +inline void Waterfall::Private::handle_event(Waterfall* object, const Event& event) { + if (detail::update_hover_tooltip(tooltip, event)) + detail::Internal_Access::mark_dirty(object); } template void Waterfall::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) { - object->template mark_dirty(); + detail::Internal_Access::mark_dirty(object); } } inline void Waterfall::Private::bind_sources(Frequency_Object* frequency_axis_value, Time_Object* time_axis_value) { diff --git a/render_2D/render_2D/plottable/common/Curve_Plot.cpp b/render_2D/render_2D/plottable/common/Curve_Plot.cpp index dbe6160..7e3fb3a 100644 --- a/render_2D/render_2D/plottable/common/Curve_Plot.cpp +++ b/render_2D/render_2D/plottable/common/Curve_Plot.cpp @@ -66,32 +66,6 @@ std::vector visible_curve_samples(std::vector sample if (first == samples.size()) return {}; return {samples.begin() + static_cast(first), samples.begin() + static_cast(last + 1)}; } -Point_F map_plot_point(const Abs_Axis* coordinate_axis, Plot_Coordinate coordinate, const Abs_Axis* value_axis, Plot_Value value, Axis_Orientation coordinate_orientation) { - const Axis_Pixel_Position coordinate_pixel = coordinate_axis->coordinate_to_pixel(coordinate); - const Axis_Pixel_Position value_pixel = value_axis->coordinate_to_pixel(value); - return coordinate_orientation == Axis_Orientation::horizontal ? Point_F{coordinate_pixel, value_pixel} : Point_F{value_pixel, coordinate_pixel}; -} -Rect_F map_plot_rect(const Abs_Axis* coordinate_axis, Axis_Range coordinate_range, const Abs_Axis* value_axis, Axis_Range value_range, Axis_Orientation coordinate_orientation) { - const Point_F first = map_plot_point(coordinate_axis, coordinate_range.origin, value_axis, value_range.origin, coordinate_orientation); - const Point_F second = map_plot_point(coordinate_axis, coordinate_range.target, value_axis, value_range.target, coordinate_orientation); - return Rect_F{first.x, first.y, second.x - first.x, second.y - first.y}.normalized(); -} -Curve_Prepared prepare_curve(std::span source, bool visible_only, Axis_Range visible_coordinate_range, Axis_Range value_range, const Abs_Axis* coordinate_axis, const Abs_Axis* value_axis, Axis_Orientation coordinate_orientation, Axis_Orientation value_orientation) { - Curve_Prepared result; - std::vector samples{source.begin(), source.end()}; - if (visible_only) samples = visible_curve_samples(std::move(samples), visible_coordinate_range); - for (const auto& sample : samples) if (std::isfinite(sample.coordinate) && std::isfinite(sample.value)) result.points.push_back(map_plot_point(coordinate_axis, sample.coordinate, value_axis, sample.value, coordinate_orientation)); - if (result.points.size() < 2) return result; - const Axis_Pixel_Position baseline = value_axis->coordinate_to_pixel(value_range.target); - Point_F first = result.points.front(); Point_F last = result.points.back(); - if (value_orientation == Axis_Orientation::horizontal) { first.x = baseline; last.x = baseline; } else { first.y = baseline; last.y = baseline; } - result.fill.reserve(result.points.size() + 2); result.fill.push_back(first); result.fill.insert(result.fill.end(), result.points.begin(), result.points.end()); result.fill.push_back(last); - return result; -} -Curve_Prepared prepare_curve(std::span values, Axis_Range domain, Line_Interpolation_Mode mode, bool visible_only, Axis_Range visible_coordinate_range, Axis_Range value_range, const Abs_Axis* coordinate_axis, const Abs_Axis* value_axis, Axis_Orientation coordinate_orientation, Axis_Orientation value_orientation) { - const auto samples = interpolate_curve(values, domain, mode); - return prepare_curve(samples, visible_only, visible_coordinate_range, value_range, coordinate_axis, value_axis, coordinate_orientation, value_orientation); -} Rect_F curve_paint_region(const Curve_Prepared& curve, double padding) { bool initialized{}; double left{}; diff --git a/render_2D/render_2D/plottable/common/Curve_Plot.hpp b/render_2D/render_2D/plottable/common/Curve_Plot.hpp index 024a473..568a17e 100644 --- a/render_2D/render_2D/plottable/common/Curve_Plot.hpp +++ b/render_2D/render_2D/plottable/common/Curve_Plot.hpp @@ -4,7 +4,9 @@ #include "../Plot_Types.hpp" #include #include +#include namespace aethera::render_2d::detail { +using double_buffer::detail::Internal_Access; struct Curve_Sample { Plot_Coordinate coordinate{}; /* 曲线样本的横向业务坐标。 */ Plot_Value value{}; /* 曲线样本的纵向业务值。 */ @@ -21,10 +23,48 @@ struct Curve_Partition_Range { [[nodiscard]] Curve_Partition_Range curve_partition_range(std::size_t sample_count, Plot_Partition_Count partition_index, Plot_Partition_Count partition_count, Axis_Range domain); [[nodiscard]] std::vector interpolate_curve(std::span values, Axis_Range domain, Line_Interpolation_Mode mode); [[nodiscard]] std::vector visible_curve_samples(std::vector samples, Axis_Range visible_range); -[[nodiscard]] Point_F map_plot_point(const Abs_Axis* coordinate_axis, Plot_Coordinate coordinate, const Abs_Axis* value_axis, Plot_Value value, Axis_Orientation coordinate_orientation); -[[nodiscard]] Rect_F map_plot_rect(const Abs_Axis* coordinate_axis, Axis_Range coordinate_range, const Abs_Axis* value_axis, Axis_Range value_range, Axis_Orientation coordinate_orientation); -[[nodiscard]] Curve_Prepared prepare_curve(std::span samples, bool visible_only, Axis_Range visible_coordinate_range, Axis_Range value_range, const Abs_Axis* coordinate_axis, const Abs_Axis* value_axis, Axis_Orientation coordinate_orientation, Axis_Orientation value_orientation); -[[nodiscard]] Curve_Prepared prepare_curve(std::span values, Axis_Range domain, Line_Interpolation_Mode mode, bool visible_only, Axis_Range visible_coordinate_range, Axis_Range value_range, const Abs_Axis* coordinate_axis, const Abs_Axis* value_axis, Axis_Orientation coordinate_orientation, Axis_Orientation value_orientation); +template +[[nodiscard]] Point_F map_plot_point(const Coordinate_Axis* coordinate_axis, Plot_Coordinate coordinate, const Value_Axis* value_axis, Plot_Value value, Axis_Orientation coordinate_orientation) { + const auto& coordinate_private = Internal_Access::layer(coordinate_axis); + const auto& value_private = Internal_Access::layer(value_axis); + const Axis_Pixel_Position coordinate_pixel = coordinate_private.runtime_coordinate_to_pixel(coordinate); + const Axis_Pixel_Position value_pixel = value_private.runtime_coordinate_to_pixel(value); + return coordinate_orientation == Axis_Orientation::horizontal + ? Point_F{coordinate_pixel, value_pixel} + : Point_F{value_pixel, coordinate_pixel}; +} +template +[[nodiscard]] Rect_F map_plot_rect(const Coordinate_Axis* coordinate_axis, Axis_Range coordinate_range, const Value_Axis* value_axis, Axis_Range value_range, Axis_Orientation coordinate_orientation) { + const Point_F first = map_plot_point(coordinate_axis, coordinate_range.origin, value_axis, value_range.origin, coordinate_orientation); + const Point_F second = map_plot_point(coordinate_axis, coordinate_range.target, value_axis, value_range.target, coordinate_orientation); + return Rect_F{first.x, first.y, second.x - first.x, second.y - first.y}.normalized(); +} +template +[[nodiscard]] Curve_Prepared prepare_curve(std::span source, bool visible_only, Axis_Range visible_coordinate_range, Axis_Range value_range, const Coordinate_Axis* coordinate_axis, const Value_Axis* value_axis, Axis_Orientation coordinate_orientation, Axis_Orientation value_orientation) { + Curve_Prepared result; + std::vector samples{source.begin(), source.end()}; + if (visible_only) samples = visible_curve_samples(std::move(samples), visible_coordinate_range); + for (const auto& sample : samples) + if (std::isfinite(sample.coordinate) && std::isfinite(sample.value)) + result.points.push_back(map_plot_point(coordinate_axis, sample.coordinate, value_axis, sample.value, coordinate_orientation)); + if (result.points.size() < 2) return result; + const Axis_Pixel_Position baseline = Internal_Access::layer(value_axis) + .runtime_coordinate_to_pixel(value_range.target); + Point_F first = result.points.front(); + Point_F last = result.points.back(); + if (value_orientation == Axis_Orientation::horizontal) { first.x = baseline; last.x = baseline; } + else { first.y = baseline; last.y = baseline; } + result.fill.reserve(result.points.size() + 2); + result.fill.push_back(first); + result.fill.insert(result.fill.end(), result.points.begin(), result.points.end()); + result.fill.push_back(last); + return result; +} +template +[[nodiscard]] Curve_Prepared prepare_curve(std::span values, Axis_Range domain, Line_Interpolation_Mode mode, bool visible_only, Axis_Range visible_coordinate_range, Axis_Range value_range, const Coordinate_Axis* coordinate_axis, const Value_Axis* value_axis, Axis_Orientation coordinate_orientation, Axis_Orientation value_orientation) { + const auto samples = interpolate_curve(values, domain, mode); + return prepare_curve(std::span{samples}, visible_only, visible_coordinate_range, value_range, coordinate_axis, value_axis, coordinate_orientation, value_orientation); +} /* 返回曲线及填充覆盖的 framebuffer 视图,padding 用于描边与抗锯齿重叠边界。 */ [[nodiscard]] Rect_F curve_paint_region(const Curve_Prepared& curve, double padding); void paint_curve(Painter& painter, const Curve_Prepared& curve, const Pen& pen, const Brush& brush = {}); diff --git a/render_2D/render_2D/plottable/common/Raster_Plot.cpp b/render_2D/render_2D/plottable/common/Raster_Plot.cpp index da5c76e..7fa6b42 100644 --- a/render_2D/render_2D/plottable/common/Raster_Plot.cpp +++ b/render_2D/render_2D/plottable/common/Raster_Plot.cpp @@ -44,11 +44,6 @@ std::optional raster_axis_selection(Axis_Range data_range const auto coordinate = [data_range, source_count](int index) { return data_range.origin + data_range.length() * static_cast(index) / static_cast(source_count - 1); }; return Raster_Axis_Selection{first, last, {coordinate(first), coordinate(last)}}; } -Raster_Layout raster_layout(const Abs_Axis* first_axis, Axis_Range first_range, int first_count, const Abs_Axis* second_axis, Axis_Range second_range, int second_count, Axis_Orientation first_orientation, Axis_Orientation second_orientation) { - if (first_orientation == second_orientation || first_count <= 0 || second_count <= 0) return {}; - const bool horizontal = first_orientation == Axis_Orientation::horizontal; - return {horizontal ? first_count : second_count, horizontal ? second_count : first_count, map_plot_rect(first_axis, first_range, second_axis, second_range, first_orientation), first_axis->coordinate_to_pixel(first_range.origin) > first_axis->coordinate_to_pixel(first_range.target), second_axis->coordinate_to_pixel(second_range.origin) > second_axis->coordinate_to_pixel(second_range.target), horizontal}; -} Plot_Partition_Count raster_partition_count(Plot_Partition_Grid grid) noexcept { return grid.valid() ? grid.columns * grid.rows : 0; } diff --git a/render_2D/render_2D/plottable/common/Raster_Plot.hpp b/render_2D/render_2D/plottable/common/Raster_Plot.hpp index fa07bc0..8614e0c 100644 --- a/render_2D/render_2D/plottable/common/Raster_Plot.hpp +++ b/render_2D/render_2D/plottable/common/Raster_Plot.hpp @@ -2,11 +2,13 @@ #include "../../axis/Axis.hpp" #include "../../render/Blend2D_Cache.hpp" #include "../Plot_Types.hpp" +#include "Curve_Plot.hpp" #include #include #include #include namespace aethera::render_2d::detail { +using double_buffer::detail::Internal_Access; struct Raster_Layout { int width{}; /* 像素矩阵宽度。 */ int height{}; /* 像素矩阵高度。 */ @@ -33,7 +35,19 @@ struct Raster_Partition { }; [[nodiscard]] Plot_Ratio normalized_plot_value(Plot_Value value, Axis_Range range); [[nodiscard]] std::optional raster_axis_selection(Axis_Range data_range, Axis_Range visible_range, int source_count, bool visible_only); -[[nodiscard]] Raster_Layout raster_layout(const Abs_Axis* first_axis, Axis_Range first_range, int first_count, const Abs_Axis* second_axis, Axis_Range second_range, int second_count, Axis_Orientation first_orientation, Axis_Orientation second_orientation); +template +[[nodiscard]] Raster_Layout raster_layout(const First_Axis* first_axis, Axis_Range first_range, int first_count, const Second_Axis* second_axis, Axis_Range second_range, int second_count, Axis_Orientation first_orientation, Axis_Orientation second_orientation) { + if (first_orientation == second_orientation || first_count <= 0 || second_count <= 0) return {}; + const bool horizontal = first_orientation == Axis_Orientation::horizontal; + return {horizontal ? first_count : second_count, + horizontal ? second_count : first_count, + map_plot_rect(first_axis, first_range, second_axis, second_range, first_orientation), + Internal_Access::layer(first_axis).runtime_coordinate_to_pixel(first_range.origin) > + Internal_Access::layer(first_axis).runtime_coordinate_to_pixel(first_range.target), + Internal_Access::layer(second_axis).runtime_coordinate_to_pixel(second_range.origin) > + Internal_Access::layer(second_axis).runtime_coordinate_to_pixel(second_range.target), + horizontal}; +} [[nodiscard]] Plot_Partition_Count raster_partition_count( Plot_Partition_Grid grid) noexcept; [[nodiscard]] Raster_Partition raster_partition( diff --git a/render_2D/render_2D/scene/Render_Scene_2D.cpp b/render_2D/render_2D/scene/Render_Scene_2D.cpp index 2d50c71..78e2992 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.cpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.cpp @@ -4,25 +4,26 @@ Render_Scene_2D::Private::~Private() = default; bool Render_Scene_2D::State::operator==(const State&) const = default; bool Render_Scene_2D::Prop::operator==(const Prop&) const = default; -std::expected -Render_Scene_2D::render(Frame_2D* frame) { - return static_cast(*d).dispatch->render(this, frame); +Render_Scene_2D::Render_Result_Type Render_Scene_2D::render(Frame_2D* frame) { + return double_buffer::detail::Internal_Access::get(this).render(this, frame); +} +void Render_Scene_2D::set_frame_callback(Frame_Callback callback) { + double_buffer::detail::Internal_Access::get(this).set_frame_callback(this, std::move(callback)); } -void Render_Scene_2D::set_frame_callback(Frame_Callback callback) { static_cast(*d).dispatch->set_frame_callback(this, std::move(callback)); } void Render_Scene_2D::set_frame_retired_callback(Frame_Callback callback) { - static_cast(*d).dispatch->set_frame_retired_callback( - this, std::move(callback)); + double_buffer::detail::Internal_Access::get(this).set_frame_retired_callback(this, std::move(callback)); } +void Render_Scene_2D::activate_view() { + double_buffer::detail::Internal_Access::get(this).set_view_active(this, true); +} +void Render_Scene_2D::deactivate_view() { + double_buffer::detail::Internal_Access::get(this).set_view_active(this, false); +} +void Render_Scene_2D::reset_frame_statistics() { + double_buffer::detail::Internal_Access::get(this).reset_frame_statistics(this); +} + Task_Graph& Render_Scene_2D::completion_taskflow() { return static_cast(*d).completion_graph; } -void Render_Scene_2D::activate_view() { - static_cast(*d).dispatch->set_active(this, true); -} -void Render_Scene_2D::deactivate_view() { - static_cast(*d).dispatch->set_active(this, false); -} -void Render_Scene_2D::reset_frame_statistics() { - static_cast(*d).dispatch->reset_statistics(this); -} } diff --git a/render_2D/render_2D/scene/Render_Scene_2D.hpp b/render_2D/render_2D/scene/Render_Scene_2D.hpp index f23edba..ab13141 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.hpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.hpp @@ -27,7 +27,7 @@ struct Render_Scene_2D : Def struct Builder : Prev_Builder { using Base = Prev_Builder; - using Final_Builder = typename Object::Builder; + using Final_Builder = typename Object::template Builder; Builder(); template requires std::derived_from Final_Builder& add_renderable(Renderable_Object* renderable); @@ -37,16 +37,19 @@ struct Render_Scene_2D : Def; + using Render_Result_Type = std::expected; + using Render_Frame_Type = Frame_2D; + using Retired_Frame_Callback_Capability = void; + [[nodiscard]] Render_Result_Type render(Frame_2D* frame); + void set_frame_callback(Frame_Callback callback); + void set_frame_retired_callback(Frame_Callback callback); /* 向调用方拥有的帧合成一次;Scene::advance 到完成像素发布保持不可重入。 */ - [[nodiscard]] std::expected render(Frame_2D* frame); /* * 安装完成帧回调;回调在 Scene 像素/发布阶段结束并释放下一帧准入后执行。 * 因而回调可以继续执行外接编码/发送 Taskflow,同时下一帧 Scene 已可开始。 * 调用方拥有的 Frame 必须存活到回调返回。 */ - void set_frame_callback(Frame_Callback callback); /* callback/frame_ready/trace 完全收尾后通知调用方归还物理 Frame 槽。 */ - void set_frame_retired_callback(Frame_Callback callback); /* * 返回最终像素完成后、帧回调前执行的直接 Taskflow。 * 只能在 Scene 没有运行时修改该图;禁止在执行期间 emplace/erase/clear。 @@ -54,11 +57,11 @@ struct Render_Scene_2D : Def, Dependency_Graph_Error> Render_Scene_2D:: * fully built Prepare/Paint DAG instead of publishing a background-only * bootstrap frame. */ - scene->advance(); + double_buffer::detail::Internal_Access::advance(scene.get()); /* after_advance writes derived DAG diagnostics into the State write side; * publish that already-computed result before handing the Scene to Plot. */ - scene->advance(); + double_buffer::detail::Internal_Access::advance(scene.get()); return scene; } struct Render_Scene_2D::Private : Prev_Private { - using Render_Run = std::expected (*)(Root*, Frame_2D*); - using Callback_Run = void (*)(Root*, Frame_Callback); - using Active_Run = void (*)(Root*, bool); struct Paint_Node { Root* object{}; /* Paint 拓扑位置对应的最终对象;Scene 不拥有。 */ Renderable_2D_Base::Private* private_data{}; /* 对象的二维能力层;对象存活期间有效。 */ @@ -63,14 +60,6 @@ struct Render_Scene_2D::Private : Prev_Private { Renderable_2D_Base* object{}; /* 当前 Paint 图中的事件候选对象;Scene 不拥有。 */ Renderable_2D_Base::Private* private_data{}; /* 候选对象的二维能力层;仅在本次 Prepare 分发期间有效。 */ }; - struct Dispatch { - void (*reset_statistics)(Root*); - Render_Run render; /* 向外部帧执行最终 Scene 并合成颜色层。 */ - Callback_Run set_frame_callback; /* 安装像素完成后的外接消费回调。 */ - Callback_Run set_frame_retired_callback; /* trace/frame_ready 完成后的物理帧退役回调。 */ - Active_Run set_active; /* 修改最终 Scene 的视图活动状态。 */ - }; - const Dispatch* dispatch{}; /* Builder 绑定最终 Scene 类型后的静态分派表。 */ std::mutex render_mutex{}; /* 只保护完成回调和单帧准入。 */ bool frame_in_flight{}; /* Scene::advance 到像素发布完成的唯一不可重入准入状态。 */ Frame_Callback frame_callback{}; /* Plot publish 后的外接消费出口。 */ @@ -84,7 +73,7 @@ struct Render_Scene_2D::Private : Prev_Private { ~Private(); Frame_2D* active_frame{}; /* 异步帧 DAG 借用的外部帧;完成回调前保持存活。 */ Blend2D_Cache* frame_target{}; /* 当前异步帧的最终颜色层;帧 DAG 完成后清空。 */ - /* Impl CRTP 实现:Prepare 完成后按 Paint 图拓扑边并行绘制并合成颜色层。 */ + /* 最终 Private 实现:Prepare 完成后按 Paint 图拓扑边并行绘制并合成颜色层。 */ std::vector paint_order{}; /* Paint 图当前拓扑序及 Scene 缓存分组结果。 */ std::vector cache_groups{}; /* 仅保存显式缓存根对应的执行分组。 */ template void ensure_frame_taskflow(Object* object); @@ -100,8 +89,10 @@ struct Render_Scene_2D::Private : Prev_Private { template [[nodiscard]] std::expected render(Object* object, Frame_2D* frame); - template - [[nodiscard]] static const Dispatch& dispatch_for(); + template void set_frame_callback(Object* object, Frame_Callback callback); + template void set_frame_retired_callback(Object* object, Frame_Callback callback); + template void set_view_active(Object* object, bool active); + template void reset_frame_statistics(Object* object); /* CRTP 覆盖:Builder 挂接最终 Private 后安装二维 Scene 的无虚函数业务分派。 */ template void bind_private_crtp(Object* object); @@ -109,7 +100,7 @@ struct Render_Scene_2D::Private : Prev_Private { template void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access, const Prop*, State_Access) { bool rebuild = !paint_taskflow; - object->template access_pending_dependency_graph([&](auto& paint_state) { rebuild = rebuild || paint_state.dirty(); }); + double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, [&](auto& paint_state) { rebuild = rebuild || paint_state.dirty(); }); for (auto& node : paint_order) { const bool enabled = node.private_data->cache_enabled && node.private_data->cache_enabled(node.object); @@ -121,7 +112,7 @@ void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access if (!paint_taskflow) paint_taskflow = std::make_unique("render_2d.paint"); auto& taskflow = *paint_taskflow; taskflow.clear(); - const auto dependencies = object->template current_dependency_graph(); + const auto dependencies = double_buffer::detail::Internal_Access::current_dependency_graph(object); paint_order.clear(); cache_groups.clear(); std::unordered_map cache_owner_by_object; @@ -160,25 +151,24 @@ void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access auto& paint_node = paint_order[index]; Root* root = paint_node.object; auto& data = *paint_node.private_data; - auto* dispatch = data.dispatch; - const auto task_prefix = std::string(dispatch->business_name) + ".paint"; - auto paint_if = taskflow.add_condition(task_prefix + ".condition", [this, &data, dispatch, root, index] { - auto& state = *dispatch->state.pending(root); + const auto task_prefix = std::string(data.business_name()) + ".paint"; + auto paint_if = taskflow.add_condition(task_prefix + ".condition", [this, &data, root, index] { + auto& state = *static_cast(data.pending_renderable_state(root)); state.paint_graph_rebuilt = false; state.paint_execution_time_ns = 0; - if (dispatch->paint.builder) { - const bool rebuild_graph = dispatch->paint.rebuild_predicate(root); + if (data.has_paint_graph()) { + const bool rebuild_graph = data.paint_rebuild_predicate(root); if (!data.paint_graph_built || rebuild_graph) { - *data.paint_graph = dispatch->paint.builder(root); + *data.paint_graph = data.build_paint_graph(root); data.paint_graph_built = true; state.paint_graph_rebuilt = true; - root->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(root); } state.paint_task_count = data.paint_graph->size(); - } else state.paint_task_count = dispatch->paint.run ? 1 : 0; - const bool dirty = root->template dirty(); + } else state.paint_task_count = data.has_paint_run() ? 1 : 0; + const bool dirty = double_buffer::detail::Internal_Access::dirty(root); state.paint_dirty = dirty; - state.paint_executed = paint_order[index].paint_requested && dispatch->paint.predicate(root, dirty); + state.paint_executed = paint_order[index].paint_requested && data.paint_predicate(root, dirty); if (state.paint_executed) { state.paint_execution_time_ns = static_cast( std::chrono::duration_cast( @@ -187,14 +177,16 @@ void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access return state.paint_executed ? 0 : 1; }); Task_Node paint_run; - if (dispatch->paint.builder) { + if (data.has_paint_graph()) { if (!data.paint_graph) data.paint_graph = std::make_unique(task_prefix + ".graph"); paint_run = taskflow.compose(task_prefix + ".graph", *data.paint_graph); - } else paint_run = taskflow.add(task_prefix + ".data", [dispatch, root] { if (dispatch->paint.run) dispatch->paint.run(root); }); - auto paint_done = taskflow.add(task_prefix + ".complete", [dispatch, root] { - auto& state = *dispatch->state.pending(root); + } else paint_run = taskflow.add(task_prefix + ".data", [&data, root] { + if (data.has_paint_run()) data.run_paint(root); + }); + auto paint_done = taskflow.add(task_prefix + ".complete", [&data, root] { + auto& state = *static_cast(data.pending_renderable_state(root)); if (state.paint_executed) { - root->template take_dirty(); + (void)double_buffer::detail::Internal_Access::take_dirty(root); const auto finished = static_cast( std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count()); @@ -229,7 +221,7 @@ void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access if (owner_node == paint_order.end()) throw std::logic_error("2D cache group lost its owner node"); const auto task_prefix = - std::string(owner_node->private_data->dispatch->business_name) + + std::string(owner_node->private_data->business_name()) + ".paint.cache_composite"; auto composite = taskflow.add(task_prefix, [this, cache_owner] { const auto group = std::find_if( @@ -321,7 +313,7 @@ void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access } } }); - object->template access_pending_dependency_graph([](auto& paint_state) { if (paint_state.dirty()) paint_state.take_dirty(); }); + double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, [](auto& paint_state) { if (paint_state.dirty()) paint_state.take_dirty(); }); } template void Render_Scene_2D::Private::prepare_paint_targets(Object*, Blend2D_Cache& frame, Size viewport) { @@ -333,9 +325,10 @@ void Render_Scene_2D::Private::prepare_paint_targets(Object*, Blend2D_Cache& fra for (Root* member : group.members) { const auto node = std::find_if(paint_order.begin(), paint_order.end(), [member](const Paint_Node& value) { return value.object == member; }); if (node == paint_order.end()) continue; - group.rebuild = group.rebuild || member->template dirty(); - if (node->private_data->dispatch->paint.builder) { - group.rebuild = group.rebuild || !node->private_data->paint_graph_built || node->private_data->dispatch->paint.rebuild_predicate(member); + group.rebuild = group.rebuild || double_buffer::detail::Internal_Access::dirty(member); + if (node->private_data->has_paint_graph()) { + group.rebuild = group.rebuild || !node->private_data->paint_graph_built || + node->private_data->paint_rebuild_predicate(member); } } if (group.rebuild) { @@ -343,7 +336,7 @@ void Render_Scene_2D::Private::prepare_paint_targets(Object*, Blend2D_Cache& fra target->ensure_size(viewport); target->clear(); for (Root* member : group.members) { - member->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(member); const auto node = std::find_if(paint_order.begin(), paint_order.end(), [member](const Paint_Node& value) { return value.object == member; }); if (node != paint_order.end()) { node->private_data->paint_target = target; node->paint_requested = true; } } @@ -361,7 +354,7 @@ void Render_Scene_2D::Private::prepare_paint_targets(Object*, Blend2D_Cache& fra if (node.cache_owner) continue; node.private_data->paint_target = &frame; node.paint_requested = true; - node.object->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(node.object); } } template @@ -376,29 +369,27 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { auto begin = graph.add("scene.begin", [this, object] { auto* frame = active_frame; if (!frame) throw std::logic_error("2D frame DAG lost its active frame"); - const auto& current_data = static_cast(*this); - const Prop& prop = current_data.template current_prop(); + const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer(object); frame->mark(Frame_Trace_Marker::scene_render_started); frame->mark(Frame_Trace_Marker::event_dispatch_started); dispatch_events(object, prop.viewport, frame->identity().sequence); frame->mark(Frame_Trace_Marker::event_dispatch_finished); - object->template current_dependency_graph().for_each( + double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each( [](const Dependency_Graph::Node& node) { - if (!node.object->template take_dirty()) return; - node.object->template mark_dirty(); - node.object->template mark_dirty(); + if (!double_buffer::detail::Internal_Access::take_dirty(node.object)) return; + double_buffer::detail::Internal_Access::mark_dirty(node.object); + double_buffer::detail::Internal_Access::mark_dirty(node.object); }); active_prepare_executions.clear(); - object->template current_dependency_graph().for_each_bound( + double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each_bound( [this](Renderable* renderable, Renderable::Private& data) { - bool execute = renderable->template dirty(); - if (data.dispatch->prepare.builder) + bool execute = double_buffer::detail::Internal_Access::dirty(renderable); + if (data.has_prepare_graph()) execute = execute || !data.prepare_graph_built || - data.dispatch->prepare.rebuild_predicate(renderable); + data.prepare_rebuild_predicate(renderable); if (execute) active_prepare_executions.push_back(renderable); }); - auto& private_data = static_cast(*this); - auto& state = static_cast(*private_data.state.pending); + auto& state = static_cast(double_buffer::detail::Internal_Access::pending_state(object)); state.taskflow_execution_time_ns = 0; active_prepare_started = std::chrono::steady_clock::now(); frame->mark(Frame_Trace_Marker::prepare_started); @@ -409,8 +400,7 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { auto setup_paint = graph.add("scene.paint.setup", [this, object] { auto* frame_object = active_frame; if (!frame_object) throw std::logic_error("2D Paint lost its active frame"); - auto& private_data = static_cast(*this); - auto& scene_state = static_cast(*private_data.state.pending); + auto& scene_state = static_cast(double_buffer::detail::Internal_Access::pending_state(object)); scene_state.taskflow_execution_time_ns = static_cast( std::chrono::duration_cast( std::chrono::steady_clock::now() - active_prepare_started).count()); @@ -418,7 +408,7 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { frame_object->mark(Frame_Trace_Marker::prepare_finished); frame_object->mark(Frame_Trace_Marker::paint_started); for (auto* renderable : active_prepare_executions) - renderable->template mark_dirty(); + double_buffer::detail::Internal_Access::mark_dirty(renderable); auto& frame = detail::Frame_2D_Access::render_target(frame_object); frame_target = &frame; }); @@ -429,8 +419,7 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { auto* frame_object = active_frame; if (!frame_object || !frame_target) throw std::logic_error("2D frame target preparation lost its frame"); - const auto& private_data = static_cast(*this); - const Prop& prop = private_data.template current_prop(); + const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer(object); frame_object->mark(Frame_Trace_Marker::paint_frame_target_started); frame_target->ensure_size(prop.viewport); frame_target->clear(); @@ -443,8 +432,7 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { auto* frame_object = active_frame; if (!frame_object || !frame_target) throw std::logic_error("2D background paint lost its frame target"); - const auto& private_data = static_cast(*this); - const Prop& prop = private_data.template current_prop(); + const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer(object); frame_object->mark(Frame_Trace_Marker::paint_background_started); { detail::Painter painter(*frame_target, prop.viewport); @@ -462,8 +450,7 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { auto* frame_object = active_frame; if (!frame_object || !frame_target) throw std::logic_error("2D cache target preparation lost its frame"); - const auto& private_data = static_cast(*this); - const Prop& prop = private_data.template current_prop(); + const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer(object); frame_object->mark(Frame_Trace_Marker::paint_cache_targets_started); prepare_paint_targets(object, *frame_target, prop.viewport); frame_object->mark(Frame_Trace_Marker::paint_cache_targets_finished); @@ -527,9 +514,8 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { * no Taskflow is running. The completion callback only publishes * results; it must not modify a graph that is still executing. */ - object->advance(); - const auto& private_data = static_cast(*this); - const Prop& prop = private_data.template current_prop(); + double_buffer::detail::Internal_Access::advance(object); + const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer(object); if (!prop.view_active) { release_admission(); return std::unexpected(Render_Result::view_inactive); @@ -557,23 +543,22 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { * 渲染统计并释放准入,再进入调用方 callback;callback 可以继续 * corun 外接 H264/WebRTC DAG,而下一帧已经允许开始。 */ - auto& private_data = static_cast(*this); std::unordered_set published_renderables; const auto publish_renderable_states = [&](const auto& graph) { graph.for_each_bound([&](Renderable* renderable, Renderable::Private& data) { if (published_renderables.insert(renderable).second) - data.dispatch->state.publish(renderable); + data.publish_renderable_state(renderable); }); }; publish_renderable_states( - object->template current_dependency_graph()); + double_buffer::detail::Internal_Access::current_dependency_graph(object)); publish_renderable_states( - object->template current_dependency_graph()); - auto& scene_state = static_cast(*private_data.state.pending); + double_buffer::detail::Internal_Access::current_dependency_graph(object)); + auto& scene_state = static_cast(double_buffer::detail::Internal_Access::pending_state(object)); scene_state.frame_statistics = frame_statistics.submit( *frame, Frame_Dimension::two_dimensional); - private_data.template publish_state(); + double_buffer::detail::Internal_Access::publish_state(object); active_frame = nullptr; frame_target = nullptr; @@ -624,7 +609,7 @@ void Render_Scene_2D::Private::dispatch_events(Object* object, Size viewport, /* Paint 拓扑在一帧事件批次内不会变化。只解析一次候选对象;每个事件仍按 * 当时的动态区域重新筛选、排序并完整分发,不合并也不丢弃输入。 */ std::vector candidates; - const auto graph = object->template current_dependency_graph(); + const auto graph = double_buffer::detail::Internal_Access::current_dependency_graph(object); const auto graph_result = graph.for_each_topological_view( [&](const auto& view, const Dependency_Graph::Node& node) { auto* renderable = view.object(node); @@ -653,7 +638,11 @@ void Render_Scene_2D::Private::dispatch_events(Object* object, Size viewport, order.push_back(target); } std::vector delivery_order(order.rbegin(), order.rend()); - detail::Nearest_Event_Target_Rule::apply(delivery_order, [&](const Event_Target& target) { return target.object->event_routing_distance(event); }); + detail::Nearest_Event_Target_Rule::apply(delivery_order, [&](const Event_Target& target) { + return target.private_data->event_routing_distance_run + ? target.private_data->event_routing_distance_run(target.object, event) + : std::nullopt; + }); for (const auto& target : delivery_order) { if (event.is_accepted()) break; target.private_data->event_run(target.object, event); @@ -662,44 +651,32 @@ void Render_Scene_2D::Private::dispatch_events(Object* object, Size viewport, } } template -const Render_Scene_2D::Private::Dispatch& Render_Scene_2D::Private::dispatch_for() { - static const Dispatch value{ - [](Root* root) { - auto* object = static_cast(root); - auto& data = static_cast(*object->d); - object->template publish_state( - [&data](State_Access states) { - data.frame_statistics.reset(); - data.event_statistics.reset(); - auto& state = states.template get(); - state.frame_statistics = {}; - state.event_statistics = {}; - }); - }, - [](Root* root, Frame_2D* frame) { - auto* object = static_cast(root); - return static_cast(*object->d).render(object, frame); - }, - [](Root* root, Frame_Callback callback) { - auto* object = static_cast(root); - auto& data = static_cast(*object->d); - std::lock_guard lock(data.render_mutex); - data.frame_callback = std::move(callback); - }, - [](Root* root, Frame_Callback callback) { - auto* object = static_cast(root); - auto& data = static_cast(*object->d); - std::lock_guard lock(data.render_mutex); - data.frame_retired_callback = std::move(callback); - }, - [](Root* root, bool active) { static_cast(root)->template set<&Prop::view_active>(active); } - }; - return value; +void Render_Scene_2D::Private::reset_frame_statistics(Object* object) { + double_buffer::detail::Internal_Access::publish_state(object, + [this](State_Access states) { + frame_statistics.reset(); + event_statistics.reset(); + auto& state = states.template get(); + state.frame_statistics = {}; + state.event_statistics = {}; + }); +} +template +void Render_Scene_2D::Private::set_frame_callback(Object*, Frame_Callback callback) { + std::lock_guard lock(render_mutex); + frame_callback = std::move(callback); +} +template +void Render_Scene_2D::Private::set_frame_retired_callback(Object*, Frame_Callback callback) { + std::lock_guard lock(render_mutex); + frame_retired_callback = std::move(callback); +} +template +void Render_Scene_2D::Private::set_view_active(Object* object, bool active) { + double_buffer::detail::Internal_Access::set<&Prop::view_active>(object, active); } template void Render_Scene_2D::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); - dispatch = &Private::dispatch_for(); } } diff --git a/render_2D/tests/Async_Render_Contract_Test.cpp b/render_2D/tests/Async_Render_Contract_Test.cpp index 577b5d7..eedcbc9 100644 --- a/render_2D/tests/Async_Render_Contract_Test.cpp +++ b/render_2D/tests/Async_Render_Contract_Test.cpp @@ -16,7 +16,7 @@ using namespace aethera::render_2d; template Object* build_object(Arguments&&... arguments) { - typename Object::Builder builder(std::forward(arguments)...); + typename Object::template Builder builder(std::forward(arguments)...); auto result = builder.build(); if (!result) std::_Exit(2); return std::move(result).value().release(); @@ -34,13 +34,13 @@ bool contains_color(Image_View view) { } int main() { - using Frequency = Impl; - using Power = Impl; - using Time = Impl; - using Spectrum_Object = Impl; - using Constellation_Object = Impl; - using Waterfall_Object = Impl; - using Scene_Object = Impl; + using Frequency = Frequency_Axis; + using Power = Numeric_Axis; + using Time = Time_Axis; + using Spectrum_Object = Spectrum; + using Constellation_Object = Constellation_Diagram; + using Waterfall_Object = Waterfall; + using Scene_Object = Render_Scene_2D; initialize_runtime({.workers = 4}); auto* frequency = build_object(); @@ -67,7 +67,7 @@ int main() { power->paint_taskflow().add("test.power.paint.order", verify_spectrum_precedes_axis); - typename Scene_Object::Builder scene_builder; + typename Scene_Object::template Builder scene_builder; scene_builder.add_renderable(spectrum); scene_builder.add_renderable(waterfall); scene_builder.add_renderable(constellation); diff --git a/render_2D/tests/Axis_Test.cpp b/render_2D/tests/Axis_Test.cpp index 6256ffc..0d2c50b 100644 --- a/render_2D/tests/Axis_Test.cpp +++ b/render_2D/tests/Axis_Test.cpp @@ -9,7 +9,7 @@ using namespace aethera; using namespace aethera::render_2d; template std::unique_ptr build_axis() { - auto result = typename Object::Builder{}.build(); + auto result = typename Object::template Builder{}.build(); if (!result) std::terminate(); return std::move(result).value(); } @@ -34,7 +34,7 @@ TEST(event_routing_rule, keeps_layered_targets_before_nearest_scored_targets) { EXPECT_EQ(targets, (std::vector{1, 3, 4, 2})); } TEST(axis_dispatch, numeric_axis_public_shell_reads_final_private_state) { - using Object = Impl; + using Object = Numeric_Axis; auto axis = build_axis(); axis->set<&Abs_Axis::Prop::position>(Point_F{10.0, 0.0}); axis->set<&Abs_Axis::Prop::pixel_length>(200.0); @@ -53,7 +53,7 @@ TEST(axis_dispatch, numeric_axis_public_shell_reads_final_private_state) { EXPECT_DOUBLE_EQ(axis->pixel_to_coordinate(-40.0), 5.0); } TEST(axis_dispatch, point_conversion_uses_current_orientation) { - using Object = Impl; + using Object = Numeric_Axis; auto axis = build_axis(); axis->set<&Abs_Axis::Prop::position>(Point_F{0.0, 20.0}); axis->set<&Abs_Axis::Prop::pixel_length>(100.0); @@ -80,8 +80,8 @@ TEST(render_cache, copy_owns_independent_pixels) { EXPECT_TRUE(contains_color); } TEST(axis_render, scene_prepares_and_paints_uncached_axis_into_frame) { - using Object = Impl; - using Scene_Object = Impl; + using Object = Numeric_Axis; + using Scene_Object = Render_Scene_2D; initialize_runtime({.workers = 2}); auto axis = build_axis(); auto scene = build_axis(); @@ -114,8 +114,8 @@ TEST(axis_render, scene_prepares_and_paints_uncached_axis_into_frame) { EXPECT_TRUE(contains_color); } TEST(axis_event, numeric_axis_wheel_zoom_and_drag_update_authoritative_range) { - using Object = Impl; - using Scene_Object = Impl; + using Object = Numeric_Axis; + using Scene_Object = Render_Scene_2D; initialize_runtime({.workers = 2}); auto axis = build_axis(); auto scene = build_axis(); @@ -145,8 +145,8 @@ TEST(axis_event, numeric_axis_wheel_zoom_and_drag_update_authoritative_range) { EXPECT_EQ(axis->coordinate_range(), (Axis_Range{-0.4, 8.6})); } TEST(axis_event, scene_routes_pointer_interaction_to_the_nearest_axis_segment) { - using Axis_Object = Impl; - using Scene_Object = Impl; + using Axis_Object = Numeric_Axis; + using Scene_Object = Render_Scene_2D; initialize_runtime({.workers = 2}); auto horizontal = build_axis(); auto vertical = build_axis(); @@ -183,14 +183,14 @@ TEST(axis_event, scene_routes_pointer_interaction_to_the_nearest_axis_segment) { EXPECT_DOUBLE_EQ(vertical->coordinate_range().size(), 9.0); } TEST(axis_state, invalid_numeric_range_is_rejected_without_poisoning_pending_state) { - using Object = Impl; + using Object = Numeric_Axis; auto axis = build_axis(); EXPECT_THROW((axis->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{1.0, 1.0})), std::invalid_argument); axis->advance(); EXPECT_EQ(axis->coordinate_range(), (Axis_Range{0.0, 20.0})); } TEST(axis_label, frequency_axis_selects_engineering_unit) { - using Object = Impl; + using Object = Frequency_Axis; auto axis = build_axis(); axis->advance(); EXPECT_EQ(axis->tick_label(250.0), "250 Hz"); @@ -198,7 +198,7 @@ TEST(axis_label, frequency_axis_selects_engineering_unit) { EXPECT_EQ(axis->tick_label(2'500'000.0), "2.5 MHz"); } TEST(axis_time, sample_state_drives_range_lookup_and_label_without_snapshot) { - using Object = Impl; + using Object = Time_Axis; auto axis = build_axis(); EXPECT_EQ(axis->append_time({3'723'004}), 0); EXPECT_EQ(axis->time_point_count(), 0u); @@ -214,7 +214,7 @@ TEST(axis_time, sample_state_drives_range_lookup_and_label_without_snapshot) { EXPECT_FALSE(axis->tick_to_time(99).valid()); } TEST(axis_time, appending_each_time_sample_invalidates_tick_preparation) { - using Object = Impl; + using Object = Time_Axis; auto axis = build_axis(); axis->advance(); axis->template take_dirty(); diff --git a/render_2D/tests/Partition_Configuration_Test.cpp b/render_2D/tests/Partition_Configuration_Test.cpp index 7bf7da0..ceddf76 100644 --- a/render_2D/tests/Partition_Configuration_Test.cpp +++ b/render_2D/tests/Partition_Configuration_Test.cpp @@ -16,7 +16,7 @@ using namespace aethera::render_2d; template std::unique_ptr build_object(Arguments&&... arguments) { - typename Object::Builder builder(std::forward(arguments)...); + typename Object::template Builder builder(std::forward(arguments)...); auto result = builder.build(); if (!result) std::terminate(); return std::move(result).value(); @@ -25,15 +25,15 @@ std::unique_ptr build_object(Arguments&&... arguments) { TEST(partition_configuration, builder_and_runtime_share_the_authoritative_partition_properties) { - using Frequency = Impl; - using Power = Impl; - using Time = Impl; - using Spectrum_Object = Impl; - using Trace_Object = Impl; - using Sweep_Object = Impl; - using Afterglow_Object = Impl; - using Constellation_Object = Impl; - using Waterfall_Object = Impl; + using Frequency = Frequency_Axis; + using Power = Numeric_Axis; + using Time = Time_Axis; + using Spectrum_Object = Spectrum; + using Trace_Object = Frequency_Trace; + using Sweep_Object = Sweep_Spectrum; + using Afterglow_Object = Afterglow; + using Constellation_Object = Constellation_Diagram; + using Waterfall_Object = Waterfall; auto frequency = build_object(); auto power = build_object(); diff --git a/render_2D/tests/Plottable_Migration_Test.cpp b/render_2D/tests/Plottable_Migration_Test.cpp index 539b51e..9a900a7 100644 --- a/render_2D/tests/Plottable_Migration_Test.cpp +++ b/render_2D/tests/Plottable_Migration_Test.cpp @@ -10,7 +10,7 @@ using namespace aethera::render_2d; template std::unique_ptr build_object(Args&&... args) { - typename Object::Builder builder(std::forward(args)...); + typename Object::template Builder builder(std::forward(args)...); auto result = builder.build(); if (!result) std::terminate(); return std::move(result).value(); @@ -18,7 +18,7 @@ std::unique_ptr build_object(Args&&... args) { template std::unique_ptr build_scene(Renderables*... renderables) { - typename Scene::Builder builder; + typename Scene::template Builder builder; (builder.add_renderable(renderables), ...); auto result = builder.build(); if (!result) std::terminate(); @@ -41,12 +41,12 @@ void render_once(Scene* scene) { } TEST(plottable_migration, curve_plots_share_partitioned_rendering) { - using Scene = Impl; - using Frequency = Impl; - using Numeric = Impl; - using Time = Impl; - using Trace = Impl; - using Sweep = Impl; + using Scene = Render_Scene_2D; + using Frequency = Frequency_Axis; + using Numeric = Numeric_Axis; + using Time = Time_Axis; + using Trace = Frequency_Trace; + using Sweep = Sweep_Spectrum; initialize_runtime({.workers = 2}); const Size canvas{160, 120}; auto frequency = build_object(); @@ -104,12 +104,12 @@ TEST(plottable_migration, curve_plots_share_partitioned_rendering) { } TEST(plottable_migration, raster_plots_share_partitioned_color_blocks) { - using Scene = Impl; - using Frequency = Impl; - using Numeric = Impl; - using Time = Impl; - using Glow = Impl; - using Fall = Impl; + using Scene = Render_Scene_2D; + using Frequency = Frequency_Axis; + using Numeric = Numeric_Axis; + using Time = Time_Axis; + using Glow = Afterglow; + using Fall = Waterfall; initialize_runtime({.workers = 2}); const Size canvas{160, 120}; auto frequency = build_object(); @@ -160,10 +160,10 @@ TEST(plottable_migration, raster_plots_share_partitioned_color_blocks) { } TEST(plottable_migration, direct_overlay_and_constellation_build_and_render) { - using Scene = Impl; - using Numeric = Impl; - using Diagram = Impl; - using Overlay = Impl; + using Scene = Render_Scene_2D; + using Numeric = Numeric_Axis; + using Diagram = Constellation_Diagram; + using Overlay = Selection_Rectangle_Overlay; initialize_runtime({.workers = 2}); const Size canvas{160, 120}; auto horizontal = build_object(); @@ -190,9 +190,9 @@ TEST(plottable_migration, direct_overlay_and_constellation_build_and_render) { } TEST(selection_overlay, control_extends_selection_and_plain_click_clears_it) { - using Scene = Impl; - using Numeric = Impl; - using Overlay = Impl; + using Scene = Render_Scene_2D; + using Numeric = Numeric_Axis; + using Overlay = Selection_Rectangle_Overlay; initialize_runtime({.workers = 2}); const Size canvas{160, 120}; auto horizontal = build_object(); @@ -233,10 +233,10 @@ TEST(selection_overlay, control_extends_selection_and_plain_click_clears_it) { } TEST(selection_overlay, time_axis_selection_keeps_axis_coordinates_while_window_moves) { - using Scene = Impl; - using Time = Impl; - using Numeric = Impl; - using Overlay = Impl; + using Scene = Render_Scene_2D; + using Time = Time_Axis; + using Numeric = Numeric_Axis; + using Overlay = Selection_Rectangle_Overlay; initialize_runtime({.workers = 2}); const Size canvas{160, 120}; auto time = build_object