This commit is contained in:
2026-08-19 15:42:28 +08:00
parent a6b350e087
commit c83a904859
5 changed files with 595 additions and 633 deletions
+100
View File
@@ -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
}
}
+181 -101
View File
@@ -293,7 +293,6 @@ public:
}
};
}
struct Prepare_Data_Tag {};
namespace detail {
template <auto Member>
struct State_Rely_Key {};
@@ -310,6 +309,10 @@ constexpr const void* rely_id() {
}
struct Root;
struct Rely;
namespace detail {
template <typename Tuple>
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<const Rely*> rely_graphs;
@@ -344,9 +346,6 @@ private:
protected:
template <typename Object>
void bind_object_crtp() {
prepare_call = [](Root* root) {
static_cast<Object*>(root)->prepare_data_crtp();
};
exchange_call = [](Root* root) {
static_cast<Object*>(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<Prepare_Data_Tag>());
}
dirty_tags(&pmr_resource) {}
[[nodiscard]] std::pmr::memory_resource* memory_resource() const noexcept {
return const_cast<detail::Pmr_Resource*>(&pmr_resource);
}
@@ -385,15 +382,6 @@ public:
dirty_tags.erase(current);
return true;
}
void mark_prepare_dirty() {
mark_dirty<Prepare_Data_Tag>();
}
[[nodiscard]] bool need_prepare_data() const {
return dirty<Prepare_Data_Tag>();
}
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> object;
detail::Rely_Build_Storage<typename Object::Relies> rely_storage;
template <typename... Args> requires Object_Constructible<Object, Args...>
explicit Builder(Args&&... args) : object(std::make_unique<Object>(std::forward<Args>(args)...)) {}
explicit Builder(Args&&... args) : object(std::make_unique<Object>(std::forward<Args>(args)...)),
rely_storage(object->memory_resource()) {}
Builder& set_pmr(Pmr pmr) {
object->set_pmr_resource(pmr);
return *this;
@@ -425,7 +415,7 @@ public:
}
template <typename... Rely_Tags, typename Callback> requires detail::Rely_Tags_In<typename Object::Relies, Rely_Tags...>
Builder& edit_rely(Callback&& callback) {
object->template edit_initial_rely<Rely_Tags...>(std::forward<Callback>(callback));
rely_storage.template edit<Rely_Tags...>(std::forward<Callback>(callback));
return *this;
}
template <typename Rely_Tag, typename Node_Object> requires
@@ -507,19 +497,17 @@ public:
}
);
}
std::unique_ptr<Object> build() {
std::expected<std::unique_ptr<Object>, 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<void, Rely_Error> check() const {
return object->check_rely();
return rely_storage.check();
}
};
struct Private {
template <typename Object>
void prepare_data(Object*) {}
template <typename Object>
void after_prepare_data(Object*) {}
};
struct Private {};
};
template <typename T>
concept Object_Root =
@@ -622,19 +610,20 @@ struct Impl : Base {
typename Object::State*,
const typename Object::Prop*,
const typename Object::State*) {}
template <typename Object>
void prepare_data(Object*) {}
template <typename Object>
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<Node*> 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 <typename Object> requires std::derived_from<Object, Root>
Node* add(Object* object) {
return edit_rely->add_node(object, bind_node_data);
}
template <typename Object> requires std::derived_from<Object, Root>
void add(std::initializer_list<Object*> objects) {
for (auto* object : objects) edit_rely->add_node(object, bind_node_data);
}
void add(std::initializer_list<Root*> 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<detail::State_Rely_Key<Member>>(),
target_tag,
target_dirty_key
target_dirty_key,
bind_node_data
);
}
template <typename Buffer_Tag, typename Target, typename Source> requires
@@ -747,7 +748,8 @@ public:
source,
detail::rely_id<detail::Buffer_Rely_Key<Buffer_Tag>>(),
target_tag,
target_dirty_key
target_dirty_key,
bind_node_data
);
}
template <typename Source_Tag, typename Target, typename Source> requires
@@ -759,7 +761,8 @@ public:
source,
detail::rely_id<detail::Dirty_Rely_Key<Source_Tag>>(),
target_tag,
target_dirty_key
target_dirty_key,
bind_node_data
);
}
template <auto Member, typename Target, typename Source> requires
@@ -779,16 +782,22 @@ public:
}
};
private:
template <typename Tuple>
friend struct detail::Rely_Build_Storage;
std::pmr::memory_resource* pmr_resource;
std::pmr::list<Node> list;
std::pmr::unordered_map<Root*, Node*> nodes;
std::pmr::vector<Edge> edges;
std::pmr::unordered_multimap<Dirty_Source, Dirty_Target, Dirty_Source_Hash> 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 <typename Object> requires std::derived_from<Object, Root>
static Node::Bind node_bind() {
if constexpr (requires(Object* object, Node* node) { object->template bind_rely_node<Object>(node); }) {
return [](Node* node, Root* root) {
static_cast<Object*>(root)->template bind_rely_node<Object>(node);
};
}
else {
return nullptr;
}
}
void reset() {
void bind_nodes() {
for (auto& node : list) {
if (node.bind) node.bind(&node, node.object);
}
}
template <typename Object> requires std::derived_from<Object, Root>
Node* add_node(Object* object, bool bind_node) {
auto bind = node_bind<Object>();
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 <typename Target, typename Source>
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<void, Error> execute_prepare() const {
return for_each_dag(
[](const Node& node) {
if (!node.object->take_dirty<Prepare_Data_Tag>()) 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 <Rely_Mechanism... Relies>
struct Rely_Build_Storage<std::tuple<Relies...>> {
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
private:
std::tuple<std::conditional_t<true, Rely, Relies>...> relies;
template <typename Tag>
static consteval std::size_t index() {
return tag_index<Tag, Relies...>();
}
template <std::size_t I = 0>
std::expected<void, Rely_Error> check_impl() const {
if constexpr (I == sizeof...(Relies)) {
return {};
}
else {
auto result = std::get<I>(relies).dag();
if (!result) return std::unexpected(result.error());
return check_impl<I + 1>();
}
}
template <typename Rely_Type>
void commit_one(Rely_Storage<std::tuple<Relies...>>& storage) {
using Tag = typename Rely_Type::Tag_Type;
auto& buffer = storage.template get<Tag>();
buffer.cache->swap(get<Tag>());
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 <typename Tag> requires Rely_Tag_In<Tag, std::tuple<Relies...>>
Rely& get() {
return std::get<index<Tag>()>(relies);
}
template <typename Tag> requires Rely_Tag_In<Tag, std::tuple<Relies...>>
const Rely& get() const {
return std::get<index<Tag>()>(relies);
}
template <typename... Tags, typename Callback> requires
Rely_Tags_In<std::tuple<Relies...>, Tags...> &&
Rely_Edit_Callback_For<Callback, Tags...>
void edit(Callback&& callback) {
auto editors = std::tuple{
Rely::Editor(
get<Tags>(),
rely_id<Tags>(),
rely_id<Dirty_Rely_Key<Tags>>(),
false
)...
};
std::apply(
[&](auto&... values) {
std::invoke(std::forward<Callback>(callback), values...);
},
editors
);
}
std::expected<void, Rely_Error> check() const {
return check_impl();
}
void commit(Rely_Storage<std::tuple<Relies...>>& storage) {
(commit_one<Relies>(storage), ...);
}
};
template <typename... Tags>
using Rely_Copy_Tuple = std::tuple<std::conditional_t<true, Rely, Tags>...>;
}
@@ -1236,6 +1354,7 @@ struct Attach_Object : Obj {
using Buffers = typename Obj::Buffers;
using Relies = typename Obj::Relies;
using Builder = typename Obj::template Builder<Attach_Object>;
using Attached_Object = Obj;
struct Private : Obj::Private, Double_Buffer<Prop> {
using Allocator = std::pmr::polymorphic_allocator<std::byte>;
Double_Buffer<State> state;
@@ -1299,14 +1418,6 @@ private:
};
walk_private<true, Obj>(callback);
}
void prepare_data_crtp() {
auto& private_data = static_cast<typename Obj::Private&>(d);
private_data.template prepare_data<Attach_Object>(this);
auto callback = [&](auto& value) {
value.template after_prepare_data<Attach_Object>(this);
};
walk_private<true, Obj>(callback);
}
template <typename Tuple, std::size_t I = 0>
static std::expected<void, Rely_Error> check_rely_tuple(const Tuple& tuple) {
if constexpr (I == std::tuple_size_v<Tuple>) {
@@ -1326,7 +1437,8 @@ private:
Rely::Editor(
std::get<I>(values),
detail::rely_id<Tags>(),
detail::rely_id<detail::Dirty_Rely_Key<Tags>>()
detail::rely_id<detail::Dirty_Rely_Key<Tags>>(),
true
)...
};
}
@@ -1336,25 +1448,6 @@ private:
std::index_sequence<I...>) {
(d.rely_storage.template get<Tags>().cache->swap(std::get<I>(values)), ...);
}
template <typename... Tags>
void sync_initial_rely() {
auto sync_one = [&]<typename Tag>() {
auto& buffer = d.rely_storage.template get<Tag>();
*buffer.render = *buffer.cache;
buffer.render->bind();
};
(sync_one.template operator()<Tags>(), ...);
}
template <typename... Tags>
auto make_initial_rely_editors() {
return std::tuple{
Rely::Editor(
*d.rely_storage.template get<Tags>().cache,
detail::rely_id<Tags>(),
detail::rely_id<detail::Dirty_Rely_Key<Tags>>()
)...
};
}
void exchange_unlocked() {
before_exchange();
static_cast<Double_Buffer<Prop>&>(d).exchange();
@@ -1406,19 +1499,6 @@ public:
commit_cache_rely<Tags...>(next, std::index_sequence_for<Tags...>{});
return {};
}
template <typename... Tags, typename Callback> requires
detail::Rely_Tags_In<Relies, Tags...> &&
detail::Rely_Edit_Callback_For<Callback, Tags...>
void edit_initial_rely(Callback&& callback) {
auto editors = make_initial_rely_editors<Tags...>();
std::apply(
[&](auto&... values) {
std::invoke(std::forward<Callback>(callback), values...);
},
editors
);
sync_initial_rely<Tags...>();
}
std::expected<void, Rely_Error> check_rely() const {
return d.rely_storage.check_cache();
}
View File
+314 -146
View File
@@ -1,26 +1,161 @@
#include "base.hpp"
#include <oneapi/tbb/flow_graph.h>
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<Message, Message>;
using Gateway = typename Async_Node::gateway_type;
struct Node_Data {
void (*prepare)(Root*, Gateway&){};
Renderable* renderable{};
};
private:
Node_Data node_data;
template <bool Reverse, typename Layer, typename Object, typename Callback>
static void walk_private(Object* object, Callback& callback) {
if constexpr (requires { typename Layer::Prev_Object; }) {
if constexpr (!Reverse) walk_private<Reverse, typename Layer::Prev_Object>(object, callback);
callback(static_cast<typename Layer::Private&>(object->d));
if constexpr (Reverse) walk_private<Reverse, typename Layer::Prev_Object>(object, callback);
}
}
protected:
template <typename Object>
static void execute(Object* object) {
auto& private_data = static_cast<typename Object::Private&>(object->d);
if constexpr (requires { private_data.template prepare_data<Object>(object); }) {
private_data.template prepare_data<Object>(object);
}
auto callback = [&](auto& value) {
if constexpr (requires { value.template after_prepare_data<Object>(object); }) {
value.template after_prepare_data<Object>(object);
}
};
walk_private<true, typename Object::Attached_Object>(object, callback);
}
Node_Data& prepare_node_data() {
return node_data;
}
public:
template <typename Object>
void bind_rely_node(Rely::Node* node) {
if (!node_data.prepare) {
auto* object = static_cast<Object*>(this);
object->template mark_dirty<Prepare_Data_Tag>();
node_data.prepare = [](Root* root, Gateway& gateway) {
execute(static_cast<Object*>(root));
(void)gateway.try_put(Message{});
};
}
node->data = &node_data;
}
};
struct Color_Cache {};
struct Renderable : Impl<Renderable, Root, Tagged_Buffer<Color_Cache>> {
struct Secene;
struct Renderable : Impl<Renderable, Root, Tagged_Buffer<Color_Cache>>, 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<Message, Message>;
using Gateway = typename Async_Node::gateway_type;
using Task_Call = void (*)(void*, Gateway&);
using Task_State = std::tuple<void*, Task_Call>;
public:
struct Private : Prev_Private {
template <auto Callback, typename Context> requires std::invocable<decltype(Callback), Context*>
static void set_sync(Double_Buffer<Task_State>& buffer, Context* context, bool initial) {
Task_State task{
context,
[](void* value, Gateway& gateway) {
std::invoke(Callback, static_cast<Context*>(value));
(void)gateway.try_put(Message{});
}
};
*buffer.cache = task;
if (initial) *buffer.render = task;
}
template <auto Callback, typename Context> requires std::invocable<decltype(Callback), Context*, Gateway&>
static void set_async(Double_Buffer<Task_State>& buffer, Context* context, bool initial) {
Task_State task{
context,
[](void* value, Gateway& gateway) {
gateway.reserve_wait();
std::invoke(Callback, static_cast<Context*>(value), gateway);
}
};
*buffer.cache = task;
if (initial) *buffer.render = task;
}
static void publish(Double_Buffer<Task_State>& buffer) {
Task_State value = *buffer.cache;
buffer.exchange();
*buffer.cache = value;
}
template <typename Object>
static void prepare(Object* object) {
Prepare_Data::execute(object);
}
template <typename Object>
static void bind(Renderable* renderable, Object* object) {
renderable->paint_call = [](Renderable* value) {
auto* current = static_cast<Object*>(value);
auto& private_data = static_cast<typename Object::Private&>(current->d);
private_data.template paint<Object>(current);
};
set_sync<&Private::template prepare<Object>>(renderable->prepare_task, object, true);
set_sync<&Object::paint>(renderable->paint_task, object, true);
}
template <typename Object>
void prepare_data(Object*) {}
template <typename Object>
void after_prepare_data(Object* object) {
static_cast<Renderable*>(object)->paint_dirty = true;
}
template <typename Object>
void after_exchange(
Object* object,
typename Object::Prop*,
typename Object::State*,
const typename Object::Prop*,
const typename Object::State*) {
auto* renderable = static_cast<Renderable*>(object);
publish(renderable->prepare_task);
publish(renderable->paint_task);
}
template <typename Object>
void paint(Object*) {}
};
private:
using Call = void (*)(Renderable*);
Call paint_call{};
bool paint_dirty{true};
Double_Buffer<Task_State> prepare_task;
Double_Buffer<Task_State> paint_task;
friend struct Secene;
protected:
template <typename Object>
void bind_object_crtp() {
Prev_Object::template bind_object_crtp<Object>();
paint_call = [](Renderable* renderable) {
auto* object = static_cast<Object*>(renderable);
auto& private_data = static_cast<typename Object::Private&>(object->d);
private_data.template paint<Object>(object);
};
Private::bind(this, static_cast<Object*>(this));
}
public:
template <typename Object>
void bind_rely_node(Rely::Node* node) {
Prepare_Data::template bind_rely_node<Object>(node);
prepare_node_data().renderable = this;
}
void mark_prepare_dirty() {
mark_dirty<Prepare_Data_Tag>();
}
[[nodiscard]] bool need_prepare_data() const {
return dirty<Prepare_Data_Tag>();
}
void mark_paint_dirty() {
paint_dirty = true;
}
@@ -32,84 +167,155 @@ public:
void paint() {
paint_call(this);
}
struct Private : Prev_Private {
template <typename Object>
void after_prepare_data(Object* object) {
object->mark_paint_dirty();
}
template <typename Object>
void paint(Object*) {}
};
};
struct Layout_Tag {};
struct Extra_Tag {};
struct Secene : Impl<
Secene,
Root,
Rely_Type<Prepare_Data_Tag>,
Rely_Type<Layout_Tag>,
Rely_Type<Extra_Tag>
> {
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<Renderable*> renderables;
std::pmr::unordered_map<Root*, Renderable*> renderable_nodes;
void bind_renderables() {
renderable_nodes.clear();
for (auto* object : renderables) renderable_nodes.emplace(static_cast<Root*>(object), object);
template <auto Callback, typename Context> requires std::invocable<decltype(Callback), Context*>
void set_prepare_task(Context* context) {
Private::template set_sync<Callback>(prepare_task, context, false);
}
protected:
template <typename Object>
void bind_object_crtp() {
Prev_Object::template bind_object_crtp<Object>();
prepare_execute_call = [](const Secene* scene) {
auto* object = static_cast<const Object*>(scene);
(void)object->template render_rely<Prepare_Data_Tag>().execute_prepare();
};
paint_execute_call = [](const Secene* scene) {
auto* object = static_cast<const Object*>(scene);
(void)object->template render_rely<Prepare_Data_Tag>().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 <auto Callback, typename Context> requires std::invocable<decltype(Callback), Context*, Gateway&>
void set_prepare_async_task(Context* context) {
Private::template set_async<Callback>(prepare_task, context, false);
}
public:
Secene() : renderables(memory_resource()),
renderable_nodes(memory_resource()) {}
void execute_prepare() const {
prepare_execute_call(this);
template <auto Callback, typename Context> requires std::invocable<decltype(Callback), Context*>
void set_paint_task(Context* context) {
Private::template set_sync<Callback>(paint_task, context, false);
}
void execute_paint() const {
paint_execute_call(this);
template <auto Callback, typename Context> requires std::invocable<decltype(Callback), Context*, Gateway&>
void set_paint_async_task(Context* context) {
Private::template set_async<Callback>(paint_task, context, false);
}
template <typename Object>
struct Builder : Prev_Builder<Object> {
using Base = Prev_Builder<Object>;
using Base::Base;
template <typename Render_Object> requires std::derived_from<Render_Object, Renderable>
Builder& add_renderable(std::initializer_list<Render_Object*> list) {
for (auto* object : list) {
this->object->renderables.push_back(object);
this->template add_rely_node<Prepare_Data_Tag>(object);
}
template <auto Callback> requires std::invocable<decltype(Callback), Object*>
Builder& set_prepare_task() {
auto* renderable = static_cast<Renderable*>(this->object.get());
Private::template set_sync<Callback>(renderable->prepare_task, this->object.get(), true);
return *this;
}
std::unique_ptr<Object> build() {
this->object->bind_renderables();
return std::move(this->object);
template <auto Callback> requires std::invocable<decltype(Callback), Object*, Gateway&>
Builder& set_prepare_async_task() {
auto* renderable = static_cast<Renderable*>(this->object.get());
Private::template set_async<Callback>(renderable->prepare_task, this->object.get(), true);
return *this;
}
template <auto Callback> requires std::invocable<decltype(Callback), Object*>
Builder& set_paint_task() {
auto* renderable = static_cast<Renderable*>(this->object.get());
Private::template set_sync<Callback>(renderable->paint_task, this->object.get(), true);
return *this;
}
template <auto Callback> requires std::invocable<decltype(Callback), Object*, Gateway&>
Builder& set_paint_async_task() {
auto* renderable = static_cast<Renderable*>(this->object.get());
Private::template set_async<Callback>(renderable->paint_task, this->object.get(), true);
return *this;
}
};
};
struct Secene : Impl<Secene, Root, Rely_Type<Prepare_Data_Tag>, Rely_Type<Paint_Tag>> {
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<Message, Message>;
template <typename Tag>
struct Stage : Pinned {
struct Node : Pinned {
using Gate = oneapi::tbb::flow::continue_node<Message>;
static auto body(Root* object, Prepare_Data::Node_Data* data) requires std::same_as<Tag, Prepare_Data_Tag> {
return [object, data](const Message&, typename Async_Node::gateway_type& gateway) {
if (!object->take_dirty<Prepare_Data_Tag>()) {
(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<Tag, Paint_Tag> {
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<Message> start;
std::pmr::list<Node> nodes;
std::uint64_t revision;
template <typename Object>
Stage(const Object* object, std::pmr::memory_resource* resource) :
start(graph),
nodes(resource),
revision(object->template render_rely<Tag>().revision()) {
(void)object->template for_each_rely_dag<Tag>(
[&](const Rely::View& view, const Rely::Node& rely_node) {
auto* data = static_cast<Prepare_Data::Node_Data*>(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<Node*>(dependency->runtime);
oneapi::tbb::flow::make_edge(source->execute, node->gate);
}
}
);
}
void run() {
(void)start.try_put(Message{});
graph.wait_for_all();
}
};
template <typename Object>
static void update_stages(const Object* object) {
const auto& prepare_rely = object->template render_rely<Prepare_Data_Tag>();
const auto& paint_rely = object->template render_rely<Paint_Tag>();
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<Stage<Prepare_Data_Tag>>(object, object->memory_resource());
auto paint = std::make_unique<Stage<Paint_Tag>>(object, object->memory_resource());
object->prepare_stage = std::move(prepare);
object->paint_stage = std::move(paint);
}
template <typename Object>
static void execute_prepare(const Object* object) {
if (!object->prepare_stage || !object->paint_stage) update_stages(object);
object->prepare_stage->run();
}
template <typename Object>
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 <typename Object>
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<typename Private::template Stage<Prepare_Data_Tag>> prepare_stage;
mutable std::unique_ptr<typename Private::template Stage<Paint_Tag>> paint_stage;
protected:
template <typename Object>
void bind_object_crtp() {
Prev_Object::template bind_object_crtp<Object>();
prepare_execute_call = [](const Secene* scene) {
Private::execute_prepare(static_cast<const Object*>(scene));
};
paint_execute_call = [](const Secene* scene) {
Private::execute_paint(static_cast<const Object*>(scene));
};
}
public:
void execute_prepare() const {
prepare_execute_call(this);
}
void execute_paint() const {
paint_execute_call(this);
}
template <typename Object>
struct Builder : Prev_Builder<Object> {
using Base = Prev_Builder<Object>;
using Base::Base;
std::expected<std::unique_ptr<Object>, 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<SEP, SEP_root, aethera::Tagged_Buffer<std::vector<int>>> {
struct Prop : Prev_Prop {
int value{};
};
struct State : Prev_State {
int a{};
};
struct Private : Prev_Private {
template <typename Object>
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<SEP>;
struct SEP_Child : aethera::Impl<SEP_Child, SEP> {
struct Prop : Prev_Prop {
int child_value{};
};
struct State : Prev_State {
int b{};
};
struct Private : Prev_Private {
template <typename Object>
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<SEP_Child>;
struct Data_Cache {
int value{};
};
struct Data_Object : aethera::Impl<Data_Object, SEP_root, aethera::Tagged_Buffer<Data_Cache>> {
struct Prop : Prev_Prop {};
struct State : Prev_State {
int value{};
int other{};
};
int id;
std::vector<int>* order;
Data_Object(int id, std::vector<int>* order) : id(id), order(order) {}
struct Private : Prev_Private {
int previous_value{};
template <typename Object, typename Owner, typename Member>
void before_state_set(Object*, Member Owner::* member, typename Object::State* state) {
if constexpr (std::same_as<decltype(member), decltype(&Data_Object::State::value)>) {
if (member == &Data_Object::State::value) previous_value = state->*member;
}
}
template <typename Object, typename Owner, typename Member>
void after_state_set(Object* object, Member Owner::* member, typename Object::State* state) {
if constexpr (std::same_as<decltype(member), decltype(&Data_Object::State::value)>) {
if (member == &Data_Object::State::value && state->*member != previous_value) object->mark_prepare_dirty();
}
}
template <typename Object>
void prepare_data(Object* object) {
object->order->push_back(object->id);
object->template buffer<Data_Cache>().value = object->id;
}
};
};
-386
View File
@@ -1,390 +1,4 @@
#include "../kernel/render_2D.hpp"
using Data_Object_2 = aethera::Attach_Object<Data_Object>;
struct Test_Cache {
int value{};
};
struct Test_Cache_Tag {};
struct Test_Renderable : aethera::Impl<
Test_Renderable,
aethera::_2D::Renderable,
aethera::Tagged_Buffer<Test_Cache>,
aethera::Tagged_Buffer<Test_Cache_Tag, Test_Cache>
> {
struct Prop : Prev_Prop {};
struct State : Prev_State {
int value{};
int other{};
bool operator==(const State&) const = default;
};
int id;
std::vector<int>* order;
Test_Renderable(int id, std::vector<int>* order) : id(id), order(order) {}
struct Private : Prev_Private {
int previous_value{};
template <typename Object, typename Owner, typename Member>
void before_state_set(Object*, Member Owner::* member, typename Object::State* state) {
if constexpr (std::same_as<decltype(member), decltype(&Test_Renderable::State::value)>) {
if (member == &Test_Renderable::State::value) previous_value = state->*member;
}
}
template <typename Object, typename Owner, typename Member>
void after_state_set(Object* object, Member Owner::* member, typename Object::State* state) {
if constexpr (std::same_as<decltype(member), decltype(&Test_Renderable::State::value)>) {
if (member == &Test_Renderable::State::value && state->*member != previous_value) object->mark_prepare_dirty();
}
}
template <typename Object>
void prepare_data(Object* object) {
object->order->push_back(object->id);
object->template buffer<Test_Cache>().value = object->id;
}
template <typename Object>
void paint(Object* object) {
object->order->push_back(object->id * 10);
}
};
};
using Test_Renderable_2 = aethera::Attach_Object<Test_Renderable>;
struct Pmr_Buffer_Tag {};
struct Pmr_Object : aethera::Impl<
Pmr_Object,
SEP_root,
aethera::Tagged_Buffer<Pmr_Buffer_Tag, std::pmr::vector<int>>
> {
struct Prop : Prev_Prop {};
struct State : Prev_State {};
struct Private : Prev_Private {};
};
using Pmr_Object_2 = aethera::Attach_Object<Pmr_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<SEP>);
static_assert(aethera::Object<SEP_Child>);
static_assert(aethera::Object<Data_Object>);
static_assert(aethera::Object<aethera::_2D::Renderable>);
static_assert(aethera::Object<aethera::_2D::Secene>);
static_assert(aethera::Object<Test_Renderable>);
static_assert(aethera::Object<Pmr_Object>);
int main() {
using Secene_Object = aethera::Attach_Object<aethera::_2D::Secene>;
using Prepare_Tag = aethera::Prepare_Data_Tag;
using Layout_Tag = aethera::_2D::Layout_Tag;
using Extra_Tag = aethera::_2D::Extra_Tag;
std::vector<int> order;
std::vector<int> direct_order;
Data_Object_2 direct_data(9, &direct_order);
direct_data.prepare_data();
direct_data.exchange_object();
if (direct_order != std::vector<int>({9})) return 112;
if (direct_data.render_buffer<Data_Cache>().value != 9) return 113;
Test_Renderable_2 direct_renderable(8, &direct_order);
direct_renderable.paint();
if (direct_order != std::vector<int>({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<int> pmr_initial;
pmr_initial.assign({1, 2, 3});
pmr_buffer_builder.set<Pmr_Buffer_Tag>(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<Pmr_Buffer_Tag>().get_allocator().resource() != pmr_buffer_object->memory_resource()) return 109;
pmr_buffer_object->buffer<Pmr_Buffer_Tag>().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>(Data_Cache{40});
auto data_object = data_builder.build();
Test_Renderable_2::Builder c_builder(3, &order);
c_builder.set<Test_Cache>(Test_Cache{30});
c_builder.set<Test_Cache_Tag>(Test_Cache{300});
auto c_object = c_builder.build();
Test_Renderable_2::Builder b_builder(2, &order);
b_builder.set<Test_Cache>(Test_Cache{20});
auto b_object = b_builder.build();
Test_Renderable_2::Builder a_builder(1, &order);
a_builder.set<Test_Cache>(Test_Cache{10});
auto a_object = a_builder.build();
if (data_object->render_buffer<Data_Cache>().value != 40) return 1;
if (c_object->render_buffer<Test_Cache>().value != 30) return 2;
if (c_object->render_buffer<Test_Cache_Tag>().value != 300) return 3;
if (b_object->render_buffer<Test_Cache>().value != 20) return 4;
if (a_object->render_buffer<Test_Cache>().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<Layout_Tag, Extra_Tag>(
[&](auto& layout, auto& extra) {
layout.template add_rely<&Data_Object::State::other>(
c_object.get(),
data_object.get()
);
extra.template add_dirty_rely<Layout_Tag>(
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<Prepare_Tag>().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<Prepare_Tag>();
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<aethera::Root*> view_order;
bool view_query_ok{true};
auto view_walk = scene_object->for_each_rely_dag<Prepare_Tag>(
[&](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<aethera::Root*>({
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<int>({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<int>({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<int>({3, 2, 1})) return 34;
order.clear();
scene_object->execute_paint();
if (order != std::vector<int>({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<Test_Cache_Tag>().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<Test_Cache_Tag>().value != 300) return 39;
scene_object->execute_prepare();
if (order != std::vector<int>({1})) return 40;
order.clear();
scene_object->execute_paint();
if (order != std::vector<int>({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<Layout_Tag>()) return 43;
if (!b_object->dirty<Extra_Tag>()) return 44;
if (a_object->dirty<Extra_Tag>()) return 45;
if (!c_object->take_dirty<Layout_Tag>()) return 46;
if (!b_object->take_dirty<Extra_Tag>()) return 47;
// Runtime multi-rely edit is transactional: any invalid graph rolls every edited cache graph back.
auto invalid_runtime = scene_object->edit_rely<Prepare_Tag, Extra_Tag>(
[&](auto& prepare, auto& extra) {
prepare.template add_rely<Test_Cache>(
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<Prepare_Tag>().contains_rely(c_object.get(), a_object.get())) return 50;
if (scene_object->rely<Extra_Tag>().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<Prepare_Tag, Extra_Tag>(
[&](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<Test_Cache>(
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<Prepare_Tag>().contains_rely(a_object.get(), b_object.get())) return 54;
if (!scene_object->rely<Extra_Tag>().contains_rely(a_object.get(), data_object.get())) return 55;
if (!scene_object->render_rely<Prepare_Tag>().contains_rely(a_object.get(), b_object.get())) return 56;
if (scene_object->render_rely<Extra_Tag>().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<Layout_Tag>()) return 58;
if (!b_object->dirty<Extra_Tag>()) return 59;
if (a_object->dirty<Extra_Tag>()) return 60;
c_object->take_dirty<Layout_Tag>();
b_object->take_dirty<Extra_Tag>();
scene_object->exchange();
if (c_object->render_buffer<Test_Cache_Tag>().value != 301) return 61;
if (scene_object->render_rely<Prepare_Tag>().contains_rely(a_object.get(), b_object.get())) return 62;
if (!scene_object->render_rely<Extra_Tag>().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<Layout_Tag>()) return 64;
if (!b_object->dirty<Extra_Tag>()) return 65;
if (!a_object->dirty<Extra_Tag>()) return 66;
c_object->take_dirty<Layout_Tag>();
b_object->take_dirty<Extra_Tag>();
a_object->take_dirty<Extra_Tag>();
// Removed one-parameter Tagged_Buffer edge no longer propagates, while another Buffer Tag edge remains active.
b_object->buffer<Test_Cache>().value = 222;
if (a_object->need_prepare_data()) return 67;
c_object->buffer<Test_Cache_Tag>().value = 302;
if (!a_object->need_prepare_data()) return 68;
scene_object->execute_prepare();
if (order != std::vector<int>({1})) return 69;
order.clear();
scene_object->execute_paint();
if (order != std::vector<int>({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<int>>(std::vector<int>{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<int>>() != std::vector<int>({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<int> global_initial;
global_initial.assign({5, 6, 7});
global_builder.set<Pmr_Buffer_Tag>(global_initial);
auto global_object = global_builder.build();
if (global_object->pmr().resource() != &global_resource) return 106;
if (global_object->render_buffer<Pmr_Buffer_Tag>().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;
}