From 2b8f3ed0e760380b870ffddc04e9cc7ddf805115 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Thu, 20 Aug 2026 11:55:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=95=E5=85=A5=E5=AD=90=E5=9B=BE=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + kernel/src/kernel/base/core.hpp | 175 ++++++++- kernel/src/kernel/base/object.hpp | 76 +++- kernel/src/kernel/base/rely.hpp | 103 ++++-- kernel/src/kernel/base/rely_storage.hpp | 52 +-- kernel/src/kernel/render_2D.cpp | 361 ++++++++++++++---- kernel/src/kernel/render_2D.hpp | 463 ++++++++++++++---------- 7 files changed, 906 insertions(+), 326 deletions(-) diff --git a/.gitignore b/.gitignore index af92971..987cdc0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /render_2D/old/ /render_3D/old/ +/webapp_gallery/node_modules/ +/webapp_gallery/dist/ diff --git a/kernel/src/kernel/base/core.hpp b/kernel/src/kernel/base/core.hpp index e839172..e076f3e 100644 --- a/kernel/src/kernel/base/core.hpp +++ b/kernel/src/kernel/base/core.hpp @@ -122,14 +122,24 @@ public: std::swap(pending, current); } }; +namespace detail { +struct State_Root {}; +} +template +struct State_Type : Prev { + using Tag_Type = Tag; + using Prev_State = Prev; +}; template struct Tagged_Buffer { using Tag_Type = Tag; using Value_Type = T; }; -template +struct Root; +template struct Rely_Type { using Tag_Type = Tag; + using Object_Type = Object; private: std::atomic_flag clean = ATOMIC_FLAG_INIT; protected: @@ -163,17 +173,42 @@ template struct Is_Tagged_Buffer> : std::true_type {}; template struct Is_Rely_Type : std::false_type {}; -template -struct Is_Rely_Type> : std::true_type {}; +template +struct Is_Rely_Type> : std::true_type {}; +template +struct Is_State_Type : std::false_type {}; +template +struct Is_State_Type> : std::true_type {}; template concept Buffer_Type = Is_Tagged_Buffer::value; template concept Rely_Mechanism = Is_Rely_Type::value; template +concept State_Mechanism = Is_State_Type::value; +template concept Mechanism_Type = requires { - requires Buffer_Type || Rely_Mechanism; + requires Buffer_Type || Rely_Mechanism || State_Mechanism; }; template +concept Tagged_State = Prop_State && requires { + typename T::Tag_Type; + typename T::Prev_State; + requires std::derived_from>; +}; +template +struct State_Layers { + using Type = std::tuple<>; +}; +template +struct State_Layers { + using Type = decltype(std::tuple_cat( + std::declval>(), + std::declval::Type>() + )); +}; +template +using State_Layers_T = typename State_Layers::Type; +template struct Member_Pointer_Traits; template struct Member_Pointer_Traits { @@ -197,6 +232,8 @@ template using Mechanism_Buffer_Tuple = std::conditional_t, std::tuple, std::tuple<>>; template using Mechanism_Rely_Tuple = std::conditional_t, std::tuple, std::tuple<>>; +template +using Mechanism_State_Tuple = std::conditional_t, std::tuple, std::tuple<>>; template struct Has_Tag : std::false_type {}; template @@ -220,7 +257,7 @@ template struct Is_Rely_List : std::false_type {}; template struct Is_Rely_List> : Tagged_List_Check< - (Rely_Mechanism && ...), + (Rely_Mechanism && ...) && (std::derived_from && ...), Relies... > {}; template @@ -237,6 +274,122 @@ concept Rely_Tag_In = requires { requires Rely_List; requires Has_Tag::value; }; +template +struct Rely_Type_By_Tag; +template +struct Rely_Type_By_Tag> { +private: + template + struct Find { + using Type = std::conditional_t< + std::same_as, + First, + typename Find::Type + >; + }; + template + struct Find { + using Type = Last; + }; +public: + using Type = typename Find::Type; +}; +template requires Rely_Tag_In +using Rely_Declaration = typename Rely_Type_By_Tag::Type; +template requires Rely_Tag_In +using Rely_Bound_Object = typename Rely_Declaration::Object_Type; +template +struct Is_State_List : std::false_type {}; +template +struct Is_State_List> : Tagged_List_Check< + (Tagged_State && ...), + States... +> {}; +template +concept State_List = Is_State_List::value; +template +concept State_Chain = Tagged_State && State_List>; +template +concept State_Tag_In = requires { + requires State_List; + requires Has_Tag::value; +}; +template +concept State_Chain_Matches = State_Chain && State_List && [] { + using Layers = State_Layers_T; + if constexpr (std::tuple_size_v != std::tuple_size_v) return false; + else { + return [](std::index_sequence) { + return (std::same_as< + typename std::tuple_element_t::Tag_Type, + typename std::tuple_element_t - I - 1, Tuple>::Tag_Type + > && ...); + }(std::make_index_sequence>{}); + } +}(); +template +struct Rebind_State; +template +struct Rebind_State, Prev> { + using Type = State_Type; +}; +template +using Rebind_State_T = typename Rebind_State::Type; +template +consteval std::size_t state_tag_index() { + std::size_t result{}; + std::size_t current{}; + ((std::same_as ? result = current : result, ++current), ...); + return result; +} +template +struct State_By_Tag; +template +struct State_By_Tag { +private: + using Layers = State_Layers_T; + template + static consteval std::size_t index(std::index_sequence) { + std::size_t result{}; + ((void)(std::same_as::Tag_Type> ? result = I : result), ...); + return result; + } +public: + using Type = std::tuple_element_t>{}), Layers>; +}; +template +using State_Value = typename State_By_Tag::Type; +template +concept State_Callback_For = State_Tag_In && State_Chain_Matches && std::invocable&>; +template +struct State_Callback_Storage_Impl; +template +struct State_Callback_Storage_Impl> { +private: + std::tuple...> callbacks; +public: + template requires + (std::same_as || ...) + void set(Callback&& callback) { + constexpr auto index = state_tag_index(); + using State = std::tuple_element_t>; + std::get(callbacks) = std::function{std::forward(callback)}; + } + template requires (std::same_as || ...) + void clear() { + constexpr auto index = state_tag_index(); + std::get(callbacks) = {}; + } + template requires (std::same_as || ...) + void notify(const State& state) { + constexpr auto index = state_tag_index(); + using Layer = std::tuple_element_t>; + auto& callback = std::get(callbacks); + if (callback) callback(static_cast(state)); + } +}; +template +using State_Callback_Storage = State_Callback_Storage_Impl>; template consteval std::size_t tag_index() { std::size_t result{}; @@ -307,7 +460,6 @@ constexpr const void* rely_id() { return &rely_token; } } -struct Root; struct Rely; template concept Root_Derived = std::derived_from; @@ -315,8 +467,10 @@ template concept Object_Core = requires { requires Root_Derived; typename T::Private; + typename T::States; requires Prop_State; requires Prop_State; + requires detail::State_Chain_Matches; requires detail::Buffer_List; requires detail::Rely_List; }; @@ -331,11 +485,13 @@ enum class Rely_Error { missing_dependency, cycle }; +struct Root_State_Tag {}; struct Root { using Buffers = std::tuple<>; using Relies = std::tuple<>; + using States = std::tuple>; struct Prop {}; - struct State { + struct State : State_Type { bool operator==(const State&) const = default; }; private: @@ -416,6 +572,11 @@ concept Rely_Object = requires { requires Prop_State; requires Buffer_List; }; +template +concept Bound_Rely_Object = Rely_Object && requires(T* object) { + typename T::Private; + object->d; +}; template concept State_Rely_Source = requires { requires Rely_Object; diff --git a/kernel/src/kernel/base/object.hpp b/kernel/src/kernel/base/object.hpp index 792906f..c0c3325 100644 --- a/kernel/src/kernel/base/object.hpp +++ b/kernel/src/kernel/base/object.hpp @@ -7,13 +7,18 @@ concept Object = requires { typename T::This_Object; typename T::Prev_Object; typename T::Prev_Prop; - typename T::Prev_State; + typename T::State_Tag; + typename T::template Prev_State; typename T::template Prev_Builder; typename T::Prev_Private; + typename T::States; requires std::same_as; requires Object_Root; requires std::derived_from; - requires std::derived_from; + requires detail::State_Tag_In; + requires detail::State_Chain>; + requires std::derived_from>; + requires detail::State_Chain_Matches; requires std::derived_from, typename T::template Prev_Builder>; requires std::derived_from; }; @@ -28,12 +33,28 @@ using Impl_Relies = decltype(std::tuple_cat( std::declval(), std::declval>()... )); +template +using Local_States = decltype(std::tuple_cat( + std::declval>()... +)); +template +using Impl_States = decltype(std::tuple_cat( + std::declval(), + std::declval>() +)); +template +using Impl_State_Base = Rebind_State_T< + std::tuple_element_t<0, Local_States>, + typename Base::State +>; template concept Impl_Mechanisms = requires { requires Object_Root; requires (Mechanism_Type && ...); + requires std::tuple_size_v> == 1; requires Buffer_List>; requires Rely_List>; + requires State_List>; }; } template @@ -51,10 +72,14 @@ struct Impl : Base { using Prev_Prop = typename Base::Prop; template using Prev_Builder = typename Base::template Builder; - using Prev_State = typename Base::State; + using State_Declaration = std::tuple_element_t<0, detail::Local_States>; + using State_Tag = typename State_Declaration::Tag_Type; + template requires std::same_as + using Prev_State = detail::Rebind_State_T; using Base_Private = typename Base::Private; using Buffers = detail::Impl_Buffers; using Relies = detail::Impl_Relies; + using States = detail::Impl_States; struct Private : Base_Private { template void before_state_set(Self*, Member Owner::*, State*) {} @@ -81,11 +106,13 @@ struct Attach_Object : Obj { using State = typename Obj::State; using Buffers = typename Obj::Buffers; using Relies = typename Obj::Relies; + using States = typename Obj::States; using Builder = typename Obj::template Builder; using Attached_Object = Obj; struct Private : Obj::Private, Double_Buffer { using Allocator = std::pmr::polymorphic_allocator; Double_Buffer state; + detail::State_Callback_Storage state_callbacks; detail::Buffer_Storage buffer_storage; detail::Rely_Storage rely_storage; explicit Private(std::pmr::memory_resource* resource) : Double_Buffer(std::allocator_arg, Allocator{resource}), @@ -99,7 +126,7 @@ struct Attach_Object : Obj { } private: friend struct Root; - Lock lock; + mutable Lock lock; template void walk_private(Callback& callback) { if constexpr (requires { typename Layer::Prev_Object; }) { @@ -171,20 +198,22 @@ public: *buffer.current = *buffer.pending; } template Tag> - [[nodiscard]] const Rely& rely() const { - return *d.rely_storage.template get().pending; + [[nodiscard]] auto rely() const { + using Object = detail::Rely_Bound_Object; + return d.rely_storage.template get().pending->template typed_view(); } template Tag> - [[nodiscard]] const Rely& current_rely() const { - return *d.rely_storage.template get().current; + [[nodiscard]] auto current_rely() const { + using Object = detail::Rely_Bound_Object; + return d.rely_storage.template get().current->template typed_view(); } - template ... Tags, detail::Rely_Access_Callback_For Callback> requires - detail::Unique_Types::value + template ... Tags, typename Callback> requires + detail::Unique_Types::value && detail::Rely_Access_Callback_For void access_rely(Callback&& callback) { d.rely_storage.template access(std::forward(callback)); } - template ... Tags, detail::Rely_Edit_Callback_For Callback> requires - detail::Unique_Types::value + template ... Tags, typename Callback> requires + detail::Unique_Types::value && detail::Rely_Edit_Callback_For std::expected edit_rely(Callback&& callback) { std::lock_guard guard(lock); return d.rely_storage.template edit(std::forward(callback)); @@ -192,7 +221,8 @@ public: std::expected validate_rely() const { return d.rely_storage.validate_pending(); } - template Tag, detail::Rely_View_Node_Callback Callback> + template Tag, typename Callback> requires + detail::Rely_View_Node_Callback> std::expected for_each_rely_topological(Callback&& callback) const { return current_rely().for_each_topological_view(std::forward(callback)); } @@ -206,6 +236,26 @@ public: d.pending->*member = std::forward(value); return *this; } + template Callback> + void set_state_callback(Callback&& callback) { + std::lock_guard guard(lock); + d.state_callbacks.template set(std::forward(callback)); + } + template Tag> + void clear_state_callback() { + std::lock_guard guard(lock); + d.state_callbacks.template clear(); + } + template Callback> + void access_state(Callback&& callback) const { + std::lock_guard guard(lock); + using Layer = detail::State_Value; + std::invoke(std::forward(callback), static_cast(*d.state.current)); + } + template Tag> + void notify_state() { + d.state_callbacks.template notify(*d.state.current); + } template Value> void update_state(Value&& value) { std::lock_guard guard(lock); diff --git a/kernel/src/kernel/base/rely.hpp b/kernel/src/kernel/base/rely.hpp index 120cb26..a0593b8 100644 --- a/kernel/src/kernel/base/rely.hpp +++ b/kernel/src/kernel/base/rely.hpp @@ -74,6 +74,49 @@ public: rely->for_each_edge(std::forward(callback)); } }; + template + class Typed_View : public View { + public: + using Object_Type = Object; + using Private_Type = typename Object::Private; + explicit Typed_View(const Rely& value) : View(value) {} + [[nodiscard]] bool bound(const Node& node) const noexcept { + return node.data != nullptr; + } + [[nodiscard]] Object* object(const Node& node) const { + if (!bound(node)) return nullptr; + return static_cast(node.object); + } + [[nodiscard]] Private_Type* private_data(const Node& node) const noexcept { + return static_cast(node.data); + } + [[nodiscard]] Private_Type* private_data(Root* root) const noexcept { + auto* node = this->find(root); + return node ? private_data(*node) : nullptr; + } + template Callback> + void for_each_bound(Callback&& callback) const { + this->rely->for_each( + [&](const Node& node) { + auto* data = private_data(node); + if (data) std::invoke(callback, static_cast(node.object), *data); + } + ); + } + template Callback> + std::expected for_each_topological_view(Callback&& callback) const { + return this->rely->for_each_topological( + [&](const Node& node) { + std::invoke(callback, *this, node); + } + ); + } + template Callback> + void for_each(Callback&& callback) const { + this->rely->for_each(std::forward(callback)); + } + }; + template class Editor : public View { private: Rely* edit_rely; @@ -81,6 +124,7 @@ public: const void* target_dirty_key; bool bind_node_data; public: + using Object_Type = Bound_Object; Editor(Rely& value, const void* tag, const void* dirty_key) : Editor(value, tag, dirty_key, true) {} Editor(Rely& value, const void* tag, const void* dirty_key, bool value_bind_node_data) : View(value), edit_rely(&value), @@ -90,21 +134,19 @@ public: void clear() { edit_rely->reset(); } - template + template requires std::derived_from Node* add(Object* object) { - return edit_rely->add_node(object, bind_node_data); + return edit_rely->template add_node(object, bind_node_data); } - template + template requires std::derived_from void add(std::initializer_list objects) { - for (auto* object : objects) edit_rely->add_node(object, bind_node_data); - } - void add(std::initializer_list objects) { - for (auto* object : objects) edit_rely->add_node(object, bind_node_data); + for (auto* object : objects) edit_rely->template add_node(object, bind_node_data); } bool remove(Root* object) { return edit_rely->remove_node(object); } - void clear_dependencies(Root* target) { + template requires std::derived_from + void clear_dependencies(Target* target) { edit_rely->clear_dependencies_impl(target); } void remove_dependents(Root* source) { @@ -113,9 +155,10 @@ public: void disconnect(Root* object) { edit_rely->disconnect_impl(object); } - template Source> + template Source> requires + std::derived_from Node* add_dependency(Target* target, Source* source) { - return edit_rely->template add_dependency_runtime( + return edit_rely->template add_dependency_runtime( target, source, detail::rely_id>(), @@ -124,9 +167,10 @@ public: bind_node_data ); } - template Source> + template Source> requires + std::derived_from Node* add_dependency(Target* target, Source* source) { - return edit_rely->template add_dependency_runtime( + return edit_rely->template add_dependency_runtime( target, source, detail::rely_id>(), @@ -135,9 +179,10 @@ public: bind_node_data ); } - template + template requires + std::derived_from Node* add_dirty_dependency(Target* target, Source* source) { - return edit_rely->template add_dependency_runtime( + return edit_rely->template add_dependency_runtime( target, source, detail::rely_id>(), @@ -146,15 +191,18 @@ public: bind_node_data ); } - template Source> + template Source> requires + std::derived_from bool remove_dependency(Target* target, Source* source) { return edit_rely->remove_edge(target, source, detail::rely_id>(), target_tag); } - template Source> + template Source> requires + std::derived_from bool remove_dependency(Target* target, Source* source) { return edit_rely->remove_edge(target, source, detail::rely_id>(), target_tag); } - bool remove_dependency(Root* target, Root* source) { + template requires std::derived_from + bool remove_dependency(Target* target, Root* source) { return edit_rely->remove_dependency_impl(target, source); } }; @@ -239,12 +287,13 @@ private: ++structure_revision; rebuild(); } - template + template static Node::Bind node_bind() { - if constexpr (requires(Object* object, Node* node) { object->bind_rely_node(node, object); }) { + if constexpr (std::derived_from && detail::Bound_Rely_Object) { return [](Node* node, Root* root) { auto* object = static_cast(root); - object->bind_rely_node(node, object); + if constexpr (requires { object->bind_rely_object(object); }) object->bind_rely_object(object); + node->data = static_cast(&object->d); }; } else { @@ -256,9 +305,9 @@ private: if (node.bind) node.bind(&node, node.object); } } - template + template Node* add_node(Object* object, bool bind_node) { - auto bind = node_bind(); + auto bind = node_bind(); auto current = nodes.find(object); if (current != nodes.end()) { current->second->bind = bind; @@ -294,7 +343,7 @@ private: dirty_edges.swap(other.dirty_edges); std::swap(structure_revision, other.structure_revision); } - template + template Node* add_dependency_runtime( Target* target, Source* source, @@ -302,8 +351,8 @@ private: const void* target_tag, const void* target_dirty_key, bool bind_node) { - auto* target_node = add_node(target, bind_node); - auto* source_node = add_node(source, bind_node); + auto* target_node = add_node(target, bind_node); + auto* source_node = add_node(source, bind_node); add_edge(target_node, source_node, source_key, target_tag, target_dirty_key); return source_node; } @@ -554,6 +603,10 @@ public: } ); } + template + [[nodiscard]] Typed_View typed_view() const { + return Typed_View{*this}; + } void emit(Root* source, const void* key) const { auto [first, last] = dirty_edges.equal_range(Dirty_Source{source, key}); for (; first != last; ++first) { diff --git a/kernel/src/kernel/base/rely_storage.hpp b/kernel/src/kernel/base/rely_storage.hpp index 29781da..47183d7 100644 --- a/kernel/src/kernel/base/rely_storage.hpp +++ b/kernel/src/kernel/base/rely_storage.hpp @@ -2,17 +2,17 @@ #include "rely.hpp" namespace aethera { namespace detail { -template -concept Rely_Edit_Callback_For = std::invocable< +template +concept Rely_Edit_Callback_For = Rely_List && std::invocable< Callback, - std::conditional_t... + Rely::Editor>&... >; -template -concept Rely_View_Node_Callback = std::invocable; +template +concept Rely_View_Node_Callback = Root_Derived && std::invocable&, const Rely::Node&>; template concept Rely_Current_Callback = std::invocable; -template -concept Rely_Access_Callback_For = std::invocable&...>; +template +concept Rely_Access_Callback_For = Rely_List && std::invocable&...>; } 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; @@ -75,19 +75,21 @@ public: const auto& get() const { return std::get()>(relies); } - template >... Tags, Rely_Access_Callback_For Callback> requires Unique_Types::value + template >... Tags, typename Callback> requires + Unique_Types::value && Rely_Access_Callback_For, Tags...> void access(Callback&& callback) { std::invoke( std::forward(callback), - static_cast&>(get())... + static_cast>&>(get())... ); } - template >... Tags, Rely_Edit_Callback_For Callback> requires Unique_Types::value + template >... Tags, typename Callback> requires + Unique_Types::value && Rely_Edit_Callback_For, Tags...> std::expected edit(Callback&& callback) { std::tuple...> next(*get().pending...); auto editors = [&](std::index_sequence) { return std::tuple{ - Rely::Editor( + Rely::Editor>>( std::get(next), rely_id(), rely_id>(), @@ -195,10 +197,11 @@ public: const Rely& get() const { return std::get()>(relies); } - template >... Tags, Rely_Edit_Callback_For Callback> requires Unique_Types::value + template >... Tags, typename Callback> requires + Unique_Types::value && Rely_Edit_Callback_For, Tags...> void edit(Callback&& callback) { auto editors = std::tuple{ - Rely::Editor( + Rely::Editor>>( get(), rely_id(), rely_id>(), @@ -248,13 +251,15 @@ struct Root::Builder { object->template set_initial_buffer(std::forward(value)); return *this; } - template ... Rely_Tags, detail::Rely_Edit_Callback_For Callback> requires - detail::Unique_Types::value + template ... Rely_Tags, typename Callback> requires + detail::Unique_Types::value && + detail::Rely_Edit_Callback_For Builder& edit_rely(Callback&& callback) { rely_storage.template edit(std::forward(callback)); return *this; } - template Rely_Tag, Root_Derived Node_Object> + template Rely_Tag, detail::Bound_Rely_Object Node_Object> requires + (std::derived_from>) Builder& add_dependency_node(Node_Object* node) { return edit_rely( [&](auto& editor) { @@ -274,7 +279,8 @@ struct Root::Builder { detail::Rely_Tag_In Rely_Tag, auto Member, detail::Rely_Object Target, - detail::State_Rely_Source Source> + detail::State_Rely_Source Source> requires + (detail::Bound_Rely_Object && std::derived_from>) Builder& add_dependency(Target* target, Source* source) { return edit_rely( [&](auto& editor) { @@ -286,7 +292,8 @@ struct Root::Builder { detail::Rely_Tag_In Rely_Tag, typename Buffer_Tag, detail::Rely_Object Target, - detail::Buffer_Rely_Source Source> + detail::Buffer_Rely_Source Source> requires + (detail::Bound_Rely_Object && std::derived_from>) Builder& add_dependency(Target* target, Source* source) { return edit_rely( [&](auto& editor) { @@ -298,7 +305,8 @@ struct Root::Builder { detail::Rely_Tag_In Rely_Tag, typename Source_Tag, Root_Derived Target, - Root_Derived Source> + Root_Derived Source> requires + (detail::Bound_Rely_Object && std::derived_from>) Builder& add_dirty_dependency(Target* target, Source* source) { return edit_rely( [&](auto& editor) { @@ -310,7 +318,8 @@ struct Root::Builder { detail::Rely_Tag_In Rely_Tag, auto Member, detail::Rely_Object Target, - detail::State_Rely_Source Source> + detail::State_Rely_Source Source> requires + (detail::Bound_Rely_Object && std::derived_from>) Builder& remove_dependency(Target* target, Source* source) { return edit_rely( [&](auto& editor) { @@ -322,7 +331,8 @@ struct Root::Builder { detail::Rely_Tag_In Rely_Tag, typename Buffer_Tag, detail::Rely_Object Target, - detail::Buffer_Rely_Source Source> + detail::Buffer_Rely_Source Source> requires + (detail::Bound_Rely_Object && std::derived_from>) Builder& remove_dependency(Target* target, Source* source) { return edit_rely( [&](auto& editor) { diff --git a/kernel/src/kernel/render_2D.cpp b/kernel/src/kernel/render_2D.cpp index 37b8ef6..dfa3533 100644 --- a/kernel/src/kernel/render_2D.cpp +++ b/kernel/src/kernel/render_2D.cpp @@ -1,71 +1,284 @@ #include "render_2D.hpp" +#include +#include #include namespace aethera::_2D { namespace { +class Stage_Registry : Pinned { +private: + struct Binding { + const tf::Taskflow* owner; + Root* object; + Renderable::Private* data; + detail::Renderable_Stage stage; + detail::Stage_Observer_Point point; + }; + std::unordered_map bindings; + std::mutex mutex; +public: + void bind(const tf::Taskflow* owner, std::size_t task_hash, Root* object, Renderable::Private* data, detail::Renderable_Stage stage, detail::Stage_Observer_Point point) { + std::lock_guard guard(mutex); + bindings.insert_or_assign(task_hash, Binding{owner, object, data, stage, point}); + } + void clear(const tf::Taskflow* owner) { + std::lock_guard guard(mutex); + std::erase_if( + bindings, + [owner](const auto& value) { + return value.second.owner == owner; + } + ); + } + void on_entry(std::size_t task_hash, std::uint64_t now) { + std::lock_guard guard(mutex); + auto current = bindings.find(task_hash); + if (current == bindings.end() || current->second.point != detail::Stage_Observer_Point::End) return; + auto& binding = current->second; + auto& state = *binding.data->dispatch->state_access(binding.object); + if (binding.stage == detail::Renderable_Stage::Prepare) { + if (state.prepare_executed) state.prepare_execution_time_ns = now - state.prepare_execution_time_ns; + } + else if (state.paint_executed) { + state.paint_execution_time_ns = now - state.paint_execution_time_ns; + } + } + void on_exit(std::size_t task_hash, std::uint64_t now) { + std::lock_guard guard(mutex); + auto current = bindings.find(task_hash); + if (current == bindings.end() || current->second.point != detail::Stage_Observer_Point::Begin) return; + auto& binding = current->second; + auto& state = *binding.data->dispatch->state_access(binding.object); + if (binding.stage == detail::Renderable_Stage::Prepare) { + state.prepare_execution_time_ns = state.prepare_executed ? now : 0; + } + else { + state.paint_execution_time_ns = state.paint_executed ? now : 0; + } + } +}; class Task_Observer : public tf::ObserverInterface { private: using Clock = std::chrono::steady_clock; + Stage_Registry* stage_registry; + struct Task_Statistics { + std::atomic_size_t count{}; + std::atomic_uint64_t total_time_ns{}; + std::atomic_uint64_t min_time_ns{std::numeric_limits::max()}; + std::atomic_uint64_t max_time_ns{}; + }; + struct Worker_Statistics { + std::atomic_size_t task_count{}; + std::atomic_size_t peak_queue_size{}; + std::atomic_size_t max_queue_capacity{}; + std::atomic_uint64_t task_time_ns{}; + std::atomic_uint64_t busy_time_ns{}; + std::atomic_uint64_t min_task_time_ns{std::numeric_limits::max()}; + std::atomic_uint64_t max_task_time_ns{}; + }; std::vector> starts; + std::vector worker_busy_starts; + std::unique_ptr worker_statistics; + std::size_t worker_statistics_count{}; + std::array task_types; std::atomic_size_t active_tasks{}; std::atomic_size_t peak_active_tasks{}; + std::atomic_size_t active_workers{}; + std::atomic_size_t peak_active_workers{}; std::atomic_size_t completed_tasks{}; + std::atomic_size_t named_tasks{}; std::atomic_size_t peak_queue_size{}; + std::atomic_size_t max_queue_capacity{}; + std::atomic_size_t max_predecessors{}; + std::atomic_size_t max_successors{}; + std::atomic_size_t max_strong_dependencies{}; + std::atomic_size_t max_weak_dependencies{}; std::atomic_uint64_t total_execution_time_ns{}; - static void update_peak(std::atomic_size_t& value, std::size_t next) noexcept { + std::atomic_uint64_t worker_busy_time_ns{}; + std::atomic_uint64_t first_task_time_ns{}; + std::atomic_uint64_t last_task_time_ns{}; + mutable std::mutex longest_mutex; + std::uint64_t longest_task_time_ns{}; + std::size_t longest_task_hash{}; + std::string longest_task_name; + tf::TaskType longest_task_type{tf::TaskType::UNDEFINED}; + static std::uint64_t clock_ns(Clock::time_point value) noexcept { + return static_cast(std::chrono::duration_cast(value.time_since_epoch()).count()); + } + template + static void update_max(std::atomic& value, T next) noexcept { auto current = value.load(std::memory_order_relaxed); while (current < next && !value.compare_exchange_weak(current, next, std::memory_order_relaxed)) {} } + static void update_min(std::atomic_uint64_t& value, std::uint64_t next) noexcept { + auto current = value.load(std::memory_order_relaxed); + while (next < current && !value.compare_exchange_weak(current, next, std::memory_order_relaxed)) {} + } + static void update_first(std::atomic_uint64_t& value, std::uint64_t next) noexcept { + auto current = value.load(std::memory_order_relaxed); + while ((current == 0 || next < current) && !value.compare_exchange_weak(current, next, std::memory_order_relaxed)) {} + } + static std::size_t task_type_index(tf::TaskType type) noexcept { + return static_cast(type); + } public: + explicit Task_Observer(Stage_Registry* registry) : stage_registry(registry) {} void set_up(std::size_t workers) override { starts.resize(workers); + worker_busy_starts.resize(workers); + worker_statistics = std::make_unique(workers); + worker_statistics_count = workers; for (auto& worker : starts) worker.reserve(8); } - void on_entry(tf::WorkerView worker, tf::TaskView) override { - starts[worker.id()].push_back(Clock::now()); - auto active = active_tasks.fetch_add(1, std::memory_order_relaxed) + 1; - update_peak(peak_active_tasks, active); - update_peak(peak_queue_size, worker.queue_size()); - } - void on_exit(tf::WorkerView worker, tf::TaskView) override { + void on_entry(tf::WorkerView worker, tf::TaskView task) override { auto now = Clock::now(); + stage_registry->on_entry(task.hash_value(), clock_ns(now)); + auto& worker_starts = starts[worker.id()]; + if (worker_starts.empty()) { + worker_busy_starts[worker.id()] = now; + auto active = active_workers.fetch_add(1, std::memory_order_relaxed) + 1; + update_max(peak_active_workers, active); + } + worker_starts.push_back(now); + auto active = active_tasks.fetch_add(1, std::memory_order_relaxed) + 1; + update_max(peak_active_tasks, active); + update_max(peak_queue_size, worker.queue_size()); + update_max(max_queue_capacity, worker.queue_capacity()); + auto& worker_state = worker_statistics[worker.id()]; + update_max(worker_state.peak_queue_size, worker.queue_size()); + update_max(worker_state.max_queue_capacity, worker.queue_capacity()); + update_max(max_predecessors, task.num_predecessors()); + update_max(max_successors, task.num_successors()); + update_max(max_strong_dependencies, task.num_strong_dependencies()); + update_max(max_weak_dependencies, task.num_weak_dependencies()); + if (!task.name().empty()) named_tasks.fetch_add(1, std::memory_order_relaxed); + update_first(first_task_time_ns, clock_ns(now)); + } + void on_exit(tf::WorkerView worker, tf::TaskView task) override { + auto now = Clock::now(); + stage_registry->on_exit(task.hash_value(), clock_ns(now)); auto& worker_starts = starts[worker.id()]; auto start = worker_starts.back(); worker_starts.pop_back(); - auto elapsed = std::chrono::duration_cast(now - start).count(); - total_execution_time_ns.fetch_add(static_cast(elapsed), std::memory_order_relaxed); + auto elapsed = static_cast(std::chrono::duration_cast(now - start).count()); + total_execution_time_ns.fetch_add(elapsed, std::memory_order_relaxed); completed_tasks.fetch_add(1, std::memory_order_relaxed); + auto& worker_state = worker_statistics[worker.id()]; + worker_state.task_count.fetch_add(1, std::memory_order_relaxed); + worker_state.task_time_ns.fetch_add(elapsed, std::memory_order_relaxed); + update_min(worker_state.min_task_time_ns, elapsed); + update_max(worker_state.max_task_time_ns, elapsed); + auto type_index = task_type_index(task.type()); + if (type_index < task_types.size()) { + auto& type = task_types[type_index]; + type.count.fetch_add(1, std::memory_order_relaxed); + type.total_time_ns.fetch_add(elapsed, std::memory_order_relaxed); + update_min(type.min_time_ns, elapsed); + update_max(type.max_time_ns, elapsed); + } + { + std::lock_guard guard(longest_mutex); + if (longest_task_time_ns < elapsed) { + longest_task_time_ns = elapsed; + longest_task_hash = task.hash_value(); + longest_task_name = task.name(); + longest_task_type = task.type(); + } + } active_tasks.fetch_sub(1, std::memory_order_relaxed); + if (worker_starts.empty()) { + auto busy = static_cast(std::chrono::duration_cast(now - worker_busy_starts[worker.id()]).count()); + worker_busy_time_ns.fetch_add(busy, std::memory_order_relaxed); + worker_state.busy_time_ns.fetch_add(busy, std::memory_order_relaxed); + active_workers.fetch_sub(1, std::memory_order_relaxed); + } + update_max(last_task_time_ns, clock_ns(now)); } - std::size_t active_count() const noexcept { - return active_tasks.load(std::memory_order_relaxed); - } - std::size_t peak_active_count() const noexcept { - return peak_active_tasks.load(std::memory_order_relaxed); - } - std::size_t completed_count() const noexcept { - return completed_tasks.load(std::memory_order_relaxed); - } - std::size_t peak_queue() const noexcept { - return peak_queue_size.load(std::memory_order_relaxed); - } - std::uint64_t total_execution_time() const noexcept { - return total_execution_time_ns.load(std::memory_order_relaxed); + void write_state(Task_Runtime_State& state, std::size_t workers, std::size_t active_topologies) const { + state.worker_count = workers; + state.active_topology_count = active_topologies; + state.active_task_count = active_tasks.load(std::memory_order_relaxed); + state.peak_active_task_count = peak_active_tasks.load(std::memory_order_relaxed); + state.active_worker_count = active_workers.load(std::memory_order_relaxed); + state.peak_active_worker_count = peak_active_workers.load(std::memory_order_relaxed); + state.observed_task_count = completed_tasks.load(std::memory_order_relaxed); + state.named_task_count = named_tasks.load(std::memory_order_relaxed); + state.peak_observed_worker_queue_size = peak_queue_size.load(std::memory_order_relaxed); + state.max_observed_worker_queue_capacity = max_queue_capacity.load(std::memory_order_relaxed); + state.max_predecessors = max_predecessors.load(std::memory_order_relaxed); + state.max_successors = max_successors.load(std::memory_order_relaxed); + state.max_strong_dependencies = max_strong_dependencies.load(std::memory_order_relaxed); + state.max_weak_dependencies = max_weak_dependencies.load(std::memory_order_relaxed); + state.total_task_time_ns = total_execution_time_ns.load(std::memory_order_relaxed); + state.worker_busy_time_ns = worker_busy_time_ns.load(std::memory_order_relaxed); + auto first = first_task_time_ns.load(std::memory_order_relaxed); + auto last = last_task_time_ns.load(std::memory_order_relaxed); + state.observed_wall_time_ns = first && last >= first ? last - first : 0; + state.worker_utilization = workers && state.observed_wall_time_ns ? + static_cast(state.worker_busy_time_ns) * 100.0 / static_cast(state.observed_wall_time_ns) / static_cast(workers) : 0.0; + for (std::size_t i = 0; i < task_types.size(); ++i) { + const auto& source = task_types[i]; + auto& target = state.task_types[i]; + target.count = source.count.load(std::memory_order_relaxed); + target.total_time_ns = source.total_time_ns.load(std::memory_order_relaxed); + auto min = source.min_time_ns.load(std::memory_order_relaxed); + target.min_time_ns = target.count ? min : 0; + target.max_time_ns = source.max_time_ns.load(std::memory_order_relaxed); + } + state.workers.resize(worker_statistics_count); + for (std::size_t i = 0; i < worker_statistics_count; ++i) { + const auto& source = worker_statistics[i]; + auto& target = state.workers[i]; + target.id = i; + target.task_count = source.task_count.load(std::memory_order_relaxed); + target.peak_observed_queue_size = source.peak_queue_size.load(std::memory_order_relaxed); + target.max_observed_queue_capacity = source.max_queue_capacity.load(std::memory_order_relaxed); + target.task_time_ns = source.task_time_ns.load(std::memory_order_relaxed); + target.busy_time_ns = source.busy_time_ns.load(std::memory_order_relaxed); + target.idle_time_ns = state.observed_wall_time_ns > target.busy_time_ns ? state.observed_wall_time_ns - target.busy_time_ns : 0; + auto min = source.min_task_time_ns.load(std::memory_order_relaxed); + target.min_task_time_ns = target.task_count ? min : 0; + target.max_task_time_ns = source.max_task_time_ns.load(std::memory_order_relaxed); + target.utilization = state.observed_wall_time_ns ? static_cast(target.busy_time_ns) * 100.0 / static_cast(state.observed_wall_time_ns) : 0.0; + } + std::lock_guard guard(longest_mutex); + state.longest_task_time_ns = longest_task_time_ns; + state.longest_task_hash = longest_task_hash; + state.longest_task_name = longest_task_name; + state.longest_task_type = longest_task_type; } }; class Task_Resource : Pinned { private: std::unique_ptr executor; std::shared_ptr observer; + Stage_Registry stage_registry; std::pmr::memory_resource* memory; + std::atomic_size_t active_taskflows{}; + std::atomic_size_t peak_active_taskflows{}; + std::atomic_size_t completed_taskflows{}; + std::atomic_size_t failed_taskflows{}; + Task_Runtime_State state; + ::aethera::detail::State_Callback_Storage state_callbacks; + std::recursive_mutex state_mutex; void create_executor(std::size_t workers, std::shared_ptr worker_interface) { executor = std::make_unique(workers, std::move(worker_interface)); - observer = executor->make_observer(); + observer = executor->make_observer(&stage_registry); + state = {}; } void ensure_executor() { - if (!executor) { - executor = std::make_unique(); - observer = executor->make_observer(); - } + std::lock_guard guard(state_mutex); + if (executor) return; + executor = std::make_unique(); + observer = executor->make_observer(&stage_registry); + } + void publish_state() { + std::lock_guard guard(state_mutex); + observer->write_state(state, executor->num_workers(), executor->num_topologies()); + state.active_taskflow_count = active_taskflows.load(std::memory_order_relaxed); + state.peak_active_taskflow_count = peak_active_taskflows.load(std::memory_order_relaxed); + state.completed_taskflow_count = completed_taskflows.load(std::memory_order_relaxed); + state.failed_taskflow_count = failed_taskflows.load(std::memory_order_relaxed); + state_callbacks.template notify(state); } public: Task_Resource() : memory(std::pmr::get_default_resource()) {} @@ -74,39 +287,50 @@ public: return value; } void initialize(std::size_t workers, std::shared_ptr worker_interface, Pmr pmr) { - create_executor(workers, std::move(worker_interface)); + std::lock_guard guard(state_mutex); memory = pmr.resource(); + create_executor(workers, std::move(worker_interface)); + active_taskflows.store(0, std::memory_order_relaxed); + peak_active_taskflows.store(0, std::memory_order_relaxed); + completed_taskflows.store(0, std::memory_order_relaxed); + failed_taskflows.store(0, std::memory_order_relaxed); } std::uint64_t run(tf::Taskflow& taskflow) { ensure_executor(); + auto active = active_taskflows.fetch_add(1, std::memory_order_relaxed) + 1; + auto peak = peak_active_taskflows.load(std::memory_order_relaxed); + while (peak < active && !peak_active_taskflows.compare_exchange_weak(peak, active, std::memory_order_relaxed)) {} auto start = std::chrono::steady_clock::now(); - executor->run(taskflow).get(); - auto elapsed = std::chrono::steady_clock::now() - start; - return static_cast(std::chrono::duration_cast(elapsed).count()); + try { + executor->run(taskflow).get(); + } catch (...) { + active_taskflows.fetch_sub(1, std::memory_order_relaxed); + failed_taskflows.fetch_add(1, std::memory_order_relaxed); + publish_state(); + throw; + } + auto elapsed = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count()); + active_taskflows.fetch_sub(1, std::memory_order_relaxed); + completed_taskflows.fetch_add(1, std::memory_order_relaxed); + publish_state(); + return elapsed; + } + void bind_stage_observer(const tf::Taskflow* owner, std::size_t task_hash, Root* object, Renderable::Private* data, detail::Renderable_Stage stage, detail::Stage_Observer_Point point) { + stage_registry.bind(owner, task_hash, object, data, stage, point); + } + void clear_stage_observers(const tf::Taskflow* owner) { + stage_registry.clear(owner); } std::pmr::memory_resource* memory_resource() const noexcept { return memory; } - std::size_t worker_count() const noexcept { - return executor ? executor->num_workers() : 0; + void set_state_callback(std::function callback) { + std::lock_guard guard(state_mutex); + state_callbacks.template set(std::move(callback)); } - std::size_t active_topology_count() const noexcept { - return executor ? executor->num_topologies() : 0; - } - std::size_t active_count() const noexcept { - return observer ? observer->active_count() : 0; - } - std::size_t peak_active_count() const noexcept { - return observer ? observer->peak_active_count() : 0; - } - std::size_t completed_count() const noexcept { - return observer ? observer->completed_count() : 0; - } - std::size_t peak_queue_size() const noexcept { - return observer ? observer->peak_queue() : 0; - } - std::uint64_t total_execution_time_ns() const noexcept { - return observer ? observer->total_execution_time() : 0; + void clear_state_callback() { + std::lock_guard guard(state_mutex); + state_callbacks.template clear(); } }; } @@ -114,32 +338,23 @@ void initialize_runtime(std::size_t workers, std::shared_ptr callback) { + Task_Resource::instance().set_state_callback(std::move(callback)); +} +void clear_runtime_state_callback() { + Task_Resource::instance().clear_state_callback(); +} std::uint64_t run_taskflow(tf::Taskflow& taskflow) { return Task_Resource::instance().run(taskflow); } +void bind_stage_observer(const tf::Taskflow* owner, std::size_t task_hash, Root* object, Renderable::Private* data, Renderable_Stage stage, Stage_Observer_Point point) { + Task_Resource::instance().bind_stage_observer(owner, task_hash, object, data, stage, point); +} +void clear_stage_observers(const tf::Taskflow* owner) { + Task_Resource::instance().clear_stage_observers(owner); +} std::pmr::memory_resource* task_memory_resource() noexcept { return Task_Resource::instance().memory_resource(); } -std::size_t task_worker_count() noexcept { - return Task_Resource::instance().worker_count(); -} -std::size_t task_active_topology_count() noexcept { - return Task_Resource::instance().active_topology_count(); -} -std::size_t task_active_count() noexcept { - return Task_Resource::instance().active_count(); -} -std::size_t task_peak_active_count() noexcept { - return Task_Resource::instance().peak_active_count(); -} -std::size_t task_completed_count() noexcept { - return Task_Resource::instance().completed_count(); -} -std::size_t task_peak_queue_size() noexcept { - return Task_Resource::instance().peak_queue_size(); -} -std::uint64_t task_total_execution_time_ns() noexcept { - return Task_Resource::instance().total_execution_time_ns(); -} } } diff --git a/kernel/src/kernel/render_2D.hpp b/kernel/src/kernel/render_2D.hpp index d030120..d88b079 100644 --- a/kernel/src/kernel/render_2D.hpp +++ b/kernel/src/kernel/render_2D.hpp @@ -1,74 +1,87 @@ #pragma once #include "base.hpp" +#include +#include #include namespace aethera::_2D { struct Prepare_Data_Tag {}; struct Paint_Tag {}; struct Color_Cache {}; +struct Task_Runtime_State_Tag {}; +struct Scene_State_Tag {}; +struct Renderable_State_Tag {}; +struct Task_Type_State { + std::size_t count{}; + std::uint64_t total_time_ns{}; + std::uint64_t min_time_ns{}; + std::uint64_t max_time_ns{}; + bool operator==(const Task_Type_State&) const = default; +}; +struct Task_Worker_State { + std::size_t id{}; + std::size_t task_count{}; + std::size_t peak_observed_queue_size{}; + std::size_t max_observed_queue_capacity{}; + std::uint64_t task_time_ns{}; + std::uint64_t busy_time_ns{}; + std::uint64_t idle_time_ns{}; + std::uint64_t min_task_time_ns{}; + std::uint64_t max_task_time_ns{}; + double utilization{}; + bool operator==(const Task_Worker_State&) const = default; +}; +struct Task_Runtime_State : State_Type { + std::size_t worker_count{}; + std::size_t active_topology_count{}; + std::size_t active_taskflow_count{}; + std::size_t peak_active_taskflow_count{}; + std::size_t completed_taskflow_count{}; + std::size_t failed_taskflow_count{}; + std::size_t active_task_count{}; + std::size_t peak_active_task_count{}; + std::size_t active_worker_count{}; + std::size_t peak_active_worker_count{}; + std::size_t observed_task_count{}; + std::size_t named_task_count{}; + std::size_t peak_observed_worker_queue_size{}; + std::size_t max_observed_worker_queue_capacity{}; + std::size_t max_predecessors{}; + std::size_t max_successors{}; + std::size_t max_strong_dependencies{}; + std::size_t max_weak_dependencies{}; + std::size_t longest_task_hash{}; + std::string longest_task_name; + tf::TaskType longest_task_type{tf::TaskType::UNDEFINED}; + std::uint64_t longest_task_time_ns{}; + std::uint64_t total_task_time_ns{}; + std::uint64_t worker_busy_time_ns{}; + std::uint64_t observed_wall_time_ns{}; + double worker_utilization{}; + std::array task_types{}; + std::vector workers; + bool operator==(const Task_Runtime_State&) const = default; +}; void initialize_runtime(std::size_t workers = std::thread::hardware_concurrency(), std::shared_ptr worker_interface = nullptr, Pmr pmr = {}); namespace detail { +void set_runtime_state_callback(std::function callback); +void clear_runtime_state_callback(); std::uint64_t run_taskflow(tf::Taskflow& taskflow); std::pmr::memory_resource* task_memory_resource() noexcept; -std::size_t task_worker_count() noexcept; -std::size_t task_active_topology_count() noexcept; -std::size_t task_active_count() noexcept; -std::size_t task_peak_active_count() noexcept; -std::size_t task_completed_count() noexcept; -std::size_t task_peak_queue_size() noexcept; -std::uint64_t task_total_execution_time_ns() noexcept; } -struct Renderable; -struct Scene : Impl, Rely_Type> { +template requires + std::same_as && std::invocable +void set_runtime_state_callback(Callback&& callback) { + detail::set_runtime_state_callback(std::function{std::forward(callback)}); +} +template requires std::same_as +void clear_runtime_state_callback() { + detail::clear_runtime_state_callback(); +} +struct Renderable : Impl, Tagged_Buffer> { struct Prop : Prev_Prop {}; - struct State : Prev_State { - bool taskflow_rebuilt{}; - std::size_t renderable_count{}; - std::size_t taskflow_task_count{}; - std::uint64_t taskflow_execution_time_ns{}; - std::size_t executor_workers{}; - std::size_t executor_active_topologies{}; - std::size_t executor_active_tasks{}; - std::size_t executor_peak_active_tasks{}; - std::size_t executor_completed_tasks{}; - std::size_t executor_peak_queue_size{}; - std::uint64_t executor_total_task_time_ns{}; - bool operator==(const State&) const = default; - }; - struct Private : Prev_Private { - struct Result {}; - friend struct Renderable; - private: - tf::Taskflow taskflow; - std::size_t renderable_count{}; - bool taskflow_built{}; - bool taskflow_rebuilt{}; - public: - void after_exchange(Attached auto* object, Prop*, State*, const Prop*, const State*); - template Callback> - void process(Attached auto* object, Callback&& callback) { - auto& state = static_cast(*object->d.state.current); - state.taskflow_rebuilt = taskflow_rebuilt; - state.renderable_count = renderable_count; - state.taskflow_task_count = taskflow.num_tasks(); - state.taskflow_execution_time_ns = 0; - if (taskflow_built && !taskflow.empty()) state.taskflow_execution_time_ns = detail::run_taskflow(taskflow); - state.executor_workers = detail::task_worker_count(); - state.executor_active_topologies = detail::task_active_topology_count(); - state.executor_active_tasks = detail::task_active_count(); - state.executor_peak_active_tasks = detail::task_peak_active_count(); - state.executor_completed_tasks = detail::task_completed_count(); - state.executor_peak_queue_size = detail::task_peak_queue_size(); - state.executor_total_task_time_ns = detail::task_total_execution_time_ns(); - Result result; - std::invoke(std::forward(callback), std::as_const(result)); - } - }; -}; -struct Renderable : Impl> { - struct Prop : Prev_Prop {}; - struct State : Prev_State { + struct State : Prev_State { bool prepare_dirty{}; bool paint_dirty{}; bool prepare_executed{}; @@ -77,19 +90,17 @@ struct Renderable : Impl> { bool paint_graph_rebuilt{}; std::size_t prepare_task_count{}; std::size_t paint_task_count{}; + std::uint64_t prepare_execution_time_ns{}; + std::uint64_t paint_execution_time_ns{}; bool operator==(const State&) const = default; }; protected: static void run_prepare_data(Attached auto* object) { using Object = std::remove_pointer_t; auto& private_data = static_cast(object->d); - if constexpr (requires { private_data.prepare_data(object); }) { - private_data.prepare_data(object); - } + if constexpr (requires { private_data.prepare_data(object); }) private_data.prepare_data(object); auto callback = [&](auto& value) { - if constexpr (requires { value.after_prepare_data(object); }) { - value.after_prepare_data(object); - } + if constexpr (requires { value.after_prepare_data(object); }) value.after_prepare_data(object); }; walk_private(object, callback); } @@ -99,20 +110,22 @@ public: using Rebuild_Predicate = bool (*)(Root*); using Graph_Builder = tf::Taskflow (*)(Root*, std::pmr::memory_resource*); using State_Access = State* (*)(Root*); - Run_Predicate prepare_predicate{}; - Run_Predicate paint_predicate{}; - Rebuild_Predicate prepare_rebuild_predicate{}; - Rebuild_Predicate paint_rebuild_predicate{}; - Graph_Builder prepare_builder{}; - Graph_Builder paint_builder{}; - State_Access state_access{}; + using State_Notify = void (*)(Root*); + struct Dispatch { + Run_Predicate prepare_predicate; + Run_Predicate paint_predicate; + Rebuild_Predicate prepare_rebuild_predicate; + Rebuild_Predicate paint_rebuild_predicate; + Graph_Builder prepare_builder; + Graph_Builder paint_builder; + State_Access state_access; + State_Notify state_notify; + }; + const Dispatch* dispatch{}; tf::Taskflow prepare_graph; tf::Taskflow paint_graph; - tf::Taskflow prepare_stage; - tf::Taskflow paint_stage; bool prepare_graph_built{}; bool paint_graph_built{}; - bool stages_built{}; bool should_rebuild_prepare_graph(Attached auto*, const State&) { return false; } @@ -129,7 +142,7 @@ public: tf::Taskflow taskflow; taskflow.emplace([object] { run_prepare_data(object); - }); + }).name("renderable.prepare.data"); return taskflow; } tf::Taskflow build_paint_graph(Attached auto* object, const State&, std::pmr::memory_resource*) { @@ -138,109 +151,118 @@ public: taskflow.emplace([object] { auto& private_data = static_cast(object->d); private_data.paint(object); - }); + }).name("renderable.paint.data"); return taskflow; } void prepare_data(Attached auto*) {} void after_prepare_data(Attached auto*) {} void paint(Attached auto*) {} - private: - void build_stages(Root* root) { - prepare_stage.clear(); - paint_stage.clear(); - auto prepare_if = prepare_stage.emplace([this, root] { - auto& state = *state_access(root); - state.prepare_graph_rebuilt = false; - bool rebuild = prepare_rebuild_predicate(root); - if (!prepare_graph_built || rebuild) { - prepare_graph = prepare_builder(root, detail::task_memory_resource()); - prepare_graph_built = true; - state.prepare_graph_rebuilt = true; - root->template mark_dirty(); - } - bool dirty = root->template dirty(); - state.prepare_dirty = dirty; - state.prepare_task_count = prepare_graph.num_tasks(); - state.prepare_executed = prepare_predicate(root, dirty); - return state.prepare_executed ? 0 : -1; - }); - auto prepare_run = prepare_stage.composed_of(prepare_graph); - auto prepare_done = prepare_stage.emplace([root] { - root->template take_dirty(); - root->template mark_dirty(); - }); - prepare_if.precede(prepare_run); - prepare_run.precede(prepare_done); - auto paint_if = paint_stage.emplace([this, root] { - auto& state = *state_access(root); - state.paint_graph_rebuilt = false; - bool rebuild = paint_rebuild_predicate(root); - if (!paint_graph_built || rebuild) { - paint_graph = paint_builder(root, detail::task_memory_resource()); - paint_graph_built = true; - state.paint_graph_rebuilt = true; - root->template mark_dirty(); - } - bool dirty = root->template dirty(); - state.paint_dirty = dirty; - state.paint_task_count = paint_graph.num_tasks(); - state.paint_executed = paint_predicate(root, dirty); - return state.paint_executed ? 0 : -1; - }); - auto paint_run = paint_stage.composed_of(paint_graph); - auto paint_done = paint_stage.emplace([root] { - root->template take_dirty(); - }); - paint_if.precede(paint_run); - paint_run.precede(paint_done); - stages_built = true; - } - friend struct Renderable; }; private: - void bind_rely_node(Rely::Node* node, Attached auto* object) { + void bind_rely_object(Attached auto* object) { using Object = std::remove_pointer_t; auto& data = static_cast(object->d); - data.prepare_predicate = [](Root* root, bool dirty) { - auto* value = static_cast(root); - auto& private_data = static_cast(value->d); - return private_data.should_prepare(value, *value->d.state.current, dirty); + static const Private::Dispatch dispatch{ + [](Root* root, bool dirty) { + auto* value = static_cast(root); + auto& private_data = static_cast(value->d); + return private_data.should_prepare(value, *value->d.state.current, dirty); + }, + [](Root* root, bool dirty) { + auto* value = static_cast(root); + auto& private_data = static_cast(value->d); + return private_data.should_paint(value, *value->d.state.current, dirty); + }, + [](Root* root) { + auto* value = static_cast(root); + auto& private_data = static_cast(value->d); + return private_data.should_rebuild_prepare_graph(value, *value->d.state.current); + }, + [](Root* root) { + auto* value = static_cast(root); + auto& private_data = static_cast(value->d); + return private_data.should_rebuild_paint_graph(value, *value->d.state.current); + }, + [](Root* root, std::pmr::memory_resource* resource) { + auto* value = static_cast(root); + auto& private_data = static_cast(value->d); + return private_data.build_prepare_graph(value, *value->d.state.current, resource); + }, + [](Root* root, std::pmr::memory_resource* resource) { + auto* value = static_cast(root); + auto& private_data = static_cast(value->d); + return private_data.build_paint_graph(value, *value->d.state.current, resource); + }, + [](Root* root) { + auto* value = static_cast(root); + return static_cast(value->d.state.current); + }, + [](Root* root) { + auto* value = static_cast(root); + value->template notify_state(); + } }; - data.paint_predicate = [](Root* root, bool dirty) { - auto* value = static_cast(root); - auto& private_data = static_cast(value->d); - return private_data.should_paint(value, *value->d.state.current, dirty); - }; - data.prepare_rebuild_predicate = [](Root* root) { - auto* value = static_cast(root); - auto& private_data = static_cast(value->d); - return private_data.should_rebuild_prepare_graph(value, *value->d.state.current); - }; - data.paint_rebuild_predicate = [](Root* root) { - auto* value = static_cast(root); - auto& private_data = static_cast(value->d); - return private_data.should_rebuild_paint_graph(value, *value->d.state.current); - }; - data.prepare_builder = [](Root* root, std::pmr::memory_resource* resource) { - auto* value = static_cast(root); - auto& private_data = static_cast(value->d); - return private_data.build_prepare_graph(value, *value->d.state.current, resource); - }; - data.paint_builder = [](Root* root, std::pmr::memory_resource* resource) { - auto* value = static_cast(root); - auto& private_data = static_cast(value->d); - return private_data.build_paint_graph(value, *value->d.state.current, resource); - }; - data.state_access = [](Root* root) { - auto* value = static_cast(root); - return static_cast(value->d.state.current); - }; - if (!data.stages_built) data.build_stages(object); + data.dispatch = &dispatch; object->template mark_dirty(); - node->data = &data; } friend struct ::aethera::Rely; }; +namespace detail { +enum class Renderable_Stage { + Prepare, + Paint +}; +enum class Stage_Observer_Point { + Begin, + End +}; +void bind_stage_observer(const tf::Taskflow* owner, std::size_t task_hash, Root* object, Renderable::Private* data, Renderable_Stage stage, Stage_Observer_Point point); +void clear_stage_observers(const tf::Taskflow* owner); +} +struct Scene : Impl, Rely_Type, Rely_Type> { + struct Prop : Prev_Prop {}; + struct State : Prev_State { + bool taskflow_rebuilt{}; + std::size_t renderable_count{}; + std::size_t taskflow_task_count{}; + std::size_t taskflow_dependency_count{}; + std::size_t taskflow_max_predecessors{}; + std::size_t taskflow_max_successors{}; + std::uint64_t taskflow_execution_time_ns{}; + bool operator==(const State&) const = default; + }; + struct Private : Prev_Private { + struct Result {}; + private: + tf::Taskflow taskflow; + std::size_t renderable_count{}; + std::size_t taskflow_dependency_count{}; + std::size_t taskflow_max_predecessors{}; + std::size_t taskflow_max_successors{}; + bool taskflow_built{}; + bool taskflow_rebuilt{}; + public: + ~Private() { + detail::clear_stage_observers(&taskflow); + } + void after_exchange(Attached auto* object, Prop*, State*, const Prop*, const State*); + template Callback> + void process(Attached auto* object, Callback&& callback) { + auto& state = static_cast(*object->d.state.current); + state.taskflow_rebuilt = taskflow_rebuilt; + state.renderable_count = renderable_count; + state.taskflow_task_count = taskflow.num_tasks(); + state.taskflow_dependency_count = taskflow_dependency_count; + state.taskflow_max_predecessors = taskflow_max_predecessors; + state.taskflow_max_successors = taskflow_max_successors; + state.taskflow_execution_time_ns = 0; + if (taskflow_built && !taskflow.empty()) state.taskflow_execution_time_ns = detail::run_taskflow(taskflow); + object->template notify_state(); + Result result; + std::invoke(std::forward(callback), std::as_const(result)); + } + }; +}; void Scene::Private::after_exchange(Attached auto* object, Prop*, State*, const Prop*, const State*) { auto* resource = detail::task_memory_resource(); taskflow_rebuilt = false; @@ -262,40 +284,97 @@ void Scene::Private::after_exchange(Attached auto* object, Prop*, State*, const } ); if (!taskflow_dirty) return; - const auto& prepare_dependencies = object->template current_rely(); - const auto& paint_dependencies = object->template current_rely(); - std::pmr::unordered_set renderables{resource}; - prepare_dependencies.for_each( - [&](const Rely::Node& node) { - if (node.data) renderables.insert(node.object); + auto prepare_dependencies = object->template current_rely(); + auto paint_dependencies = object->template current_rely(); + std::pmr::unordered_set renderables{resource}; + prepare_dependencies.for_each_bound( + [&](Renderable* renderable, Renderable::Private&) { + renderables.insert(renderable); } ); - paint_dependencies.for_each( - [&](const Rely::Node& node) { - if (node.data) renderables.insert(node.object); + paint_dependencies.for_each_bound( + [&](Renderable* renderable, Renderable::Private&) { + renderables.insert(renderable); } ); renderable_count = renderables.size(); + detail::clear_stage_observers(&taskflow); taskflow.clear(); struct Stage_Tasks { - tf::Task prepare; - tf::Task paint; + tf::Task prepare_entry; + tf::Task prepare_exit; + tf::Task paint_entry; + tf::Task paint_exit; }; std::pmr::unordered_map stage_tasks{resource}; - for (auto* root : renderables) { - const Rely::Node* rely_node = prepare_dependencies.find(root); - if (!rely_node || !rely_node->data) rely_node = paint_dependencies.find(root); - if (!rely_node || !rely_node->data) continue; - auto* data = static_cast(rely_node->data); - auto prepare = taskflow.composed_of(data->prepare_stage); - auto paint = taskflow.composed_of(data->paint_stage); - prepare.precede(paint); - stage_tasks.emplace(root, Stage_Tasks{prepare, paint}); + for (auto* renderable : renderables) { + Root* root = renderable; + auto* data = prepare_dependencies.private_data(root); + if (!data) data = paint_dependencies.private_data(root); + if (!data) continue; + auto* dispatch = data->dispatch; + auto prepare_if = taskflow.emplace([data, dispatch, root] { + auto& state = *dispatch->state_access(root); + state.prepare_graph_rebuilt = false; + state.prepare_execution_time_ns = 0; + bool rebuild = dispatch->prepare_rebuild_predicate(root); + if (!data->prepare_graph_built || rebuild) { + data->prepare_graph = dispatch->prepare_builder(root, detail::task_memory_resource()); + data->prepare_graph_built = true; + state.prepare_graph_rebuilt = true; + root->template mark_dirty(); + } + bool dirty = root->template dirty(); + state.prepare_dirty = dirty; + state.prepare_task_count = data->prepare_graph.num_tasks(); + state.prepare_executed = dispatch->prepare_predicate(root, dirty); + return state.prepare_executed ? 0 : 1; + }).name("renderable.prepare.condition"); + auto prepare_run = taskflow.composed_of(data->prepare_graph).name("renderable.prepare.graph"); + auto prepare_done = taskflow.emplace([dispatch, root] { + auto& state = *dispatch->state_access(root); + if (!state.prepare_executed) return; + root->template take_dirty(); + root->template mark_dirty(); + }).name("renderable.prepare.complete"); + prepare_if.precede(prepare_run, prepare_done); + prepare_run.precede(prepare_done); + auto paint_if = taskflow.emplace([data, dispatch, root] { + auto& state = *dispatch->state_access(root); + state.paint_graph_rebuilt = false; + state.paint_execution_time_ns = 0; + bool rebuild = dispatch->paint_rebuild_predicate(root); + if (!data->paint_graph_built || rebuild) { + data->paint_graph = dispatch->paint_builder(root, detail::task_memory_resource()); + data->paint_graph_built = true; + state.paint_graph_rebuilt = true; + root->template mark_dirty(); + } + bool dirty = root->template dirty(); + state.paint_dirty = dirty; + state.paint_task_count = data->paint_graph.num_tasks(); + state.paint_executed = dispatch->paint_predicate(root, dirty); + return state.paint_executed ? 0 : 1; + }).name("renderable.paint.condition"); + auto paint_run = taskflow.composed_of(data->paint_graph).name("renderable.paint.graph"); + auto paint_done = taskflow.emplace([dispatch, root] { + auto& state = *dispatch->state_access(root); + if (state.paint_executed) root->template take_dirty(); + dispatch->state_notify(root); + }).name("renderable.paint.complete"); + paint_if.precede(paint_run, paint_done); + paint_run.precede(paint_done); + prepare_done.precede(paint_if); + detail::bind_stage_observer(&taskflow, prepare_if.hash_value(), root, data, detail::Renderable_Stage::Prepare, detail::Stage_Observer_Point::Begin); + detail::bind_stage_observer(&taskflow, prepare_done.hash_value(), root, data, detail::Renderable_Stage::Prepare, detail::Stage_Observer_Point::End); + detail::bind_stage_observer(&taskflow, paint_if.hash_value(), root, data, detail::Renderable_Stage::Paint, detail::Stage_Observer_Point::Begin); + detail::bind_stage_observer(&taskflow, paint_done.hash_value(), root, data, detail::Renderable_Stage::Paint, detail::Stage_Observer_Point::End); + stage_tasks.emplace(root, Stage_Tasks{prepare_if, prepare_done, paint_if, paint_done}); } - auto connect_dependencies = [&](const Rely& rely, bool prepare) { + auto connect_dependencies = [&](const auto& rely, bool prepare) { rely.for_each( [&](const Rely::Node& rely_node) { - if (!rely_node.data) return; + if (!rely.private_data(rely_node)) return; auto target = stage_tasks.find(rely_node.object); if (target == stage_tasks.end()) return; std::pmr::unordered_set visited{resource}; @@ -305,11 +384,11 @@ void Scene::Private::after_exchange(Attached auto* object, Prop*, State*, const const auto* dependency = pending.back(); pending.pop_back(); if (!visited.insert(dependency->object).second) continue; - if (dependency->data) { + if (rely.private_data(*dependency)) { auto source = stage_tasks.find(dependency->object); if (source != stage_tasks.end()) { - auto source_task = prepare ? source->second.prepare : source->second.paint; - auto target_task = prepare ? target->second.prepare : target->second.paint; + auto source_task = prepare ? source->second.prepare_exit : source->second.paint_exit; + auto target_task = prepare ? target->second.prepare_entry : target->second.paint_entry; source_task.precede(target_task); } continue; @@ -321,6 +400,16 @@ void Scene::Private::after_exchange(Attached auto* object, Prop*, State*, const }; connect_dependencies(prepare_dependencies, true); connect_dependencies(paint_dependencies, false); + taskflow_dependency_count = 0; + taskflow_max_predecessors = 0; + taskflow_max_successors = 0; + taskflow.for_each_task( + [&](tf::Task task) { + taskflow_dependency_count += task.num_successors(); + taskflow_max_predecessors = std::max(taskflow_max_predecessors, task.num_predecessors()); + taskflow_max_successors = std::max(taskflow_max_successors, task.num_successors()); + } + ); object->template access_rely( [](auto& prepare_state, auto& paint_state) { if (prepare_state.dirty()) prepare_state.take_dirty();