diff --git a/.gitignore b/.gitignore index 2c7a9bb..1bc30c5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /third_party/datoviz/ /output/ /webapp_gallery/.playwright-cli/ +/logs/ diff --git a/Kernel/src/renderive/scene/Inheritance.hpp b/Kernel/src/renderive/scene/Inheritance.hpp new file mode 100644 index 0000000..1171fbb --- /dev/null +++ b/Kernel/src/renderive/scene/Inheritance.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include "renderive/inheritance/Inheritance.hpp" + +#include + +namespace renderive::scene_inheritance { + +struct Builder_Chain {}; +struct State_Chain {}; +struct Impl_Chain {}; +struct Editor_Chain {}; + +template +struct Builder_Root + : inheritance::Type_Node {}; + +template +struct Builder_Node : inheritance::Type_Node { + using Builder_Base = inheritance::Type_Node; + using Builder_Base::Builder_Base; +}; + +template +struct State_Root + : inheritance::Type_Node {}; + +template +struct State_Node : inheritance::Type_Node { + using State_Base = inheritance::Type_Node; + using State_Base::State_Base; +}; + +template +struct Editor_Root + : inheritance::Type_Node {}; + +template +struct Editor_Node : inheritance::Type_Node { + using Editor_Base = inheritance::Type_Node; + using Editor_Base::Editor_Base; +}; + +template +struct Observer_Root { + using Owner = Layer; +}; + +template +struct Observer_Node : Parent { + using Owner = Layer; +}; + +template +struct Observer_Handler { + using Owner = Layer; + + static void handle(Layer& layer, const Event& event) + requires requires { + typename Layer::Observer; + typename Layer::Observer::Owner; + requires std::same_as; + Layer::Observer::handle(layer, event); + } + { + Layer::Observer::handle(layer, event); + } +}; + +template +struct Impl_Root + : inheritance::Dispatch_Root { + using Impl_Base = + inheritance::Dispatch_Root; + using Scene_Event = Event; + using Handler = Observer_Handler; + using Impl_Base::Impl_Base; +}; + +template +struct Impl_Node + : inheritance::Dispatch_Node { + using Impl_Base = + inheritance::Dispatch_Node; + using Scene_Event = typename Parent::Scene_Event; + using Handler = Observer_Handler; + using next_Observer = Observer_Node; + using Impl_Base::Impl_Base; +}; + +} // namespace renderive::scene_inheritance diff --git a/Kernel/src/renderive/scene/Scene2D_Context.hpp b/Kernel/src/renderive/scene/Scene2D_Context.hpp index 9ff1d95..3deb92b 100644 --- a/Kernel/src/renderive/scene/Scene2D_Context.hpp +++ b/Kernel/src/renderive/scene/Scene2D_Context.hpp @@ -40,7 +40,8 @@ public: template requires Scene2D_Frame_Control_Constructible explicit Scene2D_Context(std::pmr::memory_resource& memory_resource, Args&&... args) - : Scene_2D_Base(memory_resource), Scene_State_Strategy(State{}), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())) {} + : Scene_2D_Base(memory_resource, std::make_unique()), + Scene_State_Strategy(State{}), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())) {} template requires Scene2D_Frame_Control_Constructible Scene2D_Context(With_Observer state_observer, With_Observer scene_observer, Args&&... args) @@ -48,7 +49,10 @@ public: template requires Scene2D_Frame_Control_Constructible Scene2D_Context(std::pmr::memory_resource& memory_resource, With_Observer state_observer, With_Observer scene_observer, Args&&... args) - : Scene_2D_Base(memory_resource), Scene_State_Strategy(State{}, std::move(state_observer)), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())), scene_observer_(std::move(scene_observer.observer)) {} + : Scene_2D_Base( + memory_resource, + std::make_unique(std::move(scene_observer.observer))), + Scene_State_Strategy(State{}, std::move(state_observer)), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())) {} ~Scene2D_Context() override { this->shutdown(); } @@ -89,13 +93,31 @@ protected: snapshot.set_scene_state( this->Scene_State_Strategy::render_state_value()); } - void observe_scene(const Scene_Base::Observation& observation) noexcept override { - scene_observer_.observe(observation); - } - std::uint64_t observer_now_ns() const noexcept override { - return scene_observer_.now_ns(); - } private: + struct Context_State_Layer + : renderive::scene_inheritance::State_Node< + Context_State_Layer, Scene_2D_Base::State_Layer> {}; + + struct Impl + : renderive::scene_inheritance::Impl_Node { + struct Observer + : renderive::scene_inheritance::Observer_Node< + Impl, Scene_2D_Base::Impl::Observer> { + static void handle(Impl& layer, + const Scene_Base::Observation& observation) { + layer.scene_observer.observe(observation); + } + }; + + Impl() = default; + explicit Impl(Scene_Observer observer) + : scene_observer(std::move(observer)) {} + std::uint64_t now_ns() const noexcept override { + return scene_observer.now_ns(); + } + Scene_Observer scene_observer; + }; + template static Frame_Control make_frame_control(std::pmr::memory_resource& memory_resource, Args&&... args) { if constexpr (std::constructible_from) { @@ -120,5 +142,4 @@ private: } } Cache final_color_cache_; - Scene_Observer scene_observer_; }; diff --git a/Kernel/src/renderive/scene/Scene3D_Context.hpp b/Kernel/src/renderive/scene/Scene3D_Context.hpp index adb8d6b..11adb0b 100644 --- a/Kernel/src/renderive/scene/Scene3D_Context.hpp +++ b/Kernel/src/renderive/scene/Scene3D_Context.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include "renderive/base/observer/Observer.hpp" @@ -30,7 +31,8 @@ public: template requires Scene3D_Frame_Control_Constructible explicit Scene3D_Context(std::pmr::memory_resource& memory_resource, Args&&... args) - : Scene_3D_Base(memory_resource), Scene_State_Strategy(State{}), + : Scene_3D_Base(memory_resource, std::make_unique()), + Scene_State_Strategy(State{}), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)) {} template @@ -40,11 +42,12 @@ public: template requires Scene3D_Frame_Control_Constructible Scene3D_Context(std::pmr::memory_resource& memory_resource, With_Observer state_observer, With_Observer scene_observer, Args&&... args) - : Scene_3D_Base(memory_resource), + : Scene_3D_Base( + memory_resource, + std::make_unique(std::move(scene_observer.observer))), Scene_State_Strategy(State{}, std::move(state_observer)), frame_control(make_frame_control(this->memory_resource(), - std::forward(args)...)), - scene_observer_(std::move(scene_observer.observer)) {} + std::forward(args)...)) {} ~Scene3D_Context() override { this->shutdown(); } @@ -63,13 +66,31 @@ protected: snapshot.set_scene_state( this->Scene_State_Strategy::render_state_value()); } - void observe_scene(const Scene_Base::Observation& observation) noexcept override { - scene_observer_.observe(observation); - } - std::uint64_t observer_now_ns() const noexcept override { - return scene_observer_.now_ns(); - } private: + struct Context_State_Layer + : renderive::scene_inheritance::State_Node< + Context_State_Layer, Scene_3D_Base::State_Layer> {}; + + struct Impl + : renderive::scene_inheritance::Impl_Node { + struct Observer + : renderive::scene_inheritance::Observer_Node< + Impl, Scene_3D_Base::Impl::Observer> { + static void handle(Impl& layer, + const Scene_Base::Observation& observation) { + layer.scene_observer.observe(observation); + } + }; + + Impl() = default; + explicit Impl(Scene_Observer observer) + : scene_observer(std::move(observer)) {} + std::uint64_t now_ns() const noexcept override { + return scene_observer.now_ns(); + } + Scene_Observer scene_observer; + }; + template static Frame_Control make_frame_control(std::pmr::memory_resource& memory_resource, Args&&... args) { if constexpr (std::constructible_from) { @@ -78,5 +99,4 @@ private: return Frame_Control(std::forward(args)...); } } - Scene_Observer scene_observer_; }; diff --git a/Kernel/src/renderive/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index 514d963..62c77d9 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -23,131 +23,6 @@ static_assert(TF_VERSION == 400100, "Renderive requires Taskflow 4.1.0"); -namespace { - -bool contains_id(const std::vector& values, Renderable_Id id) { - return std::find(values.begin(), values.end(), id) != values.end(); -} - -bool contains_id(const std::pmr::vector& values, - Renderable_Id id) { - return std::find(values.begin(), values.end(), id) != values.end(); -} - -} // namespace - -void Scene_Base::Id_Topology::erase_id(std::pmr::vector& values, Renderable_Id id) noexcept { - std::erase(values, id); -} -void Scene_Base::Id_Topology::validate_endpoint(Renderable_Id id) const { - if (!nodes_.contains(id)) - throw std::invalid_argument("topology endpoint is not attached"); -} -void Scene_Base::Id_Topology::attach(Renderable_Id id) { - if (id == 0 || nodes_.contains(id)) - throw std::invalid_argument("topology node is already attached"); - nodes_.try_emplace(id, *nodes_.get_allocator().resource()); -} -void Scene_Base::Id_Topology::erase(Renderable_Id id) { - const auto iterator = nodes_.find(id); - if (iterator == nodes_.end()) - return; - if (!iterator->second.parents.empty() || !iterator->second.children.empty()) - throw std::logic_error("topology node still has relationships"); - nodes_.erase(iterator); -} -bool Scene_Base::Id_Topology::replace_parents(Renderable_Id child, const std::vector& parents) { - validate_endpoint(child); - std::vector unique; - unique.reserve(parents.size()); - for (const Renderable_Id parent : parents) { - validate_endpoint(parent); - if (!contains_id(unique, parent)) - unique.push_back(parent); - } - auto& child_node = nodes_.at(child); - if (child_node.parents.size() == unique.size() && std::all_of(unique.begin(), unique.end(), [&](Renderable_Id parent) { - return contains_id(child_node.parents, parent); - })) - return false; - const std::vector previous(child_node.parents.begin(), child_node.parents.end()); - for (const Renderable_Id parent : previous) - erase_id(nodes_.at(parent).children, child); - child_node.parents.clear(); - child_node.parents.insert(child_node.parents.end(), unique.begin(), unique.end()); - for (const Renderable_Id parent : unique) { - auto& children = nodes_.at(parent).children; - if (!contains_id(children, child)) - children.push_back(child); - } - return true; -} -bool Scene_Base::Id_Topology::add_parent(Renderable_Id child, Renderable_Id parent) { - validate_endpoint(child); - validate_endpoint(parent); - auto& child_node = nodes_.at(child); - if (contains_id(child_node.parents, parent)) - return false; - child_node.parents.push_back(parent); - nodes_.at(parent).children.push_back(child); - return true; -} -bool Scene_Base::Id_Topology::clear_parents(Renderable_Id child) { - validate_endpoint(child); - auto& child_node = nodes_.at(child); - if (child_node.parents.empty()) - return false; - const std::vector parents(child_node.parents.begin(), child_node.parents.end()); - for (const Renderable_Id parent : parents) - erase_id(nodes_.at(parent).children, child); - child_node.parents.clear(); - return true; -} -bool Scene_Base::Id_Topology::contains(Renderable_Id id) const noexcept { - return nodes_.contains(id); -} -bool Scene_Base::Id_Topology::isolated(Renderable_Id id) const { - const auto& value = node(id); - return value.parents.empty() && value.children.empty(); -} -const Scene_Base::Topology_Node& Scene_Base::Id_Topology::node(Renderable_Id id) const { - return nodes_.at(id); -} -std::vector Scene_Base::Id_Topology::ids() const { - std::vector result; - result.reserve(nodes_.size()); - for (const auto& [id, node] : nodes_) { - static_cast(node); - result.push_back(id); - } - return result; -} -std::vector Scene_Base::Id_Topology::order() const { - std::unordered_map indegree; - indegree.reserve(nodes_.size()); - std::priority_queue, std::greater<>> ready; - for (const auto& [id, node] : nodes_) { - indegree.emplace(id, node.parents.size()); - if (node.parents.empty()) - ready.push(id); - } - std::vector result; - result.reserve(nodes_.size()); - while (!ready.empty()) { - const Renderable_Id id = ready.top(); - ready.pop(); - result.push_back(id); - for (const Renderable_Id child : nodes_.at(id).children) { - auto& value = indegree.at(child); - if (--value == 0) - ready.push(child); - } - } - if (result.size() != nodes_.size()) - throw std::logic_error("topology contains a cycle"); - return result; -} - class Scene_Base::Execution_Context { public: static tf::Executor& executor() { @@ -171,11 +46,14 @@ private: Scene_Base::Scene_Base() : Scene_Base(*std::pmr::get_default_resource()) {} Scene_Base::Scene_Base(std::pmr::memory_resource& upstream_memory_resource) + : Scene_Base(upstream_memory_resource, std::make_unique()) {} +Scene_Base::Scene_Base(std::pmr::memory_resource& upstream_memory_resource, + std::unique_ptr impl) : scene_lifetime_(std::make_shared(*this)), memory_domain_(std::allocate_shared(std::pmr::polymorphic_allocator(&upstream_memory_resource), upstream_memory_resource)), + impl_(std::move(impl)), renderables_(&memory_domain_->resource()), - display_topology_(memory_domain_->resource()), - dependency_topology_(memory_domain_->resource()), + dependency_resolver_(memory_domain_->resource()), color_caches_(&memory_domain_->resource()), task_(memory_domain_->resource()), renderable_edit_queue_(&memory_domain_->resource()), @@ -187,7 +65,7 @@ Scene_Base::~Scene_Base() { shutdown(); } Scene_Base::Attach_Builder::Attach_Builder(Scene_Base& scene) - : scene_(scene), task_lock_(scene.task_mutex_), editor_(scene) { + : scene_(scene), task_lock_(scene.task_mutex_) { if (scene.runtime_started_) throw std::logic_error("scene attach builder is only available before runtime starts"); } @@ -202,31 +80,19 @@ Scene_Base::Attach_Builder::~Attach_Builder() { } void Scene_Base::Attach_Builder::attach(Renderable renderable) { std::lock_guard lock(scene_.model_mutex_); - editor_.attach(std::move(renderable)); -} -void Scene_Base::Attach_Builder::set_display_parent(const Renderable& child, const Renderable& parent) { - std::lock_guard lock(scene_.model_mutex_); - editor_.set_display_parent(child, parent); -} -void Scene_Base::Attach_Builder::add_display_parent(const Renderable& child, const Renderable& parent) { - std::lock_guard lock(scene_.model_mutex_); - editor_.add_display_parent(child, parent); -} -void Scene_Base::Attach_Builder::clear_display_parent(const Renderable& child) { - std::lock_guard lock(scene_.model_mutex_); - editor_.clear_display_parent(child); + Renderable_Editor(scene_).attach(std::move(renderable)); } void Scene_Base::Attach_Builder::set_dependency_parent(const Renderable& child, const Renderable& parent) { std::lock_guard lock(scene_.model_mutex_); - editor_.set_dependency_parent(child, parent); + Renderable_Editor(scene_).set_dependency_parent(child, parent); } void Scene_Base::Attach_Builder::add_dependency_parent(const Renderable& child, const Renderable& parent) { std::lock_guard lock(scene_.model_mutex_); - editor_.add_dependency_parent(child, parent); + Renderable_Editor(scene_).add_dependency_parent(child, parent); } void Scene_Base::Attach_Builder::clear_dependency_parent(const Renderable& child) { std::lock_guard lock(scene_.model_mutex_); - editor_.clear_dependency_parent(child); + Renderable_Editor(scene_).clear_dependency_parent(child); } Scene_Base::Attach_Builder Scene_Base::attach_builder() { return Attach_Builder(*this); @@ -243,10 +109,10 @@ void Scene_Base::Renderable_Editor::attach(Renderable renderable) { throw std::logic_error("renderable id is already attached"); return; } - if (!scene_.display_topology_.contains(id)) - scene_.display_topology_.attach(id); - if (!scene_.dependency_topology_.contains(id)) - scene_.dependency_topology_.attach(id); + if (!scene_.dependency_resolver_.contains(id)) + scene_.dependency_resolver_.attach(id); + if (auto* composition = scene_.composition_relationships()) + composition->attach(id); auto cache = scene_.make_renderable_color_cache(); if (!scene_.renderables_.try_emplace(id, renderable).second) throw std::logic_error("renderable id is already attached"); @@ -276,38 +142,12 @@ void Scene_Base::Renderable_Editor::detach(const Renderable& renderable) { scene_.renderables_.erase(id); scene_.notify_model_dirty(); } -void Scene_Base::Renderable_Editor::set_display_parent(const Renderable& child, const Renderable& parent) { - if (!child || !parent) - throw std::invalid_argument("display relationship endpoint is null"); - scene_.validate_renderable_scene(*child); - scene_.validate_renderable_scene(*parent); - if (scene_.display_topology_.replace_parents( - child->d_func().renderable_id, - {parent->d_func().renderable_id})) - scene_.notify_model_dirty(); -} -void Scene_Base::Renderable_Editor::add_display_parent(const Renderable& child, const Renderable& parent) { - if (!child || !parent) - throw std::invalid_argument("display relationship endpoint is null"); - scene_.validate_renderable_scene(*child); - scene_.validate_renderable_scene(*parent); - if (scene_.display_topology_.add_parent(child->d_func().renderable_id, - parent->d_func().renderable_id)) - scene_.notify_model_dirty(); -} -void Scene_Base::Renderable_Editor::clear_display_parent(const Renderable& child) { - if (!child) - throw std::invalid_argument("display child is null"); - scene_.validate_renderable_scene(*child); - if (scene_.display_topology_.clear_parents(child->d_func().renderable_id)) - scene_.notify_model_dirty(); -} void Scene_Base::Renderable_Editor::set_dependency_parent(const Renderable& child, const Renderable& parent) { if (!child || !parent) throw std::invalid_argument("dependency relationship endpoint is null"); scene_.validate_renderable_scene(*child); scene_.validate_renderable_scene(*parent); - if (!scene_.dependency_topology_.replace_parents( + if (!scene_.dependency_resolver_.replace_parents( child->d_func().renderable_id, {parent->d_func().renderable_id})) return; @@ -319,7 +159,7 @@ void Scene_Base::Renderable_Editor::add_dependency_parent(const Renderable& chil throw std::invalid_argument("dependency relationship endpoint is null"); scene_.validate_renderable_scene(*child); scene_.validate_renderable_scene(*parent); - if (!scene_.dependency_topology_.add_parent(child->d_func().renderable_id, + if (!scene_.dependency_resolver_.add_parent(child->d_func().renderable_id, parent->d_func().renderable_id)) return; child->d_func().invalidate_prepare(); @@ -329,7 +169,7 @@ void Scene_Base::Renderable_Editor::clear_dependency_parent(const Renderable& ch if (!child) throw std::invalid_argument("dependency child is null"); scene_.validate_renderable_scene(*child); - if (!scene_.dependency_topology_.clear_parents(child->d_func().renderable_id)) + if (!scene_.dependency_resolver_.clear_parents(child->d_func().renderable_id)) return; child->d_func().invalidate_prepare(); scene_.notify_model_dirty(); @@ -337,8 +177,118 @@ void Scene_Base::Renderable_Editor::clear_dependency_parent(const Renderable& ch void Scene_Base::edit_renderables(Renderable_Edit edit) { if (!edit) throw std::invalid_argument("renderable edit callback is empty"); - enqueue_renderable_edit(std::move(edit)); + enqueue_renderable_edit([this, edit = std::move(edit)]() mutable { + Renderable_Editor editor(*this); + edit(editor); + }); } + +Scene_2D_Base::Scene_2D_Base() + : Scene_2D_Base(*std::pmr::get_default_resource()) {} +Scene_2D_Base::Scene_2D_Base(std::pmr::memory_resource& memory_resource) + : Scene_2D_Base(memory_resource, std::make_unique()) {} +Scene_2D_Base::Scene_2D_Base(std::pmr::memory_resource& memory_resource, + std::unique_ptr impl) + : Scene_Base(memory_resource, std::move(impl)), + display_resolver_(this->memory_resource()) {} + +Scene_2D_Base::Attach_Builder Scene_2D_Base::attach_builder() { + return Attach_Builder(*this); +} + +void Scene_2D_Base::edit_renderables(Renderable_Edit edit) { + if (!edit) + throw std::invalid_argument("renderable edit callback is empty"); + enqueue_renderable_edit([this, edit = std::move(edit)]() mutable { + Renderable_Editor editor(*this); + edit(editor); + }); +} + +void Scene_2D_Base::with_initial_display_edit(std::function edit) { + std::lock_guard lock(model_mutex_); + edit(); +} + +void Scene_2D_Base::Attach_Builder::set_display_parent( + const Renderable& child, const Renderable& parent) { + scene_2d_.with_initial_display_edit([&] { + Renderable_Editor(scene_2d_).set_display_parent(child, parent); + }); +} +void Scene_2D_Base::Attach_Builder::add_display_parent( + const Renderable& child, const Renderable& parent) { + scene_2d_.with_initial_display_edit([&] { + Renderable_Editor(scene_2d_).add_display_parent(child, parent); + }); +} +void Scene_2D_Base::Attach_Builder::clear_display_parent( + const Renderable& child) { + scene_2d_.with_initial_display_edit([&] { + Renderable_Editor(scene_2d_).clear_display_parent(child); + }); +} + +void Scene_2D_Base::Renderable_Editor::set_display_parent( + const Renderable& child, const Renderable& parent) { + scene_2d_.set_display_parent_locked(child, parent); +} +void Scene_2D_Base::Renderable_Editor::add_display_parent( + const Renderable& child, const Renderable& parent) { + scene_2d_.add_display_parent_locked(child, parent); +} +void Scene_2D_Base::Renderable_Editor::clear_display_parent( + const Renderable& child) { + scene_2d_.clear_display_parent_locked(child); +} + +void Scene_2D_Base::set_display_parent_locked(const Renderable& child, + const Renderable& parent) { + if (!child || !parent) + throw std::invalid_argument("display relationship endpoint is null"); + validate_renderable_scene(*child); + validate_renderable_scene(*parent); + if (display_resolver_.replace_parents( + child->renderable_id(), {parent->renderable_id()})) + notify_model_dirty(); +} +void Scene_2D_Base::add_display_parent_locked(const Renderable& child, + const Renderable& parent) { + if (!child || !parent) + throw std::invalid_argument("display relationship endpoint is null"); + validate_renderable_scene(*child); + validate_renderable_scene(*parent); + if (display_resolver_.add_parent(child->renderable_id(), + parent->renderable_id())) + notify_model_dirty(); +} +void Scene_2D_Base::clear_display_parent_locked(const Renderable& child) { + if (!child) + throw std::invalid_argument("display child is null"); + validate_renderable_scene(*child); + if (display_resolver_.clear_parents(child->renderable_id())) + notify_model_dirty(); +} + +Scene_3D_Base::Scene_3D_Base() + : Scene_3D_Base(*std::pmr::get_default_resource()) {} +Scene_3D_Base::Scene_3D_Base(std::pmr::memory_resource& memory_resource) + : Scene_3D_Base(memory_resource, std::make_unique()) {} +Scene_3D_Base::Scene_3D_Base(std::pmr::memory_resource& memory_resource, + std::unique_ptr impl) + : Scene_Base(memory_resource, std::move(impl)) {} +Scene_3D_Base::Attach_Builder Scene_3D_Base::attach_builder() { + return Attach_Builder(*this); +} +void Scene_3D_Base::edit_renderables(Renderable_Edit edit) { + if (!edit) + throw std::invalid_argument("renderable edit callback is empty"); + enqueue_renderable_edit([this, edit = std::move(edit)]() mutable { + Renderable_Editor editor(*this); + edit(editor); + }); +} + void Scene_Base::render() { submit_render(nullptr); } @@ -382,21 +332,24 @@ void Scene_Base::submit_render(Abstract_Frame* frame) { ? Render_Task::Frame{std::ref(*frame)} : Render_Task::Frame{std::make_shared()}; task.plan = compile_render_plan(*snapshot, task); + task.topology = + std::make_shared(topology_snapshot()); task.completion = std::make_shared(); snapshot->capture_ticket_ = capture_controller_.begin_frame(); task.snapshot = std::move(snapshot); current_completion_ = task.completion; const Observation submitted_observation{ - Observation_Event::render_submitted, observer_now_ns(), + Observation_Event::render_submitted, d_func().now_ns(), task.snapshot->render_sequence, task.snapshot->scene_state_revision_, - task.snapshot->renderables.size()}; + task.snapshot->renderables.size(), task.topology, task.snapshot, + task.plan}; task_ = std::move(task); task_pending_ = true; task_lock.unlock(); Scene_Base* previous_observer_scene = std::exchange(active_submitted_observer_scene_, this); - observe_scene(submitted_observation); + d_func().dispatch(submitted_observation); active_submitted_observer_scene_ = previous_observer_scene; task_ready_.notify_one(); @@ -431,11 +384,11 @@ void Scene_Base::wait_for_render() { if (exception) std::rethrow_exception(exception); } -void Scene_Base::enqueue_renderable_edit(Renderable_Edit edit) { +void Scene_Base::enqueue_renderable_edit(std::function edit) { std::lock_guard lock(task_mutex_); enqueue_renderable_edit_locked(std::move(edit)); } -void Scene_Base::enqueue_renderable_edit_locked(Renderable_Edit edit) { +void Scene_Base::enqueue_renderable_edit_locked(std::function edit) { if (stop_) throw std::logic_error("scene is shutting down"); runtime_started_ = true; @@ -443,17 +396,17 @@ void Scene_Base::enqueue_renderable_edit_locked(Renderable_Edit edit) { task_ready_.notify_one(); } void Scene_Base::cleanup_detached_topology_locked() { - const auto cleanup = [this](Id_Topology& topology, const char* name) { - for (const Renderable_Id id : topology.ids()) { - if (renderables_.contains(id)) - continue; - if (!topology.isolated(id)) - throw std::logic_error(std::string(name) + " topology still references detached renderable " + std::to_string(id)); - topology.erase(id); - } - }; - cleanup(display_topology_, "display"); - cleanup(dependency_topology_, "dependency"); + for (const Renderable_Id id : dependency_resolver_.ids()) { + if (renderables_.contains(id)) + continue; + if (!dependency_resolver_.isolated(id)) + throw std::logic_error( + "dependency graph still references detached renderable " + + std::to_string(id)); + dependency_resolver_.erase(id); + } + if (auto* composition = composition_relationships()) + composition->cleanup(); } void Scene_Base::validate_structure_locked() { for (const auto& [id, renderable] : renderables_) { @@ -463,22 +416,21 @@ void Scene_Base::validate_structure_locked() { if (!renderable->d_func().real_time_data_state->attached.load( std::memory_order_acquire)) throw std::logic_error("scene contains renderable whose attached state is false"); - if (!display_topology_.contains(id)) - throw std::logic_error("display topology is missing attached renderable " + std::to_string(id)); - if (!dependency_topology_.contains(id)) - throw std::logic_error("dependency topology is missing attached renderable " + std::to_string(id)); + if (!dependency_resolver_.contains(id)) + throw std::logic_error("dependency graph is missing attached renderable " + std::to_string(id)); } - const auto validate_topology = [this](const Id_Topology& topology, const char* name) { - const auto order = topology.order(); - if (order.size() != renderables_.size()) - throw std::logic_error(std::string(name) + " topology does not match attached renderables"); - for (const Renderable_Id id : order) { - if (!renderables_.contains(id)) - throw std::logic_error(std::string(name) + " topology references detached renderable " + std::to_string(id)); - } - }; - validate_topology(display_topology_, "display"); - validate_topology(dependency_topology_, "dependency"); + const auto dependency = dependency_resolver_.resolve(); + if (dependency.order.size() != renderables_.size()) + throw std::logic_error( + "dependency graph does not match attached renderables"); + for (const Renderable_Id id : dependency.order) { + if (!renderables_.contains(id)) + throw std::logic_error( + "dependency graph references detached renderable " + + std::to_string(id)); + } + if (const auto* composition = composition_relationships()) + composition->validate(); } [[noreturn]] void Scene_Base::structure_fail_fast(const char* stage, std::exception_ptr exception) noexcept { std::fprintf(stderr, "Renderive scene structure failure during %s", stage ? stage : "unknown stage"); @@ -522,7 +474,7 @@ void Scene_Base::request_render_graph_rebuild(Renderable_Base& renderable) { notify_model_dirty(); return; } - enqueue_renderable_edit_locked([owner = std::move(owner)](Renderable_Editor&) { + enqueue_renderable_edit_locked([owner = std::move(owner)] { owner->d_func().reset_render_graph(); owner->d_func().notify_scene_model_dirty(); }); @@ -579,33 +531,49 @@ std::size_t Scene_Base::renderable_count() const { Scene_Base::Topology_Snapshot Scene_Base::topology_snapshot() const { std::lock_guard lock(model_mutex_); Topology_Snapshot snapshot; - const auto ids = display_topology_.order(); + const auto dependency = dependency_resolver_.resolve(); + const auto* composition = composition_relationships(); + const auto display = composition + ? composition->resolve() + : renderive::scene::dependency::Resolution< + Renderable_Id>{}; + const auto& ids = dependency.order; snapshot.renderables.reserve(ids.size()); - snapshot.display.reserve(ids.size()); - snapshot.dependency.reserve(ids.size()); + snapshot.display.reserve(display.relationships.size()); + snapshot.dependency.reserve(dependency.relationships.size()); for (const Renderable_Id id : ids) snapshot.renderables.push_back(renderables_.at(id)); - const auto append_relationships = [&](const Id_Topology& topology, + const auto append_relationships = [&](const auto& resolution, auto& output) { - for (const Renderable_Id id : ids) { - const auto& parents = topology.node(id).parents; - if (parents.empty()) { + for (const Renderable_Id id : resolution.order) { + if (std::ranges::none_of( + resolution.relationships, + [id](const auto& relationship) { + return relationship.child == id; + })) output.push_back({renderables_.at(id), {}}); - continue; - } - for (const Renderable_Id parent : parents) - output.push_back({renderables_.at(id), renderables_.at(parent)}); } + for (const auto& relationship : resolution.relationships) + output.push_back({renderables_.at(relationship.child), + renderables_.at(relationship.parent)}); }; - append_relationships(display_topology_, snapshot.display); - append_relationships(dependency_topology_, snapshot.dependency); + if (!display.order.empty()) + append_relationships(display, snapshot.display); + append_relationships(dependency, snapshot.dependency); return snapshot; } std::vector Scene_Base::paint_order_snapshot() const { std::lock_guard lock(model_mutex_); std::vector result; - for (const Renderable_Id id : display_topology_.order()) + const auto* composition = composition_relationships(); + auto display = composition + ? composition->resolve() + : renderive::scene::dependency::Resolution< + Renderable_Id>{}; + if (display.order.empty()) + display = dependency_resolver_.resolve(); + for (const Renderable_Id id : display.order) result.push_back(renderables_.at(id)); return result; } @@ -613,8 +581,14 @@ std::vector Scene_Base::paint_order_snapshot() con std::shared_ptr Scene_Base::snapshot_live_model() { auto snapshot = std::make_shared(); std::lock_guard lock(model_mutex_); - const auto dependency_order = dependency_topology_.order(); - snapshot->display_order = display_topology_.order(); + const auto dependency = dependency_resolver_.resolve(); + const auto* composition = composition_relationships(); + const auto display = composition + ? composition->resolve() + : renderive::scene::dependency::Resolution< + Renderable_Id>{}; + const auto& dependency_order = dependency.order; + snapshot->display_order = display.order; snapshot->renderables.reserve(dependency_order.size()); for (const Renderable_Id id : dependency_order) { const Renderable& owner = renderables_.at(id); @@ -646,8 +620,7 @@ std::shared_ptr Scene_Base::snapshot_live_model() { state.prepare_required = !prepare_valid; state.paint_required = !paint_valid; state.render_graph = renderable_data.render_graph_snapshot(); - const auto& parents = dependency_topology_.node(id).parents; - state.dependency_parent_ids.assign(parents.begin(), parents.end()); + state.dependency_parent_ids = dependency_resolver_.parents(id); state.owner_ = owner; if (const auto cache = color_caches_.find(id); cache != color_caches_.end()) @@ -795,9 +768,57 @@ std::uint64_t Scene_Base::acquire_scene_state() { void Scene_Base::capture_scene_state(Frame_Render_Snapshot&) const {} -void Scene_Base::observe_scene(const Observation&) noexcept {} +renderive::scene::detail::Composition_Relationships* +Scene_Base::composition_relationships() noexcept { + auto* scene_2d = dynamic_cast(this); + return scene_2d + ? static_cast< + renderive::scene::detail::Composition_Relationships*>( + scene_2d) + : nullptr; +} +const renderive::scene::detail::Composition_Relationships* +Scene_Base::composition_relationships() const noexcept { + const auto* scene_2d = dynamic_cast(this); + return scene_2d + ? static_cast(scene_2d) + : nullptr; +} -std::uint64_t Scene_Base::observer_now_ns() const noexcept { +void Scene_2D_Base::attach(Renderable_Id id) { + if (!display_resolver_.contains(id)) + display_resolver_.attach(id); +} +void Scene_2D_Base::cleanup() { + for (const Renderable_Id id : display_resolver_.ids()) { + if (renderables_.contains(id)) + continue; + if (!display_resolver_.isolated(id)) + throw std::logic_error( + "display graph still references detached renderable " + + std::to_string(id)); + display_resolver_.erase(id); + } +} +void Scene_2D_Base::validate() const { + const auto display = display_resolver_.resolve(); + if (display.order.size() != renderables_.size()) + throw std::logic_error( + "display graph does not match attached renderables"); + for (const Renderable_Id id : display.order) { + if (!renderables_.contains(id)) + throw std::logic_error( + "display graph references detached renderable " + + std::to_string(id)); + } +} +renderive::scene::dependency::Resolution +Scene_2D_Base::resolve() const { + return display_resolver_.resolve(); +} + +std::uint64_t Scene_Base::Impl::now_ns() const noexcept { return 0; } @@ -875,10 +896,9 @@ void Scene_Base::render_loop() { { std::lock_guard lock(model_mutex_); Scene_Base* previous = std::exchange(active_renderable_edit_scene_, this); - Renderable_Editor editor(*this); for (auto& command : edits) { try { - command.edit(editor); + command.execute(); cleanup_detached_topology_locked(); validate_structure_locked(); } catch (...) { @@ -896,23 +916,26 @@ void Scene_Base::render_loop() { continue; } const auto& snapshot = *task.snapshot; - observe_scene({Observation_Event::render_started, observer_now_ns(), + d_func().dispatch({Observation_Event::render_started, d_func().now_ns(), snapshot.render_sequence, snapshot.scene_state_revision_, - snapshot.renderables.size()}); + snapshot.renderables.size(), task.topology, + task.snapshot, task.plan}); std::exception_ptr exception; try { execute_taskflow(task); - observe_scene({Observation_Event::render_completed, observer_now_ns(), + d_func().dispatch({Observation_Event::render_completed, d_func().now_ns(), snapshot.render_sequence, snapshot.scene_state_revision_, - snapshot.renderables.size()}); + snapshot.renderables.size(), task.topology, + task.snapshot, task.plan}); } catch (...) { exception = std::current_exception(); - observe_scene({Observation_Event::render_failed, observer_now_ns(), + d_func().dispatch({Observation_Event::render_failed, d_func().now_ns(), snapshot.render_sequence, snapshot.scene_state_revision_, - snapshot.renderables.size()}); + snapshot.renderables.size(), task.topology, + task.snapshot, task.plan}); } { std::lock_guard lock(task_mutex_); diff --git a/Kernel/src/renderive/scene/base/Scene_Base.hpp b/Kernel/src/renderive/scene/base/Scene_Base.hpp index af00475..c7b635d 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.hpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.hpp @@ -24,6 +24,8 @@ #include "renderive/renderable/base/Renderable_Base.hpp" #include "renderive/renderable/base/Renderive_Owner.hpp" #include "renderive/render_graph/Render_Plan.hpp" +#include "renderive/scene/Inheritance.hpp" +#include "renderive/scene/dependency/Dependency_Resolver.hpp" #include "Abstract_Frame.hpp" #include "Frame_Render_Snapshot.hpp" #include "Frame_Viewport.hpp" @@ -32,6 +34,18 @@ class Color_Cache; +namespace renderive::scene::detail { + +struct Composition_Relationships { + virtual ~Composition_Relationships() = default; + virtual void attach(Renderable_Id id) = 0; + virtual void cleanup() = 0; + virtual void validate() const = 0; + virtual dependency::Resolution resolve() const = 0; +}; + +} // namespace renderive::scene::detail + class Scene_Compositor { public: virtual ~Scene_Compositor() = default; @@ -55,24 +69,24 @@ public: class Attach_Builder; - class Renderable_Editor { + class Renderable_Editor + : public renderive::scene_inheritance::Editor_Root { public: void attach(Renderable renderable); void detach(const Renderable& renderable); - void set_display_parent(const Renderable& child, const Renderable& parent); - void add_display_parent(const Renderable& child, const Renderable& parent); - void clear_display_parent(const Renderable& child); void set_dependency_parent(const Renderable& child, const Renderable& parent); void add_dependency_parent(const Renderable& child, const Renderable& parent); void clear_dependency_parent(const Renderable& child); - private: + protected: explicit Renderable_Editor(Scene_Base& scene) : scene_(scene) {} Scene_Base& scene_; + private: friend class Scene_Base; friend class Attach_Builder; }; - class Attach_Builder { + class Attach_Builder + : public renderive::scene_inheritance::Builder_Root { public: Attach_Builder(const Attach_Builder&) = delete; Attach_Builder& operator=(const Attach_Builder&) = delete; @@ -80,22 +94,30 @@ public: Attach_Builder& operator=(Attach_Builder&&) = delete; ~Attach_Builder(); void attach(Renderable renderable); - void set_display_parent(const Renderable& child, const Renderable& parent); - void add_display_parent(const Renderable& child, const Renderable& parent); - void clear_display_parent(const Renderable& child); void set_dependency_parent(const Renderable& child, const Renderable& parent); void add_dependency_parent(const Renderable& child, const Renderable& parent); void clear_dependency_parent(const Renderable& child); - private: + protected: explicit Attach_Builder(Scene_Base& scene); Scene_Base& scene_; std::unique_lock task_lock_; - Renderable_Editor editor_; + private: friend class Scene_Base; }; using Renderable_Edit = std::function; + struct Topology_Relationship { + Const_Renderable child; + Const_Renderable parent; + }; + + struct Topology_Snapshot { + std::vector renderables; + std::vector display; + std::vector dependency; + }; + enum class Observation_Event { render_submitted, render_started, @@ -109,17 +131,9 @@ public: std::uint64_t render_sequence{}; std::uint64_t scene_state_revision{}; std::size_t renderable_count{}; - }; - - struct Topology_Relationship { - Const_Renderable child; - Const_Renderable parent; - }; - - struct Topology_Snapshot { - std::vector renderables; - std::vector display; - std::vector dependency; + std::shared_ptr topology; + std::shared_ptr frame_snapshot; + std::shared_ptr render_plan; }; Scene_Base(); @@ -166,54 +180,37 @@ public: protected: + struct State_Layer + : renderive::scene_inheritance::State_Root {}; + + struct Impl + : renderive::scene_inheritance::Impl_Root { + struct Observer : renderive::scene_inheritance::Observer_Root {}; + virtual std::uint64_t now_ns() const noexcept; + virtual ~Impl() = default; + }; + + Scene_Base(std::pmr::memory_resource& upstream_memory_resource, + std::unique_ptr impl); + Impl& d_func() noexcept { return *impl_; } + const Impl& d_func() const noexcept { return *impl_; } + virtual Frame_Control_Strategy_Base& frame_control_strategy_impl(); virtual const Frame_Control_Strategy_Base& frame_control_strategy_impl() const; virtual std::shared_ptr make_renderable_color_cache(); virtual Frame_Viewport frame_viewport() const; virtual std::uint64_t acquire_scene_state(); virtual void capture_scene_state(Frame_Render_Snapshot& snapshot) const; - virtual void observe_scene(const Observation& observation) noexcept; - virtual std::uint64_t observer_now_ns() const noexcept; - std::unique_lock lock_render_idle(); [[nodiscard]] bool is_render_worker_thread() const noexcept; bool consume_model_dirty() noexcept; void invalidate_renderables(); void shutdown() noexcept; + void enqueue_renderable_edit(std::function edit); private: friend class Renderable_Base; - - struct Topology_Node { - explicit Topology_Node(std::pmr::memory_resource& memory_resource) - : parents(&memory_resource), children(&memory_resource) {} - - std::pmr::vector parents; - std::pmr::vector children; - }; - - class Id_Topology { - public: - explicit Id_Topology(std::pmr::memory_resource& memory_resource) - : nodes_(&memory_resource) {} - - void attach(Renderable_Id id); - void erase(Renderable_Id id); - bool replace_parents(Renderable_Id child, const std::vector& parents); - bool add_parent(Renderable_Id child, Renderable_Id parent); - bool clear_parents(Renderable_Id child); - [[nodiscard]] bool contains(Renderable_Id id) const noexcept; - [[nodiscard]] bool isolated(Renderable_Id id) const; - [[nodiscard]] const Topology_Node& node(Renderable_Id id) const; - [[nodiscard]] std::vector ids() const; - [[nodiscard]] std::vector order() const; - - private: - void validate_endpoint(Renderable_Id id) const; - static void erase_id(std::pmr::vector& values, Renderable_Id id) noexcept; - - std::pmr::unordered_map nodes_; - }; + friend class Scene_2D_Base; using Composite_Render_Node_Function = std::function; @@ -239,13 +236,14 @@ private: std::shared_ptr snapshot; std::shared_ptr plan; + std::shared_ptr topology; std::pmr::vector execution_bindings; Frame frame; std::shared_ptr completion; }; struct Renderable_Edit_Command { - Renderable_Edit edit; + std::function execute; }; class Execution_Context; @@ -258,8 +256,7 @@ private: }; void submit_render(Abstract_Frame* frame); - void enqueue_renderable_edit(Renderable_Edit edit); - void enqueue_renderable_edit_locked(Renderable_Edit edit); + void enqueue_renderable_edit_locked(std::function edit); void request_render_graph_rebuild(Renderable_Base& renderable); [[nodiscard]] std::shared_ptr snapshot_live_model(); [[nodiscard]] std::shared_ptr @@ -274,6 +271,10 @@ private: const Renderable& renderable) const; void validate_structure_locked(); void cleanup_detached_topology_locked(); + renderive::scene::detail::Composition_Relationships* + composition_relationships() noexcept; + const renderive::scene::detail::Composition_Relationships* + composition_relationships() const noexcept; [[noreturn]] static void structure_fail_fast(const char* stage, std::exception_ptr exception = {}) noexcept; inline static thread_local Scene_Base* active_execution_scene_{}; @@ -282,10 +283,11 @@ private: std::shared_ptr scene_lifetime_; std::shared_ptr memory_domain_; + std::unique_ptr impl_; std::atomic_bool model_dirty_{true}; std::pmr::unordered_map renderables_; - Id_Topology display_topology_; - Id_Topology dependency_topology_; + renderive::scene::dependency::Dependency_Resolver + dependency_resolver_; std::pmr::unordered_map> color_caches_; mutable std::mutex model_mutex_; @@ -313,16 +315,117 @@ private: Capture_Repository capture_repository_; }; -class Scene_2D_Base : public Scene_Base { +class Scene_2D_Base + : public Scene_Base, + private renderive::scene::detail::Composition_Relationships { public: - Scene_2D_Base() = default; - explicit Scene_2D_Base(std::pmr::memory_resource& memory_resource) - : Scene_Base(memory_resource) {} + class Renderable_Editor + : public renderive::scene_inheritance::Editor_Node< + Renderable_Editor, Scene_Base::Renderable_Editor> { + public: + void set_display_parent(const Renderable& child, const Renderable& parent); + void add_display_parent(const Renderable& child, const Renderable& parent); + void clear_display_parent(const Renderable& child); + private: + explicit Renderable_Editor(Scene_2D_Base& scene) + : renderive::scene_inheritance::Editor_Node< + Renderable_Editor, Scene_Base::Renderable_Editor>(scene), + scene_2d_(scene) {} + Scene_2D_Base& scene_2d_; + friend class Scene_2D_Base; + friend class Attach_Builder; + }; + + class Attach_Builder + : public renderive::scene_inheritance::Builder_Node< + Attach_Builder, Scene_Base::Attach_Builder> { + public: + void set_display_parent(const Renderable& child, const Renderable& parent); + void add_display_parent(const Renderable& child, const Renderable& parent); + void clear_display_parent(const Renderable& child); + private: + explicit Attach_Builder(Scene_2D_Base& scene) + : renderive::scene_inheritance::Builder_Node< + Attach_Builder, Scene_Base::Attach_Builder>(scene), + scene_2d_(scene) {} + Scene_2D_Base& scene_2d_; + friend class Scene_2D_Base; + }; + + using Renderable_Edit = std::function; + + Scene_2D_Base(); + explicit Scene_2D_Base(std::pmr::memory_resource& memory_resource); + Attach_Builder attach_builder(); + void edit_renderables(Renderable_Edit edit); + +protected: + struct State_Layer + : renderive::scene_inheritance::State_Node< + State_Layer, Scene_Base::State_Layer> {}; + struct Impl : renderive::scene_inheritance::Impl_Node { + struct Observer + : renderive::scene_inheritance::Observer_Node< + Impl, Scene_Base::Impl::Observer> {}; + }; + Scene_2D_Base(std::pmr::memory_resource& memory_resource, + std::unique_ptr impl); + +private: + friend class Scene_Base; + void with_initial_display_edit(std::function edit); + void set_display_parent_locked(const Renderable& child, + const Renderable& parent); + void add_display_parent_locked(const Renderable& child, + const Renderable& parent); + void clear_display_parent_locked(const Renderable& child); + void attach(Renderable_Id id) override; + void cleanup() override; + void validate() const override; + renderive::scene::dependency::Resolution resolve() + const override; + + renderive::scene::dependency::Dependency_Resolver + display_resolver_; }; class Scene_3D_Base : public Scene_Base { public: - Scene_3D_Base() = default; - explicit Scene_3D_Base(std::pmr::memory_resource& memory_resource) - : Scene_Base(memory_resource) {} + class Renderable_Editor + : public renderive::scene_inheritance::Editor_Node< + Renderable_Editor, Scene_Base::Renderable_Editor> { + private: + explicit Renderable_Editor(Scene_3D_Base& scene) + : renderive::scene_inheritance::Editor_Node< + Renderable_Editor, Scene_Base::Renderable_Editor>(scene) {} + friend class Scene_3D_Base; + }; + + class Attach_Builder + : public renderive::scene_inheritance::Builder_Node< + Attach_Builder, Scene_Base::Attach_Builder> { + private: + explicit Attach_Builder(Scene_3D_Base& scene) + : renderive::scene_inheritance::Builder_Node< + Attach_Builder, Scene_Base::Attach_Builder>(scene) {} + friend class Scene_3D_Base; + }; + + using Renderable_Edit = std::function; + Scene_3D_Base(); + explicit Scene_3D_Base(std::pmr::memory_resource& memory_resource); + Attach_Builder attach_builder(); + void edit_renderables(Renderable_Edit edit); + +protected: + struct State_Layer + : renderive::scene_inheritance::State_Node< + State_Layer, Scene_Base::State_Layer> {}; + struct Impl : renderive::scene_inheritance::Impl_Node { + struct Observer + : renderive::scene_inheritance::Observer_Node< + Impl, Scene_Base::Impl::Observer> {}; + }; + Scene_3D_Base(std::pmr::memory_resource& memory_resource, + std::unique_ptr impl); }; diff --git a/Kernel/src/renderive/scene/dependency/Dependency_Resolver.hpp b/Kernel/src/renderive/scene/dependency/Dependency_Resolver.hpp new file mode 100644 index 0000000..05c0ac8 --- /dev/null +++ b/Kernel/src/renderive/scene/dependency/Dependency_Resolver.hpp @@ -0,0 +1,208 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace renderive::scene::dependency { + +template +struct Relationship { + Id child{}; + Id parent{}; + bool operator==(const Relationship&) const = default; +}; + +template +struct Resolution { + std::vector order; + std::vector> relationships; +}; + +// Stores only declared relationships. Order and relationship snapshots are +// derived on demand, so mutations have one authoritative source. +template > +class Dependency_Resolver { +public: + explicit Dependency_Resolver(std::pmr::memory_resource& resource) + : nodes_(&resource) {} + + void attach(Id id) { + if (!nodes_.try_emplace(id, resource()).second) + throw std::logic_error("dependency id is already attached"); + } + + void erase(Id id) { + validate_endpoint(id); + for (auto& [node_id, node] : nodes_) { + static_cast(node_id); + erase_id(node.parents, id); + erase_id(node.children, id); + } + nodes_.erase(id); + } + + bool replace_parents(Id child, const std::vector& parents) { + validate_endpoint(child); + std::vector unique; + unique.reserve(parents.size()); + for (const Id parent : parents) { + validate_relationship(child, parent); + if (std::find(unique.begin(), unique.end(), parent) == unique.end()) + unique.push_back(parent); + } + std::sort(unique.begin(), unique.end()); + auto& current = nodes_.at(child).parents; + if (std::equal(current.begin(), current.end(), unique.begin(), + unique.end())) + return false; + const std::vector previous(current.begin(), current.end()); + assign_parents(child, unique); + try { + validate_acyclic(); + } catch (...) { + assign_parents(child, previous); + throw; + } + return true; + } + + bool add_parent(Id child, Id parent) { + validate_relationship(child, parent); + auto parents = std::vector(nodes_.at(child).parents.begin(), + nodes_.at(child).parents.end()); + if (std::find(parents.begin(), parents.end(), parent) != parents.end()) + return false; + parents.push_back(parent); + return replace_parents(child, parents); + } + + bool clear_parents(Id child) { + validate_endpoint(child); + if (nodes_.at(child).parents.empty()) + return false; + return replace_parents(child, {}); + } + + [[nodiscard]] bool contains(Id id) const noexcept { + return nodes_.contains(id); + } + + [[nodiscard]] bool isolated(Id id) const { + validate_endpoint(id); + const auto& node = nodes_.at(id); + return node.parents.empty() && node.children.empty(); + } + + [[nodiscard]] std::vector ids() const { + std::vector result; + result.reserve(nodes_.size()); + for (const auto& [id, node] : nodes_) { + static_cast(node); + result.push_back(id); + } + std::sort(result.begin(), result.end()); + return result; + } + + [[nodiscard]] std::vector parents(Id id) const { + validate_endpoint(id); + const auto& values = nodes_.at(id).parents; + return {values.begin(), values.end()}; + } + + [[nodiscard]] Resolution resolve() const { + Resolution result; + result.relationships.reserve(edge_count()); + for (const Id child : ids()) { + for (const Id parent : nodes_.at(child).parents) + result.relationships.push_back({child, parent}); + } + + std::unordered_map indegree; + indegree.reserve(nodes_.size()); + std::priority_queue, std::greater> ready; + for (const auto& [id, node] : nodes_) { + indegree.emplace(id, node.parents.size()); + if (node.parents.empty()) + ready.push(id); + } + result.order.reserve(nodes_.size()); + while (!ready.empty()) { + const Id id = ready.top(); + ready.pop(); + result.order.push_back(id); + for (const Id child : nodes_.at(id).children) { + auto& degree = indegree.at(child); + if (--degree == 0) + ready.push(child); + } + } + if (result.order.size() != nodes_.size()) + throw std::logic_error("dependency graph contains a cycle"); + return result; + } + +private: + struct Node { + explicit Node(std::pmr::memory_resource* resource) + : parents(resource), children(resource) {} + std::pmr::vector parents; + std::pmr::vector children; + }; + + [[nodiscard]] std::pmr::memory_resource* resource() const noexcept { + return nodes_.get_allocator().resource(); + } + + void validate_endpoint(Id id) const { + if (!nodes_.contains(id)) + throw std::invalid_argument("dependency endpoint is not attached"); + } + + void validate_relationship(Id child, Id parent) const { + validate_endpoint(child); + validate_endpoint(parent); + if (child == parent) + throw std::invalid_argument("dependency cannot reference itself"); + } + + void validate_acyclic() const { static_cast(resolve()); } + + void assign_parents(Id child, const std::vector& parents) { + auto& current = nodes_.at(child).parents; + for (const Id parent : current) + erase_id(nodes_.at(parent).children, child); + current.assign(parents.begin(), parents.end()); + for (const Id parent : current) { + auto& children = nodes_.at(parent).children; + if (std::find(children.begin(), children.end(), child) == + children.end()) + children.push_back(child); + } + } + + [[nodiscard]] std::size_t edge_count() const noexcept { + std::size_t result{}; + for (const auto& [id, node] : nodes_) { + static_cast(id); + result += node.parents.size(); + } + return result; + } + + static void erase_id(std::pmr::vector& values, Id id) noexcept { + values.erase(std::remove(values.begin(), values.end(), id), + values.end()); + } + + std::pmr::unordered_map nodes_; +}; + +} // namespace renderive::scene::dependency diff --git a/Kernel/tests/renderive/scene/Dependency_Resolver_Test.cpp b/Kernel/tests/renderive/scene/Dependency_Resolver_Test.cpp new file mode 100644 index 0000000..4fae206 --- /dev/null +++ b/Kernel/tests/renderive/scene/Dependency_Resolver_Test.cpp @@ -0,0 +1,62 @@ +#include + +#include +#include + +#include "renderive/scene/dependency/Dependency_Resolver.hpp" + +namespace { + +enum class Preparation { + acquire_source, + transform_vertices, + upload_visual +}; + +using Resolver = + renderive::scene::dependency::Dependency_Resolver; + +TEST(dependency_resolver_test, + resolves_declared_dependencies_in_deterministic_order) { + std::pmr::unsynchronized_pool_resource memory; + Resolver resolver(memory); + resolver.attach(Preparation::upload_visual); + resolver.attach(Preparation::acquire_source); + resolver.attach(Preparation::transform_vertices); + resolver.add_parent(Preparation::transform_vertices, + Preparation::acquire_source); + resolver.add_parent(Preparation::upload_visual, + Preparation::transform_vertices); + + const auto resolution = resolver.resolve(); + EXPECT_EQ(resolution.order, + (std::vector{Preparation::acquire_source, + Preparation::transform_vertices, + Preparation::upload_visual})); +} + +TEST(dependency_resolver_test, + rejected_cycle_preserves_the_previous_authoritative_relationships) { + std::pmr::unsynchronized_pool_resource memory; + Resolver resolver(memory); + resolver.attach(Preparation::acquire_source); + resolver.attach(Preparation::transform_vertices); + resolver.attach(Preparation::upload_visual); + resolver.add_parent(Preparation::transform_vertices, + Preparation::acquire_source); + resolver.add_parent(Preparation::upload_visual, + Preparation::transform_vertices); + + EXPECT_THROW( + resolver.add_parent(Preparation::acquire_source, + Preparation::upload_visual), + std::logic_error); + + EXPECT_TRUE(resolver.parents(Preparation::acquire_source).empty()); + EXPECT_EQ(resolver.resolve().order, + (std::vector{Preparation::acquire_source, + Preparation::transform_vertices, + Preparation::upload_visual})); +} + +} // namespace diff --git a/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp b/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp index 2972950..eb03e14 100644 --- a/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp @@ -155,8 +155,6 @@ TEST(scene3d_context_test, runtime_edit_explicitly_reparents_children_before_det attach.attach(grandparent); attach.attach(parent); attach.attach(child); - attach.set_display_parent(parent, grandparent); - attach.set_display_parent(child, parent); attach.set_dependency_parent(parent, grandparent); attach.set_dependency_parent(child, parent); }); @@ -164,28 +162,19 @@ TEST(scene3d_context_test, runtime_edit_explicitly_reparents_children_before_det scene.wait_for_render(); EXPECT_EQ(child->render_count, 1); wait_renderable_edit(scene, [grandparent, parent, child](auto& editor) { - editor.set_display_parent(child, grandparent); editor.set_dependency_parent(child, grandparent); - editor.clear_display_parent(parent); editor.clear_dependency_parent(parent); editor.detach(parent); }); const auto topology = scene.topology_snapshot(); - bool display_checked{}; bool dependency_checked{}; - for (const auto& relation : topology.display) { - if (relation.child.get() == child.get()) { - EXPECT_EQ(relation.parent.get(), grandparent.get()); - display_checked = true; - } - } for (const auto& relation : topology.dependency) { if (relation.child.get() == child.get()) { EXPECT_EQ(relation.parent.get(), grandparent.get()); dependency_checked = true; } } - EXPECT_TRUE(display_checked); + EXPECT_TRUE(topology.display.empty()); EXPECT_TRUE(dependency_checked); scene.render(); scene.wait_for_render(); diff --git a/Kernel/tests/renderive/scene/Scene_Inheritance_Test.cpp b/Kernel/tests/renderive/scene/Scene_Inheritance_Test.cpp new file mode 100644 index 0000000..871de4e --- /dev/null +++ b/Kernel/tests/renderive/scene/Scene_Inheritance_Test.cpp @@ -0,0 +1,128 @@ +#include + +#include +#include +#include + +#include "renderive/inheritance/Registry.hpp" +#include "renderive/scene/Inheritance.hpp" + +namespace { + +struct Lifecycle_Event { + std::vector& handled_by; +}; + +struct Scene_Impl + : renderive::scene_inheritance::Impl_Root { + struct Observer : + renderive::scene_inheritance::Observer_Root { + static void handle(Scene_Impl&, const Lifecycle_Event& event) { + event.handled_by.emplace_back("scene"); + } + }; +}; + +struct Scene2D_Impl + : renderive::scene_inheritance::Impl_Node { + struct Observer + : renderive::scene_inheritance::Observer_Node< + Scene2D_Impl, Scene_Impl::Observer> { + static void handle(Scene2D_Impl&, const Lifecycle_Event& event) { + event.handled_by.emplace_back("scene_2d"); + } + }; +}; + +struct Plot_Scene_Impl + : renderive::scene_inheritance::Impl_Node { + struct Observer + : renderive::scene_inheritance::Observer_Node< + Plot_Scene_Impl, Scene2D_Impl::Observer> { + static void handle(Plot_Scene_Impl&, const Lifecycle_Event& event) { + event.handled_by.emplace_back("plot_scene"); + } + }; +}; + +struct Scene_Editor + : renderive::scene_inheritance::Editor_Root {}; +struct Scene2D_Editor + : renderive::scene_inheritance::Editor_Node {}; +struct Plot_Scene_Editor + : renderive::scene_inheritance::Editor_Node {}; + +struct Scene_Builder + : renderive::scene_inheritance::Builder_Root {}; +struct Scene2D_Builder + : renderive::scene_inheritance::Builder_Node {}; +struct Plot_Scene_Builder + : renderive::scene_inheritance::Builder_Node {}; + +struct Scene_State + : renderive::scene_inheritance::State_Root {}; +struct Scene2D_State + : renderive::scene_inheritance::State_Node {}; +struct Plot_Scene_State + : renderive::scene_inheritance::State_Node {}; + +TEST(scene_inheritance_test, + observer_dispatch_follows_the_scene_chain_parent_before_child) { + Plot_Scene_Impl impl; + std::vector handled_by; + impl.dispatch({handled_by}); + EXPECT_EQ(handled_by, + (std::vector{"scene", "scene_2d", "plot_scene"})); +} + +TEST(scene_inheritance_test, + editor_chain_registers_each_semantic_layer_at_its_own_position) { + using Registry = Plot_Scene_Editor::Inheritance_Registry; + using Chain = renderive::scene_inheritance::Editor_Chain; + static_assert(renderive::inheritance::Chain_Registration_Count_V< + Registry, Chain> == 3); + static_assert(std::same_as< + renderive::inheritance::Type_At_Position_T, + Scene_Editor>); + static_assert(std::same_as< + renderive::inheritance::Type_At_Position_T, + Plot_Scene_Editor>); + SUCCEED(); +} + +TEST(scene_inheritance_test, + builder_and_state_chains_extend_without_sharing_registrations) { + using Builder_Registry = Plot_Scene_Builder::Inheritance_Registry; + using State_Registry = Plot_Scene_State::Inheritance_Registry; + using Builder_Chain = renderive::scene_inheritance::Builder_Chain; + using State_Chain = renderive::scene_inheritance::State_Chain; + static_assert(renderive::inheritance::Chain_Registration_Count_V< + Builder_Registry, Builder_Chain> == 3); + static_assert(renderive::inheritance::Chain_Registration_Count_V< + Builder_Registry, State_Chain> == 0); + static_assert(renderive::inheritance::Chain_Registration_Count_V< + State_Registry, State_Chain> == 3); + static_assert(renderive::inheritance::Chain_Registration_Count_V< + State_Registry, Builder_Chain> == 0); + static_assert(std::same_as< + renderive::inheritance::Type_At_Position_T< + Builder_Registry, Builder_Chain, 2>, + Plot_Scene_Builder>); + static_assert(std::same_as< + renderive::inheritance::Type_At_Position_T< + State_Registry, State_Chain, 2>, + Plot_Scene_State>); + SUCCEED(); +} + +} // namespace diff --git a/Kernel/tests/renderive/scene/Scene_State_Observer_Test.cpp b/Kernel/tests/renderive/scene/Scene_State_Observer_Test.cpp index dcd33ad..60b0cf8 100644 --- a/Kernel/tests/renderive/scene/Scene_State_Observer_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene_State_Observer_Test.cpp @@ -16,12 +16,17 @@ struct Scene_State_Observer_Test_Time_Source { }; struct Scene_State_Observer_Test_Data { std::vector events; + std::atomic complete_snapshot_count{}; }; struct Scene_State_Observer_Test_Recorder { static constexpr bool enabled = true; std::shared_ptr data{std::make_shared()}; void observe(const Scene_Base::Observation& observation) noexcept { data->events.push_back(observation.event); + if (observation.topology && observation.frame_snapshot && + observation.render_plan) + data->complete_snapshot_count.fetch_add(1, + std::memory_order_relaxed); } }; using Scene_State_Observer_Test_Scene_Observer = Observer_State; @@ -37,6 +42,7 @@ TEST(scene_state_observer_test, scene_uses_double_state_and_observes_lifecycle) scene.wait_for_render(); EXPECT_EQ(render.read(static_cast(scene)).value, 9); EXPECT_EQ(recorder.data->events, (std::vector{Scene_Base::Observation_Event::render_submitted, Scene_Base::Observation_Event::render_started, Scene_Base::Observation_Event::render_completed})); + EXPECT_EQ(recorder.data->complete_snapshot_count.load(), 3); } struct Scene_Reentrant_Observer_Data { std::function callback; diff --git a/Kernel/tests/renderive/scene/Scene_Test_Helpers.hpp b/Kernel/tests/renderive/scene/Scene_Test_Helpers.hpp index 434e4f6..f78d9c5 100644 --- a/Kernel/tests/renderive/scene/Scene_Test_Helpers.hpp +++ b/Kernel/tests/renderive/scene/Scene_Test_Helpers.hpp @@ -2,21 +2,21 @@ #include #include #include "renderive/scene/base/Scene_Base.hpp" -template -void attach_initial(Scene_Base& scene, const Owners&... owners) { +template +void attach_initial(Scene& scene, const Owners&... owners) { auto builder = scene.attach_builder(); (builder.attach(owners), ...); } -template -void with_attach_builder(Scene_Base& scene, Edit&& edit) { +template +void with_attach_builder(Scene& scene, Edit&& edit) { auto builder = scene.attach_builder(); edit(builder); } -template -void wait_renderable_edit(Scene_Base& scene, Edit&& edit) { +template +void wait_renderable_edit(Scene& scene, Edit&& edit) { std::promise completed; auto future = completed.get_future(); - scene.edit_renderables([edit = std::forward(edit), &completed](Scene_Base::Renderable_Editor& editor) mutable { + scene.edit_renderables([edit = std::forward(edit), &completed](auto& editor) mutable { edit(editor); completed.set_value(); }); diff --git a/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp b/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp index 2a5c6f2..4197d41 100644 --- a/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp +++ b/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp @@ -473,7 +473,7 @@ TEST(scene_base_test, detached_topology_reference_fails_fast_after_callback) { }); scene.render(); }, - "dependency topology still references detached renderable"); + "dependency graph still references detached renderable"); } TEST(scene_base_test, wait_for_render_propagates_background_render_failure) { Scene2D_Context<> scene; diff --git a/render_2D/render_2D/renderable/Renderable_Builder.h b/render_2D/render_2D/renderable/Renderable_Builder.h index 9287328..e3bcb7a 100644 --- a/render_2D/render_2D/renderable/Renderable_Builder.h +++ b/render_2D/render_2D/renderable/Renderable_Builder.h @@ -14,7 +14,7 @@ namespace detail { class Paint_Overlay; template requires std::derived_from && std::derived_from -void attach_renderable_dependency(::Scene_Base::Attach_Builder& builder, const renderive_Owner& renderable, const renderive_Owner& axis) { +void attach_renderable_dependency(::Scene_2D_Base::Attach_Builder& builder, const renderive_Owner& renderable, const renderive_Owner& axis) { if (!axis) return; builder.add_dependency_parent(renderable, axis); @@ -24,10 +24,10 @@ void attach_renderable_dependency(::Scene_Base::Attach_Builder& builder, const r builder.add_display_parent(axis, renderable); } template -void attach_renderable_dependency(::Scene_Base::Attach_Builder&, const renderive_Owner&, const Value&) {} +void attach_renderable_dependency(::Scene_2D_Base::Attach_Builder&, const renderive_Owner&, const Value&) {} template requires std::derived_from -void attach_renderable(::Scene_Base::Attach_Builder& builder, const renderive_Owner& renderable, const renderive_Owner& parent, const Args&... args) { +void attach_renderable(::Scene_2D_Base::Attach_Builder& builder, const renderive_Owner& renderable, const renderive_Owner& parent, const Args&... args) { builder.attach(renderable); builder.add_display_parent(renderable, parent); builder.add_dependency_parent(renderable, parent); @@ -41,9 +41,9 @@ struct Renderable_Builder { using Validator = Validator_Type; using Self = Renderable_Builder; Renderable_Builder() requires std::default_initializable && std::default_initializable = default; - explicit Renderable_Builder(::Scene_Base::Attach_Builder* attach_builder) requires std::default_initializable && std::default_initializable : attach_builder_(attach_builder) {} - explicit Renderable_Builder(Properties properties, ::Scene_Base::Attach_Builder* attach_builder = nullptr) requires std::default_initializable : properties(std::move(properties)), attach_builder_(attach_builder) {} - Renderable_Builder(Properties properties, Validator validator, ::Scene_Base::Attach_Builder* attach_builder = nullptr) : properties(std::move(properties)), validator(std::move(validator)), attach_builder_(attach_builder) {} + explicit Renderable_Builder(::Scene_2D_Base::Attach_Builder* attach_builder) requires std::default_initializable && std::default_initializable : attach_builder_(attach_builder) {} + explicit Renderable_Builder(Properties properties, ::Scene_2D_Base::Attach_Builder* attach_builder = nullptr) requires std::default_initializable : properties(std::move(properties)), attach_builder_(attach_builder) {} + Renderable_Builder(Properties properties, Validator validator, ::Scene_2D_Base::Attach_Builder* attach_builder = nullptr) : properties(std::move(properties)), validator(std::move(validator)), attach_builder_(attach_builder) {} template Value> Self& set(Value&& value) { properties.*Member = std::forward(value); @@ -82,6 +82,6 @@ private: } Properties properties{}; [[no_unique_address]] Validator validator{}; - ::Scene_Base::Attach_Builder* attach_builder_{}; + ::Scene_2D_Base::Attach_Builder* attach_builder_{}; }; } diff --git a/web_server/app/render_2D/Gallery_Scene2D.cpp b/web_server/app/render_2D/Gallery_Scene2D.cpp index db7da70..e7eddd0 100644 --- a/web_server/app/render_2D/Gallery_Scene2D.cpp +++ b/web_server/app/render_2D/Gallery_Scene2D.cpp @@ -1013,7 +1013,7 @@ private: }; write_web_performance_log(line.dump()); } - void build_axes(::Scene_Base::Attach_Builder& attach) { + void build_axes(::Scene_2D_Base::Attach_Builder& attach) { constexpr Color axis_color{118, 145, 184, 255}; Range coordinate_range{88'000'000.0, 108'000'000.0}; if (case_id_ == "sweep_spectrum") @@ -1098,7 +1098,7 @@ private: if (case_id_ == "axis_lab") style(time_axis_); } - void build_primary(::Scene_Base::Attach_Builder& attach) { + void build_primary(::Scene_2D_Base::Attach_Builder& attach) { if (case_id_ == "axis_lab") { primary_ = renderive_static_owner_cast(frequency_domain_axis_); return;