From c83a90485963724da0fc6a01a17b181a869f72d2 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Wed, 19 Aug 2026 15:42:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kernel/env.ps1 | 100 +++++++ kernel/src/kernel/base.hpp | 282 +++++++++++++------- kernel/src/kernel/render_2D.cpp | 0 kernel/src/kernel/render_2D.hpp | 460 ++++++++++++++++++++++---------- kernel/src/test/main.cpp | 386 --------------------------- 5 files changed, 595 insertions(+), 633 deletions(-) create mode 100644 kernel/env.ps1 create mode 100644 kernel/src/kernel/render_2D.cpp diff --git a/kernel/env.ps1 b/kernel/env.ps1 new file mode 100644 index 0000000..0fcda53 --- /dev/null +++ b/kernel/env.ps1 @@ -0,0 +1,100 @@ +param( + [string]$DatovizBuildDirectory, + [string]$CTestExecutable = 'ctest', + [ValidateSet('Debug', 'Release', 'RelWithDebInfo', 'MinSizeRel')] + [string]$BuildType = 'Debug', + [string]$Toolchain = 'vs2022' +) + +$ErrorActionPreference = 'Stop' + +$render3DRoot = $PSScriptRoot +$workspaceRoot = Split-Path -Parent $render3DRoot +$aeRoot = Split-Path -Parent (Split-Path -Parent $workspaceRoot) +$cacheVariant = "${Toolchain}_${BuildType}" +$pthreadsRuntimeDirectory = Join-Path $aeRoot "cached_external_library\${cacheVariant}_render_3D\install\PThreads4W\x86_64\${BuildType}\bin" +$oneTBBRuntimeDirectory = Join-Path $aeRoot "cached_external_library\${cacheVariant}_render_kernel\install\oneTBB\bin" +$windowsSdkRuntimeDirectory = 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64' +$runtimeDirectories = @( + $pthreadsRuntimeDirectory + $oneTBBRuntimeDirectory + $windowsSdkRuntimeDirectory +) + +$vulkanSdkRoot = if ($env:VULKAN_SDK) { + $env:VULKAN_SDK +} else { + 'C:\VulkanSDK\1.4.357.0' +} +$runtimeDirectories += Join-Path $vulkanSdkRoot 'Bin' +$pthreadRuntimeName = if ($BuildType -eq 'Debug') { 'pthreadVC3d.dll' } else { 'pthreadVC3.dll' } +$oneTBBRuntimeName = if ($BuildType -eq 'Debug') { 'tbb12_debug.dll' } else { 'tbb12.dll' } +$shadercRuntimeLibrary = Join-Path $vulkanSdkRoot 'Bin\shaderc_shared.dll' +$runtimeLibraries = @( + (Join-Path $pthreadsRuntimeDirectory $pthreadRuntimeName) + (Join-Path $oneTBBRuntimeDirectory $oneTBBRuntimeName) + $shadercRuntimeLibrary +) +$linkDirectories = @( + 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.44.35207\lib\x64' + 'C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\ucrt\x64' + 'C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\um\x64' +) + +$missingDirectories = @($runtimeDirectories | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Container) }) +if ($missingDirectories.Count -ne 0) { + throw "Renderive 3D runtime directories are missing: $($missingDirectories -join ', ')" +} +$missingLibraries = @($runtimeLibraries | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Leaf) }) +if ($missingLibraries.Count -ne 0) { + throw "Renderive 3D runtime libraries are missing: $($missingLibraries -join ', ')" +} +$missingLinkDirectories = @($linkDirectories | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Container) }) +if ($missingLinkDirectories.Count -ne 0) { + throw "Renderive 3D link directories are missing: $($missingLinkDirectories -join ', ')" +} + +$pathEntries = @($env:PATH -split ';' | Where-Object { $_ }) +foreach ($runtimeDirectory in $runtimeDirectories) { + if ($pathEntries -notcontains $runtimeDirectory) { + $pathEntries = @($runtimeDirectory) + $pathEntries + } +} +$env:PATH = $pathEntries -join ';' + +$libEntries = @($env:LIB -split ';' | Where-Object { $_ }) +foreach ($linkDirectory in $linkDirectories) { + if ($libEntries -notcontains $linkDirectory) { + $libEntries = @($linkDirectory) + $libEntries + } +} +$env:LIB = $libEntries -join ';' + +$env:VULKAN_SDK = $vulkanSdkRoot +$env:DVZ_SHADERC_RUNTIME_LIBRARY = $shadercRuntimeLibrary +$env:DVZ_WHEEL_RUNTIME_DIRS = Join-Path $vulkanSdkRoot 'Bin' + +# Datoviz's upstream tests use POSIX-style absolute /tmp paths. On Windows those paths resolve +# against the current drive root, so prepare the equivalent directory on the workspace drive. +$windowsTempDirectory = Join-Path ([System.IO.Path]::GetPathRoot($workspaceRoot)) 'tmp' +New-Item -ItemType Directory -Path $windowsTempDirectory -Force | Out-Null +$env:TMPDIR = $windowsTempDirectory +$env:TEMP = $windowsTempDirectory +$env:TMP = $windowsTempDirectory + +Write-Host 'Renderive render_3D runtime environment is ready.' +Write-Host " PThreads4W: $pthreadsRuntimeDirectory" +Write-Host " oneTBB: $oneTBBRuntimeDirectory" +Write-Host " Windows SDK: $windowsSdkRuntimeDirectory" +Write-Host " Link roots: $($linkDirectories -join '; ')" +Write-Host " Vulkan SDK: $vulkanSdkRoot" +Write-Host " shaderc: $env:DVZ_SHADERC_RUNTIME_LIBRARY" +Write-Host " TEMP: $windowsTempDirectory" + +if ($DatovizBuildDirectory) { + $resolvedBuildDirectory = (Resolve-Path -LiteralPath $DatovizBuildDirectory).Path + & $CTestExecutable --test-dir $resolvedBuildDirectory -L datoviz --output-on-failure -j 1 + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } +} diff --git a/kernel/src/kernel/base.hpp b/kernel/src/kernel/base.hpp index 1ec7f75..8badad7 100644 --- a/kernel/src/kernel/base.hpp +++ b/kernel/src/kernel/base.hpp @@ -293,7 +293,6 @@ public: } }; } -struct Prepare_Data_Tag {}; namespace detail { template struct State_Rely_Key {}; @@ -310,6 +309,10 @@ constexpr const void* rely_id() { } struct Root; struct Rely; +namespace detail { +template +struct Rely_Build_Storage; +} enum class Rely_Error { self_dependency, missing_dependency, @@ -324,7 +327,6 @@ struct Root { }; private: using Call = void (*)(Root*); - Call prepare_call{}; Call exchange_call{}; detail::Pmr_Resource pmr_resource; std::pmr::vector rely_graphs; @@ -344,9 +346,6 @@ private: protected: template void bind_object_crtp() { - prepare_call = [](Root* root) { - static_cast(root)->prepare_data_crtp(); - }; exchange_call = [](Root* root) { static_cast(root)->exchange(); }; @@ -357,9 +356,7 @@ protected: public: Root() : pmr_resource(), rely_graphs(&pmr_resource), - dirty_tags(&pmr_resource) { - dirty_tags.push_back(detail::rely_id()); - } + dirty_tags(&pmr_resource) {} [[nodiscard]] std::pmr::memory_resource* memory_resource() const noexcept { return const_cast(&pmr_resource); } @@ -385,15 +382,6 @@ public: dirty_tags.erase(current); return true; } - void mark_prepare_dirty() { - mark_dirty(); - } - [[nodiscard]] bool need_prepare_data() const { - return dirty(); - } - void prepare_data() { - prepare_call(this); - } void exchange_object() { exchange_call(this); } @@ -401,8 +389,10 @@ public: struct Builder { using Prop = typename Object::Prop; std::unique_ptr object; + detail::Rely_Build_Storage rely_storage; template requires Object_Constructible - explicit Builder(Args&&... args) : object(std::make_unique(std::forward(args)...)) {} + explicit Builder(Args&&... args) : object(std::make_unique(std::forward(args)...)), + rely_storage(object->memory_resource()) {} Builder& set_pmr(Pmr pmr) { object->set_pmr_resource(pmr); return *this; @@ -425,7 +415,7 @@ public: } template requires detail::Rely_Tags_In Builder& edit_rely(Callback&& callback) { - object->template edit_initial_rely(std::forward(callback)); + rely_storage.template edit(std::forward(callback)); return *this; } template requires @@ -507,19 +497,17 @@ public: } ); } - std::unique_ptr build() { + std::expected, Rely_Error> build() { + auto check_result = check(); + if (!check_result) return std::unexpected(check_result.error()); + rely_storage.commit(object->d.rely_storage); return std::move(object); } std::expected check() const { - return object->check_rely(); + return rely_storage.check(); } }; - struct Private { - template - void prepare_data(Object*) {} - template - void after_prepare_data(Object*) {} - }; + struct Private {}; }; template concept Object_Root = @@ -622,19 +610,20 @@ struct Impl : Base { typename Object::State*, const typename Object::Prop*, const typename Object::State*) {} - template - void prepare_data(Object*) {} - template - void after_prepare_data(Object*) {} }; using Prev_Private = Private; }; struct Rely { using Error = Rely_Error; struct Node { + using Bind = void (*)(Node*, Root*); Root* object; std::pmr::vector rely; - Node(Root* value, std::pmr::memory_resource* resource) : object(value), rely(resource) {} + Bind bind; + void* data{}; + mutable void* runtime{}; + 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), rely(resource), bind(value_bind) {} }; struct Edge { Root* target; @@ -703,16 +692,27 @@ public: Rely* edit_rely; const void* target_tag; const void* target_dirty_key; + bool bind_node_data; public: - Editor(Rely& value, const void* tag, const void* dirty_key) : View(value), edit_rely(&value), target_tag(tag), target_dirty_key(dirty_key) {} + 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), + target_tag(tag), + target_dirty_key(dirty_key), + bind_node_data(value_bind_node_data) {} void clear() { edit_rely->reset(); } - Node* add(Root* object) { - return edit_rely->add_node(object); + template requires std::derived_from + Node* add(Object* object) { + return edit_rely->add_node(object, bind_node_data); + } + 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); + for (auto* object : objects) edit_rely->add_node(object, bind_node_data); } bool remove(Root* object) { return edit_rely->remove_node(object); @@ -735,7 +735,8 @@ public: source, detail::rely_id>(), target_tag, - target_dirty_key + target_dirty_key, + bind_node_data ); } template requires @@ -747,7 +748,8 @@ public: source, detail::rely_id>(), target_tag, - target_dirty_key + target_dirty_key, + bind_node_data ); } template requires @@ -759,7 +761,8 @@ public: source, detail::rely_id>(), target_tag, - target_dirty_key + target_dirty_key, + bind_node_data ); } template requires @@ -779,16 +782,22 @@ public: } }; private: + template + friend struct detail::Rely_Build_Storage; std::pmr::memory_resource* pmr_resource; std::pmr::list list; std::pmr::unordered_map nodes; std::pmr::vector edges; std::pmr::unordered_multimap dirty_edges; + std::uint64_t structure_revision{}; mutable bool bound{}; - Node* emplace_node(Root* object) { + Node* emplace_node(Root* object, Node::Bind bind) { auto current = nodes.find(object); - if (current != nodes.end()) return current->second; - list.emplace_back(object, pmr_resource); + if (current != nodes.end()) { + current->second->bind = bind; + return current->second; + } + list.emplace_back(object, pmr_resource, bind); auto* result = &list.back(); nodes.emplace(object, result); return result; @@ -848,20 +857,54 @@ private: 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; rebuild(); } - Node* add_node(Root* object) { - return emplace_node(object); + template requires std::derived_from + static Node::Bind node_bind() { + if constexpr (requires(Object* object, Node* node) { object->template bind_rely_node(node); }) { + return [](Node* node, Root* root) { + static_cast(root)->template bind_rely_node(node); + }; + } + else { + return nullptr; + } } - void reset() { + void bind_nodes() { + for (auto& node : list) { + if (node.bind) node.bind(&node, node.object); + } + } + template requires std::derived_from + Node* add_node(Object* object, bool bind_node) { + auto bind = node_bind(); + auto current = nodes.find(object); + if (current != nodes.end()) { + current->second->bind = bind; + if (bind_node && bind) bind(current->second, object); + return current->second; + } + auto* node = emplace_node(object, bind); + if (bind_node && bind) bind(node, object); + ++structure_revision; + return node; + } + void clear_data() { dirty_edges.clear(); edges.clear(); nodes.clear(); list.clear(); } + void reset() { + if (list.empty() && edges.empty()) return; + clear_data(); + ++structure_revision; + } void copy_from(const Rely& other) { - for (const auto& node : other.list) emplace_node(node.object); + for (const auto& node : other.list) emplace_node(node.object, node.bind)->data = node.data; edges = other.edges; + structure_revision = other.structure_revision; rebuild(); } void swap_data(Rely& other) noexcept { @@ -869,6 +912,7 @@ private: nodes.swap(other.nodes); edges.swap(other.edges); dirty_edges.swap(other.dirty_edges); + std::swap(structure_revision, other.structure_revision); } template Node* add_rely_runtime( @@ -876,9 +920,10 @@ private: Source* source, const void* source_key, const void* target_tag, - const void* target_dirty_key) { - auto* target_node = add_node(target); - auto* source_node = add_node(source); + const void* target_dirty_key, + bool 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; } @@ -894,6 +939,7 @@ private: } ); if (old_size == edges.size()) return false; + ++structure_revision; rebuild(); return true; } @@ -914,28 +960,38 @@ private: return &value == node; } ); + ++structure_revision; rebuild(); return true; } void clear_rely_impl(Root* target) { + auto old_size = edges.size(); std::erase_if(edges, [&](const Edge& edge) { return edge.target == target; }); + if (old_size == edges.size()) return; + ++structure_revision; rebuild(); } void remove_rely_to_impl(Root* source) { + auto old_size = edges.size(); std::erase_if(edges, [&](const Edge& edge) { return edge.source == source; }); + if (old_size == edges.size()) return; + ++structure_revision; rebuild(); } void disconnect_impl(Root* object) { + auto old_size = edges.size(); std::erase_if( edges, [&](const Edge& edge) { return edge.target == object || edge.source == object; } ); + if (old_size == edges.size()) return; + ++structure_revision; rebuild(); } bool remove_rely_impl(Root* target, Root* source) { @@ -947,6 +1003,7 @@ private: } ); if (old_size == edges.size()) return false; + ++structure_revision; rebuild(); return true; } @@ -968,7 +1025,7 @@ public: Rely& operator=(const Rely& other) { if (this == &other) return *this; unbind(); - reset(); + clear_data(); copy_from(other); return *this; } @@ -988,8 +1045,8 @@ public: else { Rely this_value(*this, other.pmr_resource); Rely other_value(other, pmr_resource); - reset(); - other.reset(); + clear_data(); + other.clear_data(); copy_from(other_value); other.copy_from(this_value); } @@ -1015,6 +1072,9 @@ public: std::pmr::memory_resource* memory_resource() const noexcept { return pmr_resource; } + std::uint64_t revision() const noexcept { + return structure_revision; + } Node* find(Root* object) { auto current = nodes.find(object); if (current == nodes.end()) return nullptr; @@ -1120,14 +1180,6 @@ public: } ); } - std::expected execute_prepare() const { - return for_each_dag( - [](const Node& node) { - if (!node.object->take_dirty()) return; - node.object->prepare_data(); - } - ); - } void emit(Root* source, const void* key) const { auto [first, last] = dirty_edges.equal_range(Dirty_Source{source, key}); for (; first != last; ++first) { @@ -1226,6 +1278,72 @@ public: ); } }; +template +struct Rely_Build_Storage> { + using allocator_type = std::pmr::polymorphic_allocator; +private: + std::tuple...> relies; + template + static consteval std::size_t index() { + return tag_index(); + } + template + std::expected check_impl() const { + if constexpr (I == sizeof...(Relies)) { + return {}; + } + else { + auto result = std::get(relies).dag(); + if (!result) return std::unexpected(result.error()); + return check_impl(); + } + } + template + void commit_one(Rely_Storage>& storage) { + using Tag = typename Rely_Type::Tag_Type; + auto& buffer = storage.template get(); + buffer.cache->swap(get()); + buffer.cache->bind_nodes(); + *buffer.render = *buffer.cache; + buffer.render->bind(); + } +public: + Rely_Build_Storage() : Rely_Build_Storage(std::pmr::get_default_resource()) {} + explicit Rely_Build_Storage(std::pmr::memory_resource* resource) : relies(std::allocator_arg, allocator_type{resource}) {} + template requires Rely_Tag_In> + Rely& get() { + return std::get()>(relies); + } + template requires Rely_Tag_In> + const Rely& get() const { + return std::get()>(relies); + } + template requires + Rely_Tags_In, Tags...> && + Rely_Edit_Callback_For + void edit(Callback&& callback) { + auto editors = std::tuple{ + Rely::Editor( + get(), + rely_id(), + rely_id>(), + false + )... + }; + std::apply( + [&](auto&... values) { + std::invoke(std::forward(callback), values...); + }, + editors + ); + } + std::expected check() const { + return check_impl(); + } + void commit(Rely_Storage>& storage) { + (commit_one(storage), ...); + } +}; template using Rely_Copy_Tuple = std::tuple...>; } @@ -1236,6 +1354,7 @@ struct Attach_Object : Obj { using Buffers = typename Obj::Buffers; using Relies = typename Obj::Relies; 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; @@ -1299,14 +1418,6 @@ private: }; walk_private(callback); } - void prepare_data_crtp() { - auto& private_data = static_cast(d); - private_data.template prepare_data(this); - auto callback = [&](auto& value) { - value.template after_prepare_data(this); - }; - walk_private(callback); - } template static std::expected check_rely_tuple(const Tuple& tuple) { if constexpr (I == std::tuple_size_v) { @@ -1326,7 +1437,8 @@ private: Rely::Editor( std::get(values), detail::rely_id(), - detail::rely_id>() + detail::rely_id>(), + true )... }; } @@ -1336,25 +1448,6 @@ private: std::index_sequence) { (d.rely_storage.template get().cache->swap(std::get(values)), ...); } - template - void sync_initial_rely() { - auto sync_one = [&]() { - auto& buffer = d.rely_storage.template get(); - *buffer.render = *buffer.cache; - buffer.render->bind(); - }; - (sync_one.template operator()(), ...); - } - template - auto make_initial_rely_editors() { - return std::tuple{ - Rely::Editor( - *d.rely_storage.template get().cache, - detail::rely_id(), - detail::rely_id>() - )... - }; - } void exchange_unlocked() { before_exchange(); static_cast&>(d).exchange(); @@ -1406,19 +1499,6 @@ public: commit_cache_rely(next, std::index_sequence_for{}); return {}; } - template requires - detail::Rely_Tags_In && - detail::Rely_Edit_Callback_For - void edit_initial_rely(Callback&& callback) { - auto editors = make_initial_rely_editors(); - std::apply( - [&](auto&... values) { - std::invoke(std::forward(callback), values...); - }, - editors - ); - sync_initial_rely(); - } std::expected check_rely() const { return d.rely_storage.check_cache(); } diff --git a/kernel/src/kernel/render_2D.cpp b/kernel/src/kernel/render_2D.cpp new file mode 100644 index 0000000..e69de29 diff --git a/kernel/src/kernel/render_2D.hpp b/kernel/src/kernel/render_2D.hpp index 012fd97..9a36d7d 100644 --- a/kernel/src/kernel/render_2D.hpp +++ b/kernel/src/kernel/render_2D.hpp @@ -1,26 +1,161 @@ #include "base.hpp" +#include namespace aethera::_2D { +struct Prepare_Data_Tag {}; +struct Paint_Tag {}; +struct Renderable; +struct Prepare_Data { + using Message = oneapi::tbb::flow::continue_msg; + using Async_Node = oneapi::tbb::flow::async_node; + using Gateway = typename Async_Node::gateway_type; + struct Node_Data { + void (*prepare)(Root*, Gateway&){}; + Renderable* renderable{}; + }; +private: + Node_Data node_data; + template + static void walk_private(Object* object, Callback& callback) { + if constexpr (requires { typename Layer::Prev_Object; }) { + if constexpr (!Reverse) walk_private(object, callback); + callback(static_cast(object->d)); + if constexpr (Reverse) walk_private(object, callback); + } + } +protected: + template + static void execute(Object* object) { + auto& private_data = static_cast(object->d); + if constexpr (requires { private_data.template prepare_data(object); }) { + private_data.template prepare_data(object); + } + auto callback = [&](auto& value) { + if constexpr (requires { value.template after_prepare_data(object); }) { + value.template after_prepare_data(object); + } + }; + walk_private(object, callback); + } + Node_Data& prepare_node_data() { + return node_data; + } +public: + template + void bind_rely_node(Rely::Node* node) { + if (!node_data.prepare) { + auto* object = static_cast(this); + object->template mark_dirty(); + node_data.prepare = [](Root* root, Gateway& gateway) { + execute(static_cast(root)); + (void)gateway.try_put(Message{}); + }; + } + node->data = &node_data; + } +}; struct Color_Cache {}; -struct Renderable : Impl> { +struct Secene; +struct Renderable : Impl>, Prepare_Data { struct Prop : Prev_Prop {}; struct State : Prev_State { bool operator==(const State&) const = default; }; +private: + using Message = oneapi::tbb::flow::continue_msg; + using Async_Node = oneapi::tbb::flow::async_node; + using Gateway = typename Async_Node::gateway_type; + using Task_Call = void (*)(void*, Gateway&); + using Task_State = std::tuple; +public: + struct Private : Prev_Private { + template requires std::invocable + static void set_sync(Double_Buffer& buffer, Context* context, bool initial) { + Task_State task{ + context, + [](void* value, Gateway& gateway) { + std::invoke(Callback, static_cast(value)); + (void)gateway.try_put(Message{}); + } + }; + *buffer.cache = task; + if (initial) *buffer.render = task; + } + template requires std::invocable + static void set_async(Double_Buffer& buffer, Context* context, bool initial) { + Task_State task{ + context, + [](void* value, Gateway& gateway) { + gateway.reserve_wait(); + std::invoke(Callback, static_cast(value), gateway); + } + }; + *buffer.cache = task; + if (initial) *buffer.render = task; + } + static void publish(Double_Buffer& buffer) { + Task_State value = *buffer.cache; + buffer.exchange(); + *buffer.cache = value; + } + template + static void prepare(Object* object) { + Prepare_Data::execute(object); + } + template + static void bind(Renderable* renderable, Object* object) { + renderable->paint_call = [](Renderable* value) { + auto* current = static_cast(value); + auto& private_data = static_cast(current->d); + private_data.template paint(current); + }; + set_sync<&Private::template prepare>(renderable->prepare_task, object, true); + set_sync<&Object::paint>(renderable->paint_task, object, true); + } + template + void prepare_data(Object*) {} + template + void after_prepare_data(Object* object) { + static_cast(object)->paint_dirty = true; + } + template + void after_exchange( + Object* object, + typename Object::Prop*, + typename Object::State*, + const typename Object::Prop*, + const typename Object::State*) { + auto* renderable = static_cast(object); + publish(renderable->prepare_task); + publish(renderable->paint_task); + } + template + void paint(Object*) {} + }; private: using Call = void (*)(Renderable*); Call paint_call{}; bool paint_dirty{true}; + Double_Buffer prepare_task; + Double_Buffer paint_task; + friend struct Secene; protected: template void bind_object_crtp() { Prev_Object::template bind_object_crtp(); - paint_call = [](Renderable* renderable) { - auto* object = static_cast(renderable); - auto& private_data = static_cast(object->d); - private_data.template paint(object); - }; + Private::bind(this, static_cast(this)); } public: + template + void bind_rely_node(Rely::Node* node) { + Prepare_Data::template bind_rely_node(node); + prepare_node_data().renderable = this; + } + void mark_prepare_dirty() { + mark_dirty(); + } + [[nodiscard]] bool need_prepare_data() const { + return dirty(); + } void mark_paint_dirty() { paint_dirty = true; } @@ -32,84 +167,155 @@ public: void paint() { paint_call(this); } - struct Private : Prev_Private { - template - void after_prepare_data(Object* object) { - object->mark_paint_dirty(); - } - template - void paint(Object*) {} - }; -}; -struct Layout_Tag {}; -struct Extra_Tag {}; -struct Secene : Impl< - Secene, - Root, - Rely_Type, - Rely_Type, - Rely_Type -> { - struct Prop : Prev_Prop {}; - struct State : Prev_State {}; -private: - using Call = void (*)(const Secene*); - Call prepare_execute_call{}; - Call paint_execute_call{}; - std::pmr::vector renderables; - std::pmr::unordered_map renderable_nodes; - void bind_renderables() { - renderable_nodes.clear(); - for (auto* object : renderables) renderable_nodes.emplace(static_cast(object), object); + template requires std::invocable + void set_prepare_task(Context* context) { + Private::template set_sync(prepare_task, context, false); } -protected: - template - void bind_object_crtp() { - Prev_Object::template bind_object_crtp(); - prepare_execute_call = [](const Secene* scene) { - auto* object = static_cast(scene); - (void)object->template render_rely().execute_prepare(); - }; - paint_execute_call = [](const Secene* scene) { - auto* object = static_cast(scene); - (void)object->template render_rely().for_each_dag( - [&](const auto& node) { - auto current = scene->renderable_nodes.find(node.object); - if (current == scene->renderable_nodes.end()) return; - auto* renderable = current->second; - if (!renderable->take_paint_dirty()) return; - renderable->paint(); - } - ); - }; + template requires std::invocable + void set_prepare_async_task(Context* context) { + Private::template set_async(prepare_task, context, false); } -public: - Secene() : renderables(memory_resource()), - renderable_nodes(memory_resource()) {} - void execute_prepare() const { - prepare_execute_call(this); + template requires std::invocable + void set_paint_task(Context* context) { + Private::template set_sync(paint_task, context, false); } - void execute_paint() const { - paint_execute_call(this); + template requires std::invocable + void set_paint_async_task(Context* context) { + Private::template set_async(paint_task, context, false); } template struct Builder : Prev_Builder { using Base = Prev_Builder; using Base::Base; - template requires std::derived_from - Builder& add_renderable(std::initializer_list list) { - for (auto* object : list) { - this->object->renderables.push_back(object); - this->template add_rely_node(object); - } + template requires std::invocable + Builder& set_prepare_task() { + auto* renderable = static_cast(this->object.get()); + Private::template set_sync(renderable->prepare_task, this->object.get(), true); return *this; } - std::unique_ptr build() { - this->object->bind_renderables(); - return std::move(this->object); + template requires std::invocable + Builder& set_prepare_async_task() { + auto* renderable = static_cast(this->object.get()); + Private::template set_async(renderable->prepare_task, this->object.get(), true); + return *this; + } + template requires std::invocable + Builder& set_paint_task() { + auto* renderable = static_cast(this->object.get()); + Private::template set_sync(renderable->paint_task, this->object.get(), true); + return *this; + } + template requires std::invocable + Builder& set_paint_async_task() { + auto* renderable = static_cast(this->object.get()); + Private::template set_async(renderable->paint_task, this->object.get(), true); + return *this; } }; +}; +struct Secene : Impl, Rely_Type> { + struct Prop : Prev_Prop {}; + struct State : Prev_State {}; struct Private : Prev_Private { + using Message = oneapi::tbb::flow::continue_msg; + using Async_Node = oneapi::tbb::flow::async_node; + template + struct Stage : Pinned { + struct Node : Pinned { + using Gate = oneapi::tbb::flow::continue_node; + static auto body(Root* object, Prepare_Data::Node_Data* data) requires std::same_as { + return [object, data](const Message&, typename Async_Node::gateway_type& gateway) { + if (!object->take_dirty()) { + (void)gateway.try_put(Message{}); + return; + } + if (!data->renderable) { + data->prepare(object, gateway); + return; + } + auto& task = *data->renderable->prepare_task.render; + std::get<1>(task)(std::get<0>(task), gateway); + }; + } + static auto body(Root*, Prepare_Data::Node_Data* data) requires std::same_as { + return [renderable = data->renderable](const Message&, typename Async_Node::gateway_type& gateway) { + if (!renderable->take_paint_dirty()) { + (void)gateway.try_put(Message{}); + return; + } + auto& task = *renderable->paint_task.render; + std::get<1>(task)(std::get<0>(task), gateway); + }; + } + Gate gate; + Async_Node execute; + Node(oneapi::tbb::flow::graph& graph, Root* object, Prepare_Data::Node_Data* data) : + gate( + graph, + [](const Message&) { + return Message{}; + } + ), + execute(graph, oneapi::tbb::flow::unlimited, body(object, data)) { + oneapi::tbb::flow::make_edge(gate, execute); + } + }; + oneapi::tbb::flow::graph graph; + oneapi::tbb::flow::broadcast_node start; + std::pmr::list nodes; + std::uint64_t revision; + template + Stage(const Object* object, std::pmr::memory_resource* resource) : + start(graph), + nodes(resource), + revision(object->template render_rely().revision()) { + (void)object->template for_each_rely_dag( + [&](const Rely::View& view, const Rely::Node& rely_node) { + auto* data = static_cast(rely_node.data); + nodes.emplace_back(graph, rely_node.object, data); + auto* node = &nodes.back(); + rely_node.runtime = node; + auto dependencies = view.get_rely(rely_node.object); + if (dependencies.empty()) oneapi::tbb::flow::make_edge(start, node->gate); + for (const auto* dependency : dependencies) { + auto* source = static_cast(dependency->runtime); + oneapi::tbb::flow::make_edge(source->execute, node->gate); + } + } + ); + } + void run() { + (void)start.try_put(Message{}); + graph.wait_for_all(); + } + }; + template + static void update_stages(const Object* object) { + const auto& prepare_rely = object->template render_rely(); + const auto& paint_rely = object->template render_rely(); + if ( + object->prepare_stage && + object->paint_stage && + object->prepare_stage->revision == prepare_rely.revision() && + object->paint_stage->revision == paint_rely.revision()) return; + if (object->prepare_stage) object->prepare_stage->graph.wait_for_all(); + if (object->paint_stage) object->paint_stage->graph.wait_for_all(); + auto prepare = std::make_unique>(object, object->memory_resource()); + auto paint = std::make_unique>(object, object->memory_resource()); + object->prepare_stage = std::move(prepare); + object->paint_stage = std::move(paint); + } + template + static void execute_prepare(const Object* object) { + if (!object->prepare_stage || !object->paint_stage) update_stages(object); + object->prepare_stage->run(); + } + template + static void execute_paint(const Object* object) { + if (!object->prepare_stage || !object->paint_stage) update_stages(object); + object->prepare_stage->run(); + object->paint_stage->run(); + } template void after_exchange( Object* object, @@ -121,87 +327,49 @@ public: object->for_each_render_rely( [&](const Rely& rely) { rely.for_each( - [&](const auto& node) { + [&](const Rely::Node& node) { if (exchanged.insert(node.object).second) node.object->exchange_object(); } ); } ); + update_stages(object); + } + }; +private: + using Call = void (*)(const Secene*); + Call prepare_execute_call{}; + Call paint_execute_call{}; + mutable std::unique_ptr> prepare_stage; + mutable std::unique_ptr> paint_stage; +protected: + template + void bind_object_crtp() { + Prev_Object::template bind_object_crtp(); + prepare_execute_call = [](const Secene* scene) { + Private::execute_prepare(static_cast(scene)); + }; + paint_execute_call = [](const Secene* scene) { + Private::execute_paint(static_cast(scene)); + }; + } +public: + void execute_prepare() const { + prepare_execute_call(this); + } + void execute_paint() const { + paint_execute_call(this); + } + template + struct Builder : Prev_Builder { + using Base = Prev_Builder; + using Base::Base; + std::expected, Rely_Error> build() { + auto result = Base::build(); + if (!result) return std::unexpected(result.error()); + Private::update_stages(result->get()); + return result; } }; }; } -struct SEP_root : aethera::Root {}; -struct SEP : aethera::Impl>> { - struct Prop : Prev_Prop { - int value{}; - }; - struct State : Prev_State { - int a{}; - }; - struct Private : Prev_Private { - template - void before_exchange( - Object*, - typename Object::Prop*, - typename Object::State* inside_state, - const typename Object::Prop*, - const typename Object::State*) { - ++inside_state->a; - } - }; -}; -using SEP_2 = aethera::Attach_Object; -struct SEP_Child : aethera::Impl { - struct Prop : Prev_Prop { - int child_value{}; - }; - struct State : Prev_State { - int b{}; - }; - struct Private : Prev_Private { - template - void before_exchange( - Object*, - typename Object::Prop*, - typename Object::State* inside_state, - const typename Object::Prop*, - const typename Object::State*) { - inside_state->b += 10; - } - }; -}; -using SEP_Child_2 = aethera::Attach_Object; -struct Data_Cache { - int value{}; -}; -struct Data_Object : aethera::Impl> { - struct Prop : Prev_Prop {}; - struct State : Prev_State { - int value{}; - int other{}; - }; - int id; - std::vector* order; - Data_Object(int id, std::vector* order) : id(id), order(order) {} - struct Private : Prev_Private { - int previous_value{}; - template - void before_state_set(Object*, Member Owner::* member, typename Object::State* state) { - if constexpr (std::same_as) { - if (member == &Data_Object::State::value) previous_value = state->*member; - } - } - template - void after_state_set(Object* object, Member Owner::* member, typename Object::State* state) { - if constexpr (std::same_as) { - if (member == &Data_Object::State::value && state->*member != previous_value) object->mark_prepare_dirty(); - } - } - template - void prepare_data(Object* object) { - object->order->push_back(object->id); - object->template buffer().value = object->id; - } - }; -}; diff --git a/kernel/src/test/main.cpp b/kernel/src/test/main.cpp index 71fee59..9e7e69e 100644 --- a/kernel/src/test/main.cpp +++ b/kernel/src/test/main.cpp @@ -1,390 +1,4 @@ #include "../kernel/render_2D.hpp" -using Data_Object_2 = aethera::Attach_Object; -struct Test_Cache { - int value{}; -}; -struct Test_Cache_Tag {}; -struct Test_Renderable : aethera::Impl< - Test_Renderable, - aethera::_2D::Renderable, - aethera::Tagged_Buffer, - aethera::Tagged_Buffer -> { - struct Prop : Prev_Prop {}; - struct State : Prev_State { - int value{}; - int other{}; - bool operator==(const State&) const = default; - }; - int id; - std::vector* order; - Test_Renderable(int id, std::vector* order) : id(id), order(order) {} - struct Private : Prev_Private { - int previous_value{}; - template - void before_state_set(Object*, Member Owner::* member, typename Object::State* state) { - if constexpr (std::same_as) { - if (member == &Test_Renderable::State::value) previous_value = state->*member; - } - } - template - void after_state_set(Object* object, Member Owner::* member, typename Object::State* state) { - if constexpr (std::same_as) { - if (member == &Test_Renderable::State::value && state->*member != previous_value) object->mark_prepare_dirty(); - } - } - template - void prepare_data(Object* object) { - object->order->push_back(object->id); - object->template buffer().value = object->id; - } - template - void paint(Object* object) { - object->order->push_back(object->id * 10); - } - }; -}; -using Test_Renderable_2 = aethera::Attach_Object; -struct Pmr_Buffer_Tag {}; -struct Pmr_Object : aethera::Impl< - Pmr_Object, - SEP_root, - aethera::Tagged_Buffer> -> { - struct Prop : Prev_Prop {}; - struct State : Prev_State {}; - struct Private : Prev_Private {}; -}; -using Pmr_Object_2 = aethera::Attach_Object; -struct Counting_Resource : std::pmr::memory_resource { - std::pmr::memory_resource* upstream{std::pmr::new_delete_resource()}; - std::size_t allocations{}; -private: - void* do_allocate(std::size_t bytes, std::size_t alignment) override { - ++allocations; - return upstream->allocate(bytes, alignment); - } - void do_deallocate(void* pointer, std::size_t bytes, std::size_t alignment) override { - upstream->deallocate(pointer, bytes, alignment); - } - bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { - return this == &other; - } -}; -static_assert(aethera::Object); -static_assert(aethera::Object); -static_assert(aethera::Object); -static_assert(aethera::Object); -static_assert(aethera::Object); -static_assert(aethera::Object); -static_assert(aethera::Object); int main() { - using Secene_Object = aethera::Attach_Object; - using Prepare_Tag = aethera::Prepare_Data_Tag; - using Layout_Tag = aethera::_2D::Layout_Tag; - using Extra_Tag = aethera::_2D::Extra_Tag; - std::vector order; - std::vector direct_order; - Data_Object_2 direct_data(9, &direct_order); - direct_data.prepare_data(); - direct_data.exchange_object(); - if (direct_order != std::vector({9})) return 112; - if (direct_data.render_buffer().value != 9) return 113; - Test_Renderable_2 direct_renderable(8, &direct_order); - direct_renderable.paint(); - if (direct_order != std::vector({9, 80})) return 114; - Secene_Object direct_scene; - direct_scene.execute_prepare(); - direct_scene.execute_paint(); - Counting_Resource buffer_resource; - Pmr_Object_2::Builder pmr_buffer_builder; - pmr_buffer_builder.set_pmr(buffer_resource); - std::pmr::vector pmr_initial; - pmr_initial.assign({1, 2, 3}); - pmr_buffer_builder.set(pmr_initial); - auto pmr_buffer_object = pmr_buffer_builder.build(); - if (pmr_buffer_object->pmr().resource() != &buffer_resource) return 108; - if (pmr_buffer_object->render_buffer().get_allocator().resource() != pmr_buffer_object->memory_resource()) return 109; - pmr_buffer_object->buffer().push_back(4); - if (buffer_resource.allocations == 0) return 110; - Counting_Resource scene_resource; - // Buffer Builder initialization writes cache/render together. - Data_Object_2::Builder data_builder(4, &order); - data_builder.set(Data_Cache{40}); - auto data_object = data_builder.build(); - Test_Renderable_2::Builder c_builder(3, &order); - c_builder.set(Test_Cache{30}); - c_builder.set(Test_Cache{300}); - auto c_object = c_builder.build(); - Test_Renderable_2::Builder b_builder(2, &order); - b_builder.set(Test_Cache{20}); - auto b_object = b_builder.build(); - Test_Renderable_2::Builder a_builder(1, &order); - a_builder.set(Test_Cache{10}); - auto a_object = a_builder.build(); - if (data_object->render_buffer().value != 40) return 1; - if (c_object->render_buffer().value != 30) return 2; - if (c_object->render_buffer().value != 300) return 3; - if (b_object->render_buffer().value != 20) return 4; - if (a_object->render_buffer().value != 10) return 5; - // Builder initial rely editing is direct; check() validates all rely graphs together. - Secene_Object::Builder scene; - scene.set_pmr(scene_resource); - scene.add_renderable({a_object.get(), b_object.get(), c_object.get()}); - scene.add_rely< - Prepare_Tag, - &Data_Object::State::value - >(c_object.get(), data_object.get()); - scene.add_rely< - Prepare_Tag, - &Test_Renderable::State::value - >(b_object.get(), c_object.get()); - scene.add_rely< - Prepare_Tag, - Test_Cache - >(a_object.get(), b_object.get()); - scene.add_rely< - Prepare_Tag, - Test_Cache_Tag - >(a_object.get(), c_object.get()); - scene.edit_rely( - [&](auto& layout, auto& extra) { - layout.template add_rely<&Data_Object::State::other>( - c_object.get(), - data_object.get() - ); - extra.template add_dirty_rely( - b_object.get(), - c_object.get() - ); - } - ); - scene.add_rely< - Prepare_Tag, - Test_Cache - >(c_object.get(), a_object.get()); - auto invalid_initial = scene.check(); - if (invalid_initial) return 6; - if (invalid_initial.error() != aethera::Rely_Error::cycle) return 7; - scene.remove_rely< - Prepare_Tag, - Test_Cache - >(c_object.get(), a_object.get()); - auto initial_check = scene.check(); - if (!initial_check) return 8; - auto scene_object = scene.build(); - if (scene_object->pmr().resource() != &scene_resource) return 103; - if (scene_object->render_rely().memory_resource() != scene_object->memory_resource()) return 104; - if (scene_resource.allocations == 0) return 105; - // Rely keeps node/edge introspection and DAG order in the same graph. - const auto& prepare_graph = scene_object->render_rely(); - if (prepare_graph.size() != 4) return 9; - if (!prepare_graph.contains(data_object.get())) return 10; - if (!prepare_graph.contains_rely(c_object.get(), data_object.get())) return 11; - if (!prepare_graph.contains_rely(b_object.get(), c_object.get())) return 12; - if (!prepare_graph.contains_rely(a_object.get(), b_object.get())) return 13; - if (prepare_graph.find_edges(a_object.get(), c_object.get()).size() != 1) return 14; - auto initial_dag = prepare_graph.dag(); - if (!initial_dag) return 15; - if (initial_dag->size() != 4) return 16; - if ((*initial_dag)[0]->object != data_object.get()) return 17; - if ((*initial_dag)[1]->object != c_object.get()) return 18; - if ((*initial_dag)[2]->object != b_object.get()) return 19; - if ((*initial_dag)[3]->object != a_object.get()) return 20; - std::vector view_order; - bool view_query_ok{true}; - auto view_walk = scene_object->for_each_rely_dag( - [&](const aethera::Rely::View& view, const aethera::Rely::Node& node) { - view_order.push_back(node.object); - if (node.object == c_object.get()) { - auto dependencies = view.get_rely(node.object); - view_query_ok = view_query_ok && - dependencies.size() == 1 && - dependencies[0]->object == data_object.get(); - } - if (node.object == a_object.get()) { - auto dependencies = view.get_rely(node.object); - bool has_b{}; - bool has_c{}; - for (const auto* dependency : dependencies) { - has_b = has_b || dependency->object == b_object.get(); - has_c = has_c || dependency->object == c_object.get(); - } - auto dependents = view.find_rely_to(c_object.get()); - bool has_b_dependent{}; - bool has_a_dependent{}; - for (const auto* dependent : dependents) { - has_b_dependent = has_b_dependent || dependent->object == b_object.get(); - has_a_dependent = has_a_dependent || dependent->object == a_object.get(); - } - view_query_ok = view_query_ok && - dependencies.size() == 2 && - has_b && - has_c && - dependents.size() == 2 && - has_b_dependent && - has_a_dependent && - view.find_edges(a_object.get(), c_object.get()).size() == 1; - } - } - ); - if (!view_walk) return 100; - if (!view_query_ok) return 101; - if (view_order != std::vector({ - data_object.get(), - c_object.get(), - b_object.get(), - a_object.get() - })) - return 102; - if (!data_object->need_prepare_data()) return 21; - if (!c_object->need_prepare_data()) return 22; - if (!b_object->need_prepare_data()) return 23; - if (!a_object->need_prepare_data()) return 24; - // Initial prepare follows DAG topology; paint only visits Renderable nodes. - scene_object->execute_prepare(); - if (order != std::vector({4, 3, 2, 1})) return 25; - if (data_object->need_prepare_data()) return 26; - if (c_object->need_prepare_data()) return 27; - if (b_object->need_prepare_data()) return 28; - if (a_object->need_prepare_data()) return 29; - order.clear(); - scene_object->execute_paint(); - if (order != std::vector({30, 20, 10})) return 30; - order.clear(); - // State member dirty -> prepare dirty -> Buffer dirty can continue infecting downstream nodes. - c_object->update_state<&Test_Renderable::State::value>(1); - if (!c_object->need_prepare_data()) return 31; - if (!b_object->need_prepare_data()) return 32; - if (a_object->need_prepare_data()) return 33; - scene_object->execute_prepare(); - if (order != std::vector({3, 2, 1})) return 34; - order.clear(); - scene_object->execute_paint(); - if (order != std::vector({30, 20, 10})) return 35; - order.clear(); - // Explicit Buffer Tag is an independent dirty source from the one-parameter Tagged_Buffer Tag. - c_object->buffer().value = 301; - if (!a_object->need_prepare_data()) return 36; - if (b_object->need_prepare_data()) return 37; - if (c_object->need_prepare_data()) return 38; - if (c_object->render_buffer().value != 300) return 39; - scene_object->execute_prepare(); - if (order != std::vector({1})) return 40; - order.clear(); - scene_object->execute_paint(); - if (order != std::vector({10})) return 41; - order.clear(); - // State source can target a custom dirty Tag, which can itself be another rely source. - data_object->update_state<&Data_Object::State::other>(1); - if (data_object->need_prepare_data()) return 42; - if (!c_object->dirty()) return 43; - if (!b_object->dirty()) return 44; - if (a_object->dirty()) return 45; - if (!c_object->take_dirty()) return 46; - if (!b_object->take_dirty()) return 47; - // Runtime multi-rely edit is transactional: any invalid graph rolls every edited cache graph back. - auto invalid_runtime = scene_object->edit_rely( - [&](auto& prepare, auto& extra) { - prepare.template add_rely( - c_object.get(), - a_object.get() - ); - extra.template add_rely<&Data_Object::State::other>( - a_object.get(), - data_object.get() - ); - } - ); - if (invalid_runtime) return 48; - if (invalid_runtime.error() != aethera::Rely_Error::cycle) return 49; - if (scene_object->rely().contains_rely(c_object.get(), a_object.get())) return 50; - if (scene_object->rely().contains_rely(a_object.get(), data_object.get())) return 51; - // A valid multi-rely transaction commits all edited cache graphs together. - bool editor_view_ok{}; - auto valid_runtime = scene_object->edit_rely( - [&](auto& prepare, auto& extra) { - editor_view_ok = - prepare.contains(a_object.get()) && - prepare.contains_rely(a_object.get(), b_object.get()) && - prepare.find_edges(a_object.get(), b_object.get()).size() == 1; - prepare.template remove_rely( - a_object.get(), - b_object.get() - ); - extra.template add_rely<&Data_Object::State::other>( - a_object.get(), - data_object.get() - ); - } - ); - if (!editor_view_ok) return 52; - if (!valid_runtime) return 53; - if (scene_object->rely().contains_rely(a_object.get(), b_object.get())) return 54; - if (!scene_object->rely().contains_rely(a_object.get(), data_object.get())) return 55; - if (!scene_object->render_rely().contains_rely(a_object.get(), b_object.get())) return 56; - if (scene_object->render_rely().contains_rely(a_object.get(), data_object.get())) return 57; - // Cache graph edits do not affect active render graphs before exchange(). - data_object->update_state<&Data_Object::State::other>(2); - if (!c_object->dirty()) return 58; - if (!b_object->dirty()) return 59; - if (a_object->dirty()) return 60; - c_object->take_dirty(); - b_object->take_dirty(); - scene_object->exchange(); - if (c_object->render_buffer().value != 301) return 61; - if (scene_object->render_rely().contains_rely(a_object.get(), b_object.get())) return 62; - if (!scene_object->render_rely().contains_rely(a_object.get(), data_object.get())) return 63; - // After exchange(), the new render graph becomes the active dirty propagation graph. - data_object->update_state<&Data_Object::State::other>(3); - if (!c_object->dirty()) return 64; - if (!b_object->dirty()) return 65; - if (!a_object->dirty()) return 66; - c_object->take_dirty(); - b_object->take_dirty(); - a_object->take_dirty(); - // Removed one-parameter Tagged_Buffer edge no longer propagates, while another Buffer Tag edge remains active. - b_object->buffer().value = 222; - if (a_object->need_prepare_data()) return 67; - c_object->buffer().value = 302; - if (!a_object->need_prepare_data()) return 68; - scene_object->execute_prepare(); - if (order != std::vector({1})) return 69; - order.clear(); - scene_object->execute_paint(); - if (order != std::vector({10})) return 70; - order.clear(); - // Inherited Prop/State/Private hooks and one-parameter Tagged_Buffer still compose through Impl layers. - SEP_Child_2::Builder sep_builder; - sep_builder.set(&SEP_Child::Prop::value, 123); - sep_builder.set(&SEP_Child::Prop::child_value, 456); - sep_builder.set>(std::vector{1, 2, 3}); - auto sep = sep_builder.build(); - if (sep->d.render->value != 0) return 71; - if (sep->d.render->child_value != 0) return 72; - if (sep->render_buffer>() != std::vector({1, 2, 3})) return 73; - sep->update_state<&SEP_Child::State::a>(1); - sep->update_state<&SEP_Child::State::b>(2); - sep->exchange( - [](const SEP_Child::State& state) { - if (state.a != 2) std::terminate(); - if (state.b != 12) std::terminate(); - } - ); - if (sep->d.render->value != 123) return 74; - if (sep->d.render->child_value != 456) return 75; - Counting_Resource global_resource; - auto* old_default = aethera::Pmr::set_default_resource(global_resource); - { - Pmr_Object_2::Builder global_builder; - std::pmr::vector global_initial; - global_initial.assign({5, 6, 7}); - global_builder.set(global_initial); - auto global_object = global_builder.build(); - if (global_object->pmr().resource() != &global_resource) return 106; - if (global_object->render_buffer().get_allocator().resource() != global_object->memory_resource()) return 107; - if (global_resource.allocations == 0) return 111; - } - aethera::Pmr::set_default_resource(old_default); return 0; }