diff --git a/kernel/src/kernel/Frame_Policy/Frame_Scheduler.cpp b/kernel/src/kernel/Frame_Policy/Frame_Scheduler.cpp index d4eab34..484f099 100644 --- a/kernel/src/kernel/Frame_Policy/Frame_Scheduler.cpp +++ b/kernel/src/kernel/Frame_Policy/Frame_Scheduler.cpp @@ -1,4 +1,5 @@ #include "Frame_Scheduler.hpp" +#include #include #include #include @@ -37,7 +38,7 @@ Nanoseconds fps_period(double fps) { struct Frame_Scheduler::Timer::State { enum struct Mode : std::uint8_t { stopped, once, periodic }; - Frame_Scheduler::Private* owner{}; + not_null owner; std::uint64_t id{}; Handler handler{}; std::atomic_bool alive{true}; @@ -46,7 +47,8 @@ struct Frame_Scheduler::Timer::State { Scheduler_Clock::time_point next_deadline{}; /* 真实 deadline,不按 tick 累积。 */ TimerEvent> event; - State(Frame_Scheduler::Private* value_owner, std::uint64_t value_id, + State(not_null value_owner, + std::uint64_t value_id, Handler value_handler); }; @@ -232,11 +234,11 @@ struct Frame_Scheduler::Private { }; Frame_Scheduler::Timer::State::State( - Frame_Scheduler::Private* value_owner, std::uint64_t value_id, + not_null value_owner, std::uint64_t value_id, Handler value_handler) : owner(value_owner), id(value_id), handler(std::move(value_handler)), event([this] { - if (owner) owner->fire(*this); + owner->fire(*this); }) {} Frame_Scheduler::Frame_Scheduler() : d(std::make_unique()) {} @@ -257,8 +259,7 @@ Frame_Scheduler::Timer::Timer(std::shared_ptr state) noexcept Frame_Scheduler::Timer::~Timer() noexcept { if (!state_) return; state_->alive.store(false, std::memory_order_release); - if (state_->owner) - state_->owner->enqueue({Private::Command_Type::destroy, state_, {}}); + state_->owner->enqueue({Private::Command_Type::destroy, state_, {}}); } Frame_Scheduler::Timer::Timer(Timer&& other) noexcept diff --git a/kernel/src/kernel/Task_Graph.cpp b/kernel/src/kernel/Task_Graph.cpp index eeca8ae..644260f 100644 --- a/kernel/src/kernel/Task_Graph.cpp +++ b/kernel/src/kernel/Task_Graph.cpp @@ -79,9 +79,10 @@ struct Task_Graph::Private { } } - void bind_frame(Render_Frame* frame) noexcept { + void bind_frame( + std::optional> frame) noexcept { for (auto& node : nodes) { - node.task.data(frame); + node.task.data(frame ? frame->get() : nullptr); if (node.child) node.child->bind_frame(frame); } } @@ -106,7 +107,7 @@ std::vector Task_Graph_Access::nodes( } void Task_Graph_Access::bind_frame(Task_Graph& graph, - Render_Frame* frame) noexcept { + std::optional> frame) noexcept { graph.d->bind_frame(frame); } } diff --git a/kernel/src/kernel/Task_Graph_Internal.hpp b/kernel/src/kernel/Task_Graph_Internal.hpp index 5035554..c1afaca 100644 --- a/kernel/src/kernel/Task_Graph_Internal.hpp +++ b/kernel/src/kernel/Task_Graph_Internal.hpp @@ -1,8 +1,10 @@ #pragma once #include "Task_Graph.hpp" #include "frame.hpp" +#include "ownership.hpp" #include #include +#include #include #include @@ -12,7 +14,9 @@ struct Task_Graph_Access { [[nodiscard]] static void* native_storage(Task_Graph& graph) noexcept; [[nodiscard]] static std::vector nodes( const Task_Graph& graph); - static void bind_frame(Task_Graph& graph, Render_Frame* frame) noexcept; + static void bind_frame( + Task_Graph& graph, + std::optional> frame) noexcept; }; /* Task_Graph.cpp 到全局 Kernel Executor 的内部桥;不得在模块公共头文件中声明。 */ void corun_taskflow_until(std::function predicate); diff --git a/kernel/src/kernel/Taskflow_Frame_Access.hpp b/kernel/src/kernel/Taskflow_Frame_Access.hpp index b83b48f..e94b6d2 100644 --- a/kernel/src/kernel/Taskflow_Frame_Access.hpp +++ b/kernel/src/kernel/Taskflow_Frame_Access.hpp @@ -1,12 +1,13 @@ #pragma once #include "frame.hpp" #include "Task_Graph.hpp" +#include "ownership.hpp" #include #include namespace aethera::detail { struct Taskflow_Graph_Token { - Render_Frame* frame{}; /* DAG 运行记录所属外部帧;帧完成前有效。 */ + not_null frame; /* DAG 运行记录所属外部帧;帧完成前有效。 */ std::size_t index{}; /* 帧内 graph 记录索引。 */ }; struct Taskflow_Graph_Timing { diff --git a/kernel/src/kernel/double_buffer/Dependency_Graph.hpp b/kernel/src/kernel/double_buffer/Dependency_Graph.hpp index 5527ab6..ea232aa 100644 --- a/kernel/src/kernel/double_buffer/Dependency_Graph.hpp +++ b/kernel/src/kernel/double_buffer/Dependency_Graph.hpp @@ -6,16 +6,16 @@ struct Dependency_Graph { using Error = Dependency_Graph_Error; struct Node { using Bind = void (*)(Node*, Root*); - Root* object; - std::pmr::vector dependencies; + aethera::not_null object; + std::pmr::vector> dependencies; Bind bind; void* data{}; Node(Root* value, std::pmr::memory_resource* resource) : Node(value, resource, nullptr) {} Node(Root* value, std::pmr::memory_resource* resource, Bind value_bind) : object(value), dependencies(resource), bind(value_bind) {} }; struct Edge { - Root* target; - Root* source; + aethera::not_null target; + aethera::not_null source; const void* source_key; const void* target_tag; const void* target_dirty_key; @@ -23,26 +23,26 @@ struct Dependency_Graph { }; private: struct Dirty_Source { - Root* object; + aethera::not_null object; const void* key; bool operator==(const Dirty_Source&) const = default; }; struct Dirty_Source_Hash { std::size_t operator()(const Dirty_Source& value) const { - auto object = std::hash{}(value.object); + auto object = std::hash{}(value.object.get()); auto key = std::hash{}(value.key); return object ^ (key + 0x9e3779b9 + (object << 6) + (object >> 2)); } }; struct Dirty_Target { - Root* object; + aethera::not_null object; const void* tag; const void* dirty_key; }; public: struct View { protected: - const Dependency_Graph* dependency_graph; + aethera::not_null dependency_graph; explicit View(const Dependency_Graph& value) : dependency_graph(&value) {} friend struct Dependency_Graph; public: @@ -86,7 +86,7 @@ public: } [[nodiscard]] Object* object(const Node& node) const { if (!bound(node)) return nullptr; - return static_cast(node.object); + return static_cast(node.object.get()); } [[nodiscard]] Private_Type* private_data(const Node& node) const noexcept { return static_cast(node.data); @@ -100,7 +100,8 @@ public: this->dependency_graph->for_each( [&](const Node& node) { auto* data = private_data(node); - if (data) std::invoke(callback, static_cast(node.object), *data); + if (data) std::invoke( + callback, static_cast(node.object.get()), *data); } ); } @@ -121,7 +122,7 @@ public: template struct Editor : public View { private: - Dependency_Graph* edit_dependency_graph; + aethera::not_null edit_dependency_graph; const void* target_tag; const void* target_dirty_key; bool bind_node_data; @@ -254,23 +255,23 @@ private: friend struct detail::Dependency_Graph_Storage; template friend struct detail::Dependency_Graph_Build_Storage; - std::pmr::memory_resource* pmr_resource; + aethera::not_null pmr_resource; std::pmr::list list; - std::pmr::unordered_map nodes; + std::pmr::unordered_map> nodes; std::pmr::vector edges; std::pmr::unordered_multimap dirty_edges; std::uint64_t structure_revision{}; mutable bool bound{}; - Dependency_Graph* mirror{}; + std::optional> mirror{}; Node* emplace_node(Root* object, Node::Bind bind) { auto current = nodes.find(object); if (current != nodes.end()) { current->second->bind = bind; - return current->second; + return current->second.get(); } - list.emplace_back(object, pmr_resource, bind); + list.emplace_back(object, pmr_resource.get(), bind); auto* result = &list.back(); - nodes.emplace(object, result); + nodes.emplace(object, aethera::not_null{result}); return result; } static bool contains_node_dependency(Node* node, Node* dependency) { @@ -284,17 +285,17 @@ private: edges.begin(), edges.end(), [&](const Edge& edge) { - return edge.target == target && edge.source == source; + return edge.target.get() == target && edge.source.get() == source; } ) != edges.end(); } void rebuild_node_dependencies() { for (auto& node : list) node.dependencies.clear(); for (const auto& edge : edges) { - auto target = nodes.find(edge.target); - auto source = nodes.find(edge.source); + auto target = nodes.find(edge.target.get()); + auto source = nodes.find(edge.source.get()); if (target == nodes.end() || source == nodes.end()) continue; - add_node_dependency(target->second, source->second); + add_node_dependency(target->second.get(), source->second.get()); } } void rebuild_dirty_edges() { @@ -326,7 +327,8 @@ private: const void* source_key, const void* target_tag, const void* target_dirty_key) { - Edge edge{target->object, source->object, source_key, target_tag, target_dirty_key}; + Edge edge{target->object, source->object, source_key, target_tag, + target_dirty_key}; if (std::find(edges.begin(), edges.end(), edge) != edges.end()) return; edges.push_back(edge); ++structure_revision; @@ -349,7 +351,7 @@ private: } void bind_nodes() { for (auto& node : list) { - if (node.bind) node.bind(&node, node.object); + if (node.bind) node.bind(&node, node.object.get()); } } template @@ -358,8 +360,8 @@ private: auto current = nodes.find(object); if (current != nodes.end()) { current->second->bind = bind; - if (bind_node && bind) bind(current->second, object); - return current->second; + if (bind_node && bind) bind(current->second.get(), object); + return current->second.get(); } auto* node = emplace_node(object, bind); if (bind_node && bind) bind(node, object); @@ -378,7 +380,7 @@ private: } void detach_destroyed(Root* object) { remove_node(object); - if (mirror) mirror->remove_node(object); + if (mirror) (*mirror)->remove_node(object); } void reset() { if (list.empty() && edges.empty()) return; @@ -386,7 +388,8 @@ private: ++structure_revision; } void copy_from(const Dependency_Graph& other) { - for (const auto& node : other.list) emplace_node(node.object, node.bind)->data = node.data; + for (const auto& node : other.list) + emplace_node(node.object.get(), node.bind)->data = node.data; edges = other.edges; structure_revision = other.structure_revision; rebuild(); @@ -416,8 +419,8 @@ private: std::erase_if( edges, [&](const Edge& edge) { - return edge.target == target && - edge.source == source && + return edge.target.get() == target && + edge.source.get() == source && edge.source_key == source_key && edge.target_tag == target_tag; } @@ -430,11 +433,11 @@ private: bool remove_node(Root* object) { auto current = nodes.find(object); if (current == nodes.end()) return false; - Node* node = current->second; + Node* node = current->second.get(); std::erase_if( edges, [&](const Edge& edge) { - return edge.target == object || edge.source == object; + return edge.target.get() == object || edge.source.get() == object; } ); nodes.erase(current); @@ -451,7 +454,7 @@ private: void clear_dependencies_impl(Root* target) { auto old_size = edges.size(); std::erase_if(edges, [&](const Edge& edge) { - return edge.target == target; + return edge.target.get() == target; }); if (old_size == edges.size()) return; ++structure_revision; @@ -460,7 +463,7 @@ private: void remove_dependents_impl(Root* source) { auto old_size = edges.size(); std::erase_if(edges, [&](const Edge& edge) { - return edge.source == source; + return edge.source.get() == source; }); if (old_size == edges.size()) return; ++structure_revision; @@ -471,7 +474,7 @@ private: std::erase_if( edges, [&](const Edge& edge) { - return edge.target == object || edge.source == object; + return edge.target.get() == object || edge.source.get() == object; } ); if (old_size == edges.size()) return; @@ -483,7 +486,7 @@ private: std::erase_if( edges, [&](const Edge& edge) { - return edge.target == target && edge.source == source; + return edge.target.get() == target && edge.source.get() == source; } ); if (old_size == edges.size()) return false; @@ -503,7 +506,8 @@ public: nodes(resource), edges(resource), dirty_edges(resource) {} - Dependency_Graph(const Dependency_Graph& other) : Dependency_Graph(other.pmr_resource) { + Dependency_Graph(const Dependency_Graph& other) + : Dependency_Graph(other.pmr_resource.get()) { copy_from(other); } Dependency_Graph& operator=(const Dependency_Graph& other) { @@ -527,8 +531,8 @@ public: swap_data(other); } else { - Dependency_Graph this_value(*this, other.pmr_resource); - Dependency_Graph other_value(other, pmr_resource); + Dependency_Graph this_value(*this, other.pmr_resource.get()); + Dependency_Graph other_value(other, pmr_resource.get()); clear_data(); other.clear_data(); copy_from(other_value); @@ -539,12 +543,14 @@ public: } void bind() const { if (bound) return; - for (const auto& node : list) node.object->bind_dependency_graph(this); + for (const auto& node : list) + node.object->bind_dependency_graph(aethera::not_null{this}); bound = true; } void unbind() const { if (!bound) return; - for (const auto& node : list) node.object->unbind_dependency_graph(this); + for (const auto& node : list) + node.object->unbind_dependency_graph(aethera::not_null{this}); bound = false; } bool empty() const { @@ -554,17 +560,17 @@ public: return list.size(); } std::pmr::memory_resource* memory_resource() const noexcept { - return pmr_resource; + return pmr_resource.get(); } Node* find(Root* object) { auto current = nodes.find(object); if (current == nodes.end()) return nullptr; - return current->second; + return current->second.get(); } const Node* find(Root* object) const { auto current = nodes.find(object); if (current == nodes.end()) return nullptr; - return current->second; + return current->second.get(); } bool contains(Root* object) const { return nodes.contains(object); @@ -575,14 +581,14 @@ public: std::vector dependents(Root* source) { std::vector result; for (auto& node : list) { - if (depends_on(node.object, source)) result.push_back(&node); + if (depends_on(node.object.get(), source)) result.push_back(&node); } return result; } std::vector dependents(Root* source) const { std::vector result; for (const auto& node : list) { - if (depends_on(node.object, source)) result.push_back(&node); + if (depends_on(node.object.get(), source)) result.push_back(&node); } return result; } @@ -590,13 +596,14 @@ public: std::vector result; const auto& values = nodes.at(object)->dependencies; result.reserve(values.size()); - for (const auto* dependency : values) result.push_back(dependency); + for (const auto dependency : values) result.push_back(dependency.get()); return result; } std::vector find_edges(Root* target, Root* source) const { std::vector result; for (const auto& edge : edges) { - if (edge.target == target && edge.source == source) result.push_back(edge); + if (edge.target.get() == target && edge.source.get() == source) + result.push_back(edge); } return result; } @@ -611,7 +618,7 @@ public: visiting, visited }; - std::pmr::unordered_map visits{pmr_resource}; + std::pmr::unordered_map visits{pmr_resource.get()}; std::vector result; result.reserve(list.size()); for (const auto& node : list) visits.emplace(&node, Visit::none); @@ -621,12 +628,13 @@ public: if (current->second == Visit::visited) return {}; if (current->second == Visit::visiting) return std::unexpected(Error::cycle); current->second = Visit::visiting; - for (const auto* dependency : node->dependencies) { - if (dependency == node) return std::unexpected(Error::self_dependency); - auto dependency_state = visits.find(dependency); + for (const auto dependency : node->dependencies) { + if (dependency.get() == node) + return std::unexpected(Error::self_dependency); + auto dependency_state = visits.find(dependency.get()); if (dependency_state == visits.end()) return std::unexpected(Error::missing_dependency); if (dependency_state->second == Visit::visiting) return std::unexpected(Error::cycle); - auto check = self(self, dependency); + auto check = self(self, dependency.get()); if (!check) return std::unexpected(check.error()); } current->second = Visit::visited; @@ -673,9 +681,9 @@ public: }; inline Root::~Root() { while (!dependency_graphs.empty()) { - auto* dependency_graph = dependency_graphs.back(); + const auto dependency_graph = dependency_graphs.back(); dependency_graphs.pop_back(); - const_cast(dependency_graph)->detach_destroyed(this); + const_cast(dependency_graph.get())->detach_destroyed(this); } delete d; } diff --git a/kernel/src/kernel/double_buffer/Dependency_Graph_Storage.hpp b/kernel/src/kernel/double_buffer/Dependency_Graph_Storage.hpp index 1d1b8ea..7d056c9 100644 --- a/kernel/src/kernel/double_buffer/Dependency_Graph_Storage.hpp +++ b/kernel/src/kernel/double_buffer/Dependency_Graph_Storage.hpp @@ -17,11 +17,13 @@ concept Dependency_Graph_Access_Callback_For = Dependency_Graph_List && U inline bool Root::mark_dirty_id(const void* tag, const void* dirty_key) { if (std::find(dirty_tags.begin(), dirty_tags.end(), tag) != dirty_tags.end()) return false; dirty_tags.push_back(tag); - for (const auto* dependency_graph : dependency_graphs) dependency_graph->emit(this, dirty_key); + for (const auto dependency_graph : dependency_graphs) + dependency_graph->emit(this, dirty_key); return true; } inline void Root::emit_dependency(const void* key) { - for (const auto* dependency_graph : dependency_graphs) dependency_graph->emit(this, key); + for (const auto dependency_graph : dependency_graphs) + dependency_graph->emit(this, key); } namespace detail { template diff --git a/kernel/src/kernel/double_buffer/Mpmc_Triple_Buffer.hpp b/kernel/src/kernel/double_buffer/Mpmc_Triple_Buffer.hpp index 4a752a8..3245361 100644 --- a/kernel/src/kernel/double_buffer/Mpmc_Triple_Buffer.hpp +++ b/kernel/src/kernel/double_buffer/Mpmc_Triple_Buffer.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -38,7 +39,7 @@ public: } void advance() { std::unique_lock lock(exchange_lock_); - auto* expired_query = query_; + const auto expired_query = query_; query_ = rendering_; rendering_ = collecting_; collecting_ = expired_query; @@ -123,9 +124,9 @@ private: mutable Queue first_{}; /* First physical MPMC queue; role changes only under exchange_lock_. */ mutable Queue second_{}; /* Second physical MPMC queue; role changes only under exchange_lock_. */ mutable Queue third_{}; /* Third physical MPMC queue; role changes only under exchange_lock_. */ - Queue* collecting_{&first_}; /* Concurrent producer destination until the next exchange. */ - Queue* rendering_{&second_}; /* Immutable-to-producers queue consumed by the renderer. */ - mutable Queue* query_{&third_}; /* Last completed render input exposed to observers. */ + aethera::not_null collecting_{&first_}; /* Concurrent producer destination until the next exchange. */ + aethera::not_null rendering_{&second_}; /* Immutable-to-producers queue consumed by the renderer. */ + mutable aethera::not_null query_{&third_}; /* Last completed render input exposed to observers. */ }; template struct Mpmc_Triple_Buffer_Storage_Set; diff --git a/kernel/src/kernel/double_buffer/mechanism.hpp b/kernel/src/kernel/double_buffer/mechanism.hpp index 765ed20..d97e4e0 100644 --- a/kernel/src/kernel/double_buffer/mechanism.hpp +++ b/kernel/src/kernel/double_buffer/mechanism.hpp @@ -1,5 +1,6 @@ #pragma once #include "Mpmc_Triple_Buffer.hpp" +#include #include #include #include @@ -30,17 +31,17 @@ concept Prop_State = std::default_initializable && std::assignable_from value; public: Pmr() noexcept : value(std::pmr::get_default_resource()) {} explicit Pmr(Resource& resource) noexcept : value(&resource) {} explicit Pmr(Resource* resource) noexcept : value(resource ? resource : std::pmr::get_default_resource()) {} [[nodiscard]] Resource* resource() const noexcept { - return value; + return value.get(); } template std::pmr::polymorphic_allocator allocator() const noexcept { - return std::pmr::polymorphic_allocator{value}; + return std::pmr::polymorphic_allocator{value.get()}; } static Resource* default_resource() noexcept { return std::pmr::get_default_resource(); @@ -57,18 +58,19 @@ struct Pmr_Resource : public std::pmr::memory_resource { private: using Resource = std::pmr::memory_resource; struct Header { - Resource* resource; - void* base; + aethera::not_null resource; + aethera::not_null base; std::size_t bytes; std::size_t alignment; }; - Resource* upstream; + aethera::not_null upstream; void* do_allocate(std::size_t bytes, std::size_t alignment) override { auto effective_alignment = std::max(alignment, alignof(Header)); auto total = bytes + sizeof(Header) + effective_alignment - 1; - auto* resource = upstream; - void* base = resource->allocate(total, effective_alignment); - auto begin = reinterpret_cast(base) + sizeof(Header); + const auto resource = upstream; + const aethera::not_null base{ + resource->allocate(total, effective_alignment)}; + auto begin = reinterpret_cast(base.get()) + sizeof(Header); auto aligned = (begin + effective_alignment - 1) & ~(static_cast(effective_alignment) - 1); auto* header = reinterpret_cast(aligned - sizeof(Header)); std::construct_at(header, Header{resource, base, total, effective_alignment}); @@ -76,12 +78,12 @@ private: } void do_deallocate(void* pointer, std::size_t, std::size_t) override { auto* header = reinterpret_cast(reinterpret_cast(pointer) - sizeof(Header)); - auto* resource = header->resource; - void* base = header->base; + const auto resource = header->resource; + const auto base = header->base; auto bytes = header->bytes; auto alignment = header->alignment; std::destroy_at(header); - resource->deallocate(base, bytes, alignment); + resource->deallocate(base.get(), bytes, alignment); } bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; @@ -93,7 +95,7 @@ public: upstream = resource ? resource : std::pmr::get_default_resource(); } Resource* resource() const noexcept { - return upstream; + return upstream.get(); } }; } @@ -111,8 +113,8 @@ struct Double_Buffer : Pinned { private: std::tuple buf; public: - Value* pending; - Value* current; + aethera::not_null pending; + aethera::not_null current; Double_Buffer() : Double_Buffer(std::allocator_arg, allocator_type{std::pmr::get_default_resource()}) {} Double_Buffer(std::allocator_arg_t, const allocator_type& allocator) : buf(std::allocator_arg, allocator), pending(&std::get < 0 > (buf)), @@ -601,7 +603,7 @@ template requires detail::State_Chain; - State_Type* state; + aethera::not_null state; template requires detail::State_Chain> friend struct State_Access; public: @@ -622,7 +624,7 @@ template requires detail::Prop_Chain; - Prop_Type* prop; + aethera::not_null prop; template requires detail::Prop_Chain> friend struct Prop_Access; public: @@ -642,7 +644,7 @@ Prop_Access(Prop_Type&) -> Prop_Access; template struct Private_Access { private: - Private_Type* private_data; + aethera::not_null private_data; template friend struct Private_Access; public: @@ -752,20 +754,22 @@ private: using Call = void (*)(Root*); Call advance_call{}; detail::Pmr_Resource pmr_resource; - std::pmr::vector dependency_graphs; + std::pmr::vector> dependency_graphs; std::pmr::vector dirty_tags; protected: - Private* d{}; /* Builder::build() 挂接的最终 Private;由 Root 析构释放。 */ + aethera::owner d{}; /* Builder::build() 挂接的最终 Private;由 Root 析构释放。 */ private: void set_pmr_resource(Pmr pmr) noexcept { pmr_resource.set_resource(pmr.resource()); } bool mark_dirty_id(const void* tag, const void* dirty_key); void emit_dependency(const void* key); - void bind_dependency_graph(const Dependency_Graph* dependency_graph) { + void bind_dependency_graph( + aethera::not_null dependency_graph) { if (std::find(dependency_graphs.begin(), dependency_graphs.end(), dependency_graph) == dependency_graphs.end()) dependency_graphs.push_back(dependency_graph); } - void unbind_dependency_graph(const Dependency_Graph* dependency_graph) { + void unbind_dependency_graph( + aethera::not_null dependency_graph) { std::erase(dependency_graphs, dependency_graph); } friend struct Dependency_Graph; diff --git a/kernel/src/kernel/double_buffer/model.hpp b/kernel/src/kernel/double_buffer/model.hpp index 2e4724d..cc98bf1 100644 --- a/kernel/src/kernel/double_buffer/model.hpp +++ b/kernel/src/kernel/double_buffer/model.hpp @@ -215,35 +215,35 @@ private: } public: [[nodiscard]] typename Base::Axis_Range_Type runtime_coordinate_range() const override { - return static_cast(*this).coordinate_range(runtime().object); + return static_cast(*this).coordinate_range(runtime().object.get()); } [[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); + return static_cast(*this).coordinate_to_pixel(runtime().object.get(), 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); + return static_cast(*this).pixel_to_coordinate(runtime().object.get(), 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); + return static_cast(*this).point_to_coordinate(runtime().object.get(), 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); + return static_cast(*this).pixel_sample_count(runtime().object.get(), 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); + return static_cast(*this).tick_step(runtime().object.get(), 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); + return static_cast(*this).tick_label(runtime().object.get(), 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); + return static_cast(*this).sub_tick_count(runtime().object.get(), major_step); } }; @@ -263,15 +263,15 @@ private: } public: [[nodiscard]] std::size_t runtime_time_point_count() const override { - return static_cast(*this).time_point_count(runtime().object); + return static_cast(*this).time_point_count(runtime().object.get()); } 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); + return static_cast(*this).append_time(runtime().object.get(), 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); + return static_cast(*this).tick_to_time(runtime().object.get(), tick); } }; @@ -287,7 +287,7 @@ struct Attached_Private : Runtime_Private_Base, Commit_Double_Buffer< using States = typename Obj::States; /* CRTP 最终 Builder:所有基类流式接口都返回本类型,确保 build() 分派到最派生业务 Builder。 */ using Private = Attached_Private; - Obj* object; + aethera::not_null object; using Allocator = std::pmr::polymorphic_allocator; mutable Lock advance_lock; /* 仅保护全部双缓冲在 advance/publish 边界的交换。 */ Commit_Double_Buffer state; @@ -345,14 +345,18 @@ private: template void before_prop_set(Member Owner::* member) { Prop_Access props{*data().pending}; - auto callback = [&](auto& private_data) { private_data.before_prop_set(object, member, props); }; - Obj::template walk_private(object, callback); + auto callback = [&](auto& private_data) { + private_data.before_prop_set(object.get(), member, props); + }; + Obj::template walk_private(object.get(), callback); } template void after_prop_set(Member Owner::* member) { Prop_Access props{*data().pending}; - auto callback = [&](auto& private_data) { private_data.after_prop_set(object, member, props); }; - Obj::template walk_private(object, callback); + auto callback = [&](auto& private_data) { + private_data.after_prop_set(object.get(), member, props); + }; + Obj::template walk_private(object.get(), callback); } template void emit_prop_dependencies() { @@ -364,17 +368,17 @@ private: void before_state_set(Member Owner::* member) { State_Access pending_states{*data().state.pending}; auto callback = [&](auto& private_data) { - private_data.before_state_set(object, member, pending_states); + private_data.before_state_set(object.get(), member, pending_states); }; - Obj::template walk_private(object, callback); + Obj::template walk_private(object.get(), 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(object, member, pending_states); + private_data.after_state_set(object.get(), member, pending_states); }; - Obj::template walk_private(object, callback); + Obj::template walk_private(object.get(), callback); } /* 同时发布字段级键和字段所属 State Tag 的状态层级键。 */ template @@ -388,28 +392,28 @@ private: State_Access current_states{std::as_const(*data().state.current)}; auto callback = [&](auto& private_data) { private_data.before_advance( - object, - data().pending, + object.get(), + data().pending.get(), pending_states, - data().current, + data().current.get(), current_states ); }; - Obj::template walk_private(object, callback); + Obj::template walk_private(object.get(), 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( - object, - data().pending, + object.get(), + data().pending.get(), pending_states, - data().current, + data().current.get(), current_states ); }; - Obj::template walk_private(object, callback); + Obj::template walk_private(object.get(), callback); } void exchange() { // Prop 与 State 均从外部 pending 提交到内部 current,交换后 pending 同步为最新编辑基线。 @@ -465,13 +469,15 @@ public: template Tag> [[nodiscard]] auto pending_dependency_graph() const { using Bound_Object = detail::Dependency_Graph_Bound_Object; - const Dependency_Graph* graph = data().dependency_graph_storage.template get().pending; + const auto graph = + data().dependency_graph_storage.template get().pending; return graph->typed_view(); } template Tag> [[nodiscard]] auto current_dependency_graph() const { using Bound_Object = detail::Dependency_Graph_Bound_Object; - const Dependency_Graph* graph = data().dependency_graph_storage.template get().current; + const auto graph = + data().dependency_graph_storage.template get().current; return graph->typed_view(); } template ... Tags, detail::Dependency_Graph_Access_Callback_For Callback> diff --git a/kernel/src/kernel/event.hpp b/kernel/src/kernel/event.hpp index fcb2912..bd5127a 100644 --- a/kernel/src/kernel/event.hpp +++ b/kernel/src/kernel/event.hpp @@ -1,9 +1,11 @@ #pragma once #include "frame_statistics.hpp" +#include "ownership.hpp" #include #include #include #include +#include #include namespace aethera { enum struct Event_Type : std::uint8_t { @@ -70,9 +72,10 @@ struct Event { const Event_Timeline_Time occurred_at; /* 控制器消费的原始事件时间,不参与内核排队耗时统计。 */ private: friend struct Scene; - void observe_with(Event_Statistics_Accumulator* accumulator) noexcept; + void observe_with( + not_null accumulator) noexcept; std::uint64_t created_steady_ns_{}; /* 事件对象进入 Scene 前的内核单调时刻。 */ - Event_Statistics_Accumulator* statistics_{}; /* 所属 Scene 的统计器;不拥有且不得跨 Scene。 */ + std::optional> statistics_{}; /* 所属 Scene 的统计器;不拥有且不得跨 Scene。 */ mutable bool accepted{}; /* 处理链是否已经消费事件。 */ mutable std::atomic_uint64_t dispatch_frame_sequence_{}; mutable std::atomic_uint64_t dispatch_started_steady_ns_{}; diff --git a/kernel/src/kernel/event.ipp b/kernel/src/kernel/event.ipp index 9ac1cc2..3e8b870 100644 --- a/kernel/src/kernel/event.ipp +++ b/kernel/src/kernel/event.ipp @@ -24,7 +24,7 @@ inline void Event::mark_dispatch_completed() const noexcept { const auto completed = detail::event_steady_time_ns(); dispatch_completed_steady_ns_.store(completed, std::memory_order_release); if (statistics_) - statistics_->submit( + (*statistics_)->submit( type, created_steady_ns_, dispatch_started_steady_ns_.load(std::memory_order_acquire), completed); @@ -36,7 +36,7 @@ inline Event::Dispatch_Timing Event::dispatch_timing() const noexcept { dispatch_completed_steady_ns_.load(std::memory_order_acquire)}; } inline void Event::observe_with( - Event_Statistics_Accumulator* accumulator) noexcept { + not_null accumulator) noexcept { statistics_ = accumulator; } constexpr Keyboard_Modifier operator|(Keyboard_Modifier left, Keyboard_Modifier right) noexcept { return static_cast(static_cast(left) | static_cast(right)); } diff --git a/kernel/src/kernel/frame.cpp b/kernel/src/kernel/frame.cpp index a087f4b..9df51e8 100644 --- a/kernel/src/kernel/frame.cpp +++ b/kernel/src/kernel/frame.cpp @@ -195,15 +195,17 @@ Taskflow_Frame_Trace Render_Frame::take_taskflow_trace() { }); struct Node_Context { - const Taskflow_Graph_Trace* graph{}; - const Taskflow_Graph_Trace::Node* node{}; + not_null graph; + not_null node; }; std::unordered_map nodes; std::unordered_map native_id_by_node_id; std::unordered_map> children_by_parent_id; for (const auto& graph : result.graphs) { for (const auto& node : graph.nodes) { - nodes.try_emplace(node.native_id, Node_Context{&graph, &node}); + nodes.try_emplace( + node.native_id, + Node_Context{not_null{&graph}, not_null{&node}}); native_id_by_node_id.try_emplace(node.node_id, node.native_id); if (!node.parent_node_id.empty()) children_by_parent_id[node.parent_node_id].push_back(node.native_id); @@ -408,13 +410,12 @@ detail::Taskflow_Graph_Token detail::Taskflow_Frame_Access::begin_graph( graph.submitted_ms = std::chrono::duration( Clock::now() - frame.d->created_at).count(); frame.d->taskflow_graphs.push_back(std::move(graph)); - return {&frame, frame.d->taskflow_graphs.size() - 1}; + return {not_null{&frame}, frame.d->taskflow_graphs.size() - 1}; } void detail::Taskflow_Frame_Access::finish_graph( detail::Taskflow_Graph_Token token, const detail::Taskflow_Graph_Timing& timing) noexcept { - if (!token.frame) return; auto& data = *token.frame->d; const auto elapsed_ms = [&](Clock::time_point value) { return value == Clock::time_point{} ? 0.0 diff --git a/kernel/src/kernel/ownership.hpp b/kernel/src/kernel/ownership.hpp new file mode 100644 index 0000000..295bb1d --- /dev/null +++ b/kernel/src/kernel/ownership.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace aethera { +using gsl::not_null; +using gsl::owner; +} diff --git a/kernel/src/kernel/render_common.cpp b/kernel/src/kernel/render_common.cpp index 71fba57..bc4f23f 100644 --- a/kernel/src/kernel/render_common.cpp +++ b/kernel/src/kernel/render_common.cpp @@ -36,7 +36,8 @@ tf::Taskflow& native_taskflow(Task_Graph& graph) noexcept { return *static_cast( detail::Task_Graph_Access::native_storage(graph)); } -Render_Frame* task_frame(const tf::TaskView& task) noexcept { +std::optional> task_frame( + const tf::TaskView& task) noexcept { /* * Taskflow v4.1.0 ABI: TaskView 的 offset 0 是唯一的 const Node&,Task 的 * offset 0 是唯一的 Node*。这里把同一 Node 转成公开 Task 句柄,只读取 @@ -49,7 +50,10 @@ Render_Frame* task_frame(const tf::TaskView& task) noexcept { auto* node = *reinterpret_cast(std::addressof(task)); tf::Task handle{}; *reinterpret_cast(std::addressof(handle)) = node; - return static_cast(handle.data()); + auto* frame = static_cast(handle.data()); + return frame + ? std::optional>{not_null{frame}} + : std::nullopt; } #if defined(_WIN32) std::uint64_t thread_cpu_ns(HANDLE thread) noexcept { @@ -119,7 +123,7 @@ private: std::uint64_t maximum_segment_ns{}; /* 已结束连续独占片段的最大墙钟。 */ Clock::time_point cooperative_wait_started{}; /* 主动 corun 让出 Worker 的墙钟起点。 */ std::uint64_t cooperative_wait_ns{}; /* 已累计 cooperative wait 墙钟。 */ - Render_Frame* frame{}; /* 进入任务时唯一活动的按帧捕获。 */ + std::optional> frame{}; /* 进入任务时唯一活动的按帧捕获。 */ std::size_t queue_size{}; /* 进入任务时 worker 队列深度。 */ std::size_t queue_capacity{}; /* 进入任务时 worker 队列容量。 */ std::uint64_t native_id{}; /* 嵌套 corun 返回外层任务时恢复其原生身份。 */ @@ -356,7 +360,7 @@ public: record.native_id = static_cast(task.hash_value()); record.type = task.type(); worker_starts.push_back(std::move(record)); - Render_Frame* frame = task_frame(task); + const auto frame = task_frame(task); worker_starts.back().frame = frame; const auto active_depth = worker_state.active_depth.fetch_add( 1, std::memory_order_relaxed) + 1; @@ -430,7 +434,7 @@ public: if (start.frame) { try { trace_task = detail::Taskflow_Frame_Access::append_task( - *start.frame, worker.id(), + **start.frame, worker.id(), static_cast(task.hash_value()), start.queue_size, start.queue_capacity, start.entered, start.started, finished, start.cooperative_wait_ns); @@ -443,7 +447,7 @@ public: if (start.frame) { if (trace_task) detail::Taskflow_Frame_Access::finish_task_observer( - *start.frame, worker.id(), *trace_task, completed); + **start.frame, worker.id(), *trace_task, completed); } if (worker_starts.empty()) { auto busy = static_cast( @@ -662,7 +666,7 @@ struct Task_Resource : Pinned { private: std::unique_ptr executor; std::shared_ptr observer; - std::pmr::memory_resource* memory; + not_null memory; std::atomic_size_t active_taskflows{}; std::atomic_size_t peak_active_taskflows{}; std::atomic_size_t completed_taskflows{}; @@ -790,7 +794,7 @@ public: "Taskflow worker occupation limit must be positive"); std::lock_guard guard(state_mutex); configuration = value; - memory = configuration.pmr.resource(); + memory = not_null{configuration.pmr.resource()}; create_executor(configuration.workers, std::move(worker_interface)); active_taskflows.store(0, std::memory_order_relaxed); peak_active_taskflows.store(0, std::memory_order_relaxed); @@ -798,12 +802,12 @@ public: failed_taskflows.store(0, std::memory_order_relaxed); } std::uint64_t run(Task_Graph& taskflow) { - detail::Task_Graph_Access::bind_frame(taskflow, nullptr); + detail::Task_Graph_Access::bind_frame(taskflow, std::nullopt); return run_synchronous(taskflow, nullptr); } void run(Task_Graph& taskflow, std::function completion) { if (!completion) throw std::invalid_argument("Taskflow completion is empty"); - detail::Task_Graph_Access::bind_frame(taskflow, nullptr); + detail::Task_Graph_Access::bind_frame(taskflow, std::nullopt); run_asynchronous(taskflow, false, [completion = std::move(completion)]( detail::Taskflow_Graph_Timing) mutable { completion(); @@ -820,17 +824,19 @@ public: * 3. 多 worker 会并发附加 trace;Frame 必须按 worker 隔离单写者,不能共享写同一容器。 */ detail::Task_Graph_Access::bind_frame( - taskflow, frame.taskflow_trace_requested() ? &frame : nullptr); + taskflow, frame.taskflow_trace_requested() + ? std::optional>{not_null{&frame}} + : std::nullopt); detail::Taskflow_Graph_Timing timing{}; try { const auto elapsed = run_synchronous(taskflow, &timing); - detail::Task_Graph_Access::bind_frame(taskflow, nullptr); + detail::Task_Graph_Access::bind_frame(taskflow, std::nullopt); timing.observer_unbind_finished = std::chrono::steady_clock::now(); detail::Taskflow_Frame_Access::finish_graph(token, timing); return elapsed; } catch (...) { - detail::Task_Graph_Access::bind_frame(taskflow, nullptr); + detail::Task_Graph_Access::bind_frame(taskflow, std::nullopt); timing.observer_unbind_finished = std::chrono::steady_clock::now(); detail::Taskflow_Frame_Access::finish_graph(token, timing); throw; @@ -843,11 +849,14 @@ public: frame, taskflow, stage); try { detail::Task_Graph_Access::bind_frame( - taskflow, frame.taskflow_trace_requested() ? &frame : nullptr); + taskflow, frame.taskflow_trace_requested() + ? std::optional>{ + not_null{&frame}} + : std::nullopt); run_asynchronous(taskflow, true, [this, &taskflow, &frame, token, completion = std::move(completion)]( detail::Taskflow_Graph_Timing timing) mutable { - detail::Task_Graph_Access::bind_frame(taskflow, nullptr); + detail::Task_Graph_Access::bind_frame(taskflow, std::nullopt); timing.observer_unbind_finished = std::chrono::steady_clock::now(); detail::Taskflow_Frame_Access::finish_graph(token, timing); completion(); @@ -855,7 +864,7 @@ public: } catch (...) { detail::Taskflow_Graph_Timing timing{}; - detail::Task_Graph_Access::bind_frame(taskflow, nullptr); + detail::Task_Graph_Access::bind_frame(taskflow, std::nullopt); timing.observer_unbind_finished = std::chrono::steady_clock::now(); detail::Taskflow_Frame_Access::finish_graph(token, timing); throw; @@ -884,7 +893,7 @@ public: observer->resume_worker(worker->id()); } std::pmr::memory_resource* memory_resource() const noexcept { - return memory; + return memory.get(); } void set_state_callback(std::function callback) { ensure_executor(); diff --git a/kernel/src/kernel/scene.ipp b/kernel/src/kernel/scene.ipp index d3cbd11..9a1d1ea 100644 --- a/kernel/src/kernel/scene.ipp +++ b/kernel/src/kernel/scene.ipp @@ -29,7 +29,11 @@ struct Scene::Private : Prev_Private { void process(Object* object, Callback&& callback) requires std::invocable; /* 派生渲染 Scene 使用:把调用方外部帧贯穿公共 Prepare 阶段并追加诊断标记。 */ template - void process(Object* object, Render_Frame* frame, Callback&& callback) requires std::invocable; + void process( + Object* object, + std::optional> frame, + Callback&& callback) + requires std::invocable; /* 派生 Scene 的 Private 还可覆盖 Def::Private 的四个 State 生命周期 hook,并通过 State_Access::get() 访问状态层。 */ }; struct Scene::Private::Runtime { @@ -60,17 +64,22 @@ std::shared_ptr Scene::make_event(Arguments&&... arguments) { } template void Scene::Private::process(Object* object, Callback&& callback) requires std::invocable { - process(object, static_cast(nullptr), std::forward(callback)); + process(object, std::nullopt, std::forward(callback)); } template -void Scene::Private::process(Object* object, Render_Frame* frame, Callback&& callback) requires std::invocable { - if (frame) frame->mark(Frame_Trace_Marker::prepare_started); +void Scene::Private::process( + Object* object, + std::optional> frame, + Callback&& callback) + requires std::invocable { + if (frame) (*frame)->mark(Frame_Trace_Marker::prepare_started); if (runtime->taskflow && !runtime->taskflow->empty()) - if (frame && frame->taskflow_trace_requested()) - (void)detail::run_taskflow(*runtime->taskflow, *frame, "scene.renderables"); + if (frame && (*frame)->taskflow_trace_requested()) + (void)detail::run_taskflow( + *runtime->taskflow, **frame, "scene.renderables"); else (void)detail::run_taskflow(*runtime->taskflow); - if (frame) frame->mark(Frame_Trace_Marker::prepare_finished); + if (frame) (*frame)->mark(Frame_Trace_Marker::prepare_finished); double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each_bound( [](Renderable* renderable, Renderable::Private& data) { data.publish_renderable_state(renderable); @@ -184,23 +193,28 @@ void Scene::Private::after_advance(Object* object, dependency_graph.for_each( [&](const Dependency_Graph::Node& dependency_node) { if (!dependency_graph.private_data(dependency_node)) return; - auto target = render_tasks.find(dependency_node.object); + auto target = render_tasks.find(dependency_node.object.get()); if (target == render_tasks.end()) return; std::pmr::unordered_set visited{resource}; - std::pmr::vector pending{resource}; - for (const auto* dependency : dependency_node.dependencies) pending.push_back(dependency); + std::pmr::vector> pending{ + resource}; + for (const auto dependency : dependency_node.dependencies) + pending.push_back(dependency); while (!pending.empty()) { - const auto* dependency = pending.back(); + const auto dependency = pending.back(); pending.pop_back(); - if (!visited.insert(dependency->object).second) continue; + if (!visited.insert(dependency->object.get()).second) + continue; if (dependency_graph.private_data(*dependency)) { - auto source = render_tasks.find(dependency->object); + auto source = render_tasks.find( + dependency->object.get()); if (source != render_tasks.end()) { source->second.exit.precede(target->second.entry); } continue; } - for (const auto* next : dependency->dependencies) pending.push_back(next); + for (const auto next : dependency->dependencies) + pending.push_back(next); } } ); diff --git a/mcp/core/runtime/Gallery_Plots.cpp b/mcp/core/runtime/Gallery_Plots.cpp index d6e1200..53548b8 100644 --- a/mcp/core/runtime/Gallery_Plots.cpp +++ b/mcp/core/runtime/Gallery_Plots.cpp @@ -36,11 +36,12 @@ std::span gallery_plot_definitions() noexcept { return definitions; } -const Plot_Definition* find_gallery_plot_definition( - std::string_view id) noexcept { +std::optional> +find_gallery_plot_definition(std::string_view id) noexcept { const auto found = std::ranges::find(definitions, id, &Plot_Definition::id); - return found == definitions.end() ? nullptr : &*found; + if (found == definitions.end()) return std::nullopt; + return not_null{&*found}; } std::string_view plot_dimension_name(Plot_Dimension dimension) noexcept { diff --git a/mcp/core/runtime/Gallery_Plots.hpp b/mcp/core/runtime/Gallery_Plots.hpp index ae0049c..aacb87a 100644 --- a/mcp/core/runtime/Gallery_Plots.hpp +++ b/mcp/core/runtime/Gallery_Plots.hpp @@ -1,6 +1,8 @@ #pragma once #include "Gallery_Plots_2D.hpp" #include "Gallery_Plots_3D.hpp" +#include +#include #include #include @@ -18,8 +20,8 @@ struct Plot_Definition { [[nodiscard]] std::span gallery_plot_definitions() noexcept; -[[nodiscard]] const Plot_Definition* find_gallery_plot_definition( - std::string_view id) noexcept; +[[nodiscard]] std::optional> +find_gallery_plot_definition(std::string_view id) noexcept; [[nodiscard]] std::string_view plot_dimension_name( Plot_Dimension dimension) noexcept; } diff --git a/mcp/core/runtime/Gallery_Plots_2D.cpp b/mcp/core/runtime/Gallery_Plots_2D.cpp index 0e6dc9a..d5a3713 100644 --- a/mcp/core/runtime/Gallery_Plots_2D.cpp +++ b/mcp/core/runtime/Gallery_Plots_2D.cpp @@ -508,16 +508,19 @@ std::unique_ptr make_time_axis( return std::move(result).value(); } template -std::unique_ptr selection_overlay(Horizontal_Axis* horizontal_axis, Vertical_Axis* vertical_axis) { +std::unique_ptr selection_overlay( + not_null horizontal_axis, + not_null vertical_axis) { auto result = Selection_Object::Builder(horizontal_axis, vertical_axis).build(); if (!result) throw std::logic_error("selection overlay axes form an invalid dependency graph"); return std::move(result).value(); } template -void resize_axes(Scene_2D* scene, Size viewport, Axes*... axes) { +void resize_axes(not_null scene, Size viewport, + not_null... axes) { const Size previous_viewport = scene->template read_prop().viewport; if (previous_viewport == viewport || previous_viewport.empty()) return; - const auto resize_axis = [&](auto* axis) { + const auto resize_axis = [&](auto axis) { const auto layout = axis->template read_prop(); const double horizontal_scale = static_cast(viewport.width) / previous_viewport.width; const double vertical_scale = static_cast(viewport.height) / previous_viewport.height; @@ -549,7 +552,10 @@ std::shared_ptr make_axes_plot() { .add_dependency_node(numeric.get()) .add_dependency_node(time.get()) .build(); - auto update = [scene = scene.get(), frequency = frequency.get(), numeric = numeric.get(), time = time.get()](const Plot_Render_Tick& event, bool) { + auto update = [scene = not_null{scene.get()}, + frequency = not_null{frequency.get()}, + numeric = not_null{numeric.get()}, + time = not_null{time.get()}](const Plot_Render_Tick& event, bool) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, frequency, numeric, time); constexpr double day_milliseconds = 86'400'000.0; time->append_time(Time_Of_Day{ @@ -570,7 +576,9 @@ std::shared_ptr make_axes_plot() { generator_number_field("value_min", "数值下界", "数值轴可见坐标下界。", -100.0, -1'000'000'000.0, 1'000'000'000.0), generator_number_field("value_max", "数值上界", "数值轴可见坐标上界,必须大于下界。", 0.0, -1'000'000'000.0, 1'000'000'000.0) }); - auto generate = [frequency = frequency.get(), numeric = numeric.get(), time = time.get()](const Json& input) { + auto generate = [frequency = not_null{frequency.get()}, + numeric = not_null{numeric.get()}, + time = not_null{time.get()}](const Json& input) { try { const auto sample_count = generator_count(input, "time_sample_count"); const auto time_step = generator_count(input, "time_step_ms"); @@ -604,20 +612,25 @@ std::shared_ptr make_spectrum_plot() { constexpr Size canvas{720, 420}; auto frequency = make_frequency_axis(); auto vertical = make_numeric_axis(Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}); - auto spectrum = *Spectrum::Builder(frequency.get(), vertical.get(), 4u) + auto spectrum = *Spectrum::Builder( + not_null{frequency.get()}, not_null{vertical.get()}, 4u) .set(&Spectrum::Prop::frequency_range, Axis_Range{0.0, 100.0}) .set(&Spectrum::Prop::max_hold_visible, true) .build(); - auto selection = selection_overlay(frequency.get(), vertical.get()); + auto selection = selection_overlay( + not_null{frequency.get()}, not_null{vertical.get()}); auto scene = *Scene_2D::Builder() .set(&Render_Scene_2D::Prop::viewport, canvas) .set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255}) .set(&Render_Scene_2D::Prop::view_active, true) - .add_renderable(spectrum.get()) - .add_renderable(selection.get()) + .add_renderable(not_null{spectrum.get()}) + .add_renderable(not_null{selection.get()}) .add_dependency(selection.get(), spectrum.get()) .build(); - auto update = [scene = scene.get(), raw = spectrum.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool demo_data) { + auto update = [scene = not_null{scene.get()}, + raw = not_null{spectrum.get()}, + frequency = not_null{frequency.get()}, + vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, frequency, vertical); if (!demo_data) return; std::array < double, 256 > samples{}; @@ -667,17 +680,22 @@ std::shared_ptr make_frequency_trace_plot() { Axis_Orientation::horizontal, {64.0, 370.0}, 620.0); auto vertical = make_numeric_axis( Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-1.2, 1.2}); - auto trace = *Frequency_Trace::Builder(time.get(), vertical.get(), 4u).build(); - auto selection = selection_overlay(time.get(), vertical.get()); + auto trace = *Frequency_Trace::Builder( + not_null{time.get()}, not_null{vertical.get()}, 4u).build(); + auto selection = selection_overlay( + not_null{time.get()}, not_null{vertical.get()}); auto scene = *Scene_2D::Builder{} .set(&Render_Scene_2D::Prop::viewport, canvas) .set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255}) .set(&Render_Scene_2D::Prop::view_active, true) - .add_renderable(trace.get()) - .add_renderable(selection.get()) + .add_renderable(not_null{trace.get()}) + .add_renderable(not_null{selection.get()}) .add_dependency(selection.get(), trace.get()) .build(); - auto update = [scene = scene.get(), raw = trace.get(), time = time.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool demo_data) { + auto update = [scene = not_null{scene.get()}, + raw = not_null{trace.get()}, + time = not_null{time.get()}, + vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, time, vertical); if (!demo_data) return; constexpr double day_milliseconds = 86'400'000.0; @@ -702,21 +720,26 @@ std::shared_ptr make_sweep_spectrum_plot() { auto frequency = make_frequency_axis(); auto vertical = make_numeric_axis( Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}); - auto sweep = *Sweep_Spectrum::Builder(frequency.get(), vertical.get(), 4u) + auto sweep = *Sweep_Spectrum::Builder( + not_null{frequency.get()}, not_null{vertical.get()}, 4u) .set(&Sweep_Spectrum::Prop::frequency_range, Axis_Range{0.0, 100.0}) .set(&Sweep_Spectrum::Prop::bins_per_block, std::size_t{8}) .set(&Sweep_Spectrum::Prop::block_count, std::size_t{64}) .build(); - auto selection = selection_overlay(frequency.get(), vertical.get()); + auto selection = selection_overlay( + not_null{frequency.get()}, not_null{vertical.get()}); auto scene = *Scene_2D::Builder{} .set(&Render_Scene_2D::Prop::viewport, canvas) .set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255}) .set(&Render_Scene_2D::Prop::view_active, true) - .add_renderable(sweep.get()) - .add_renderable(selection.get()) + .add_renderable(not_null{sweep.get()}) + .add_renderable(not_null{selection.get()}) .add_dependency(selection.get(), sweep.get()) .build(); - auto update = [scene = scene.get(), raw = sweep.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool demo_data) { + auto update = [scene = not_null{scene.get()}, + raw = not_null{sweep.get()}, + frequency = not_null{frequency.get()}, + vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, frequency, vertical); if (!demo_data) return; const auto& state = raw->template read_prop(); @@ -751,21 +774,26 @@ std::shared_ptr make_afterglow_plot() { auto vertical = make_numeric_axis( Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}); auto afterglow = *Afterglow::Builder( - frequency.get(), vertical.get(), Plot_Partition_Grid{4, 3}) + not_null{frequency.get()}, not_null{vertical.get()}, + Plot_Partition_Grid{4, 3}) .set(&Afterglow::Prop::frequency_range, Axis_Range{0.0, 100.0}) .set(&Afterglow::Prop::power_range, Axis_Range{-110.0, 0.0}) .set(&Afterglow::Prop::power_point_size, 96) .build(); - auto selection = selection_overlay(frequency.get(), vertical.get()); + auto selection = selection_overlay( + not_null{frequency.get()}, not_null{vertical.get()}); auto scene = *Scene_2D::Builder{} .set(&Render_Scene_2D::Prop::viewport, canvas) .set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255}) .set(&Render_Scene_2D::Prop::view_active, true) - .add_renderable(afterglow.get()) - .add_renderable(selection.get()) + .add_renderable(not_null{afterglow.get()}) + .add_renderable(not_null{selection.get()}) .add_dependency(selection.get(), afterglow.get()) .build(); - auto update = [scene = scene.get(), raw = afterglow.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool demo_data) { + auto update = [scene = not_null{scene.get()}, + raw = not_null{afterglow.get()}, + frequency = not_null{frequency.get()}, + vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, frequency, vertical); if (!demo_data) return; std::array < double, 192 > values{}; @@ -795,20 +823,25 @@ std::shared_ptr make_waterfall_plot() { auto time = make_time_axis( Axis_Orientation::vertical, {64.0, 370.0}, -320.0); auto waterfall = *Waterfall::Builder( - frequency.get(), time.get(), Plot_Partition_Grid{4, 3}) + not_null{frequency.get()}, not_null{time.get()}, + Plot_Partition_Grid{4, 3}) .set(&Waterfall::Prop::frequency_range, Axis_Range{0.0, 100.0}) .set(&Waterfall::Prop::power_range, Axis_Range{-110.0, 0.0}) .build(); - auto selection = selection_overlay(frequency.get(), time.get()); + auto selection = selection_overlay( + not_null{frequency.get()}, not_null{time.get()}); auto scene = *Scene_2D::Builder{} .set(&Render_Scene_2D::Prop::viewport, canvas) .set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255}) .set(&Render_Scene_2D::Prop::view_active, true) - .add_renderable(waterfall.get()) - .add_renderable(selection.get()) + .add_renderable(not_null{waterfall.get()}) + .add_renderable(not_null{selection.get()}) .add_dependency(selection.get(), waterfall.get()) .build(); - auto update = [scene = scene.get(), raw = waterfall.get(), frequency = frequency.get(), time = time.get()](const Plot_Render_Tick& event, bool demo_data) { + auto update = [scene = not_null{scene.get()}, + raw = not_null{waterfall.get()}, + frequency = not_null{frequency.get()}, + time = not_null{time.get()}](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, frequency, time); if (!demo_data) return; constexpr double day_milliseconds = 86'400'000.0; @@ -849,20 +882,25 @@ std::shared_ptr make_constellation_plot() { auto vertical = make_numeric_axis( Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-1.2, 1.2}); auto constellation = *Constellation_Diagram::Builder( - horizontal.get(), vertical.get(), 4u) + not_null{horizontal.get()}, + not_null{vertical.get()}, 4u) .set(&Constellation_Diagram::Prop::i_range, Axis_Range{-1.2, 1.2}) .set(&Constellation_Diagram::Prop::q_range, Axis_Range{-1.2, 1.2}) .build(); - auto selection = selection_overlay(horizontal.get(), vertical.get()); + auto selection = selection_overlay( + not_null{horizontal.get()}, not_null{vertical.get()}); auto scene = *Scene_2D::Builder{} .set(&Render_Scene_2D::Prop::viewport, canvas) .set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255}) .set(&Render_Scene_2D::Prop::view_active, true) - .add_renderable(constellation.get()) - .add_renderable(selection.get()) + .add_renderable(not_null{constellation.get()}) + .add_renderable(not_null{selection.get()}) .add_dependency(selection.get(), constellation.get()) .build(); - auto update = [scene = scene.get(), raw = constellation.get(), horizontal = horizontal.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool demo_data) { + auto update = [scene = not_null{scene.get()}, + raw = not_null{constellation.get()}, + horizontal = not_null{horizontal.get()}, + vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, horizontal, vertical); if (!demo_data) return; const auto& state = raw->template read_prop(); @@ -904,14 +942,17 @@ std::shared_ptr make_selection_overlay_plot() { Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, {0.0, 100.0}); auto vertical = make_numeric_axis( Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {0.0, 100.0}); - auto selection = *Selection_Rectangle_Overlay::Builder(horizontal.get(), vertical.get()).build(); + auto selection = *Selection_Rectangle_Overlay::Builder( + not_null{horizontal.get()}, not_null{vertical.get()}).build(); auto scene = *Scene_2D::Builder{} .set(&Render_Scene_2D::Prop::viewport, canvas) .set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255}) .set(&Render_Scene_2D::Prop::view_active, true) - .add_renderable(selection.get()) + .add_renderable(not_null{selection.get()}) .build(); - auto update = [scene = scene.get(), horizontal = horizontal.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool) { + auto update = [scene = not_null{scene.get()}, + horizontal = not_null{horizontal.get()}, + vertical = not_null{vertical.get()}](const Plot_Render_Tick& event, bool) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, horizontal, vertical); }; auto view = make_scene_view< diff --git a/mcp/core/runtime/Gallery_Plots_3D.cpp b/mcp/core/runtime/Gallery_Plots_3D.cpp index fb30880..b3f2afe 100644 --- a/mcp/core/runtime/Gallery_Plots_3D.cpp +++ b/mcp/core/runtime/Gallery_Plots_3D.cpp @@ -437,11 +437,15 @@ public: } void update(const Plot_Render_Tick& request) override { if constexpr (requires(Data_Generator& generator, Visual_Object& visual, - Axes_Object& axes, Marker_Object* markers, + Axes_Object& axes, + std::optional> markers, const Plot_Render_Tick& frame) { generator.update(visual, axes, markers, frame); }) { - data_generator_.update(*visual_, *axes_, markers_.get(), request); + const auto markers = markers_ + ? std::optional{not_null{markers_.get()}} + : std::nullopt; + data_generator_.update(*visual_, *axes_, markers, request); } } private: @@ -494,13 +498,13 @@ std::shared_ptr make_visual_plot(std::string label, auto camera = std::move(camera_result).value(); auto axes = std::move(axes_result).value(); auto scene_builder = Scene_3D::Builder{}; - scene_builder.add_camera(camera.get()) - .add_axes(axes.get()) - .add_renderable(visual.get()) + scene_builder.add_camera(not_null{camera.get()}) + .add_axes(not_null{axes.get()}) + .add_renderable(not_null{visual.get()}) .set(&Render_Scene_3D::Prop::viewport, Extent{720, 420}) .set(&Render_Scene_3D::Prop::clear_color, Linear_Color{0.018F, 0.027F, 0.047F, 1.0F}) .set(&Render_Scene_3D::Prop::view_active, true); - if (markers) scene_builder.add_renderable(markers.get()); + if (markers) scene_builder.add_renderable(not_null{markers.get()}); auto scene_result = scene_builder.build(); if (!scene_result) throw std::logic_error("3D Gallery scene dependency graph is invalid"); auto scene = std::move(scene_result).value(); @@ -744,7 +748,7 @@ struct Spectrogram_Data_Generator { } } void update(Mesh_Visual& visual, Axes_3D&, - Marker_Visual* markers, + std::optional> markers, const Plot_Render_Tick& request) { if (!parameters.animation_enabled || request.sequence % parameters.update_every_n_frames != 0) @@ -752,15 +756,15 @@ struct Spectrogram_Data_Generator { const double animation_seconds = request.time_milliseconds / 1000.0 * parameters.animation_speed; auto mesh = spectrogram_mesh(parameters, animation_seconds); visual.template set<&Mesh_Visual::Prop::items>(std::move(mesh)); - if (markers == nullptr) return; - auto items = markers->template read_prop().items; + if (!markers) return; + auto items = (*markers)->template read_prop().items; for (auto& marker : items) { const float time = std::clamp((marker.position.x + 1.0F) * 0.5F, 0.0F, 1.0F); const float frequency = std::clamp((marker.position.y + 1.0F) * 0.5F, 0.0F, 1.0F); marker.position.z = -1.0F + 2.0F * spectrogram_level( time, frequency, animation_seconds, parameters.ridge_count); } - markers->template set<&Marker_Visual::Prop::items>(std::move(items)); + (*markers)->template set<&Marker_Visual::Prop::items>(std::move(items)); } }; } diff --git a/mcp/core/runtime/Plot.cpp b/mcp/core/runtime/Plot.cpp index 0b0d49a..8f8b6af 100644 --- a/mcp/core/runtime/Plot.cpp +++ b/mcp/core/runtime/Plot.cpp @@ -637,11 +637,11 @@ struct Plot::Private { void refresh_schedule(); void clock_tick(const Plot_Render_Tick& tick); void render_frame(Plot_Render_Tick tick); - void publish_completed_frame(Render_Frame* completed); - void consume_completed_frame(Render_Frame* frame); - void retire_completed_frame(Render_Frame* frame); + void publish_completed_frame(not_null completed); + void consume_completed_frame(not_null frame); + void retire_completed_frame(not_null frame); void consume_retired_frames(); - void finalize_retired_frame(Render_Frame* frame); + void finalize_retired_frame(not_null frame); void attach_completion(std::unique_ptr completion); [[nodiscard]] bool mark_taskflow_trace(Render_Frame& frame); [[nodiscard]] bool mark_post_publish_taskflow_trace(); @@ -1095,7 +1095,20 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { record_plot_measurements(output); (*scene_2d)->set<&Render_Scene_2D::Prop::viewport>( Size{static_cast(tick.width), static_cast(tick.height)}); - const auto result = (*scene_2d)->render(&output); + const auto result = (*scene_2d)->render( + &output, + [weak = lifetime](not_null frame) { + if (auto owner = weak.lock()) { + try { + owner->d->publish_completed_frame(frame); + owner->d->consume_completed_frame(frame); + owner->d->retire_completed_frame(frame); + } + catch (...) { + owner->d->fail(std::current_exception()); + } + } + }); if (!result) { with_frame_policy([](auto& policy) { policy.record_scene_rejection(); @@ -1147,24 +1160,25 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { } -void Plot::Private::publish_completed_frame(Render_Frame* completed) { - if (!completed) - throw std::invalid_argument("Plot received a null completed frame"); - Managed_Frame* managed{}; +void Plot::Private::publish_completed_frame( + not_null completed) { + std::optional> managed; for (auto& slot : frame_slots) { - auto* frame = std::visit( - [](const auto& value) -> Render_Frame* { return value.get(); }, + const auto frame = std::visit( + [](const auto& value) -> not_null { + return not_null{value.get()}; + }, slot.frame); - if (frame != completed) continue; + if (frame.get() != completed.get()) continue; managed = &slot; break; } if (!managed) throw std::logic_error("completed frame has no owning Plot policy slot"); - if (managed->state.load(std::memory_order_acquire) != + if ((*managed)->state.load(std::memory_order_acquire) != Frame_State::rendering) throw std::logic_error("completed Plot frame is not rendering"); - auto* frame = completed; + const auto frame = completed; const auto& pacing = pacing_state(); const auto identity = frame->identity(); @@ -1174,7 +1188,7 @@ void Plot::Private::publish_completed_frame(Render_Frame* completed) { std::uint32_t width{}; std::uint32_t height{}; if (auto *frame_2d = - std::get_if>(&managed->frame)) { + std::get_if>(&(*managed)->frame)) { pixel_layout = Plot_Pixel_Layout::bgra8; const auto image = (*frame_2d)->image(); width = static_cast(image.width); @@ -1187,7 +1201,7 @@ void Plot::Private::publish_completed_frame(Render_Frame* completed) { height = static_cast(output.height); } } else { - auto &frame_3d = std::get>(managed->frame); + auto &frame_3d = std::get>((*managed)->frame); rendered_identity = frame_3d->rendered_identity(); const auto extent = frame_3d->extent(); width = extent.width; @@ -1197,7 +1211,7 @@ void Plot::Private::publish_completed_frame(Render_Frame* completed) { } auto pixels = std::make_shared(Plot_Pixel_Frame{ - std::move(pixel_storage), pixel_layout, managed->presentation_time, + std::move(pixel_storage), pixel_layout, (*managed)->presentation_time, identity.sequence, identity.correlation_id, rendered_identity.sequence, rendered_identity.correlation_id, width, height}); @@ -1211,21 +1225,21 @@ void Plot::Private::publish_completed_frame(Render_Frame* completed) { std::chrono::steady_clock::now() - publish_started) .count()))); auto expected = Frame_State::rendering; - if (!managed->state.compare_exchange_strong( + if (!(*managed)->state.compare_exchange_strong( expected, Frame_State::consuming, std::memory_order_acq_rel, std::memory_order_acquire)) throw std::logic_error("Plot frame left rendering before pixel publish"); } -void Plot::Private::consume_completed_frame(Render_Frame* frame) { - if (!frame) - throw std::invalid_argument("Plot received a null completed frame"); - Managed_Frame* managed{}; +void Plot::Private::consume_completed_frame(not_null frame) { + std::optional> managed; for (auto& slot : frame_slots) { - auto* address = std::visit( - [](const auto& value) -> Render_Frame* { return value.get(); }, + const auto address = std::visit( + [](const auto& value) -> not_null { + return not_null{value.get()}; + }, slot.frame); - if (address != frame) continue; + if (address.get() != frame.get()) continue; if (slot.state.load(std::memory_order_acquire) != Frame_State::consuming) throw std::logic_error("completed Plot frame was not published"); managed = &slot; @@ -1239,7 +1253,7 @@ void Plot::Private::consume_completed_frame(Render_Frame* frame) { * 释放 Plot 的 view/update 门,并立刻唤醒 busy 期间保留的 latest tick。 * 之后外接 DAG 仍在当前 Frame trace 内执行,但不会阻塞下一帧渲染。 */ - if (std::holds_alternative>(managed->frame)) + if (std::holds_alternative>((*managed)->frame)) release_render_admission(lifetime); if (post_publish_graph.empty()) return; @@ -1321,15 +1335,15 @@ void Plot::Private::consume_retired_frames() { } } -void Plot::Private::retire_completed_frame(Render_Frame* frame) { - if (!frame) - throw std::invalid_argument("Plot received a null retired frame"); - Managed_Frame* managed{}; +void Plot::Private::retire_completed_frame(not_null frame) { + std::optional> managed; for (auto& slot : frame_slots) { - auto* address = std::visit( - [](const auto& value) -> Render_Frame* { return value.get(); }, + const auto address = std::visit( + [](const auto& value) -> not_null { + return not_null{value.get()}; + }, slot.frame); - if (address != frame) continue; + if (address.get() != frame.get()) continue; managed = &slot; break; } @@ -1338,20 +1352,22 @@ void Plot::Private::retire_completed_frame(Render_Frame* frame) { auto* head = retired_frames.load(std::memory_order_relaxed); do { - managed->retired_next.store(head, std::memory_order_relaxed); + (*managed)->retired_next.store(head, std::memory_order_relaxed); } while (!retired_frames.compare_exchange_weak( - head, managed, std::memory_order_release, + head, managed->get(), std::memory_order_release, std::memory_order_relaxed)); arm_tick_consumer(lifetime); } -void Plot::Private::finalize_retired_frame(Render_Frame* frame) { - Managed_Frame* managed{}; +void Plot::Private::finalize_retired_frame(not_null frame) { + std::optional> managed; for (auto& slot : frame_slots) { - auto* address = std::visit( - [](const auto& value) -> Render_Frame* { return value.get(); }, + const auto address = std::visit( + [](const auto& value) -> not_null { + return not_null{value.get()}; + }, slot.frame); - if (address == frame) { + if (address.get() == frame.get()) { managed = &slot; break; } @@ -1360,11 +1376,11 @@ void Plot::Private::finalize_retired_frame(Render_Frame* frame) { throw std::logic_error("retired frame has no owned Plot slot"); const auto completion_latency = - managed->submitted_at.time_since_epoch().count() == 0 + (*managed)->submitted_at.time_since_epoch().count() == 0 ? std::chrono::steady_clock::duration::zero() - : std::chrono::steady_clock::now() - managed->submitted_at; + : std::chrono::steady_clock::now() - (*managed)->submitted_at; std::optional datoviz_observation; - if (auto* frame_3d = dynamic_cast(frame)) { + if (auto* frame_3d = dynamic_cast(frame.get())) { datoviz_observation = frame_3d->take_datoviz_observation(); if (!datoviz_observation) throw std::logic_error( @@ -1384,8 +1400,8 @@ void Plot::Private::finalize_retired_frame(Render_Frame* frame) { completed_frame_statistics.reset(); applied_statistics_generation = statistics_generation_value; } - managed->statistics = completed_frame_statistics.submit(*frame); - managed->statistics_generation = statistics_generation_value; + (*managed)->statistics = completed_frame_statistics.submit(*frame); + (*managed)->statistics_generation = statistics_generation_value; std::optional captured_trace; nlohmann::json captured_components; @@ -1414,11 +1430,11 @@ void Plot::Private::finalize_retired_frame(Render_Frame* frame) { } auto expected = Frame_State::consuming; - if (!managed->state.compare_exchange_strong( + if (!(*managed)->state.compare_exchange_strong( expected, Frame_State::available, std::memory_order_acq_rel, std::memory_order_acquire)) throw std::logic_error("retired Plot frame is not consuming"); - latest_statistics_frame.store(managed, std::memory_order_release); + latest_statistics_frame.store(managed->get(), std::memory_order_release); /* 物理槽是唯一背压原因;归还任意槽后只解除一次耗尽状态。 */ auto admission = Render_Admission_State::frame_slots_exhausted; @@ -1483,22 +1499,12 @@ void Plot::ensure_started() { .source = Frame_Request_Source::periodic}); } }); - /* Scene 回调只表示借用结束;Frame 的发布、统计和复用始终由本帧策略决定。 */ - if (auto* scene = std::get_if>(&d->scene)) { - (*scene)->set_frame_callback([weak](Frame_2D* frame) { - if (auto owner = weak.lock()) { - try { - owner->d->publish_completed_frame(frame); - owner->d->consume_completed_frame(frame); - owner->d->retire_completed_frame(frame); - } - catch (...) { owner->d->fail(std::current_exception()); } - } - }); - } else { + /* 3D Scene 的提交/完成回调只表示借用阶段变化;Frame 的发布、 + * 统计和复用始终由本帧策略决定。2D 回调与每次 render 直接绑定。 */ + if (std::holds_alternative>(d->scene)) { auto& scene_3d = std::get>(d->scene); scene_3d->set_submitted_frame_callback( - [weak](Frame_3D*, bool) { + [weak](not_null, bool) { if (auto owner = weak.lock()) { try { owner->d->release_render_admission(weak); @@ -1507,7 +1513,7 @@ void Plot::ensure_started() { } }); scene_3d->set_frame_callback( - [weak](Frame_3D* frame) { + [weak](not_null frame) { if (auto owner = weak.lock()) { try { owner->d->publish_completed_frame(frame); @@ -1518,7 +1524,7 @@ void Plot::ensure_started() { } }); } - /* callback 必须先于周期时钟安装,避免首帧在初始化窗口进入 Scene。 */ + /* 3D callback 必须先于周期时钟安装,避免首帧在初始化窗口进入 Scene。 */ d->refresh_schedule(); }); } diff --git a/mcp/core/runtime/Renderable_Adapter.ipp b/mcp/core/runtime/Renderable_Adapter.ipp index 89addc0..61fdebd 100644 --- a/mcp/core/runtime/Renderable_Adapter.ipp +++ b/mcp/core/runtime/Renderable_Adapter.ipp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include #include @@ -43,13 +44,13 @@ public: explicit Renderable_Adapter(Object& value) : object(&value) {} void identify(std::string_view id) { if constexpr (std::derived_from) - double_buffer::detail::Internal_Access::layer(object) + double_buffer::detail::Internal_Access::layer(object.get()) .diagnostic_component_id = id; } private: template friend struct Renderable_Field_Accessor; - Object* object; + not_null object; }; template struct Renderable_Field_Accessor; diff --git a/render_2D/render_2D/base/Frame_2D.cpp b/render_2D/render_2D/base/Frame_2D.cpp index 5221e4f..490dfd1 100644 --- a/render_2D/render_2D/base/Frame_2D.cpp +++ b/render_2D/render_2D/base/Frame_2D.cpp @@ -27,9 +27,8 @@ Image_View Frame_2D::image() const { return d->color.view(); } Pixel_Buffer Frame_2D::output_pixels() const { return d->color.output_pixels(d->output_format); } -Blend2D_Cache& detail::Frame_2D_Access::render_target(Frame_2D* frame) { - if (!frame) - throw std::invalid_argument("Render_Scene_2D requires a non-null external frame"); +Blend2D_Cache& detail::Frame_2D_Access::render_target( + not_null frame) { return frame->d->color; } } diff --git a/render_2D/render_2D/base/Frame_2D.hpp b/render_2D/render_2D/base/Frame_2D.hpp index 8c8402e..5b30fa1 100644 --- a/render_2D/render_2D/base/Frame_2D.hpp +++ b/render_2D/render_2D/base/Frame_2D.hpp @@ -1,13 +1,15 @@ #pragma once #include "../render/Blend2D_Cache.hpp" #include +#include #include #include namespace aethera::render_2d { struct Frame_2D; namespace detail { struct Frame_2D_Access { - [[nodiscard]] static Blend2D_Cache& render_target(Frame_2D* frame); + [[nodiscard]] static Blend2D_Cache& render_target( + not_null frame); }; } struct Frame_2D final : public Render_Frame { diff --git a/render_2D/render_2D/base/Renderable_2D.ipp b/render_2D/render_2D/base/Renderable_2D.ipp index 039cb3a..097c9f8 100644 --- a/render_2D/render_2D/base/Renderable_2D.ipp +++ b/render_2D/render_2D/base/Renderable_2D.ipp @@ -1,16 +1,17 @@ #pragma once +#include #include namespace aethera::render_2d { struct Renderable_2D::Private : Prev_Private { - using Scene_Attach = std::function(Root*)>; - using Cache_Access = Blend2D_Cache* (*)(Root*); - using Cache_Enabled = bool (*)(const Root*); - using Event_Region_Run = Rect_F (*)(const Root*, Size); + using Scene_Attach = std::function(not_null)>; + using Cache_Access = not_null (*)(not_null); + using Cache_Enabled = bool (*)(not_null); + using Event_Region_Run = Rect_F (*)(not_null, Size); Cache_Access pending_cache{}; /* 非空时返回最终对象本轮可写的缓存物理对象。 */ Cache_Enabled cache_enabled{}; /* 读取最终对象当前发布的运行时缓存策略。 */ Event_Region_Run event_region_run{}; /* 计算最终对象当前事件区域的无虚函数入口。 */ - Blend2D_Cache* paint_target{}; /* 仅在 Scene Paint 阶段有效的非拥有绘制目标。 */ - Blend2D_Cache* valid_cache{}; /* 缓存根最近一次完整重绘产生的权威物理缓存。 */ + std::optional> paint_target{}; /* 仅在 Scene Paint 阶段有效的非拥有绘制目标。 */ + std::optional> valid_cache{}; /* 缓存根最近一次完整重绘产生的权威物理缓存。 */ Scene_Attach scene_attach{}; /* Scene Builder 在构造期执行的所属场景及拓扑绑定。 */ /* Paint 实现使用:返回 Scene 为本轮指定的目标;未进入 Paint 阶段属于契约错误。 */ [[nodiscard]] Blend2D_Cache& paint_surface(); @@ -25,7 +26,7 @@ inline bool Renderable_2D::Prop::operator==(const Prop&) const = default; inline bool Renderable_2D::State::operator==(const State&) const = default; inline Blend2D_Cache& Renderable_2D::Private::paint_surface() { if (!paint_target) throw std::logic_error("2D renderable painted without a scene paint target"); - return *paint_target; + return **paint_target; } inline Rect_F Renderable_2D::Private::event_region(const Attached auto*, Size viewport) const { return {0.0, 0.0, static_cast(viewport.width), static_cast(viewport.height)}; @@ -40,17 +41,17 @@ template void Renderable_2D::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); double_buffer::detail::Internal_Access::mark_dirty(object); - this->event_region_run = [](const Root* root, Size viewport) { - const auto* value = static_cast(root); + this->event_region_run = [](not_null root, Size viewport) { + const auto* value = static_cast(root.get()); const auto& private_data = double_buffer::detail::Internal_Access::get(value); return private_data.event_region(value, viewport); }; - this->pending_cache = [](Root* root) { - return &double_buffer::detail::Internal_Access::pending_buffer( - static_cast(root)); + this->pending_cache = [](not_null root) { + return not_null{&double_buffer::detail::Internal_Access::pending_buffer( + static_cast(root.get()))}; }; - this->cache_enabled = [](const Root* root) { - const auto* value = static_cast(root); + this->cache_enabled = [](not_null root) { + const auto* value = static_cast(root.get()); const Renderable_2D::Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer(value); return prop.cache_enabled; diff --git a/render_2D/render_2D/base/Types.hpp b/render_2D/render_2D/base/Types.hpp index eccb7d2..0ed0436 100644 --- a/render_2D/render_2D/base/Types.hpp +++ b/render_2D/render_2D/base/Types.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace aethera::render_2d { /* 缓存依赖标签;具体字段变化通过该图使缓存拥有者失效。 */ struct Render_Cache_Tag {}; @@ -151,13 +152,13 @@ struct Pixel_Buffer { }; /* 只读图像像素视图。 */ struct Image_View { - const std::byte* data{}; /* 首行像素地址;不拥有内存。 */ + std::span data{}; /* 包含行步长填充的只读像素借用。 */ int width{}; /* 图像宽度,单位为像素。 */ int height{}; /* 图像高度,单位为像素。 */ int stride{}; /* 相邻两行起点的字节距离。 */ Pixel_Format format{Pixel_Format::bgra8_premultiplied}; /* 像素存储格式。 */ [[nodiscard]] bool empty() const noexcept { - return data == nullptr || width <= 0 || height <= 0; + return data.empty() || width <= 0 || height <= 0; } }; } diff --git a/render_2D/render_2D/plottable/Afterglow.hpp b/render_2D/render_2D/plottable/Afterglow.hpp index 7dddf5e..57000ca 100644 --- a/render_2D/render_2D/plottable/Afterglow.hpp +++ b/render_2D/render_2D/plottable/Afterglow.hpp @@ -31,12 +31,13 @@ struct Afterglow : Def struct Builder : Prev_Builder { using Base = Prev_Builder; - Builder(Frequency_Object* frequency_axis, Power_Object* power_axis, + Builder(not_null frequency_axis, + not_null power_axis, Plot_Partition_Grid partition_grid = {}); [[nodiscard]] std::expected, Dependency_Graph_Error> build(); private: - Frequency_Object* frequency_axis{}; /* 不拥有的频率轴;生命周期必须覆盖 Afterglow。 */ - Power_Object* power_axis{}; /* 不拥有的功率轴;生命周期必须覆盖 Afterglow。 */ + not_null frequency_axis; /* 不拥有的频率轴;生命周期必须覆盖 Afterglow。 */ + not_null power_axis; /* 不拥有的功率轴;生命周期必须覆盖 Afterglow。 */ }; }; } diff --git a/render_2D/render_2D/plottable/Afterglow.ipp b/render_2D/render_2D/plottable/Afterglow.ipp index 3571db1..370c6db 100644 --- a/render_2D/render_2D/plottable/Afterglow.ipp +++ b/render_2D/render_2D/plottable/Afterglow.ipp @@ -1,4 +1,5 @@ #pragma once +#include #include "common/Curve_Plot.hpp" #include "common/Raster_Plot.hpp" #include @@ -14,12 +15,13 @@ struct Afterglow::Private : Prev_Private { Plot_Ratio maximum{1.0}; /* 本帧强度归一化分母,最小为 1。 */ bool valid{}; /* 轴布局和输入数据是否足以生成色块。 */ }; - Scene_Object* scene{}; /* 不拥有的所属 Scene。 */ - Frequency_Object* frequency_axis{}; /* 不拥有的频率轴。 */ - Power_Object* power_axis{}; /* 不拥有的功率轴。 */ + std::optional> scene{}; /* 不拥有的所属 Scene。 */ + std::optional> frequency_axis{}; /* 不拥有的频率轴。 */ + std::optional> power_axis{}; /* 不拥有的功率轴。 */ Prepared prepared{}; /* 当前权威状态推导出的 Paint 输入。 */ Plot_Partition_Grid graph_partition_grid{}; /* Prepare/Paint 子图当前固化的 framebuffer 列×行网格。 */ - void bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value); + void bind_sources(not_null frequency_axis_value, + not_null power_axis_value); /* CRTP 覆盖:构建累加、归一化和着色三阶段 Prepare 子图。 */ [[nodiscard]] Task_Graph build_graph(Afterglow* object, const Prop& state); [[nodiscard]] bool should_rebuild_graph(Afterglow* object, const Prop& state); @@ -34,8 +36,8 @@ struct Afterglow::Private : Prev_Private { }; template Afterglow::Builder::Builder( - Frequency_Object* frequency_axis_value, - Power_Object* power_axis_value, + not_null frequency_axis_value, + not_null power_axis_value, Plot_Partition_Grid partition_grid) : Base(), frequency_axis(frequency_axis_value), power_axis(power_axis_value) { @@ -48,24 +50,25 @@ std::expected, Dependency_Graph_Error> Afterglow::Builde auto plot = std::move(result).value(); 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 = detail::Internal_Access::layer(object); + private_data.scene_attach = [object = not_null{plot.get()}]( + not_null root) -> std::expected { + const not_null scene{static_cast(root.get())}; + auto& data = detail::Internal_Access::layer(object.get()); data.scene = scene; - auto* frequency_axis = data.frequency_axis; - auto* power_axis = data.power_axis; - return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { - graph.add_dependency(frequency_axis, object); - graph.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); + const auto frequency_axis = data.frequency_axis.value(); + const auto power_axis = data.power_axis.value(); + return detail::Internal_Access::edit_dependency_graph(scene.get(), [&](auto& graph, auto& cache) { + graph.add_dependency(frequency_axis.get(), object.get()); + graph.add_dependency(power_axis.get(), object.get()); + cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object.get(), scene.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object.get(), power_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object.get(), power_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object.get(), power_axis.get()); + cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object.get(), power_axis.get()); }); }; return plot; @@ -115,13 +118,19 @@ inline void Afterglow::Private::prepare_frame(Afterglow* object) { prepared.source_spectra.assign(spectra.begin(), spectra.end()); }); 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 auto& frequency_layout = this->template read_current_prop( + frequency_axis.value().get()); + const auto& power_layout = this->template read_current_prop( + power_axis.value().get()); const std::size_t available = prepared.source_spectra.empty() ? 0 : prepared.source_spectra.back()->size(); const int columns = static_cast(state.frequency_point_size ? std::min(state.frequency_point_size, available) : available); const int rows = static_cast(state.power_point_size ? state.power_point_size : std::max(1.0, std::abs(power_layout.pixel_length))); - const Size canvas = this->template read_current_prop(scene).viewport; - const auto layout = detail::raster_layout(frequency_axis, state.frequency_range, columns, power_axis, state.power_range, rows, frequency_layout.orientation, power_layout.orientation); + const Size canvas = this->template read_current_prop( + scene.value().get()).viewport; + const auto layout = detail::raster_layout( + frequency_axis.value().get(), state.frequency_range, columns, + power_axis.value().get(), state.power_range, rows, + frequency_layout.orientation, power_layout.orientation); if (canvas.empty() || !layout.valid()) { prepared.valid = false; return; @@ -239,7 +248,9 @@ void Afterglow::Private::after_prop_set(Object* object, detail::Internal_Access::mark_dirty(object); } } -inline void Afterglow::Private::bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) { +inline void Afterglow::Private::bind_sources( + not_null frequency_axis_value, + not_null power_axis_value) { frequency_axis = frequency_axis_value; power_axis = power_axis_value; } diff --git a/render_2D/render_2D/plottable/Constellation_Diagram.hpp b/render_2D/render_2D/plottable/Constellation_Diagram.hpp index 125d3b8..429ba1e 100644 --- a/render_2D/render_2D/plottable/Constellation_Diagram.hpp +++ b/render_2D/render_2D/plottable/Constellation_Diagram.hpp @@ -37,12 +37,13 @@ struct Constellation_Diagram : Def struct Builder : Prev_Builder { using Base = Prev_Builder; - Builder(Axis_Object* i_axis, Axis_Object* q_axis, + Builder(not_null i_axis, + not_null q_axis, Plot_Partition_Count partition_count = 1); [[nodiscard]] std::expected, Dependency_Graph_Error> build(); private: - Axis_Object* i_axis{}; /* 不拥有的同相分量轴;生命周期必须覆盖星座图。 */ - Axis_Object* q_axis{}; /* 不拥有的正交分量轴;生命周期必须覆盖星座图。 */ + not_null i_axis; /* 不拥有的同相分量轴;生命周期必须覆盖星座图。 */ + not_null q_axis; /* 不拥有的正交分量轴;生命周期必须覆盖星座图。 */ }; }; } diff --git a/render_2D/render_2D/plottable/Constellation_Diagram.ipp b/render_2D/render_2D/plottable/Constellation_Diagram.ipp index 8d9b934..13fe753 100644 --- a/render_2D/render_2D/plottable/Constellation_Diagram.ipp +++ b/render_2D/render_2D/plottable/Constellation_Diagram.ipp @@ -1,4 +1,5 @@ #pragma once +#include #include "common/Curve_Plot.hpp" #include #include @@ -10,11 +11,12 @@ struct Constellation_Diagram::Private : Prev_Private { Size canvas{}; /* 当前 Scene viewport 的像素尺寸。 */ bool valid{}; /* 两根轴是否正交且画布有效。 */ }; - Scene_Object* scene{}; /* 不拥有的所属 Scene。 */ - Axis_Object* i_axis{}; /* 不拥有的同相分量轴。 */ - Axis_Object* q_axis{}; /* 不拥有的正交分量轴。 */ + std::optional> scene{}; /* 不拥有的所属 Scene。 */ + std::optional> i_axis{}; /* 不拥有的同相分量轴。 */ + std::optional> q_axis{}; /* 不拥有的正交分量轴。 */ Prepared prepared{}; /* 当前权威状态推导出的 Paint 输入。 */ - void bind_sources(Axis_Object* i_axis_value, Axis_Object* q_axis_value); + void bind_sources(not_null i_axis_value, + not_null q_axis_value); /* CRTP 覆盖:从当前点集和轴状态准备画布坐标。 */ void prepare_data(Constellation_Diagram* object); /* CRTP 覆盖:构建锚点前驱与并行接收点 Paint 子图。 */ @@ -32,7 +34,8 @@ struct Constellation_Diagram::Private : Prev_Private { }; template Constellation_Diagram::Builder::Builder( - Axis_Object* i_axis_value, Axis_Object* q_axis_value, + not_null i_axis_value, + not_null q_axis_value, Plot_Partition_Count partition_count) : Base(), i_axis(i_axis_value), q_axis(q_axis_value) { this->prop.partition_count = partition_count; } @@ -43,24 +46,25 @@ std::expected, Dependency_Graph_Error> Constellation_Dia auto plot = std::move(result).value(); 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 = detail::Internal_Access::layer(object); + private_data.scene_attach = [object = not_null{plot.get()}]( + not_null root) -> std::expected { + const not_null scene{static_cast(root.get())}; + auto& data = detail::Internal_Access::layer(object.get()); data.scene = scene; - auto* i_axis = data.i_axis; - auto* q_axis = data.q_axis; - return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { - graph.add_dependency(i_axis, object); - graph.add_dependency(q_axis, object); - cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); - cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, i_axis); - cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, i_axis); - cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, i_axis); - cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, i_axis); - cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, q_axis); - cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, q_axis); - cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, q_axis); - cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, q_axis); + const auto i_axis = data.i_axis.value(); + const auto q_axis = data.q_axis.value(); + return detail::Internal_Access::edit_dependency_graph(scene.get(), [&](auto& graph, auto& cache) { + graph.add_dependency(i_axis.get(), object.get()); + graph.add_dependency(q_axis.get(), object.get()); + cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object.get(), scene.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object.get(), i_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object.get(), i_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object.get(), i_axis.get()); + cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object.get(), i_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object.get(), q_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object.get(), q_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object.get(), q_axis.get()); + cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object.get(), q_axis.get()); }); }; return plot; @@ -73,10 +77,13 @@ inline void Constellation_Diagram::Private::prepare_data(Constellation_Diagram* [&](const Constellation_Point& point) { return current - point.submitted_at_ms <= state.point_lifetime_ms; }); - const auto& i_layout = this->template read_current_prop(i_axis); - const auto& q_layout = this->template read_current_prop(q_axis); + const auto& i_layout = this->template read_current_prop( + i_axis.value().get()); + const auto& q_layout = this->template read_current_prop( + q_axis.value().get()); prepared = {}; - prepared.canvas = this->template read_current_prop(scene).viewport; + prepared.canvas = this->template read_current_prop( + scene.value().get()).viewport; if (prepared.canvas.empty() || i_layout.orientation == q_layout.orientation) { return; } @@ -85,7 +92,9 @@ inline void Constellation_Diagram::Private::prepare_data(Constellation_Diagram* for (const auto& value : points) if (current - value.submitted_at_ms <= state.point_lifetime_ms) prepared.points.push_back(detail::map_plot_point( - i_axis, value.point.x, q_axis, value.point.y, i_layout.orientation)); + i_axis.value().get(), value.point.x, + q_axis.value().get(), value.point.y, + i_layout.orientation)); }); const int count = static_cast(state.type); const Plot_Coordinate center_i = state.i_range.center(); @@ -93,7 +102,10 @@ inline void Constellation_Diagram::Private::prepare_data(Constellation_Diagram* const Plot_Coordinate radius = std::min(state.i_range.size(), state.q_range.size()) * 0.4; for (int index = 0; index < count; ++index) { const Plot_Ratio angle = state.phase_offset_radians + 2.0 * std::numbers::pi * index / count; - 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.anchors.push_back(detail::map_plot_point( + i_axis.value().get(), center_i + std::cos(angle) * radius, + q_axis.value().get(), center_q + std::sin(angle) * radius, + i_layout.orientation)); } prepared.valid = true; detail::Internal_Access::mark_dirty(object); @@ -191,7 +203,9 @@ void Constellation_Diagram::Private::after_prop_set( detail::Internal_Access::mark_dirty(object); } } -inline void Constellation_Diagram::Private::bind_sources(Axis_Object* i_axis_value, Axis_Object* q_axis_value) { +inline void Constellation_Diagram::Private::bind_sources( + not_null i_axis_value, + not_null q_axis_value) { i_axis = i_axis_value; q_axis = q_axis_value; } diff --git a/render_2D/render_2D/plottable/Frequency_Trace.hpp b/render_2D/render_2D/plottable/Frequency_Trace.hpp index e609685..6b44f1c 100644 --- a/render_2D/render_2D/plottable/Frequency_Trace.hpp +++ b/render_2D/render_2D/plottable/Frequency_Trace.hpp @@ -30,12 +30,13 @@ struct Frequency_Trace : Def struct Builder : Prev_Builder { using Base = Prev_Builder; - Builder(Time_Object* time_axis, Value_Object* value_axis, + Builder(not_null time_axis, + not_null value_axis, Plot_Partition_Count partition_count = 1); [[nodiscard]] std::expected, Dependency_Graph_Error> build(); private: - Time_Object* time_axis{}; /* 不拥有的时间轴。 */ - Value_Object* value_axis{}; /* 不拥有的数值轴。 */ + not_null time_axis; /* 不拥有的时间轴。 */ + not_null value_axis; /* 不拥有的数值轴。 */ }; }; } diff --git a/render_2D/render_2D/plottable/Frequency_Trace.ipp b/render_2D/render_2D/plottable/Frequency_Trace.ipp index d07cd54..880acc3 100644 --- a/render_2D/render_2D/plottable/Frequency_Trace.ipp +++ b/render_2D/render_2D/plottable/Frequency_Trace.ipp @@ -1,4 +1,5 @@ #pragma once +#include #include "common/Curve_Plot.hpp" namespace aethera::render_2d { struct Frequency_Trace::Private : Prev_Private { @@ -8,12 +9,13 @@ struct Frequency_Trace::Private : Prev_Private { Size canvas{}; /* 当前颜色层尺寸。 */ bool valid{}; /* 两根轴正交且画布有效。 */ }; - Scene_Object* scene{}; /* 不拥有的所属 Scene。 */ - Time_Object* time_axis{}; /* 不拥有的时间轴。 */ - Value_Object* value_axis{}; /* 不拥有的数值轴。 */ + std::optional> scene{}; /* 不拥有的所属 Scene。 */ + std::optional> time_axis{}; /* 不拥有的时间轴。 */ + std::optional> value_axis{}; /* 不拥有的数值轴。 */ Prepared prepared{}; /* 当前状态和轴状态推导出的 Paint 输入。 */ Plot_Partition_Count graph_partition_count{}; /* 当前 Prepare 子图固化的分块数。 */ - void bind_sources(Time_Object* time_axis_value, Value_Object* value_axis_value); + void bind_sources(not_null time_axis_value, + not_null value_axis_value); /* CRTP 覆盖:按当前样本规模构建分块 Prepare 子图。 */ [[nodiscard]] Task_Graph build_graph(Frequency_Trace* object, const Prop& state); [[nodiscard]] bool should_rebuild_graph(Frequency_Trace* object, const Prop& state); @@ -25,15 +27,57 @@ struct Frequency_Trace::Private : Prev_Private { }; template Frequency_Trace::Builder::Builder( - Time_Object* time_axis_value, Value_Object* value_axis_value, + not_null time_axis_value, + not_null value_axis_value, Plot_Partition_Count partition_count) : Base(), time_axis(time_axis_value), value_axis(value_axis_value) { this->prop.partition_count = partition_count; } 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 = 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& graph, auto& cache) { graph.add_dependency(time_axis, object); graph.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(); + const not_null object{trace.get()}; + auto& private_data = detail::Internal_Access::get(object.get()); + private_data.bind_sources(time_axis, value_axis); + private_data.scene_attach = [object](not_null root) + -> std::expected { + const not_null scene{static_cast(root.get())}; + auto& data = detail::Internal_Access::get(object.get()); + data.scene = scene; + const auto time = data.time_axis.value(); + const auto value = data.value_axis.value(); + return detail::Internal_Access::edit_dependency_graph< + Render_Graph_Tag, Render_Cache_Tag>( + scene.get(), [&](auto& graph, auto& cache) { + graph.add_dependency(time.get(), object.get()); + graph.add_dependency(value.get(), object.get()); + cache.template add_prop_dependency< + &Render_Scene_2D::Prop::viewport>(object.get(), scene.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::position>(object.get(), time.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::pixel_length>(object.get(), time.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::orientation>(object.get(), time.get()); + cache.template add_prop_dependency< + &Time_Axis::Prop::visible_count>(object.get(), time.get()); + cache.template add_prop_dependency< + &Time_Axis::Prop::newest_at_start>(object.get(), time.get()); + cache.template add_dependency<&Time_Axis::State::next_tick>( + object.get(), time.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::position>(object.get(), value.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::pixel_length>(object.get(), value.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::orientation>(object.get(), value.get()); + cache.template add_prop_dependency< + &Numeric_Axis::Prop::coordinate_range>(object.get(), value.get()); + }); + }; + return trace; } inline bool Frequency_Trace::Private::should_rebuild_graph(Frequency_Trace*, const Prop& state) { return graph_partition_count != state.partition_count; } inline Task_Graph Frequency_Trace::Private::build_graph(Frequency_Trace* object, const Prop& state) { @@ -50,15 +94,20 @@ inline Task_Graph Frequency_Trace::Private::build_graph(Frequency_Trace* object, } 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& time_layout = static_cast( + detail::Internal_Access::current_prop(time_axis.value().get())); + const auto& value_layout = static_cast( + detail::Internal_Access::current_prop(value_axis.value().get())); const auto visible_count = static_cast(std::max( - 2, static_cast(detail::Internal_Access::current_prop(time_axis)).visible_count)); + 2, static_cast( + detail::Internal_Access::current_prop( + time_axis.value().get())).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; + prepared.canvas = static_cast( + detail::Internal_Access::current_prop(scene.value().get())).viewport; if (prepared.canvas.empty() || time_layout.orientation == value_layout.orientation) return; detail::Internal_Access::access_rendering_stream(object, [&](std::span samples) { prepared.samples.reserve(samples.size()); @@ -70,12 +119,21 @@ inline void Frequency_Trace::Private::prepare_frame(Frequency_Trace* object, Plo 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 auto& time_layout = static_cast( + detail::Internal_Access::current_prop(time_axis.value().get())); + const auto& value_layout = static_cast( + detail::Internal_Access::current_prop(value_axis.value().get())); + const auto& value_state = static_cast( + detail::Internal_Access::current_prop(value_axis.value().get())); 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); - 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); + const Axis_Range visible_range = detail::Internal_Access::layer( + time_axis.value().get()).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().get(), value_axis.value().get(), + time_layout.orientation, value_layout.orientation); } inline void Frequency_Trace::Private::paint_partition(Frequency_Trace* object, Plot_Partition_Count partition_index) { if (!prepared.valid || partition_index >= prepared.partitions.size()) return; @@ -93,5 +151,10 @@ void Frequency_Trace::Private::after_prop_set( 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; } +inline void Frequency_Trace::Private::bind_sources( + not_null time_axis_value, + not_null 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 cb85bf0..6271ddb 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.cpp @@ -12,8 +12,9 @@ Axis_Rectangle Selection_Rectangle_Overlay::Private::axis_rectangle(Axis_Point f } 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) + auto label = std::visit([coordinate](const auto value) { + return double_buffer::detail::Internal_Access::layer( + value.get()) .runtime_tick_label(coordinate); }, axis); if (!label.empty()) return label; @@ -24,20 +25,23 @@ std::string Selection_Rectangle_Overlay::Private::range_label(const Axis_Source& 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) + return std::visit([](const auto value) { + return double_buffer::detail::Internal_Access::layer( + value.get()) .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) + return std::visit([coordinate](const auto value) { + return double_buffer::detail::Internal_Access::layer( + value.get()) .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) + return std::visit([point](const auto value) { + return double_buffer::detail::Internal_Access::layer( + value.get()) .runtime_point_to_coordinate(point); }, axis); } @@ -45,9 +49,10 @@ Rect_F Selection_Rectangle_Overlay::Private::map_rect(const Axis_Source& horizon 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, + return std::visit([&](const auto horizontal_value, + const auto vertical_value) { + return detail::map_plot_rect(horizontal_value.get(), horizontal_range, + vertical_value.get(), 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 3d1c550..fc01e26 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.hpp +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.hpp @@ -31,10 +31,11 @@ struct Selection_Rectangle_Overlay : Def { using Base = Prev_Builder; template - Builder(Horizontal_Axis* horizontal_axis, Vertical_Axis* vertical_axis); + Builder(not_null horizontal_axis, + not_null vertical_axis); [[nodiscard]] std::expected, Dependency_Graph_Error> build(); private: - std::function initialize{}; /* build 成功后绑定两根最终轴及其 Scene 拓扑。 */ + std::function)> initialize{}; /* build 成功后绑定两根最终轴及其 Scene 拓扑。 */ }; }; } diff --git a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp index e069099..7101bb2 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp @@ -2,20 +2,23 @@ #include "common/Curve_Plot.hpp" #include #include +#include #include namespace aethera::render_2d { struct Selection_Rectangle_Overlay::Private : Prev_Private { - using Axis_Source = std::variant; - Scene_Object* scene{}; /* 不拥有的所属 Scene。 */ - Axis_Source horizontal_axis{}; /* 保留最终类型的水平坐标轴。 */ - Axis_Source vertical_axis{}; /* 保留最终类型的垂直坐标轴。 */ + using Axis_Source = std::variant, + not_null, not_null>; + std::optional> scene{}; /* 不拥有的所属 Scene。 */ + std::optional horizontal_axis{}; /* 保留最终类型的水平坐标轴。 */ + std::optional 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}; /* 允许提交和绘制选择框的最小像素距离。 */ template - void bind_sources(Horizontal_Axis* horizontal_axis_value, Vertical_Axis* vertical_axis_value); + void bind_sources(not_null horizontal_axis_value, + not_null vertical_axis_value); [[nodiscard]] static Axis_Rectangle axis_rectangle(Axis_Point first, Axis_Point second); [[nodiscard]] static std::string range_label(const Axis_Source& axis, Axis_Range range); [[nodiscard]] static Axis_Range coordinate_range(const Axis_Source& axis); @@ -36,7 +39,9 @@ struct Selection_Rectangle_Overlay::Private : Prev_Private { }; template template -Selection_Rectangle_Overlay::Builder::Builder(Horizontal_Axis* horizontal_axis, Vertical_Axis* vertical_axis) : Base() { +Selection_Rectangle_Overlay::Builder::Builder( + not_null horizontal_axis, + not_null vertical_axis) : Base() { static_assert((std::same_as || std::same_as || std::same_as) && @@ -44,19 +49,20 @@ Selection_Rectangle_Overlay::Builder::Builder(Horizontal_Axis* horizonta 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 = detail::Internal_Access::layer(overlay); + initialize = [horizontal_axis, vertical_axis](not_null overlay) { + auto& private_data = detail::Internal_Access::layer(overlay.get()); 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 = detail::Internal_Access::layer(object); + private_data.scene_attach = [object = overlay, horizontal_axis, + vertical_axis](not_null root) + -> std::expected { + const not_null scene{static_cast(root.get())}; + auto& data = detail::Internal_Access::layer(object.get()); data.scene = scene; - return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { - graph.add_dependency(horizontal_axis, object); - graph.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); } }; + return detail::Internal_Access::edit_dependency_graph(scene.get(), [&](auto& graph, auto& cache) { + graph.add_dependency(horizontal_axis.get(), object.get()); + graph.add_dependency(vertical_axis.get(), object.get()); + cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object.get(), scene.get()); + const auto bind_axis = [&](not_null axis) { cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object.get(), axis.get()); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object.get(), axis.get()); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object.get(), axis.get()); if constexpr (std::derived_from) { cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object.get(), axis.get()); cache.template add_prop_dependency<&Numeric_Axis::Prop::precision>(object.get(), axis.get()); cache.template add_prop_dependency<&Numeric_Axis::Prop::locale>(object.get(), axis.get()); } else if constexpr (std::derived_from) { cache.template add_prop_dependency<&Time_Axis::Prop::visible_count>(object.get(), axis.get()); cache.template add_prop_dependency<&Time_Axis::Prop::newest_at_start>(object.get(), axis.get()); cache.template add_prop_dependency<&Time_Axis::Prop::format>(object.get(), axis.get()); cache.template add_dependency<&Time_Axis::State::next_tick>(object.get(), axis.get()); } }; bind_axis(horizontal_axis); bind_axis(vertical_axis); }); }; @@ -73,31 +79,38 @@ std::expected, Dependency_Graph_Error> Selection_Rectang 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; + const Size canvas = this->template read_current_prop< + Render_Scene_2D::Base_Tag>(scene.value().get()).viewport; auto& cache = data.paint_surface(); if (canvas.empty()) return; detail::Painter painter(cache, canvas); - const auto plot_region = map_rect(horizontal_axis, coordinate_range(horizontal_axis), vertical_axis, coordinate_range(vertical_axis)); + const auto& horizontal = horizontal_axis.value(); + const auto& vertical = vertical_axis.value(); + const auto plot_region = map_rect(horizontal, coordinate_range(horizontal), vertical, coordinate_range(vertical)); auto clip = painter.scoped_clip(plot_region); const auto paint_region = [&](const Axis_Rectangle& region) { - const auto rect = map_rect(horizontal_axis, region.horizontal, vertical_axis, region.vertical); + const auto rect = map_rect(horizontal, region.horizontal, vertical, 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); - painter.text({label_position.x, label_position.y + std::max(12.0, state.label_font.size * 1.35)}, "Y " + range_label(vertical_axis, region.vertical), state.label_font, state.label_pen); + painter.text(label_position, "X " + range_label(horizontal, region.horizontal), state.label_font, state.label_pen); + painter.text({label_position.x, label_position.y + std::max(12.0, state.label_font.size * 1.35)}, "Y " + range_label(vertical, region.vertical), state.label_font, state.label_pen); }; for (const auto& region : state.selected_regions) paint_region(region); 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{coordinate_to_pixel(horizontal_axis, drag_current.horizontal), coordinate_to_pixel(vertical_axis, drag_current.vertical)}; + const Point_F current_position{ + coordinate_to_pixel(horizontal_axis.value(), drag_current.horizontal), + coordinate_to_pixel(vertical_axis.value(), drag_current.vertical)}; return std::hypot(current_position.x - press_position.x, current_position.y - press_position.y) >= minimum_drag_distance_pixels; } 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{point_to_coordinate(horizontal_axis, point), point_to_coordinate(vertical_axis, point)}; + const Axis_Point axis_point{ + point_to_coordinate(horizontal_axis.value(), point), + point_to_coordinate(vertical_axis.value(), 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); @@ -118,14 +131,19 @@ inline void Selection_Rectangle_Overlay::Private::handle_event(Selection_Rectang } 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 map_rect(horizontal_axis, coordinate_range(horizontal_axis), vertical_axis, coordinate_range(vertical_axis)); + const auto& horizontal = horizontal_axis.value(); + const auto& vertical = vertical_axis.value(); + return map_rect(horizontal, coordinate_range(horizontal), + vertical, coordinate_range(vertical)); } template 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(); } template -void Selection_Rectangle_Overlay::Private::bind_sources(Horizontal_Axis* horizontal_axis_value, Vertical_Axis* vertical_axis_value) { +void Selection_Rectangle_Overlay::Private::bind_sources( + not_null horizontal_axis_value, + not_null 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 fd5252d..31736ba 100644 --- a/render_2D/render_2D/plottable/Spectrum.hpp +++ b/render_2D/render_2D/plottable/Spectrum.hpp @@ -60,12 +60,13 @@ struct Spectrum : Def struct Builder : Prev_Builder { using Base = Prev_Builder; - Builder(Frequency_Object* frequency_axis, Power_Object* power_axis, + Builder(not_null frequency_axis, + not_null power_axis, Plot_Partition_Count partition_count = 1); [[nodiscard]] std::expected, Dependency_Graph_Error> build(); private: - Frequency_Object* frequency_axis{}; /* 不拥有的频率轴;生命周期必须覆盖 Spectrum。 */ - Power_Object* power_axis{}; /* 不拥有的功率轴;生命周期必须覆盖 Spectrum。 */ + not_null frequency_axis; /* 不拥有的频率轴;生命周期必须覆盖 Spectrum。 */ + not_null power_axis; /* 不拥有的功率轴;生命周期必须覆盖 Spectrum。 */ }; }; } diff --git a/render_2D/render_2D/plottable/Spectrum.ipp b/render_2D/render_2D/plottable/Spectrum.ipp index d811667..6d1e9f3 100644 --- a/render_2D/render_2D/plottable/Spectrum.ipp +++ b/render_2D/render_2D/plottable/Spectrum.ipp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -32,16 +33,17 @@ struct Spectrum::Private : Prev_Private { Size canvas_size{}; /* 所属 Scene viewport 决定的颜色层尺寸。 */ bool valid{}; /* 两根轴与画布是否足以生成绘制数据。 */ }; - Scene_Object* scene{}; /* 不拥有的所属二维 Scene;Builder 已登记状态层依赖。 */ - Frequency_Object* frequency_axis{}; /* 不拥有的频率轴;Prepare 直接读取其当前状态。 */ - Power_Object* power_axis{}; /* 不拥有的功率轴;Prepare 直接读取其当前状态。 */ + std::optional> scene{}; /* 不拥有的所属二维 Scene;Builder 已登记状态层依赖。 */ + std::optional> frequency_axis{}; /* 不拥有的频率轴;Prepare 直接读取其当前状态。 */ + std::optional> power_axis{}; /* 不拥有的功率轴;Prepare 直接读取其当前状态。 */ Prepared prepared{}; /* 当前权威 State、Frame 和轴状态推导出的 Paint 输入。 */ std::vector maxima{}; /* 当前样本历史逐点最大值;只由 Prepare 更新。 */ std::vector minima{}; /* 当前样本历史逐点最小值;只由 Prepare 更新。 */ bool hold_history_reset{}; /* prepare.frame 是否已用当前帧初始化整段保持值。 */ std::size_t graph_partition_count{}; /* Builder 内部绑定 Scene 与两根轴;三个来源都必须比 Spectrum 生命周期更长。 */ - void bind_render_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value); + void bind_render_sources(not_null frequency_axis_value, + not_null power_axis_value); /* CRTP State 钩子:Spectrum 业务状态写入后标记自身 Prepare;其他继承层状态由各自 Private 负责。 */ template void after_prop_set(Object* object, Member Owner::* member, Prop_Access pending_states); @@ -58,8 +60,8 @@ struct Spectrum::Private : Prev_Private { }; template Spectrum::Builder::Builder( - Frequency_Object* frequency_axis_value, - Power_Object* power_axis_value, + not_null frequency_axis_value, + not_null power_axis_value, Plot_Partition_Count partition_count) : Base(), frequency_axis(frequency_axis_value), power_axis(power_axis_value) { @@ -72,24 +74,25 @@ std::expected, Dependency_Graph_Error> Spectrum::Builder auto spectrum = std::move(result).value(); 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 = detail::Internal_Access::layer(object); + private_data.scene_attach = [object = not_null{spectrum.get()}]( + not_null root) -> std::expected { + const not_null scene{static_cast(root.get())}; + auto& data = detail::Internal_Access::layer(object.get()); data.scene = scene; - auto* frequency_axis = data.frequency_axis; - auto* power_axis = data.power_axis; - return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { - graph.add_dependency(frequency_axis, object); - graph.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); + const auto frequency_axis = data.frequency_axis.value(); + const auto power_axis = data.power_axis.value(); + return detail::Internal_Access::edit_dependency_graph(scene.get(), [&](auto& graph, auto& cache) { + graph.add_dependency(frequency_axis.get(), object.get()); + graph.add_dependency(power_axis.get(), object.get()); + cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object.get(), scene.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object.get(), power_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object.get(), power_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object.get(), power_axis.get()); + cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object.get(), power_axis.get()); }); }; return spectrum; @@ -155,6 +158,8 @@ inline Task_Graph Spectrum::Private::build_graph(Spectrum* object, const Prop& s } 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 frequency = frequency_axis.value().get(); + const auto power = power_axis.value().get(); const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); const auto& frame = double_buffer::detail::Internal_Access::current_buffer(object); hold_history_reset = maxima.size() != frame.samples.size(); @@ -162,21 +167,22 @@ inline void Spectrum::Private::prepare_frame(Spectrum* object, std::size_t parti maxima = frame.samples; minima = frame.samples; } - const auto& scene_state = this->template read_current_prop(scene); - 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); + const auto& scene_state = this->template read_current_prop( + scene.value().get()); + const auto& frequency_layout = this->template read_current_prop(frequency); + const auto& power_layout = this->template read_current_prop(power); + const auto& power_state = this->template read_current_prop(power); prepared = {}; prepared.partitions.resize(partition_count); if (frequency_layout.orientation == power_layout.orientation) return; if (scene_state.viewport.empty()) return; prepared.canvas_size = scene_state.viewport; - if (state.sweep_region_visible) prepared.sweep_region = detail::map_plot_rect(frequency_axis, state.sweep_frequency_range, power_axis, power_state.coordinate_range, frequency_layout.orientation); - prepared.markers.push_back({detail::map_plot_point(frequency_axis, state.center_frequency, power_axis, power_state.coordinate_range.origin, frequency_layout.orientation), detail::map_plot_point(frequency_axis, state.center_frequency, power_axis, power_state.coordinate_range.target, frequency_layout.orientation), Marker_Style::middle}); + if (state.sweep_region_visible) prepared.sweep_region = detail::map_plot_rect(frequency, state.sweep_frequency_range, power, power_state.coordinate_range, frequency_layout.orientation); + prepared.markers.push_back({detail::map_plot_point(frequency, state.center_frequency, power, power_state.coordinate_range.origin, frequency_layout.orientation), detail::map_plot_point(frequency, state.center_frequency, power, power_state.coordinate_range.target, frequency_layout.orientation), Marker_Style::middle}); for (std::size_t index = 0; index < state.custom_markers.size(); ++index) { const Spectrum_Frequency frequency = state.custom_markers[index]; const Marker_Style style = static_cast(index) == state.selected_marker ? Marker_Style::selected : Marker_Style::normal; - prepared.markers.push_back({detail::map_plot_point(frequency_axis, frequency, power_axis, power_state.coordinate_range.origin, frequency_layout.orientation), detail::map_plot_point(frequency_axis, frequency, power_axis, power_state.coordinate_range.target, frequency_layout.orientation), style}); + prepared.markers.push_back({detail::map_plot_point(frequency_axis.value().get(), frequency, power, power_state.coordinate_range.origin, frequency_layout.orientation), detail::map_plot_point(frequency_axis.value().get(), frequency, power, power_state.coordinate_range.target, frequency_layout.orientation), style}); } prepared.valid = true; } @@ -221,9 +227,10 @@ inline void Spectrum::Private::prepare_extreme(Spectrum* object, bool maximum) { state.frequency_range.length() * static_cast(index) / denominator; const auto& frequency_layout = this->template read_current_prop< - Abs_Axis::Base_Tag>(frequency_axis); + Abs_Axis::Base_Tag>(frequency_axis.value().get()); prepared.extremes[slot] = Prepared_Extreme{ - detail::map_plot_point(frequency_axis, frequency, power_axis, *iterator, + detail::map_plot_point(frequency_axis.value().get(), frequency, + power_axis.value().get(), *iterator, frequency_layout.orientation), maximum}; } @@ -233,16 +240,18 @@ inline void Spectrum::Private::prepare_partition(Spectrum* object, std::size_t p 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); - const auto& frequency_state = this->template read_current_prop(frequency_axis); - const auto& power_state = this->template read_current_prop(power_axis); + const auto frequency = frequency_axis.value().get(); + const auto power = power_axis.value().get(); + const auto& frequency_layout = this->template read_current_prop(frequency); + const auto& power_layout = this->template read_current_prop(power); + const auto& frequency_state = this->template read_current_prop(frequency); + const auto& power_state = this->template read_current_prop(power); const auto range = detail::curve_partition_range(frame.samples.size(), partition_index, prepared.partitions.size(), state.frequency_range); auto& partition = prepared.partitions[partition_index]; - partition.clip = detail::map_plot_rect(frequency_axis, range.domain, power_axis, power_state.coordinate_range, frequency_layout.orientation); - partition.current = detail::prepare_curve(std::span(frame.samples).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.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); + partition.clip = detail::map_plot_rect(frequency, range.domain, power, power_state.coordinate_range, frequency_layout.orientation); + partition.current = detail::prepare_curve(std::span(frame.samples).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, power, frequency_layout.orientation, power_layout.orientation); + 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, power, 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, power, frequency_layout.orientation, power_layout.orientation); } inline void Spectrum::Private::begin_paint(Spectrum* object) { auto& private_data = double_buffer::detail::Internal_Access::get(object); @@ -303,7 +312,9 @@ void Spectrum::Private::after_prop_set(Object* object, Member Owner::*, detail::Internal_Access::mark_dirty(object); } } -inline void Spectrum::Private::bind_render_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) { +inline void Spectrum::Private::bind_render_sources( + not_null frequency_axis_value, + not_null power_axis_value) { frequency_axis = frequency_axis_value; power_axis = power_axis_value; } diff --git a/render_2D/render_2D/plottable/Sweep_Spectrum.hpp b/render_2D/render_2D/plottable/Sweep_Spectrum.hpp index 7d189dc..6db76fe 100644 --- a/render_2D/render_2D/plottable/Sweep_Spectrum.hpp +++ b/render_2D/render_2D/plottable/Sweep_Spectrum.hpp @@ -42,12 +42,13 @@ struct Sweep_Spectrum : Def struct Builder : Prev_Builder { using Base = Prev_Builder; - Builder(Frequency_Object* frequency_axis, Power_Object* power_axis, + Builder(not_null frequency_axis, + not_null power_axis, Plot_Partition_Count partition_count = 1); [[nodiscard]] std::expected, Dependency_Graph_Error> build(); private: - Frequency_Object* frequency_axis{}; /* 不拥有的频率轴。 */ - Power_Object* power_axis{}; /* 不拥有的功率轴。 */ + not_null frequency_axis; /* 不拥有的频率轴。 */ + not_null power_axis; /* 不拥有的功率轴。 */ }; }; } diff --git a/render_2D/render_2D/plottable/Sweep_Spectrum.ipp b/render_2D/render_2D/plottable/Sweep_Spectrum.ipp index 050ef25..7c636ca 100644 --- a/render_2D/render_2D/plottable/Sweep_Spectrum.ipp +++ b/render_2D/render_2D/plottable/Sweep_Spectrum.ipp @@ -1,4 +1,5 @@ #pragma once +#include #include "common/Curve_Plot.hpp" #include #include @@ -13,9 +14,9 @@ struct Sweep_Spectrum::Private : Prev_Private { Size canvas{}; /* 当前 Scene viewport 的像素尺寸。 */ bool valid{}; /* 两根轴正交、画布及组合折线均有效。 */ }; - Scene_Object* scene{}; /* 不拥有的所属 Scene。 */ - Frequency_Object* frequency_axis{}; /* 不拥有的频率轴。 */ - Power_Object* power_axis{}; /* 不拥有的功率轴。 */ + std::optional> scene{}; /* 不拥有的所属 Scene。 */ + std::optional> frequency_axis{}; /* 不拥有的频率轴。 */ + std::optional> power_axis{}; /* 不拥有的功率轴。 */ /* * 完整扫频的唯一持久状态。每次交换得到一组块更新并原位覆盖相应槽位; * 三缓冲只负责跨线程交付输入组,不承担扫频历史保存职责。 @@ -25,7 +26,8 @@ struct Sweep_Spectrum::Private : Prev_Private { bool has_latest_block{}; Prepared prepared{}; /* 当前块集合推导出的绘制输入。 */ Plot_Partition_Count graph_partition_count{}; /* 当前 Prepare 子图分块数。 */ - void bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value); + void bind_sources(not_null frequency_axis_value, + not_null power_axis_value); /* CRTP 覆盖:按扫描点规模构建分块 Prepare 子图。 */ [[nodiscard]] Task_Graph build_graph(Sweep_Spectrum* object, const Prop& state); [[nodiscard]] bool should_rebuild_graph(Sweep_Spectrum* object, const Prop& state); @@ -38,8 +40,8 @@ struct Sweep_Spectrum::Private : Prev_Private { }; template Sweep_Spectrum::Builder::Builder( - Frequency_Object* frequency_axis_value, - Power_Object* power_axis_value, + not_null frequency_axis_value, + not_null power_axis_value, Plot_Partition_Count partition_count) : Base(), frequency_axis(frequency_axis_value), power_axis(power_axis_value) { @@ -47,8 +49,45 @@ 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 = 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& graph, auto& cache) { graph.add_dependency(frequency_axis, object); graph.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(); + const not_null object{sweep.get()}; + auto& private_data = detail::Internal_Access::get(object.get()); + private_data.bind_sources(frequency_axis, power_axis); + private_data.scene_attach = [object](not_null root) + -> std::expected { + const not_null scene{static_cast(root.get())}; + auto& data = detail::Internal_Access::get(object.get()); + data.scene = scene; + const auto frequency = data.frequency_axis.value(); + const auto power = data.power_axis.value(); + return detail::Internal_Access::edit_dependency_graph< + Render_Graph_Tag, Render_Cache_Tag>( + scene.get(), [&](auto& graph, auto& cache) { + graph.add_dependency(frequency.get(), object.get()); + graph.add_dependency(power.get(), object.get()); + cache.template add_prop_dependency< + &Render_Scene_2D::Prop::viewport>(object.get(), scene.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::position>(object.get(), frequency.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::pixel_length>(object.get(), frequency.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::orientation>(object.get(), frequency.get()); + cache.template add_prop_dependency< + &Numeric_Axis::Prop::coordinate_range>(object.get(), frequency.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::position>(object.get(), power.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::pixel_length>(object.get(), power.get()); + cache.template add_prop_dependency< + &Abs_Axis::Prop::orientation>(object.get(), power.get()); + cache.template add_prop_dependency< + &Numeric_Axis::Prop::coordinate_range>(object.get(), power.get()); + }); + }; + return sweep; } inline bool Sweep_Spectrum::Private::should_rebuild_graph(Sweep_Spectrum*, const Prop& state) { return graph_partition_count != state.partition_count; } inline Task_Graph Sweep_Spectrum::Private::build_graph(Sweep_Spectrum* object, const Prop& state) { @@ -72,7 +111,19 @@ inline Task_Graph Sweep_Spectrum::Private::build_graph(Sweep_Spectrum* object, c return graph; } 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 auto frequency = frequency_axis.value().get(); + const auto power = power_axis.value().get(); + const auto& state = this->template read_current_prop< + Sweep_Spectrum::Base_Tag>(object); + const auto& frequency_layout = this->template read_current_prop< + Abs_Axis::Base_Tag>(frequency); + const auto& power_layout = this->template read_current_prop< + Abs_Axis::Base_Tag>(power); + const auto& power_state = this->template read_current_prop< + Numeric_Axis::Base_Tag>(power); + prepared = {}; + prepared.canvas = this->template read_current_prop< + Render_Scene_2D::Base_Tag>(scene.value().get()).viewport; const std::size_t block_count = std::max(1, state.block_count); if (sweep_blocks.size() != block_count) { sweep_blocks.assign(block_count, {}); @@ -105,11 +156,38 @@ inline void Sweep_Spectrum::Private::prepare_frame(Sweep_Spectrum* object) { const auto domain_target = state.frequency_range.origin + state.frequency_range.length() * domain_progress; const auto latest_frequency = state.frequency_range.origin + state.frequency_range.length() * marker_progress; 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; + prepared.marker_first = detail::map_plot_point( + frequency, latest_frequency, power, power_state.coordinate_range.origin, + frequency_layout.orientation); + prepared.marker_second = detail::map_plot_point( + frequency, latest_frequency, power, power_state.coordinate_range.target, + frequency_layout.orientation); + prepared.valid = true; } 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); + if (!prepared.valid) return; + const auto frequency = frequency_axis.value().get(); + const auto power = power_axis.value().get(); + const auto& state = this->template read_current_prop< + Sweep_Spectrum::Base_Tag>(object); + const auto& frequency_layout = this->template read_current_prop< + Abs_Axis::Base_Tag>(frequency); + const auto& power_layout = this->template read_current_prop< + Abs_Axis::Base_Tag>(power); + const auto& frequency_state = this->template read_current_prop< + Numeric_Axis::Base_Tag>(frequency); + const auto& power_state = this->template read_current_prop< + Numeric_Axis::Base_Tag>(power); + 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, power, frequency_layout.orientation, + power_layout.orientation); } inline void Sweep_Spectrum::Private::paint_partition(Sweep_Spectrum* object, Plot_Partition_Count index) { if (!prepared.valid || index >= prepared.partitions.size()) return; @@ -140,7 +218,12 @@ void Sweep_Spectrum::Private::after_prop_set( 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; } +inline void Sweep_Spectrum::Private::bind_sources( + not_null frequency_axis_value, + not_null power_axis_value) { + frequency_axis = frequency_axis_value; + power_axis = power_axis_value; +} inline std::size_t Sweep_Spectrum::Private::stored_point_count() const { std::size_t count{}; for (const auto& block : sweep_blocks) diff --git a/render_2D/render_2D/plottable/Waterfall.hpp b/render_2D/render_2D/plottable/Waterfall.hpp index b5df90b..e1aacc5 100644 --- a/render_2D/render_2D/plottable/Waterfall.hpp +++ b/render_2D/render_2D/plottable/Waterfall.hpp @@ -42,12 +42,13 @@ struct Waterfall : Def struct Builder : Prev_Builder { using Base = Prev_Builder; - Builder(Frequency_Object* frequency_axis, Time_Object* time_axis, + Builder(not_null frequency_axis, + not_null time_axis, Plot_Partition_Grid partition_grid = {}); [[nodiscard]] std::expected, Dependency_Graph_Error> build(); private: - Frequency_Object* frequency_axis{}; /* 不拥有的频率轴;生命周期必须覆盖 Waterfall。 */ - Time_Object* time_axis{}; /* 不拥有的时间轴;生命周期必须覆盖 Waterfall。 */ + not_null frequency_axis; /* 不拥有的频率轴;生命周期必须覆盖 Waterfall。 */ + not_null time_axis; /* 不拥有的时间轴;生命周期必须覆盖 Waterfall。 */ }; }; } diff --git a/render_2D/render_2D/plottable/Waterfall.ipp b/render_2D/render_2D/plottable/Waterfall.ipp index abdfb07..063ad5c 100644 --- a/render_2D/render_2D/plottable/Waterfall.ipp +++ b/render_2D/render_2D/plottable/Waterfall.ipp @@ -1,4 +1,5 @@ #pragma once +#include #include "common/Curve_Plot.hpp" #include "common/Raster_Plot.hpp" #include @@ -23,13 +24,14 @@ struct Waterfall::Private : Prev_Private { int source_first{}; /* 可视频段在源频谱行中的首列。 */ bool valid{}; /* 轴布局和行数据是否足以生成色块。 */ }; - Scene_Object* scene{}; /* 不拥有的所属 Scene。 */ - Frequency_Object* frequency_axis{}; /* 不拥有的频率轴。 */ - Time_Object* time_axis{}; /* 不拥有的时间轴,同时权威决定保留行数。 */ + std::optional> scene{}; /* 不拥有的所属 Scene。 */ + std::optional> frequency_axis{}; /* 不拥有的频率轴。 */ + std::optional> time_axis{}; /* 不拥有的时间轴,同时权威决定保留行数。 */ Prepared prepared{}; /* 当前权威状态推导出的 Paint 输入。 */ detail::Hover_Tooltip_Runtime tooltip{}; /* 事件侧当前 hover 位置。 */ Plot_Partition_Grid graph_partition_grid{}; /* Prepare/Paint 子图当前固化的 framebuffer 列×行网格。 */ - void bind_sources(Frequency_Object* frequency_axis_value, Time_Object* time_axis_value); + void bind_sources(not_null frequency_axis_value, + not_null time_axis_value); /* CRTP 覆盖:按当前色块工作量构建分块 Prepare 子图。 */ [[nodiscard]] Task_Graph build_graph(Waterfall* object, const Prop& state); [[nodiscard]] bool should_rebuild_graph(Waterfall* object, const Prop& state); @@ -45,7 +47,8 @@ struct Waterfall::Private : Prev_Private { }; template Waterfall::Builder::Builder( - Frequency_Object* frequency_axis_value, Time_Object* time_axis_value, + not_null frequency_axis_value, + not_null time_axis_value, Plot_Partition_Grid partition_grid) : Base(), frequency_axis(frequency_axis_value), time_axis(time_axis_value) { @@ -58,26 +61,27 @@ std::expected, Dependency_Graph_Error> Waterfall::Builde auto plot = std::move(result).value(); 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 = detail::Internal_Access::layer(object); + private_data.scene_attach = [object = not_null{plot.get()}]( + not_null root) -> std::expected { + const not_null scene{static_cast(root.get())}; + auto& data = detail::Internal_Access::layer(object.get()); data.scene = scene; - auto* frequency_axis = data.frequency_axis; - auto* time_axis = data.time_axis; - return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { - graph.add_dependency(frequency_axis, object); - graph.add_dependency(time_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, 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); + const auto frequency_axis = data.frequency_axis.value(); + const auto time_axis = data.time_axis.value(); + return detail::Internal_Access::edit_dependency_graph(scene.get(), [&](auto& graph, auto& cache) { + graph.add_dependency(frequency_axis.get(), object.get()); + graph.add_dependency(time_axis.get(), object.get()); + cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object.get(), scene.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object.get(), frequency_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object.get(), time_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object.get(), time_axis.get()); + cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object.get(), time_axis.get()); + cache.template add_prop_dependency<&Time_Axis::Prop::visible_count>(object.get(), time_axis.get()); + cache.template add_prop_dependency<&Time_Axis::Prop::newest_at_start>(object.get(), time_axis.get()); + cache.template add_dependency<&Time_Axis::State::next_tick>(object.get(), time_axis.get()); }); }; return plot; @@ -115,7 +119,8 @@ inline void Waterfall::Private::prepare_frame(Waterfall* object) { const auto& state = this->template read_current_prop(object); detail::Internal_Access::exchange_stream(object); const auto row_limit = static_cast(std::max( - 2, this->template read_current_prop(time_axis).visible_count)); + 2, this->template read_current_prop( + time_axis.value().get()).visible_count)); detail::Internal_Access::accumulate_stream(object, row_limit); prepared = {}; std::vector> source_rows; @@ -123,9 +128,12 @@ inline void Waterfall::Private::prepare_frame(Waterfall* object) { [&](std::span> rows) { source_rows.assign(rows.begin(), rows.end()); }); - const auto& frequency_layout = this->template read_current_prop(frequency_axis); - const auto& time_layout = this->template read_current_prop(time_axis); - prepared.canvas = this->template read_current_prop(scene).viewport; + const auto& frequency_layout = this->template read_current_prop( + frequency_axis.value().get()); + const auto& time_layout = this->template read_current_prop( + time_axis.value().get()); + prepared.canvas = this->template read_current_prop( + scene.value().get()).viewport; if (source_rows.empty()) { return; } @@ -136,14 +144,21 @@ inline void Waterfall::Private::prepare_frame(Waterfall* object) { 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, - detail::Internal_Access::layer(frequency_axis).runtime_coordinate_range(), + detail::Internal_Access::layer( + frequency_axis.value().get()).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 = 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); + const int time_slots = std::max( + 2, this->template read_current_prop( + time_axis.value().get()).visible_count); + const Axis_Range time_range = detail::Internal_Access::layer( + time_axis.value().get()).runtime_coordinate_range(); + prepared.layout = detail::raster_layout( + frequency_axis.value().get(), selection->range, selection->count(), + time_axis.value().get(), time_range, time_slots, + frequency_layout.orientation, time_layout.orientation); if (prepared.canvas.empty() || !prepared.layout.valid()) { return; } @@ -168,7 +183,8 @@ inline void Waterfall::Private::prepare_frame(Waterfall* object) { if (state.tooltip_enabled && tooltip.active && prepared.layout.target.contains(tooltip.position)) { std::ostringstream text; text << std::fixed << std::setprecision(2) - << detail::Internal_Access::layer(frequency_axis) + << detail::Internal_Access::layer( + frequency_axis.value().get()) .runtime_point_to_coordinate(tooltip.position) << " Hz"; prepared.tooltip_text = text.str(); @@ -266,7 +282,9 @@ void Waterfall::Private::after_prop_set(Object* object, detail::Internal_Access::mark_dirty(object); } } -inline void Waterfall::Private::bind_sources(Frequency_Object* frequency_axis_value, Time_Object* time_axis_value) { +inline void Waterfall::Private::bind_sources( + not_null frequency_axis_value, + not_null time_axis_value) { frequency_axis = frequency_axis_value; time_axis = time_axis_value; } diff --git a/render_2D/render_2D/render/Blend2D_Cache.cpp b/render_2D/render_2D/render/Blend2D_Cache.cpp index dbd0a07..d90efb3 100644 --- a/render_2D/render_2D/render/Blend2D_Cache.cpp +++ b/render_2D/render_2D/render/Blend2D_Cache.cpp @@ -115,7 +115,7 @@ Size Blend2D_Cache::size() const noexcept { } Image_View Blend2D_Cache::view() const { if (d->image_size.empty()) return {}; - return {d->pixels.data(), d->image_size.width, d->image_size.height, + return {d->pixels, d->image_size.width, d->image_size.height, static_cast(d->row_stride()), Pixel_Format::bgra8_premultiplied}; } Pixel_Buffer Blend2D_Cache::output_pixels(Pixel_Format format) const { @@ -127,14 +127,16 @@ Pixel_Buffer Blend2D_Cache::output_pixels(Pixel_Format format) const { if (format == Pixel_Format::bgra8_premultiplied) { for (int y = 0; y < source.height; ++y) std::memcpy(result.bytes.data() + static_cast(y) * row_size, - source.data + static_cast(y) * source.stride, + source.data.data() + + static_cast(y) * source.stride, row_size); return result; } if (format != Pixel_Format::rgba8) throw std::invalid_argument("unsupported 2D output pixel format"); if (Private::rgba_converter().convert_rect( - result.bytes.data(), static_cast(row_size), source.data, + result.bytes.data(), static_cast(row_size), + source.data.data(), source.stride, static_cast(source.width), static_cast(source.height)) != BL_SUCCESS) throw std::runtime_error("Blend2D failed to convert PRGB32 to RGBA8"); @@ -304,12 +306,13 @@ Painter::Clip_Scope::Clip_Scope(Painter& owner, Rect_F rect) { } Painter::Clip_Scope::~Clip_Scope() { if (!painter) return; - painter->d->context.restore(); + (*painter)->d->context.restore(); /* restore 会回滚绘制状态;同步失效本地样式缓存,避免后续错误跳过状态设置。 */ - painter->d->fill_color.reset(); - painter->d->stroke_pen.reset(); + (*painter)->d->fill_color.reset(); + (*painter)->d->stroke_pen.reset(); } -Painter::Clip_Scope::Clip_Scope(Clip_Scope&& other) noexcept : painter(std::exchange(other.painter, nullptr)) {} +Painter::Clip_Scope::Clip_Scope(Clip_Scope&& other) noexcept + : painter(std::exchange(other.painter, {})) {} Painter::operator bool() const noexcept { return d->active; } Painter::Clip_Scope Painter::scoped_clip(Rect_F rect) { return Clip_Scope(*this, rect); } void Painter::clear() { if (d->active) d->context.clear_all(); } diff --git a/render_2D/render_2D/render/Blend2D_Cache.hpp b/render_2D/render_2D/render/Blend2D_Cache.hpp index 568cdcb..ea052ae 100644 --- a/render_2D/render_2D/render/Blend2D_Cache.hpp +++ b/render_2D/render_2D/render/Blend2D_Cache.hpp @@ -1,7 +1,9 @@ #pragma once #include "../base/Types.hpp" #include +#include #include +#include #include #include namespace aethera::render_2d { @@ -53,7 +55,7 @@ struct Painter { Clip_Scope(Clip_Scope&& other) noexcept; Clip_Scope& operator=(Clip_Scope&& other) = delete; private: - Painter* painter{}; /* 非空时析构恢复对应 Painter 的裁剪状态。 */ + std::optional> painter{}; /* 非空时析构恢复对应 Painter 的裁剪状态。 */ }; Painter(Blend2D_Cache& cache, Size size); /* diff --git a/render_2D/render_2D/scene/Render_Scene_2D.cpp b/render_2D/render_2D/scene/Render_Scene_2D.cpp index 5894360..a5cbb8b 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.cpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.cpp @@ -3,11 +3,10 @@ namespace aethera::render_2d { Render_Scene_2D::Private::~Private() = default; bool Render_Scene_2D::Prop::operator==(const Prop&) const = default; -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)); +Render_Scene_2D::Render_Result_Type Render_Scene_2D::render( + not_null frame, Frame_Callback callback) { + return double_buffer::detail::Internal_Access::get(this).render( + this, frame, std::move(callback)); } void Render_Scene_2D::activate_view() { double_buffer::detail::Internal_Access::get(this).set_view_active(this, true); diff --git a/render_2D/render_2D/scene/Render_Scene_2D.hpp b/render_2D/render_2D/scene/Render_Scene_2D.hpp index 2a96196..7adcb07 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.hpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.hpp @@ -3,6 +3,7 @@ #include "../base/Renderable_2D.hpp" #include "../render/Blend2D_Cache.hpp" #include +#include #include #include #include @@ -25,17 +26,18 @@ struct Render_Scene_2D : Def; Builder(); template requires std::derived_from - Final_Builder& add_renderable(Renderable_Object* renderable); + Final_Builder& add_renderable(not_null renderable); [[nodiscard]] std::expected, Dependency_Graph_Error> build(); private: - std::vector(Object*)>> attachments{}; /* 仅在 build 期间绑定已构造 Renderable。 */ + std::vector( + not_null)>> attachments{}; /* 仅在 build 期间绑定已构造 Renderable。 */ }; enum struct Render_Result { frame_in_flight, view_inactive, empty_viewport }; - using Frame_Callback = std::function; + using Frame_Callback = std::function)>; using Render_Result_Type = std::expected; using Render_Frame_Type = Frame_2D; - [[nodiscard]] Render_Result_Type render(Frame_2D* frame); - void set_frame_callback(Frame_Callback callback); + [[nodiscard]] Render_Result_Type render( + not_null frame, Frame_Callback callback); /* * 向调用方拥有的 Frame 合成一次。Scene 只借用传入地址;全部绘制、 * completion Taskflow 和 Trace 收口并清除借用后,唯一回调通知调用方。 diff --git a/render_2D/render_2D/scene/Render_Scene_2D.ipp b/render_2D/render_2D/scene/Render_Scene_2D.ipp index 0c64192..3c1c897 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.ipp +++ b/render_2D/render_2D/scene/Render_Scene_2D.ipp @@ -1,6 +1,7 @@ #pragma once #include "../event/Event_Routing_Rules.hpp" #include +#include #include #include #include @@ -11,10 +12,12 @@ Render_Scene_2D::Builder::Builder() : Base() {} template template requires std::derived_from -typename Render_Scene_2D::Builder::Final_Builder& Render_Scene_2D::Builder::add_renderable(Renderable_Object* renderable) { - this->template add_dependency_node(renderable); - attachments.emplace_back([renderable](Object* scene) { - auto& data = Base::private_access(renderable).template get(); +typename Render_Scene_2D::Builder::Final_Builder& Render_Scene_2D::Builder::add_renderable( + not_null renderable) { + this->template add_dependency_node(renderable.get()); + attachments.emplace_back([renderable](not_null scene) { + auto& data = Base::private_access(renderable.get()) + .template get(); if (!data.scene_attach) throw std::logic_error("2D renderable has no scene attachment contract"); auto attach = std::move(data.scene_attach); @@ -46,26 +49,25 @@ std::expected, Dependency_Graph_Error> Render_Scene_2D:: } struct Render_Scene_2D::Private : Prev_Private { struct Paint_Node { - Root* object{}; /* Paint 拓扑位置对应的最终对象;Scene 不拥有。 */ - Renderable_2D_Base::Private* private_data{}; /* 对象的二维能力层;对象存活期间有效。 */ - Root* cache_owner{}; /* 所属缓存根;空值表示直接绘制最终帧。 */ + not_null object; /* Paint 拓扑位置对应的最终对象;Scene 不拥有。 */ + not_null private_data; /* 对象的二维能力层;对象存活期间有效。 */ + std::optional> cache_owner{}; /* 所属缓存根;空值表示直接绘制最终帧。 */ bool paint_requested{}; /* Scene 按本轮组级失效结果计算的执行许可。 */ }; struct Cache_Group { - Root* owner{}; /* 显式选择缓存的二维 Renderable 根。 */ - std::vector members{}; /* 本缓存覆盖的根及无冲突后继节点。 */ + not_null owner; /* 显式选择缓存的二维 Renderable 根。 */ + std::vector> members{}; /* 本缓存覆盖的根及无冲突后继节点。 */ bool rebuild{}; /* 本轮是否因任一成员失效而整体重绘。 */ }; struct Event_Target { - Renderable_2D_Base* object{}; /* 当前 Paint 图中的事件候选对象;Scene 不拥有。 */ - Renderable_2D_Base::Private* private_data{}; /* 候选对象的二维能力层;仅在本次 Prepare 分发期间有效。 */ + not_null object; /* 当前 Paint 图中的事件候选对象;Scene 不拥有。 */ + not_null private_data; /* 候选对象的二维能力层;仅在本次 Prepare 分发期间有效。 */ }; std::atomic_bool frame_in_flight{}; /* Scene 当前是否仍借用一个外部 Frame。 */ - std::atomic> frame_callback{}; /* Scene 结束借用后的唯一完成通知。 */ std::unique_ptr frame_taskflow{}; ~Private(); - Frame_2D* active_frame{}; /* 异步帧 DAG 借用的外部帧;完成回调前保持存活。 */ - Blend2D_Cache* frame_target{}; /* 当前异步帧的最终颜色层;帧 DAG 完成后清空。 */ + std::optional> active_frame{}; /* 异步帧 DAG 借用的外部帧;完成回调前保持存活。 */ + std::optional> frame_target{}; /* 当前异步帧的最终颜色层;帧 DAG 完成后清空。 */ /* 最终 Private 实现:Prepare 完成后按 Paint 图拓扑边并行绘制并合成颜色层。 */ std::vector paint_order{}; /* Paint 图当前拓扑序及 Scene 缓存分组结果。 */ std::vector cache_groups{}; /* 仅保存显式缓存根对应的执行分组。 */ @@ -80,9 +82,9 @@ struct Render_Scene_2D::Private : Prev_Private { void dispatch_events(Object* object, Size viewport, std::uint64_t frame_sequence); template - [[nodiscard]] std::expected render(Object* object, - Frame_2D* frame); - template void set_frame_callback(Object* object, Frame_Callback callback); + [[nodiscard]] std::expected render( + Object* object, not_null frame, + Frame_Callback callback); template void set_view_active(Object* object, bool active); template void reset_diagnostics(Object* object); /* CRTP 覆盖:Builder 挂接最终 Private 后安装二维 Scene 的无虚函数业务分派。 */ @@ -103,7 +105,7 @@ void Render_Scene_2D::Private::after_advance( const bool enabled = node.private_data->cache_enabled && node.private_data->cache_enabled(node.object); if (enabled == (node.cache_owner == node.object)) continue; - node.private_data->valid_cache = nullptr; + node.private_data->valid_cache.reset(); rebuild = true; } if (!rebuild) return; @@ -112,27 +114,28 @@ void Render_Scene_2D::Private::after_advance( double_buffer::detail::Internal_Access::current_dependency_graph(object); paint_order.clear(); cache_groups.clear(); - std::unordered_map cache_owner_by_object; + std::unordered_map>> + cache_owner_by_object; std::unordered_map cache_group_by_owner; const auto order_result = dependencies.for_each_topological_view( [&](const auto& view, const Dependency_Graph::Node& node) { auto* base_data = view.private_data(node); if (!base_data) return; - auto* data = - static_cast(base_data); - Root* cache_owner{}; + const not_null data{ + static_cast(base_data)}; + std::optional> cache_owner{}; if (data->cache_enabled && data->cache_enabled(node.object)) { cache_owner = node.object; } else { bool conflict{}; - for (const auto* dependency : node.dependencies) { + for (const auto dependency : node.dependencies) { if (!view.private_data(*dependency)) continue; const auto current = - cache_owner_by_object.find(dependency->object); - Root* dependency_owner = + cache_owner_by_object.find(dependency->object.get()); + const auto dependency_owner = current == cache_owner_by_object.end() - ? nullptr + ? std::optional>{} : current->second; if (!dependency_owner || (cache_owner && cache_owner != dependency_owner)) { @@ -141,15 +144,16 @@ void Render_Scene_2D::Private::after_advance( } cache_owner = dependency_owner; } - if (conflict) cache_owner = nullptr; + if (conflict) cache_owner.reset(); } - cache_owner_by_object.emplace(node.object, cache_owner); + cache_owner_by_object.emplace(node.object.get(), cache_owner); paint_order.push_back( Paint_Node{node.object, data, cache_owner, false}); if (!cache_owner) return; auto [group, inserted] = - cache_group_by_owner.emplace(cache_owner, cache_groups.size()); - if (inserted) cache_groups.push_back(Cache_Group{cache_owner}); + cache_group_by_owner.emplace( + cache_owner->get(), cache_groups.size()); + if (inserted) cache_groups.push_back(Cache_Group{*cache_owner}); cache_groups[group->second].members.push_back(node.object); }); if (!order_result) @@ -162,24 +166,27 @@ void Render_Scene_2D::Private::prepare_paint_targets(Object*, Blend2D_Cache& fra const auto owner_node = std::find_if(paint_order.begin(), paint_order.end(), [&](const Paint_Node& value) { return value.object == group.owner; }); if (owner_node == paint_order.end() || !owner_node->private_data->pending_cache) throw std::logic_error("2D cache group lost its cache owner capability"); - group.rebuild = owner_node->private_data->valid_cache == nullptr; - for (Root* member : group.members) { + group.rebuild = !owner_node->private_data->valid_cache; + for (const auto 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 || - double_buffer::detail::Internal_Access::dirty(member); + double_buffer::detail::Internal_Access::dirty( + member.get()); } if (group.rebuild) { - auto* target = owner_node->private_data->pending_cache(group.owner); + const not_null target{ + owner_node->private_data->pending_cache(group.owner)}; target->ensure_size(viewport); target->clear(); - for (Root* member : group.members) { - double_buffer::detail::Internal_Access::mark_dirty(member); + for (const auto member : group.members) { + double_buffer::detail::Internal_Access::mark_dirty( + member.get()); 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; } } } else { - for (Root* member : group.members) { + for (const auto 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()) { node->private_data->paint_target = owner_node->private_data->valid_cache; @@ -192,7 +199,8 @@ 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; - double_buffer::detail::Internal_Access::mark_dirty(node.object); + double_buffer::detail::Internal_Access::mark_dirty( + node.object.get()); } } template @@ -205,8 +213,9 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { graph.describe("owner_kind", "scene") .describe("owner_component", "scene"); 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"); + if (!active_frame) + throw std::logic_error("2D frame DAG lost its active frame"); + const auto frame = *active_frame; const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer< Render_Scene_2D::Base_Tag>(object); @@ -218,10 +227,10 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { current_dependency_graph(object).for_each( [](const Dependency_Graph::Node& node) { if (!double_buffer::detail::Internal_Access:: - take_dirty(node.object)) + take_dirty(node.object.get())) return; double_buffer::detail::Internal_Access:: - mark_dirty(node.object); + mark_dirty(node.object.get()); }); auto& state = static_cast( double_buffer::detail::Internal_Access::pending_state(object)); @@ -233,28 +242,28 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { auto prepare_frame_target = graph.add( "scene.frame_target", [this, object] { - auto* frame = active_frame; - if (!frame || !frame_target) + if (!active_frame || !frame_target) throw std::logic_error("2D frame target lost its frame"); + const auto frame = *active_frame; const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer< Render_Scene_2D::Base_Tag>(object); frame->mark(Frame_Trace_Marker::paint_frame_target_started); - frame_target->ensure_size(prop.viewport); - frame_target->clear(); + (*frame_target)->ensure_size(prop.viewport); + (*frame_target)->clear(); frame->mark(Frame_Trace_Marker::paint_frame_target_finished); }); auto paint_background = graph.add( "scene.background", [this, object] { - auto* frame = active_frame; - if (!frame || !frame_target) + if (!active_frame || !frame_target) throw std::logic_error("2D background lost its frame target"); + const auto frame = *active_frame; const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer< Render_Scene_2D::Base_Tag>(object); frame->mark(Frame_Trace_Marker::paint_background_started); { - detail::Painter painter(*frame_target, prop.viewport); + detail::Painter painter(**frame_target, prop.viewport); painter.rect( {0.0, 0.0, static_cast(prop.viewport.width), static_cast(prop.viewport.height)}, @@ -265,22 +274,22 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { }); auto prepare_cache_targets = graph.add( "scene.cache_targets", [this, object] { - auto* frame = active_frame; - if (!frame || !frame_target) + if (!active_frame || !frame_target) throw std::logic_error("2D cache setup lost its frame"); + const auto frame = *active_frame; const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer< Render_Scene_2D::Base_Tag>(object); frame->mark(Frame_Trace_Marker::paint_cache_targets_started); - prepare_paint_targets(object, *frame_target, prop.viewport); + prepare_paint_targets(object, **frame_target, prop.viewport); frame->mark(Frame_Trace_Marker::paint_cache_targets_finished); }); auto render_ready = graph.add("scene.render.ready", [this] { if (!active_frame) throw std::logic_error("2D render lost its active frame"); - active_frame->mark(Frame_Trace_Marker::prepare_finished); - active_frame->mark(Frame_Trace_Marker::paint_started); - active_frame->mark(Frame_Trace_Marker::paint_taskflow_started); + (*active_frame)->mark(Frame_Trace_Marker::prepare_finished); + (*active_frame)->mark(Frame_Trace_Marker::paint_started); + (*active_frame)->mark(Frame_Trace_Marker::paint_taskflow_started); }); auto renderables = graph.compose("scene.renderables", *runtime->taskflow); renderables.describe("dimension", "2D").describe("backend", "Blend2D"); @@ -298,16 +307,16 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { owner->private_data->valid_cache = owner->private_data->paint_target; if (owner->private_data->valid_cache) - frame_target->composite(*owner->private_data->valid_cache); + (*frame_target)->composite(**owner->private_data->valid_cache); } }); auto finish = graph.add("scene.render.complete", [this, object] { auto& state = static_cast( double_buffer::detail::Internal_Access::pending_state(object)); state.event_statistics = event_statistics.state(); - active_frame->mark(Frame_Trace_Marker::paint_taskflow_finished); - active_frame->mark(Frame_Trace_Marker::paint_finished); - active_frame->mark(Frame_Trace_Marker::scene_render_finished); + (*active_frame)->mark(Frame_Trace_Marker::paint_taskflow_finished); + (*active_frame)->mark(Frame_Trace_Marker::paint_finished); + (*active_frame)->mark(Frame_Trace_Marker::scene_render_finished); }); auto completion = graph.compose("scene.completion", completion_graph); completion.describe("owner", "scene").describe("stage", "frame consumers"); @@ -324,13 +333,11 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { } template std::expected -Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { - if (!frame) - throw std::invalid_argument( - "Render_Scene_2D requires a non-null external frame"); +Render_Scene_2D::Private::render( + Object* object, not_null frame, + Frame_Callback callback) { frame->mark(Frame_Trace_Marker::scene_render_entered); - const auto callback = frame_callback.load(std::memory_order_acquire); - if (!callback || !*callback) + if (!callback) throw std::logic_error( "Render_Scene_2D requires a frame callback before render"); bool available{}; @@ -363,7 +370,7 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { return std::unexpected(Render_Result::empty_viewport); } ensure_frame_taskflow(object); - active_frame = frame; + active_frame = frame.get(); } catch (...) { release_admission(); @@ -372,7 +379,8 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { frame->mark(Frame_Trace_Marker::scene_render_requested); const bool trace_started = aethera::detail::begin_taskflow_trace(*frame); try { - auto completion = [this, object, frame, callback, + auto completion = [this, object, frame, + callback = std::move(callback), trace_started]() mutable { /* * render_2d.frame 的原生 Taskflow 已经完成。先发布 Scene 自身状态、 @@ -388,10 +396,10 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { frame->mark(Frame_Trace_Marker::frame_ready); if (trace_started) aethera::detail::finish_taskflow_trace(*frame); - active_frame = nullptr; - frame_target = nullptr; + active_frame.reset(); + frame_target.reset(); frame_in_flight.store(false, std::memory_order_release); - (*callback)(frame); + callback(frame); }; if (frame->taskflow_trace_requested()) aethera::detail::run_taskflow( @@ -402,8 +410,8 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { catch (...) { if (trace_started) aethera::detail::finish_taskflow_trace(*frame); - active_frame = nullptr; - frame_target = nullptr; + active_frame.reset(); + frame_target.reset(); frame_in_flight.store(false, std::memory_order_release); throw; } @@ -470,14 +478,6 @@ void Render_Scene_2D::Private::reset_diagnostics(Object* object) { }); } template -void Render_Scene_2D::Private::set_frame_callback(Object*, Frame_Callback callback) { - frame_callback.store( - callback - ? std::make_shared(std::move(callback)) - : std::shared_ptr{}, - std::memory_order_release); -} -template void Render_Scene_2D::Private::set_view_active(Object* object, bool active) { double_buffer::detail::Internal_Access::set<&Prop::view_active>(object, active); } diff --git a/render_2D/tests/Async_Render_Contract_Test.cpp b/render_2D/tests/Async_Render_Contract_Test.cpp index 99dab29..14a66c9 100644 --- a/render_2D/tests/Async_Render_Contract_Test.cpp +++ b/render_2D/tests/Async_Render_Contract_Test.cpp @@ -17,7 +17,7 @@ using namespace aethera; using namespace aethera::render_2d; template -Object* build_object(Arguments&&... arguments) { +owner build_object(Arguments&&... arguments) { typename Object::template Builder builder(std::forward(arguments)...); auto result = builder.build(); if (!result) std::_Exit(2); @@ -27,7 +27,7 @@ Object* build_object(Arguments&&... arguments) { bool contains_color(Image_View view) { for (int y = 0; y < view.height; ++y) { const auto* row = reinterpret_cast( - view.data + static_cast(y) * view.stride); + view.data.data() + static_cast(y) * view.stride); for (int x = 0; x < view.width; ++x) if (row[x] != 0) return true; } @@ -48,15 +48,16 @@ int main() { initialize_runtime({.workers = 4}); auto* frequency = build_object(); auto* power = build_object(); - auto* spectrum = build_object(frequency, power, 4u); + auto* spectrum = build_object( + not_null{frequency}, not_null{power}, 4u); auto* time = build_object