From e8e8b298064d605e0bff4334867da026902e182b Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Thu, 13 Aug 2026 15:17:02 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Kernel/#U8bbe#U8ba1#U6587#U6863.md | 11 - Kernel/CMakeLists.txt | 3 + .../src/renderive/scene/base/Scene_Base.cpp | 265 +++++++-------- .../src/renderive/scene/base/Scene_Base.hpp | 57 ++-- .../real_time_data/Real_Time_Data_Test.cpp | 25 +- .../renderable/Renderable_Test_Harness.hpp | 105 ++++++ .../renderable/base/Renderable_Base_Test.cpp | 62 ++-- .../renderable/concept/Renderable_Test.cpp | 12 +- .../Dynamic_Renderable_Lifecycle_Test.cpp | 50 +-- .../scene/Render_Plan_Execution_Test.cpp | 43 +-- .../renderive/scene/Scene2D_Context_Test.cpp | 60 ++-- .../scene/Scene2D_Render_Order_Test.cpp | 21 +- .../renderive/scene/Scene3D_Context_Test.cpp | 38 ++- .../scene/Scene_Memory_Resource_Test.cpp | 15 +- .../renderive/scene/base/Scene_Base_Test.cpp | 313 ++++++++++++++++-- .../renderive/state/State_Test_Types.hpp | 3 +- .../threading/Threading_Contract_Test.cpp | 16 +- Qt/plot/Plot.cpp | 16 +- Qt/plot/Plot.h | 7 +- Qt/tests/Qt_Bridge_Tests.cpp | 39 ++- render_2D/axis/Axis_Builder.h | 4 +- render_2D/renderable/Renderable_Builder.h | 10 +- .../tests/render_2D_Integration_Tests.cpp | 100 +++--- web_server/app/Gallery_Plot_Session.cpp | 4 +- web_server/app/Web_Plot_Session.cpp | 4 +- 25 files changed, 822 insertions(+), 461 deletions(-) delete mode 100644 Kernel/#U8bbe#U8ba1#U6587#U6863.md create mode 100644 Kernel/tests/renderive/renderable/Renderable_Test_Harness.hpp diff --git a/Kernel/#U8bbe#U8ba1#U6587#U6863.md b/Kernel/#U8bbe#U8ba1#U6587#U6863.md deleted file mode 100644 index 5cf1a8e..0000000 --- a/Kernel/#U8bbe#U8ba1#U6587#U6863.md +++ /dev/null @@ -1,11 +0,0 @@ -renderable 可以被渲染的基础元素 - -状态策略给类的状态提供线程安全访问更新机制 - -帧策略提供帧的发布生成机制 - -plot 上下文对象聚合所有策略 - -Flow 当前使用 Kernel 内部 `std::pmr::deque + mutex` 实现并发帧队列,不新增 Boost.Lockfree 依赖。多个 producer 和多个 consumer 调用的线程安全由测试覆盖,实际 renderer 消费通过 render mutex 串行化;Frame 对传入 PMR 的 allocate/deallocate 也在内部状态锁下串行化,所以允许使用 `std::pmr::unsynchronized_pool_resource`。 - -完整线程模型和多线程测试约束见 `threading.md`。 diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 7b258a7..b4650cc 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -40,6 +40,9 @@ if(RENDERIVE_BUILD_TESTS) string(MAKE_C_IDENTIFIER "${Renderive_Kernel_test_name}" Renderive_Kernel_test_name) set(Renderive_Kernel_test_target "Renderive_Kernel_${Renderive_Kernel_test_name}_${Renderive_Kernel_test_hash}") add_executable("${Renderive_Kernel_test_target}" "${Renderive_Kernel_test_source}") + target_include_directories("${Renderive_Kernel_test_target}" PRIVATE + "${Renderive_Kernel_test_dir}" + ) target_link_libraries("${Renderive_Kernel_test_target}" PRIVATE Renderive_Kernel GTest::gtest_main) add_test(NAME "${Renderive_Kernel_test_target}" COMMAND "${Renderive_Kernel_test_target}") endforeach() diff --git a/Kernel/src/renderive/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index debe032..76de4dc 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -186,70 +186,152 @@ Scene_Base::~Scene_Base() { shutdown(); } Scene_Base::Attach_Builder::Attach_Builder(Scene_Base& scene) - : scene_(&scene), task_lock_(scene.task_mutex_) { + : scene_(scene), task_lock_(scene.task_mutex_), editor_(scene) { if (scene.runtime_started_) throw std::logic_error("scene attach builder is only available before runtime starts"); } Scene_Base::Attach_Builder::~Attach_Builder() { try { - std::lock_guard lock(scene_->model_mutex_); - scene_->cleanup_detached_topology_locked(); - scene_->validate_structure_locked(); + std::lock_guard lock(scene_.model_mutex_); + scene_.cleanup_detached_topology_locked(); + scene_.validate_structure_locked(); } catch (...) { Scene_Base::structure_fail_fast("initial scene structure", std::current_exception()); } } void Scene_Base::Attach_Builder::attach(Renderable renderable) { - std::lock_guard lock(scene_->model_mutex_); - scene_->apply_attach_renderable_locked(std::move(renderable)); + std::lock_guard lock(scene_.model_mutex_); + editor_.attach(std::move(renderable)); } -void Scene_Base::Attach_Builder::set_parent(Relationship relationship, const Renderable& child, const Renderable& parent) { - std::lock_guard lock(scene_->model_mutex_); - if (relationship == Relationship::display) - scene_->apply_set_display_parent_locked(child, parent); - else - scene_->apply_set_dependency_parent_locked(child, parent); +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_parent(Relationship relationship, const Renderable& child, const Renderable& parent) { - std::lock_guard lock(scene_->model_mutex_); - if (relationship == Relationship::display) - scene_->apply_add_display_parent_locked(child, parent); - else - scene_->apply_add_dependency_parent_locked(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_parents(Relationship relationship, const Renderable& child) { - std::lock_guard lock(scene_->model_mutex_); - if (relationship == Relationship::display) - scene_->apply_clear_display_parent_locked(child); - else - scene_->apply_clear_dependency_parent_locked(child); +void Scene_Base::Attach_Builder::clear_display_parent(const Renderable& child) { + std::lock_guard lock(scene_.model_mutex_); + editor_.clear_display_parent(child); +} +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); +} +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); +} +void Scene_Base::Attach_Builder::clear_dependency_parent(const Renderable& child) { + std::lock_guard lock(scene_.model_mutex_); + editor_.clear_dependency_parent(child); } Scene_Base::Attach_Builder Scene_Base::attach_builder() { return Attach_Builder(*this); } void Scene_Base::Renderable_Editor::attach(Renderable renderable) { - scene_->apply_attach_renderable_locked(std::move(renderable)); + if (!renderable) + throw std::invalid_argument("renderable is null"); + renderable->d_func().bind_scene(scene_.scene_lifetime_); + auto& renderable_data = renderable->d_func(); + const Renderable_Id id = renderable_data.renderable_id; + if (const auto existing = scene_.renderables_.find(id); + existing != scene_.renderables_.end()) { + if (existing->second != 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); + auto cache = scene_.make_renderable_color_cache(); + if (!scene_.renderables_.try_emplace(id, renderable).second) + throw std::logic_error("renderable id is already attached"); + try { + if (cache && !scene_.color_caches_.try_emplace(id, std::move(cache)).second) + throw std::logic_error("renderable color cache is already attached"); + } catch (...) { + scene_.renderables_.erase(id); + throw; + } + renderable_data.real_time_data_state->attached.store( + true, std::memory_order_release); + renderable_data.invalidate_prepare(); + scene_.notify_model_dirty(); } void Scene_Base::Renderable_Editor::detach(const Renderable& renderable) { - scene_->apply_detach_renderable_locked(renderable); + if (!renderable) + throw std::invalid_argument("renderable is null"); + scene_.validate_renderable_scene(*renderable); + if (!scene_.is_renderable_attached_locked(renderable)) + return; + auto& renderable_data = renderable->d_func(); + const Renderable_Id id = renderable_data.renderable_id; + renderable_data.real_time_data_state->attached.store( + false, std::memory_order_release); + scene_.color_caches_.erase(id); + scene_.renderables_.erase(id); + scene_.notify_model_dirty(); } -void Scene_Base::Renderable_Editor::set_parent(Relationship relationship, const Renderable& child, const Renderable& parent) { - if (relationship == Relationship::display) - scene_->apply_set_display_parent_locked(child, parent); - else - scene_->apply_set_dependency_parent_locked(child, parent); +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_parent(Relationship relationship, const Renderable& child, const Renderable& parent) { - if (relationship == Relationship::display) - scene_->apply_add_display_parent_locked(child, parent); - else - scene_->apply_add_dependency_parent_locked(child, parent); +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_parents(Relationship relationship, const Renderable& child) { - if (relationship == Relationship::display) - scene_->apply_clear_display_parent_locked(child); - else - scene_->apply_clear_dependency_parent_locked(child); +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( + child->d_func().renderable_id, + {parent->d_func().renderable_id})) + return; + child->d_func().invalidate_prepare(); + scene_.notify_model_dirty(); +} +void Scene_Base::Renderable_Editor::add_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_.add_parent(child->d_func().renderable_id, + parent->d_func().renderable_id)) + return; + child->d_func().invalidate_prepare(); + scene_.notify_model_dirty(); +} +void Scene_Base::Renderable_Editor::clear_dependency_parent(const Renderable& child) { + 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)) + return; + child->d_func().invalidate_prepare(); + scene_.notify_model_dirty(); } void Scene_Base::edit_renderables(Renderable_Edit edit) { if (!edit) @@ -358,107 +440,6 @@ void Scene_Base::enqueue_renderable_edit_locked(Renderable_Edit edit) { renderable_edit_queue_.push_back({std::move(edit)}); task_ready_.notify_one(); } -void Scene_Base::apply_attach_renderable_locked(Renderable renderable) { - if (!renderable) - throw std::invalid_argument("renderable is null"); - renderable->d_func().bind_scene(scene_lifetime_); - auto& renderable_data = renderable->d_func(); - const Renderable_Id id = renderable_data.renderable_id; - if (const auto existing = renderables_.find(id); existing != renderables_.end()) { - if (existing->second != renderable) - throw std::logic_error("renderable id is already attached"); - return; - } - if (!display_topology_.contains(id)) - display_topology_.attach(id); - if (!dependency_topology_.contains(id)) - dependency_topology_.attach(id); - auto cache = make_renderable_color_cache(); - if (!renderables_.try_emplace(id, renderable).second) - throw std::logic_error("renderable id is already attached"); - try { - if (cache && !color_caches_.try_emplace(id, std::move(cache)).second) - throw std::logic_error("renderable color cache is already attached"); - } catch (...) { - renderables_.erase(id); - throw; - } - renderable_data.real_time_data_state->attached.store( - true, std::memory_order_release); - renderable_data.invalidate_prepare(); - notify_model_dirty(); -} -void Scene_Base::apply_detach_renderable_locked(const Renderable& renderable) { - if (!renderable) - throw std::invalid_argument("renderable is null"); - validate_renderable_scene(*renderable); - if (!is_renderable_attached_locked(renderable)) - return; - auto& renderable_data = renderable->d_func(); - const Renderable_Id id = renderable_data.renderable_id; - renderable_data.real_time_data_state->attached.store( - false, std::memory_order_release); - color_caches_.erase(id); - renderables_.erase(id); - notify_model_dirty(); -} -void Scene_Base::apply_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_topology_.replace_parents(child->d_func().renderable_id, - {parent->d_func().renderable_id})) - notify_model_dirty(); -} -void Scene_Base::apply_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_topology_.add_parent(child->d_func().renderable_id, - parent->d_func().renderable_id)) - notify_model_dirty(); -} -void Scene_Base::apply_clear_display_parent_locked(const Renderable& child) { - if (!child) - throw std::invalid_argument("display child is null"); - validate_renderable_scene(*child); - if (display_topology_.clear_parents(child->d_func().renderable_id)) - notify_model_dirty(); -} -void Scene_Base::apply_set_dependency_parent_locked(const Renderable& child, const Renderable& parent) { - if (!child || !parent) - throw std::invalid_argument("dependency relationship endpoint is null"); - validate_renderable_scene(*child); - validate_renderable_scene(*parent); - if (!dependency_topology_.replace_parents( - child->d_func().renderable_id, - {parent->d_func().renderable_id})) - return; - child->d_func().invalidate_prepare(); - notify_model_dirty(); -} -void Scene_Base::apply_add_dependency_parent_locked(const Renderable& child, const Renderable& parent) { - if (!child || !parent) - throw std::invalid_argument("dependency relationship endpoint is null"); - validate_renderable_scene(*child); - validate_renderable_scene(*parent); - if (!dependency_topology_.add_parent(child->d_func().renderable_id, - parent->d_func().renderable_id)) - return; - child->d_func().invalidate_prepare(); - notify_model_dirty(); -} -void Scene_Base::apply_clear_dependency_parent_locked(const Renderable& child) { - if (!child) - throw std::invalid_argument("dependency child is null"); - validate_renderable_scene(*child); - if (!dependency_topology_.clear_parents(child->d_func().renderable_id)) - return; - child->d_func().invalidate_prepare(); - notify_model_dirty(); -} void Scene_Base::cleanup_detached_topology_locked() { const auto cleanup = [this](Id_Topology& topology, const char* name) { for (const Renderable_Id id : topology.ids()) { diff --git a/Kernel/src/renderive/scene/base/Scene_Base.hpp b/Kernel/src/renderive/scene/base/Scene_Base.hpp index ef12b32..cddd072 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.hpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.hpp @@ -47,9 +47,23 @@ public: using Renderable = renderive_Owner; using Const_Renderable = renderive_Owner; - enum class Relationship { - display, - dependency + class Attach_Builder; + + class Renderable_Editor { + 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: + explicit Renderable_Editor(Scene_Base& scene) : scene_(scene) {} + Scene_Base& scene_; + friend class Scene_Base; + friend class Attach_Builder; }; class Attach_Builder { @@ -60,26 +74,17 @@ public: Attach_Builder& operator=(Attach_Builder&&) = delete; ~Attach_Builder(); void attach(Renderable renderable); - void set_parent(Relationship relationship, const Renderable& child, const Renderable& parent); - void add_parent(Relationship relationship, const Renderable& child, const Renderable& parent); - void clear_parents(Relationship relationship, const Renderable& child); + 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: explicit Attach_Builder(Scene_Base& scene); - Scene_Base* scene_{}; + Scene_Base& scene_; std::unique_lock task_lock_; - friend class Scene_Base; - }; - - class Renderable_Editor { - public: - void attach(Renderable renderable); - void detach(const Renderable& renderable); - void set_parent(Relationship relationship, const Renderable& child, const Renderable& parent); - void add_parent(Relationship relationship, const Renderable& child, const Renderable& parent); - void clear_parents(Relationship relationship, const Renderable& child); - private: - explicit Renderable_Editor(Scene_Base& scene) : scene_(&scene) {} - Scene_Base* scene_{}; + Renderable_Editor editor_; friend class Scene_Base; }; @@ -244,18 +249,6 @@ private: void submit_render(Abstract_Frame* frame); void enqueue_renderable_edit(Renderable_Edit edit); void enqueue_renderable_edit_locked(Renderable_Edit edit); - void apply_attach_renderable_locked(Renderable renderable); - void apply_detach_renderable_locked(const Renderable& renderable); - void apply_set_display_parent_locked(const Renderable& child, - const Renderable& parent); - void apply_add_display_parent_locked(const Renderable& child, - const Renderable& parent); - void apply_clear_display_parent_locked(const Renderable& child); - void apply_set_dependency_parent_locked(const Renderable& child, - const Renderable& parent); - void apply_add_dependency_parent_locked(const Renderable& child, - const Renderable& parent); - void apply_clear_dependency_parent_locked(const Renderable& child); void request_render_graph_rebuild(Renderable_Base& renderable); [[nodiscard]] std::shared_ptr snapshot_live_model(); std::shared_ptr compile_render_plan( diff --git a/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp b/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp index a4a7582..ce6e1e6 100644 --- a/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp +++ b/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp @@ -8,6 +8,7 @@ #include #include "renderive/real_time_data/Real_Time_Data.hpp" #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" #include "renderive/scene/Scene_Test_Helpers.hpp" struct Real_Time_Data_Test_Time_Source { @@ -16,9 +17,9 @@ struct Real_Time_Data_Test_Time_Source { return time_ns->fetch_add(10, std::memory_order_relaxed); } }; -struct Real_Time_Data_Test_Renderable : Renderable_Base { - explicit Real_Time_Data_Test_Renderable() : Renderable_Base() {} - void prepare(const Prepare_Render_Context&) override { +struct Real_Time_Data_Test_Renderable : Renderable_Test_Harness { + explicit Real_Time_Data_Test_Renderable() = default; + void prepare_for_test(const Prepare_Render_Context&) override { ++render_count; } std::atomic render_count{}; @@ -27,7 +28,7 @@ using Real_Time_Data_Test_Bridge = Observer_State; using Real_Time_Data_Test_History = History_Real_Time_Data, std::mutex, Real_Time_Data_Test_Bridge>; using Real_Time_Data_Test_Attachment = Attach_Real_Time_Data; -class Real_Time_Data_Render_Probe : public Renderable_Base { +class Real_Time_Data_Render_Probe : public Renderable_Test_Harness { class Bound_Latest : public Latest_Real_Time_Data { public: explicit Bound_Latest(Renderable_Base& owner) @@ -36,8 +37,8 @@ class Real_Time_Data_Render_Probe : public Renderable_Base { Real_Time_Data_Binding binding_; }; public: - explicit Real_Time_Data_Render_Probe() : Renderable_Base(), data(*this) {} - void prepare(const Prepare_Render_Context& context) override { + explicit Real_Time_Data_Render_Probe() : data(*this) {} + void prepare_for_test(const Prepare_Render_Context& context) override { const auto& value = context.frame.render_state.get(data); rendered_value = value.value_or(-1); } @@ -98,7 +99,7 @@ TEST(real_time_data_attachment_test, binds_updates_to_renderable_frame_strategy) auto latest = std::make_shared(); auto history = std::make_shared(); auto renderable = renderive_Owner::make(With_Real_Time_Data(latest, history)); - renderable->discard_stale_frame_on_latest_data_update = true; + renderable->discard_stale_frame_on_latest_data_update(true); { auto attach = scene.attach_builder(); attach.attach(renderable); @@ -147,7 +148,7 @@ TEST(real_time_data_attachment_test, detached_renderable_no_longer_drives_frame_ Scene2D_Context<> scene; auto latest = std::make_shared(); auto renderable = renderive_Owner::make(With_Real_Time_Data(latest)); - renderable->discard_stale_frame_on_latest_data_update = true; + renderable->discard_stale_frame_on_latest_data_update(true); { auto attach = scene.attach_builder(); attach.attach(renderable); @@ -205,7 +206,8 @@ TEST(real_time_data_attachment_test, runtime_discard_configuration_is_safe_durin }); std::thread configurator([&] { for (int index = 0; index < 1000; ++index) { - renderable->discard_stale_frame_on_latest_data_update = index % 2 != 0; + renderable->discard_stale_frame_on_latest_data_update( + index % 2 != 0); renderable->real_time_data_discard_mode = index % 2 == 0 ? Real_Time_Data_Discard_Mode::retain_all : Real_Time_Data_Discard_Mode::retain_frame_interval; } }); @@ -231,7 +233,7 @@ TEST(real_time_data_attachment_test, ignores_updates_after_bound_scene_is_destro EXPECT_EQ(scene.frame_control.state().real_time_data_update_sequence, 1); } EXPECT_NO_THROW(latest->update(2)); - EXPECT_THROW(renderable->scene(), std::logic_error); + EXPECT_THROW(renderable->scene_for_test(), std::logic_error); renderable.reset(); } TEST(real_time_data_attachment_test, source_binding_is_owned_by_renderable_lifetime) { @@ -281,7 +283,8 @@ TEST(real_time_data_attachment_test, frame_strategy_observer_can_reacquire_scene } std::atomic reacquired{}; observer_data->callback = [&] { - reacquired.store(&renderable->scene() == &scene, std::memory_order_release); + reacquired.store(&renderable->scene_for_test() == &scene, + std::memory_order_release); }; latest->update(1); EXPECT_TRUE(reacquired.load(std::memory_order_acquire)); diff --git a/Kernel/tests/renderive/renderable/Renderable_Test_Harness.hpp b/Kernel/tests/renderive/renderable/Renderable_Test_Harness.hpp new file mode 100644 index 0000000..32b7d3d --- /dev/null +++ b/Kernel/tests/renderive/renderable/Renderable_Test_Harness.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include + +#include "renderive/renderable/base/Renderable_Base_p.hpp" + +class Renderable_Test_Harness : public Renderable_Base { +private: + class Test_Impl final : public Renderable_Base::Impl { + public: + void build_default_prepare_graph(Renderable_Graph_Builder& builder) { + Renderable_Base::Impl::build_prepare_graph(builder); + } + void build_default_paint_graph(Renderable_Graph_Builder& builder) { + Renderable_Base::Impl::build_paint_graph(builder); + } + private: + void build_prepare_graph(Renderable_Graph_Builder& builder) override { + static_cast(owner()) + .build_prepare_graph_for_test(builder); + } + void build_paint_graph(Renderable_Graph_Builder& builder) override { + static_cast(owner()) + .build_paint_graph_for_test(builder); + } + void prepare(const Prepare_Render_Context& context) override { + static_cast(owner()) + .prepare_for_test(context); + } + }; + +protected: + explicit Renderable_Test_Harness( + Renderable_Configuration configuration = {}, + std::pmr::memory_resource& memory_resource = + *std::pmr::get_default_resource()) + : Renderable_Base(std::make_unique(), configuration, + memory_resource) {} + virtual void prepare_for_test(const Prepare_Render_Context&) {} + virtual void build_prepare_graph_for_test( + Renderable_Graph_Builder& builder) { + test_impl().build_default_prepare_graph(builder); + } + virtual void build_paint_graph_for_test( + Renderable_Graph_Builder& builder) { + test_impl().build_default_paint_graph(builder); + } + +public: + [[nodiscard]] Scene_Base& scene_for_test() const { + return d_func().scene(); + } + [[nodiscard]] std::pmr::memory_resource& memory_resource_for_test() const { + return d_func().memory_resource(); + } + [[nodiscard]] Renderable_Configuration configuration_for_test() const { + return d_func().configuration.load(std::memory_order_acquire); + } + void set_configuration_for_test(Renderable_Configuration configuration) { + d_func().set_configuration(configuration); + } + void invalidate_prepare_for_test() noexcept { + d_func().invalidate_prepare(); + } + void invalidate_paint_for_test() noexcept { + d_func().invalidate_paint(); + } + void rebuild_render_graph_for_test() { + d_func().rebuild_render_graph(); + } + [[nodiscard]] std::shared_ptr + render_graph_for_test() { + return d_func().render_graph_snapshot(); + } + [[nodiscard]] bool prepare_cache_valid_for_test() const noexcept { + const auto configuration = d_func().configuration.load( + std::memory_order_acquire); + const auto prepare_revision = d_func().prepare_revision.load( + std::memory_order_acquire); + return prepare_revision != std::numeric_limits::max() && + configuration.cache_enabled && + d_func().prepared_revision.load(std::memory_order_acquire) == + prepare_revision; + } + [[nodiscard]] bool paint_cache_valid_for_test() const noexcept { + if (!prepare_cache_valid_for_test()) + return false; + const auto paint_revision = d_func().paint_revision.load( + std::memory_order_acquire); + return paint_revision != std::numeric_limits::max() && + d_func().painted_revision.load(std::memory_order_acquire) == + paint_revision && + d_func().painted_prepare_revision.load( + std::memory_order_acquire) == + d_func().prepared_revision.load(std::memory_order_acquire); + } + +private: + Test_Impl& test_impl() noexcept { + return d_func(); + } +}; diff --git a/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp b/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp index 6c86c58..caa2446 100644 --- a/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp +++ b/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp @@ -5,15 +5,17 @@ #include #include #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" -struct Renderable_Base_Test_Renderable : Renderable_Base { - Renderable_Base_Test_Renderable(bool cache_enabled) : Renderable_Base({.cache_enabled = cache_enabled}) {} - void prepare(const Prepare_Render_Context&) override { +struct Renderable_Base_Test_Renderable : Renderable_Test_Harness { + Renderable_Base_Test_Renderable(bool cache_enabled) + : Renderable_Test_Harness({.cache_enabled = cache_enabled}) {} + void prepare_for_test(const Prepare_Render_Context&) override { ++render_count; } int render_count{}; }; -TEST(renderable_base_test, exposes_scene_and_strategy_through_accessors) { +TEST(renderable_base_test, implementation_binds_scene_and_strategy) { Scene2D_Context<> low_latency_scene; Scene2D_Context> manual_scene; auto low_latency_renderable = renderive_Owner::make(true); @@ -26,10 +28,16 @@ TEST(renderable_base_test, exposes_scene_and_strategy_through_accessors) { auto attach = manual_scene.attach_builder(); attach.attach(manual_renderable); } - EXPECT_EQ(&low_latency_renderable->scene(), &low_latency_scene); - EXPECT_EQ(&low_latency_renderable->scene().frame_control_strategy(), &low_latency_scene.frame_control); - EXPECT_DOUBLE_EQ(low_latency_renderable->scene().frame_control_strategy().frequency_hz(), 60.0); - EXPECT_TRUE(std::isnan(manual_renderable->scene().frame_control_strategy().frequency_hz())); + EXPECT_EQ(&low_latency_renderable->scene_for_test(), &low_latency_scene); + EXPECT_EQ(&low_latency_renderable->scene_for_test().frame_control_strategy(), + &low_latency_scene.frame_control); + EXPECT_DOUBLE_EQ(low_latency_renderable->scene_for_test() + .frame_control_strategy() + .frequency_hz(), + 60.0); + EXPECT_TRUE(std::isnan(manual_renderable->scene_for_test() + .frame_control_strategy() + .frequency_hz())); } TEST(renderable_base_test, controls_cache_with_configuration) { Scene2D_Context<> scene; @@ -46,7 +54,7 @@ TEST(renderable_base_test, controls_cache_with_configuration) { scene.wait_for_render(); EXPECT_EQ(cached->render_count, 1); EXPECT_EQ(uncached->render_count, 2); - cached->invalidate_prepare(); + cached->invalidate_prepare_for_test(); scene.render(); scene.wait_for_render(); EXPECT_EQ(cached->render_count, 2); @@ -61,15 +69,16 @@ TEST(renderable_base_test, changes_configuration_through_scene) { } scene.render(); scene.wait_for_render(); - renderable->set_configuration({.cache_enabled = true}); + renderable->set_configuration_for_test({.cache_enabled = true}); scene.render(); scene.wait_for_render(); - EXPECT_TRUE(renderable->configuration().cache_enabled); - EXPECT_EQ(renderable->render_count, 1); + EXPECT_TRUE(renderable->configuration_for_test().cache_enabled); + EXPECT_EQ(renderable->render_count, 2); } -struct Renderable_Base_Cache_Invalidation_Test_Renderable : Renderable_Base { - explicit Renderable_Base_Cache_Invalidation_Test_Renderable() : Renderable_Base({.cache_enabled = true}) {} - void prepare(const Prepare_Render_Context&) override { +struct Renderable_Base_Cache_Invalidation_Test_Renderable : Renderable_Test_Harness { + explicit Renderable_Base_Cache_Invalidation_Test_Renderable() + : Renderable_Test_Harness({.cache_enabled = true}) {} + void prepare_for_test(const Prepare_Render_Context&) override { const int count = render_count.fetch_add(1, std::memory_order_acq_rel) + 1; if (count != 1) { return; @@ -107,18 +116,18 @@ TEST(renderable_base_test, preserves_cache_invalidation_that_happens_during_rend } scene.render(); renderable->wait_started(); - renderable->invalidate_prepare(); + renderable->invalidate_prepare_for_test(); renderable->release(); scene.wait_for_render(); - EXPECT_FALSE(renderable->prepare_cache_valid()); + EXPECT_FALSE(renderable->prepare_cache_valid_for_test()); scene.render(); scene.wait_for_render(); EXPECT_EQ(renderable->render_count.load(std::memory_order_acquire), 2); - EXPECT_TRUE(renderable->prepare_cache_valid()); + EXPECT_TRUE(renderable->prepare_cache_valid_for_test()); } -struct Renderable_Base_Render_Graph_Rebuild_Test_Renderable : Renderable_Base { - explicit Renderable_Base_Render_Graph_Rebuild_Test_Renderable() : Renderable_Base() {} - void build_prepare_graph(Renderable_Graph_Builder& graph) override { +struct Renderable_Base_Render_Graph_Rebuild_Test_Renderable : Renderable_Test_Harness { + explicit Renderable_Base_Render_Graph_Rebuild_Test_Renderable() = default; + void build_prepare_graph_for_test(Renderable_Graph_Builder& graph) override { const int count = build_count.fetch_add(1, std::memory_order_acq_rel) + 1; if (count == 1) { std::unique_lock lock(mutex); @@ -143,10 +152,10 @@ struct Renderable_Base_Render_Graph_Rebuild_Test_Renderable : Renderable_Base { condition.notify_all(); } std::shared_ptr graph_snapshot() { - return render_graph(); + return render_graph_for_test(); } void request_graph_rebuild() { - rebuild_render_graph(); + rebuild_render_graph_for_test(); } std::atomic build_count{}; std::mutex mutex; @@ -201,14 +210,15 @@ TEST(renderable_base_test, configuration_snapshot_is_safe_during_scene_updates) std::atomic running{true}; std::thread writer([&] { for (int index = 0; index < 1000; ++index) { - renderable->set_configuration({.cache_enabled = index % 2 != 0}); + renderable->set_configuration_for_test( + {.cache_enabled = index % 2 != 0}); } running.store(false, std::memory_order_release); }); while (running.load(std::memory_order_acquire)) { - const auto configuration = renderable->configuration(); + const auto configuration = renderable->configuration_for_test(); static_cast(configuration.cache_enabled); } writer.join(); - EXPECT_TRUE(renderable->configuration().cache_enabled); + EXPECT_TRUE(renderable->configuration_for_test().cache_enabled); } diff --git a/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp b/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp index c534159..2ea55c4 100644 --- a/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp +++ b/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp @@ -1,19 +1,19 @@ #include #include #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" -struct Renderable_Concept_Test : Renderable_Base { - explicit Renderable_Concept_Test() : Renderable_Base() {} - void prepare(const Prepare_Render_Context&) override {} +struct Renderable_Concept_Test : Renderable_Test_Harness { + explicit Renderable_Concept_Test() = default; }; static_assert(Renderable); -TEST(renderable_concept_test, exposes_scene_and_configuration_state) { +TEST(renderable_concept_test, implementation_tracks_scene_and_configuration_state) { Scene2D_Context<> scene; auto renderable = renderive_Owner::make(); { auto attach = scene.attach_builder(); attach.attach(renderable); } - EXPECT_EQ(&renderable->scene(), &scene); - EXPECT_FALSE(renderable->configuration().cache_enabled); + EXPECT_EQ(&renderable->scene_for_test(), &scene); + EXPECT_FALSE(renderable->configuration_for_test().cache_enabled); } diff --git a/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp b/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp index fd2f537..5deaa87 100644 --- a/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp +++ b/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp @@ -9,6 +9,7 @@ #include #include #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" #include "Scene_Test_Helpers.hpp" namespace { @@ -46,12 +47,13 @@ bool wait_until(Predicate&& predicate) { } return true; } -class Counting_Renderable : public Renderable_Base { +class Counting_Renderable : public Renderable_Test_Harness { public: - Counting_Renderable() : Renderable_Base({.cache_enabled = false}) {} + Counting_Renderable() + : Renderable_Test_Harness({.cache_enabled = false}) {} std::atomic prepare_count{}; protected: - void prepare(const Prepare_Render_Context&) override { + void prepare_for_test(const Prepare_Render_Context&) override { prepare_count.fetch_add(1, std::memory_order_release); } }; @@ -64,7 +66,7 @@ public: destroyed_->store(true, std::memory_order_release); } protected: - void prepare(const Prepare_Render_Context&) override { + void prepare_for_test(const Prepare_Render_Context&) override { const int call = prepare_count.fetch_add(1, std::memory_order_acq_rel) + 1; if (call == block_on_call_) gate_->arrive_and_wait(); @@ -131,13 +133,13 @@ TEST(dynamic_renderable_lifecycle_test, reparent_edit_changes_only_the_next_fram attach.attach(first_parent); attach.attach(second_parent); attach.attach(child); - attach.set_parent(Scene_Base::Relationship::dependency, child, first_parent); + attach.set_dependency_parent(child, first_parent); } scene.render(); first_gate->wait_until_arrived(); std::atomic edited{}; scene.edit_renderables([child, second_parent, &edited](auto& editor) { - editor.set_parent(Scene_Base::Relationship::dependency, child, second_parent); + editor.set_dependency_parent(child, second_parent); edited.store(true, std::memory_order_release); }); EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 0); @@ -152,20 +154,20 @@ TEST(dynamic_renderable_lifecycle_test, reparent_edit_changes_only_the_next_fram scene.wait_for_render(); EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 2); } -class Rebuildable_Renderable final : public Renderable_Base { +class Rebuildable_Renderable final : public Renderable_Test_Harness { public: explicit Rebuildable_Renderable(std::shared_ptr first_graph_gate) - : Renderable_Base({.cache_enabled = false}), first_graph_gate_(std::move(first_graph_gate)) {} + : Renderable_Test_Harness({.cache_enabled = false}), first_graph_gate_(std::move(first_graph_gate)) {} void publish_second_graph() { graph_version_.store(2, std::memory_order_release); - rebuild_render_graph(); + rebuild_render_graph_for_test(); } std::vector executions() const { std::lock_guard lock(executions_mutex_); return executions_; } protected: - void build_prepare_graph(Renderable_Graph_Builder& builder) override { + void build_prepare_graph_for_test(Renderable_Graph_Builder& builder) override { const int version = graph_version_.load(std::memory_order_acquire); builder.emplace("versioned_prepare", version == 1 ? "Graph A" : "Graph B", [this, version](const Prepare_Render_Context&) { if (version == 1) @@ -195,23 +197,23 @@ TEST(dynamic_renderable_lifecycle_test, graph_rebuild_uses_the_same_no_inflight_ scene.wait_for_render(); EXPECT_EQ(renderable->executions(), std::vector({1, 2})); } -class Sequence_Parent final : public Renderable_Base { +class Sequence_Parent final : public Renderable_Test_Harness { public: explicit Sequence_Parent(std::atomic& completed_sequence) - : Renderable_Base({.cache_enabled = false}), completed_sequence_(completed_sequence) {} + : Renderable_Test_Harness({.cache_enabled = false}), completed_sequence_(completed_sequence) {} protected: - void prepare(const Prepare_Render_Context& context) override { + void prepare_for_test(const Prepare_Render_Context& context) override { completed_sequence_.store(context.frame.render_sequence, std::memory_order_release); } private: std::atomic& completed_sequence_; }; -class Sequence_Child final : public Renderable_Base { +class Sequence_Child final : public Renderable_Test_Harness { public: Sequence_Child(std::atomic& parent_sequence, std::atomic& violations) - : Renderable_Base({.cache_enabled = false}), parent_sequence_(parent_sequence), violations_(violations) {} + : Renderable_Test_Harness({.cache_enabled = false}), parent_sequence_(parent_sequence), violations_(violations) {} protected: - void prepare(const Prepare_Render_Context& context) override { + void prepare_for_test(const Prepare_Render_Context& context) override { if (parent_sequence_.load(std::memory_order_acquire) != context.frame.render_sequence) violations_.fetch_add(1, std::memory_order_relaxed); } @@ -228,15 +230,15 @@ TEST(dynamic_renderable_lifecycle_test, one_runtime_callback_can_apply_a_complet attach_initial(scene, parent); wait_renderable_edit(scene, [parent, child](auto& editor) { editor.attach(child); - editor.set_parent(Scene_Base::Relationship::dependency, child, parent); - editor.set_parent(Scene_Base::Relationship::display, child, parent); + editor.set_dependency_parent(child, parent); + editor.set_display_parent(child, parent); }); scene.render(); scene.wait_for_render(); EXPECT_EQ(violations.load(std::memory_order_acquire), 0); wait_renderable_edit(scene, [child](auto& editor) { - editor.clear_parents(Scene_Base::Relationship::dependency, child); - editor.clear_parents(Scene_Base::Relationship::display, child); + editor.clear_dependency_parent(child); + editor.clear_display_parent(child); editor.detach(child); }); EXPECT_EQ(scene.renderable_count(), 1u); @@ -252,12 +254,12 @@ TEST(dynamic_renderable_lifecycle_test, queued_edits_and_render_submission_prese auto child = renderive_Owner::make(parent_sequence, violations); scene.edit_renderables([parent, child](auto& editor) { editor.attach(child); - editor.set_parent(Scene_Base::Relationship::dependency, child, parent); - editor.set_parent(Scene_Base::Relationship::display, child, parent); + editor.set_dependency_parent(child, parent); + editor.set_display_parent(child, parent); }); scene.edit_renderables([child](auto& editor) { - editor.clear_parents(Scene_Base::Relationship::dependency, child); - editor.clear_parents(Scene_Base::Relationship::display, child); + editor.clear_dependency_parent(child); + editor.clear_display_parent(child); editor.detach(child); }); } diff --git a/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp b/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp index 2646a26..cfd60cb 100644 --- a/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp +++ b/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp @@ -9,21 +9,22 @@ #include #include #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" #include "Scene_Test_Helpers.hpp" namespace { -class Pipeline_Renderable : public Renderable_Base { +class Pipeline_Renderable : public Renderable_Test_Harness { public: explicit Pipeline_Renderable(bool cached = false) - : Renderable_Base({.cache_enabled = cached}) {} + : Renderable_Test_Harness({.cache_enabled = cached}) {} std::function prepare_action; std::function paint_action; std::atomic prepare_count{}; std::atomic paint_count{}; protected: - void build_prepare_graph(Renderable_Graph_Builder& builder) override { + void build_prepare_graph_for_test(Renderable_Graph_Builder& builder) override { builder.emplace("prepare", "Prepare", [this](const Prepare_Render_Context&) { ++prepare_count; @@ -31,7 +32,7 @@ protected: prepare_action(); }); } - void build_paint_graph(Renderable_Graph_Builder& builder) override { + void build_paint_graph_for_test(Renderable_Graph_Builder& builder) override { const auto paint = builder.emplace( "paint", "Paint", [this](const Paint_Render_Context&) { @@ -43,18 +44,18 @@ protected: } }; -class Dynamic_Renderable : public Renderable_Base { +class Dynamic_Renderable : public Renderable_Test_Harness { public: - explicit Dynamic_Renderable() : Renderable_Base() {} + explicit Dynamic_Renderable() = default; void set_chunk_count(std::size_t count) { chunk_count_ = count; - rebuild_render_graph(); + rebuild_render_graph_for_test(); } std::shared_ptr graph_snapshot() { - return render_graph(); + return render_graph_for_test(); } protected: - void build_prepare_graph(Renderable_Graph_Builder& builder) override { + void build_prepare_graph_for_test(Renderable_Graph_Builder& builder) override { const auto root = builder.emplace("prepare", "Prepare", [](const Prepare_Render_Context&) {}); for (std::size_t index = 0; index < chunk_count_; ++index) { @@ -97,8 +98,8 @@ TEST(render_plan_execution_test, dependency_edges_connect_prepare_only_and_layer with_attach_builder(scene, [&](auto& attach) { attach.attach(parent); attach.attach(child); - attach.set_parent(Scene_Base::Relationship::dependency, child, parent); - attach.set_parent(Scene_Base::Relationship::display, child, parent); + attach.set_dependency_parent(child, parent); + attach.set_display_parent(child, parent); }); scene.render(); scene.wait_for_render(); @@ -169,7 +170,7 @@ TEST(render_plan_execution_test, dependent_prepare_does_not_wait_for_dependency_ with_attach_builder(scene, [&](auto& attach) { attach.attach(parent); attach.attach(child); - attach.set_parent(Scene_Base::Relationship::dependency, child, parent); + attach.set_dependency_parent(child, parent); }); scene.render(); { @@ -195,8 +196,8 @@ TEST(render_plan_execution_test, prepare_and_paint_cache_validity_are_independen attach_initial(scene, renderable); scene.render(); scene.wait_for_render(); - EXPECT_TRUE(renderable->prepare_cache_valid()); - EXPECT_TRUE(renderable->paint_cache_valid()); + EXPECT_TRUE(renderable->prepare_cache_valid_for_test()); + EXPECT_TRUE(renderable->paint_cache_valid_for_test()); const auto first_plan = scene.render_plan_snapshot(); const Render_Node_Id prepare_id = node(*first_plan, renderable->renderable_id(), Render_Node_Kind::prepare).node_id; @@ -213,9 +214,9 @@ TEST(render_plan_execution_test, prepare_and_paint_cache_validity_are_independen value.kind != Render_Node_Kind::composite; }), 0); - renderable->invalidate_paint(); - EXPECT_TRUE(renderable->prepare_cache_valid()); - EXPECT_FALSE(renderable->paint_cache_valid()); + renderable->invalidate_paint_for_test(); + EXPECT_TRUE(renderable->prepare_cache_valid_for_test()); + EXPECT_FALSE(renderable->paint_cache_valid_for_test()); scene.render(); scene.wait_for_render(); const auto paint_plan = scene.render_plan_snapshot(); @@ -228,9 +229,9 @@ TEST(render_plan_execution_test, prepare_and_paint_cache_validity_are_independen value.kind == Render_Node_Kind::prepare; }), 0); - renderable->invalidate_prepare(); - EXPECT_FALSE(renderable->prepare_cache_valid()); - EXPECT_FALSE(renderable->paint_cache_valid()); + renderable->invalidate_prepare_for_test(); + EXPECT_FALSE(renderable->prepare_cache_valid_for_test()); + EXPECT_FALSE(renderable->paint_cache_valid_for_test()); scene.render(); scene.wait_for_render(); const auto rebuilt_plan = scene.render_plan_snapshot(); @@ -242,7 +243,7 @@ TEST(render_plan_execution_test, prepare_and_paint_cache_validity_are_independen } TEST(render_plan_execution_test, logical_node_ids_are_stable_and_retired_ids_are_never_reused) { - Scene_Base scene; + Scene2D_Context<> scene; auto renderable = renderive_Owner::make(); attach_initial(scene, renderable); const auto first = renderable->graph_snapshot(); diff --git a/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp b/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp index 8068cfd..4606966 100644 --- a/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp @@ -4,14 +4,16 @@ #include #include #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" #include "Scene_Test_Helpers.hpp" static_assert(std::same_as&>().topology_snapshot()), Scene_Base::Topology_Snapshot>); -struct Scene2D_Context_Test_Renderable : Renderable_Base { - explicit Scene2D_Context_Test_Renderable() : Renderable_Base({.cache_enabled = false}) {} - void prepare(const Prepare_Render_Context&) override { +struct Scene2D_Context_Test_Renderable : Renderable_Test_Harness { + explicit Scene2D_Context_Test_Renderable() + : Renderable_Test_Harness({.cache_enabled = false}) {} + void prepare_for_test(const Prepare_Render_Context&) override { ++render_count; - scene_is_2d = dynamic_cast(&scene()) != nullptr; + scene_is_2d = dynamic_cast(&scene_for_test()) != nullptr; render_thread = std::this_thread::get_id(); } std::atomic render_count{}; @@ -39,9 +41,10 @@ TEST(scene2d_context_test, swaps_renderable_cache_and_renders_in_background) { EXPECT_EQ(second->render_count.load(), 1); EXPECT_EQ(scene.frame_control_strategy().frame_control_state().publish_revision, 2); } -struct Scene2D_Final_Cache_Blocking_Renderable : Renderable_Base { - explicit Scene2D_Final_Cache_Blocking_Renderable() : Renderable_Base({.cache_enabled = false}) {} - void prepare(const Prepare_Render_Context&) override { +struct Scene2D_Final_Cache_Blocking_Renderable : Renderable_Test_Harness { + explicit Scene2D_Final_Cache_Blocking_Renderable() + : Renderable_Test_Harness({.cache_enabled = false}) {} + void prepare_for_test(const Prepare_Render_Context&) override { while (!release.load(std::memory_order_acquire)) { started.store(true, std::memory_order_release); std::this_thread::yield(); @@ -74,12 +77,13 @@ TEST(scene2d_context_test, final_color_cache_access_waits_for_render_completion) reader.join(); EXPECT_TRUE(cache_accessed.load(std::memory_order_acquire)); } -struct Scene2D_Cached_Renderable : Renderable_Base { - Scene2D_Cached_Renderable(std::uint64_t value) : Renderable_Base({.cache_enabled = true}), value(value) {} - void prepare(const Prepare_Render_Context&) override { +struct Scene2D_Cached_Renderable : Renderable_Test_Harness { + Scene2D_Cached_Renderable(std::uint64_t value) + : Renderable_Test_Harness({.cache_enabled = true}), value(value) {} + void prepare_for_test(const Prepare_Render_Context&) override { ++render_count; } - void build_paint_graph(Renderable_Graph_Builder& graph) override { + void build_paint_graph_for_test(Renderable_Graph_Builder& graph) override { graph.emplace("paint", "Paint", [this](const Paint_Render_Context& context) { dynamic_cast(context.color_cache).append(value); @@ -116,7 +120,7 @@ TEST(scene2d_context_test, dependency_reparent_invalidates_cached_child) { attach.attach(first_parent); attach.attach(second_parent); attach.attach(child); - attach.set_parent(Scene_Base::Relationship::dependency, child, first_parent); + attach.set_dependency_parent(child, first_parent); }); scene.render(); scene.wait_for_render(); @@ -124,7 +128,7 @@ TEST(scene2d_context_test, dependency_reparent_invalidates_cached_child) { EXPECT_EQ(second_parent->render_count, 1); EXPECT_EQ(child->render_count, 1); wait_renderable_edit(scene, [child, second_parent](auto& editor) { - editor.set_parent(Scene_Base::Relationship::dependency, child, second_parent); + editor.set_dependency_parent(child, second_parent); }); scene.render(); scene.wait_for_render(); @@ -141,17 +145,17 @@ TEST(scene2d_context_test, multiple_dependency_parents_invalidate_cached_child) attach.attach(first_parent); attach.attach(second_parent); attach.attach(child); - attach.set_parent(Scene_Base::Relationship::dependency, child, first_parent); - attach.add_parent(Scene_Base::Relationship::dependency, child, second_parent); + attach.set_dependency_parent(child, first_parent); + attach.add_dependency_parent(child, second_parent); }); scene.render(); scene.wait_for_render(); EXPECT_EQ(child->render_count, 1); - first_parent->invalidate_prepare(); + first_parent->invalidate_prepare_for_test(); scene.render(); scene.wait_for_render(); EXPECT_EQ(child->render_count, 2); - second_parent->invalidate_prepare(); + second_parent->invalidate_prepare_for_test(); scene.render(); scene.wait_for_render(); EXPECT_EQ(child->render_count, 3); @@ -170,9 +174,9 @@ TEST(scene2d_context_test, final_color_cache_callback_can_reenter_scene_control_ scene.render(); scene.wait_for_render(); scene.with_final_color_cache([&](const Recording_Color_Cache&) { - renderable->set_configuration({.cache_enabled = false}); + renderable->set_configuration_for_test({.cache_enabled = false}); }); - EXPECT_FALSE(renderable->configuration().cache_enabled); + EXPECT_FALSE(renderable->configuration_for_test().cache_enabled); } TEST(scene2d_context_test, detach_dependency_parent_invalidates_promoted_cached_child) { Scene2D_Context<> scene; @@ -183,8 +187,8 @@ TEST(scene2d_context_test, detach_dependency_parent_invalidates_promoted_cached_ attach.attach(grandparent); attach.attach(parent); attach.attach(child); - attach.set_parent(Scene_Base::Relationship::dependency, parent, grandparent); - attach.set_parent(Scene_Base::Relationship::dependency, child, parent); + attach.set_dependency_parent(parent, grandparent); + attach.set_dependency_parent(child, parent); }); scene.render(); scene.wait_for_render(); @@ -192,9 +196,9 @@ TEST(scene2d_context_test, detach_dependency_parent_invalidates_promoted_cached_ EXPECT_EQ(parent->render_count, 1); EXPECT_EQ(child->render_count, 1); wait_renderable_edit(scene, [grandparent, parent, child](auto& editor) { - editor.clear_parents(Scene_Base::Relationship::dependency, child); - editor.clear_parents(Scene_Base::Relationship::dependency, parent); - editor.set_parent(Scene_Base::Relationship::dependency, child, grandparent); + editor.clear_dependency_parent(child); + editor.clear_dependency_parent(parent); + editor.set_dependency_parent(child, grandparent); editor.detach(parent); }); scene.render(); @@ -232,9 +236,9 @@ TEST(scene2d_context_test, failed_attach_leaves_renderable_fully_detached) { EXPECT_NO_THROW(attach_initial(scene, renderable)); EXPECT_EQ(scene.renderable_count(), 1); } -struct Scene2D_Viewport_Snapshot_Renderable : Renderable_Base { - explicit Scene2D_Viewport_Snapshot_Renderable() : Renderable_Base() {} - void prepare(const Prepare_Render_Context& context) override { +struct Scene2D_Viewport_Snapshot_Renderable : Renderable_Test_Harness { + explicit Scene2D_Viewport_Snapshot_Renderable() = default; + void prepare_for_test(const Prepare_Render_Context& context) override { viewport = context.frame.viewport; } Frame_Viewport viewport; @@ -250,7 +254,7 @@ TEST(scene2d_context_test, published_scene_viewport_is_frozen_for_render) { scene.wait_for_render(); EXPECT_EQ(renderable->viewport, (Frame_Viewport{320, 180})); scene.publish_frame_state(); - renderable->invalidate_prepare(); + renderable->invalidate_prepare_for_test(); scene.render(); scene.wait_for_render(); EXPECT_EQ(renderable->viewport, (Frame_Viewport{640, 360})); diff --git a/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp b/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp index ee37472..8f6f5a2 100644 --- a/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp @@ -4,6 +4,7 @@ #include #include #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" struct Scene2D_Render_Order_State { void append(std::uint64_t id) { @@ -17,13 +18,13 @@ struct Scene2D_Render_Order_State { mutable std::mutex mutex; std::vector render_order; }; -struct Scene2D_Render_Order_Renderable : Renderable_Base { +struct Scene2D_Render_Order_Renderable : Renderable_Test_Harness { Scene2D_Render_Order_Renderable(Scene2D_Render_Order_State& state, std::uint64_t id) - : Renderable_Base({.cache_enabled = false}), state(&state), id(id) {} - void prepare(const Prepare_Render_Context&) override { + : Renderable_Test_Harness({.cache_enabled = false}), state(&state), id(id) {} + void prepare_for_test(const Prepare_Render_Context&) override { state->append(id); } - void build_paint_graph(Renderable_Graph_Builder& graph) override { + void build_paint_graph_for_test(Renderable_Graph_Builder& graph) override { graph.emplace("paint", "Paint", [this](const Paint_Render_Context& context) { dynamic_cast(context.color_cache).append(id); @@ -41,8 +42,8 @@ TEST(scene2d_render_order_test, separates_dependency_order_from_display_order) { auto attach = scene.attach_builder(); attach.attach(spectrum); attach.attach(axis); - attach.set_parent(Scene_Base::Relationship::dependency, spectrum, axis); - attach.set_parent(Scene_Base::Relationship::display, axis, spectrum); + attach.set_dependency_parent(spectrum, axis); + attach.set_display_parent(axis, spectrum); } scene.render(); scene.wait_for_render(); @@ -66,10 +67,10 @@ TEST(scene2d_render_order_test, shared_overlay_waits_for_every_display_parent) { attach.attach(first_plot); attach.attach(axis); attach.attach(second_plot); - attach.add_parent(Scene_Base::Relationship::dependency, first_plot, axis); - attach.add_parent(Scene_Base::Relationship::dependency, second_plot, axis); - attach.add_parent(Scene_Base::Relationship::display, axis, first_plot); - attach.add_parent(Scene_Base::Relationship::display, axis, second_plot); + attach.add_dependency_parent(first_plot, axis); + attach.add_dependency_parent(second_plot, axis); + attach.add_display_parent(axis, first_plot); + attach.add_display_parent(axis, second_plot); } scene.render(); scene.wait_for_render(); diff --git a/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp b/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp index af51252..5e789bc 100644 --- a/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp @@ -2,12 +2,13 @@ #include #include #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" #include "Scene_Test_Helpers.hpp" -struct Scene3D_Context_Test_Renderable : Renderable_Base { - explicit Scene3D_Context_Test_Renderable() : Renderable_Base() {} - void prepare(const Prepare_Render_Context&) override { - scene_is_3d = dynamic_cast(&scene()) != nullptr; +struct Scene3D_Context_Test_Renderable : Renderable_Test_Harness { + explicit Scene3D_Context_Test_Renderable() = default; + void prepare_for_test(const Prepare_Render_Context&) override { + scene_is_3d = dynamic_cast(&scene_for_test()) != nullptr; ++render_count; } std::atomic render_count{}; @@ -22,9 +23,10 @@ TEST(scene3d_context_test, uses_scene_3d_base_contract) { EXPECT_EQ(renderable->render_count.load(), 1); EXPECT_TRUE(renderable->scene_is_3d); } -struct Scene3D_Dependency_Cache_Test_Renderable : Renderable_Base { - explicit Scene3D_Dependency_Cache_Test_Renderable() : Renderable_Base({.cache_enabled = true}) {} - void prepare(const Prepare_Render_Context&) override { +struct Scene3D_Dependency_Cache_Test_Renderable : Renderable_Test_Harness { + explicit Scene3D_Dependency_Cache_Test_Renderable() + : Renderable_Test_Harness({.cache_enabled = true}) {} + void prepare_for_test(const Prepare_Render_Context&) override { ++render_count; } int render_count{}; @@ -38,15 +40,15 @@ TEST(scene3d_context_test, propagates_cache_invalidation_through_unsorted_depend attach.attach(leaf); attach.attach(middle); attach.attach(root); - attach.set_parent(Scene_Base::Relationship::dependency, leaf, middle); - attach.set_parent(Scene_Base::Relationship::dependency, middle, root); + attach.set_dependency_parent(leaf, middle); + attach.set_dependency_parent(middle, root); }); scene.render(); scene.wait_for_render(); EXPECT_EQ(root->render_count, 1); EXPECT_EQ(middle->render_count, 1); EXPECT_EQ(leaf->render_count, 1); - root->invalidate_prepare(); + root->invalidate_prepare_for_test(); scene.render(); scene.wait_for_render(); EXPECT_EQ(root->render_count, 2); @@ -62,19 +64,19 @@ TEST(scene3d_context_test, runtime_edit_explicitly_reparents_children_before_det attach.attach(grandparent); attach.attach(parent); attach.attach(child); - attach.set_parent(Scene_Base::Relationship::display, parent, grandparent); - attach.set_parent(Scene_Base::Relationship::display, child, parent); - attach.set_parent(Scene_Base::Relationship::dependency, parent, grandparent); - attach.set_parent(Scene_Base::Relationship::dependency, child, parent); + attach.set_display_parent(parent, grandparent); + attach.set_display_parent(child, parent); + attach.set_dependency_parent(parent, grandparent); + attach.set_dependency_parent(child, parent); }); scene.render(); scene.wait_for_render(); EXPECT_EQ(child->render_count, 1); wait_renderable_edit(scene, [grandparent, parent, child](auto& editor) { - editor.set_parent(Scene_Base::Relationship::display, child, grandparent); - editor.set_parent(Scene_Base::Relationship::dependency, child, grandparent); - editor.clear_parents(Scene_Base::Relationship::display, parent); - editor.clear_parents(Scene_Base::Relationship::dependency, parent); + 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(); diff --git a/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp b/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp index be49d7f..7f0637e 100644 --- a/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp @@ -7,6 +7,7 @@ #include "renderive/base/property/Property.hpp" #include "renderive/real_time_data/Real_Time_Data.hpp" #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" #include "Scene_Test_Helpers.hpp" class Scene_Memory_Resource_Test_Resource : public std::pmr::memory_resource { @@ -32,10 +33,10 @@ private: std::atomic allocation_count_{}; std::atomic deallocation_count_{}; }; -struct Scene_Memory_Resource_Test_Renderable : Renderable_Base { +struct Scene_Memory_Resource_Test_Renderable : Renderable_Test_Harness { explicit Scene_Memory_Resource_Test_Renderable(std::pmr::memory_resource& memory_resource = *std::pmr::get_default_resource()) - : Renderable_Base({.cache_enabled = false}, memory_resource) {} - void build_prepare_graph(Renderable_Graph_Builder& graph) override { + : Renderable_Test_Harness({.cache_enabled = false}, memory_resource) {} + void build_prepare_graph_for_test(Renderable_Graph_Builder& graph) override { auto first = graph.emplace("first", "prepare_memory_resource_test", [](const Prepare_Render_Context&) {}); auto second = graph.emplace("second", "render_memory_resource_test", @@ -43,7 +44,7 @@ struct Scene_Memory_Resource_Test_Renderable : Renderable_Base { graph.precede(first, second); } void build_graph_for_test() { - static_cast(render_graph()); + static_cast(render_graph_for_test()); } }; struct Scene_Memory_Resource_Test_Product { @@ -63,7 +64,7 @@ TEST(scene_memory_resource_test, renderable_builder_can_use_scene_memory_domain_ EXPECT_EQ(&scene.upstream_memory_resource(), &upstream); EXPECT_NE(&scene.memory_resource(), &upstream); auto renderable = renderive_Owner::make(scene.memory_resource()); - EXPECT_EQ(&renderable->memory_resource(), &scene.memory_resource()); + EXPECT_EQ(&renderable->memory_resource_for_test(), &scene.memory_resource()); attach_initial(scene, renderable); scene.render(); scene.wait_for_render(); @@ -74,7 +75,7 @@ TEST(scene_memory_resource_test, explicit_scene_memory_domain_remains_valid_whil Scene2D_Context<> scene(upstream); auto renderable = renderive_Owner::make(scene.memory_resource()); renderable->build_graph_for_test(); - EXPECT_EQ(&renderable->memory_resource(), &scene.memory_resource()); + EXPECT_EQ(&renderable->memory_resource_for_test(), &scene.memory_resource()); } TEST(scene_memory_resource_test, supports_independent_resource_aware_components) { Scene_Memory_Resource_Test_Resource memory_resource; @@ -100,6 +101,6 @@ TEST(scene_memory_resource_test, supports_explicit_independent_renderable_memory Scene_Memory_Resource_Test_Resource memory_resource; auto renderable = renderive_Owner::make(memory_resource); renderable->build_graph_for_test(); - EXPECT_EQ(&renderable->memory_resource(), &memory_resource); + EXPECT_EQ(&renderable->memory_resource_for_test(), &memory_resource); EXPECT_GT(memory_resource.allocation_count(), 0); } diff --git a/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp b/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp index 687aec8..2a5c6f2 100644 --- a/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp +++ b/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp @@ -3,11 +3,14 @@ #include #include #include +#include #include #include #include #include +#include #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/base/Renderable_Base_p.hpp" #include "renderive/scene/Scene.hpp" #include "renderive/scene/Scene_Test_Helpers.hpp" namespace { @@ -22,28 +25,59 @@ bool wait_until(Predicate&& predicate) { return true; } } -struct Scene_Base_Test_Renderable : Renderable_Base { - Scene_Base_Test_Renderable() = default; - void prepare(const Prepare_Render_Context& context) override { - render_sequence = context.frame.render_sequence; - ++render_count; +struct Scene_Base_Test_Renderable final : Renderable_Base { +private: + class Impl final : public Renderable_Base::Impl { + void prepare(const Prepare_Render_Context& context) override { + auto& renderable = static_cast(owner()); + renderable.render_sequence = context.frame.render_sequence; + ++renderable.render_count; + } + }; +public: + explicit Scene_Base_Test_Renderable(bool cache_enabled = false) + : Renderable_Base(std::make_unique(), + {.cache_enabled = cache_enabled}) {} + void invalidate_prepare_for_test() { + d_func().invalidate_prepare(); } std::uint64_t render_sequence{}; int render_count{}; }; -struct Scene_Base_Throwing_Renderable : Renderable_Base { - Scene_Base_Throwing_Renderable() : Renderable_Base({.cache_enabled = false}) {} - void prepare(const Prepare_Render_Context&) override { - throw std::runtime_error("render failed"); - } +struct Scene_Base_Throwing_Renderable final : Renderable_Base { +private: + class Impl final : public Renderable_Base::Impl { + void prepare(const Prepare_Render_Context&) override { + throw std::runtime_error("render failed"); + } + }; +public: + Scene_Base_Throwing_Renderable() + : Renderable_Base(std::make_unique(), + {.cache_enabled = false}) {} }; -struct Scene_Base_Blocking_Renderable : Renderable_Base { - Scene_Base_Blocking_Renderable() : Renderable_Base({.cache_enabled = false}) {} - void prepare(const Prepare_Render_Context&) override { - std::unique_lock lock(mutex); - rendering = true; - condition.notify_all(); - condition.wait(lock, [this] { return released; }); +struct Scene_Base_Blocking_Renderable final : Renderable_Base { +private: + class Impl final : public Renderable_Base::Impl { + void prepare(const Prepare_Render_Context&) override { + auto& renderable = static_cast(owner()); + std::unique_lock lock(renderable.mutex); + renderable.rendering = true; + renderable.condition.notify_all(); + renderable.condition.wait(lock, [&renderable] { + return renderable.released; + }); + } + }; +public: + Scene_Base_Blocking_Renderable() + : Renderable_Base(std::make_unique(), + {.cache_enabled = false}) {} + void set_cache_enabled_for_test(bool enabled) { + d_func().set_configuration({.cache_enabled = enabled}); + } + bool cache_enabled_for_test() const { + return d_func().configuration.load(std::memory_order_acquire).cache_enabled; } void wait_rendering() { std::unique_lock lock(mutex); @@ -80,8 +114,8 @@ TEST(scene_base_test, attach_builder_can_build_complete_initial_topology) { auto builder = scene.attach_builder(); builder.attach(parent); builder.attach(child); - builder.set_parent(Scene_Base::Relationship::display, child, parent); - builder.set_parent(Scene_Base::Relationship::dependency, child, parent); + builder.set_display_parent(child, parent); + builder.set_dependency_parent(child, parent); } const auto topology = scene.topology_snapshot(); EXPECT_EQ(topology.renderables.size(), 2u); @@ -97,6 +131,28 @@ TEST(scene_base_test, attach_builder_rejects_null_renderable) { auto builder = scene.attach_builder(); EXPECT_THROW(builder.attach({}), std::invalid_argument); } +TEST(scene_base_test, one_runtime_callback_attaches_multiple_renderables) { + Scene2D_Context<> scene; + auto first = renderive_Owner::make(); + auto second = renderive_Owner::make(); + auto third = renderive_Owner::make(); + wait_renderable_edit(scene, [first, second, third](auto& editor) { + editor.attach(first); + editor.attach(second); + editor.attach(third); + }); + EXPECT_EQ(scene.renderable_count(), 3u); + EXPECT_EQ(scene.topology_snapshot().renderables.size(), 3u); +} +TEST(scene_base_test, repeated_attach_of_same_owner_keeps_existing_attachment) { + Scene2D_Context<> scene; + auto renderable = renderive_Owner::make(); + wait_renderable_edit(scene, [renderable](auto& editor) { + editor.attach(renderable); + editor.attach(renderable); + }); + EXPECT_EQ(scene.renderable_count(), 1u); +} TEST(scene_base_test, attach_builder_is_rejected_after_runtime_starts) { Scene2D_Context<> scene; auto renderable = renderive_Owner::make(); @@ -135,12 +191,12 @@ TEST(scene_base_test, one_runtime_edit_can_rewire_and_detach_without_internal_re builder.attach(first_parent); builder.attach(second_parent); builder.attach(child); - builder.set_parent(Scene_Base::Relationship::display, child, first_parent); - builder.set_parent(Scene_Base::Relationship::dependency, child, first_parent); + builder.set_display_parent(child, first_parent); + builder.set_dependency_parent(child, first_parent); }); wait_renderable_edit(scene, [first_parent, second_parent, child](auto& editor) { - editor.set_parent(Scene_Base::Relationship::display, child, second_parent); - editor.set_parent(Scene_Base::Relationship::dependency, child, second_parent); + editor.set_display_parent(child, second_parent); + editor.set_dependency_parent(child, second_parent); editor.detach(first_parent); }); const auto topology = scene.topology_snapshot(); @@ -149,6 +205,107 @@ TEST(scene_base_test, one_runtime_edit_can_rewire_and_detach_without_internal_re return relation.child.get() == child.get() && relation.parent.get() == second_parent.get(); })); } +TEST(scene_base_test, one_runtime_edit_can_clear_detach_attach_and_relate) { + Scene2D_Context<> scene; + auto old_parent = renderive_Owner::make(); + auto replacement = renderive_Owner::make(); + auto child = renderive_Owner::make(); + with_attach_builder(scene, [&](auto& builder) { + builder.attach(old_parent); + builder.attach(child); + builder.set_display_parent(child, old_parent); + builder.set_dependency_parent(child, old_parent); + }); + wait_renderable_edit(scene, [old_parent, replacement, child](auto& editor) { + editor.clear_display_parent(child); + editor.clear_dependency_parent(child); + editor.detach(old_parent); + editor.attach(replacement); + editor.set_display_parent(child, replacement); + editor.set_dependency_parent(child, replacement); + }); + const auto topology = scene.topology_snapshot(); + EXPECT_EQ(topology.renderables.size(), 2u); + EXPECT_TRUE(std::ranges::any_of(topology.display, [&](const auto& relation) { + return relation.child.get() == child.get() && + relation.parent.get() == replacement.get(); + })); + EXPECT_TRUE(std::ranges::any_of(topology.dependency, [&](const auto& relation) { + return relation.child.get() == child.get() && + relation.parent.get() == replacement.get(); + })); +} +TEST(scene_base_test, callback_may_be_temporarily_invalid_when_final_structure_is_valid) { + Scene2D_Context<> scene; + auto old_parent = renderive_Owner::make(); + auto replacement = renderive_Owner::make(); + auto child = renderive_Owner::make(); + with_attach_builder(scene, [&](auto& builder) { + builder.attach(old_parent); + builder.attach(child); + builder.set_display_parent(child, old_parent); + builder.set_dependency_parent(child, old_parent); + }); + wait_renderable_edit(scene, [old_parent, replacement, child](auto& editor) { + editor.detach(old_parent); + editor.attach(replacement); + editor.set_display_parent(child, replacement); + editor.set_dependency_parent(child, replacement); + }); + const auto topology = scene.topology_snapshot(); + EXPECT_EQ(topology.renderables.size(), 2u); + EXPECT_TRUE(std::ranges::none_of(topology.renderables, [&](const auto& owner) { + return owner.get() == old_parent.get(); + })); +} +TEST(scene_base_test, detached_renderable_cannot_migrate_to_another_scene) { + Scene2D_Context<> first_scene; + Scene2D_Context<> second_scene; + auto renderable = renderive_Owner::make(); + attach_initial(first_scene, renderable); + wait_renderable_edit(first_scene, [renderable](auto& editor) { + editor.detach(renderable); + }); + EXPECT_EQ(first_scene.renderable_count(), 0u); + auto builder = second_scene.attach_builder(); + EXPECT_THROW(builder.attach(renderable), std::invalid_argument); +} +TEST(scene_base_test, dependency_edit_invalidates_cached_child_prepare) { + Scene2D_Context<> scene; + auto parent = renderive_Owner::make(); + auto child = renderive_Owner::make(true); + attach_initial(scene, parent, child); + scene.render(); + scene.wait_for_render(); + scene.render(); + scene.wait_for_render(); + ASSERT_EQ(child->render_count, 1); + wait_renderable_edit(scene, [parent, child](auto& editor) { + editor.set_dependency_parent(child, parent); + }); + scene.render(); + scene.wait_for_render(); + EXPECT_EQ(child->render_count, 2); +} +TEST(scene_base_test, display_edit_changes_paint_order) { + Scene2D_Context<> scene; + auto first_parent = renderive_Owner::make(); + auto second_parent = renderive_Owner::make(); + auto child = renderive_Owner::make(); + attach_initial(scene, first_parent, second_parent, child); + wait_renderable_edit(scene, [second_parent, child](auto& editor) { + editor.set_display_parent(child, second_parent); + }); + const auto first_order = scene.paint_order_snapshot(); + EXPECT_LT(std::ranges::find(first_order, second_parent), + std::ranges::find(first_order, child)); + wait_renderable_edit(scene, [first_parent, child](auto& editor) { + editor.set_display_parent(child, first_parent); + }); + const auto second_order = scene.paint_order_snapshot(); + EXPECT_LT(std::ranges::find(second_order, first_parent), + std::ranges::find(second_order, child)); +} TEST(scene_base_test, detach_releases_scene_ownership_after_edit_applies) { Scene2D_Context<> scene; auto renderable = renderive_Owner::make(); @@ -158,7 +315,7 @@ TEST(scene_base_test, detach_releases_scene_ownership_after_edit_applies) { editor.detach(renderable); }); renderable.reset(); - EXPECT_TRUE(weak.expired()); + EXPECT_TRUE(wait_until([&] { return weak.expired(); })); EXPECT_EQ(scene.renderable_count(), 0u); } TEST(scene_base_test, topology_snapshot_retains_renderable_lifetime) { @@ -183,14 +340,14 @@ TEST(scene_base_test, non_structural_configuration_update_does_not_wait_for_rend renderable->wait_rendering(); std::atomic updated{}; std::thread writer([&] { - renderable->set_configuration({.cache_enabled = true}); + renderable->set_cache_enabled_for_test(true); updated.store(true, std::memory_order_release); }); EXPECT_TRUE(wait_until([&] { return updated.load(std::memory_order_acquire); })); renderable->release(); writer.join(); scene.wait_for_render(); - EXPECT_TRUE(renderable->configuration().cache_enabled); + EXPECT_TRUE(renderable->cache_enabled_for_test()); } TEST(scene_base_test, concurrent_render_and_runtime_structure_edits_are_serialized_by_worker) { Scene2D_Context<> scene; @@ -207,11 +364,11 @@ TEST(scene_base_test, concurrent_render_and_runtime_structure_edits_are_serializ for (int index = 0; index < 50; ++index) { scene.edit_renderables([parent, child, index](auto& edit) { if (index % 2 == 0) { - edit.set_parent(Scene_Base::Relationship::dependency, child, parent); - edit.set_parent(Scene_Base::Relationship::display, child, parent); + edit.set_dependency_parent(child, parent); + edit.set_display_parent(child, parent); } else { - edit.clear_parents(Scene_Base::Relationship::dependency, child); - edit.clear_parents(Scene_Base::Relationship::display, child); + edit.clear_dependency_parent(child); + edit.clear_display_parent(child); } }); } @@ -222,6 +379,102 @@ TEST(scene_base_test, concurrent_render_and_runtime_structure_edits_are_serializ scene.wait_for_render(); EXPECT_EQ(scene.renderable_count(), 2u); } +TEST(scene_base_test, render_submission_waits_while_edit_callback_is_running) { + Scene2D_Context<> scene; + std::promise callback_entered; + auto callback_entered_future = callback_entered.get_future(); + std::promise release_callback; + auto release_callback_future = release_callback.get_future().share(); + scene.edit_renderables([&](auto&) { + callback_entered.set_value(); + release_callback_future.wait(); + }); + callback_entered_future.wait(); + std::atomic renderer_started{}; + std::atomic render_submitted{}; + std::thread renderer([&] { + renderer_started.store(true, std::memory_order_release); + scene.render(); + render_submitted.store(true, std::memory_order_release); + }); + EXPECT_TRUE(wait_until([&] { + return renderer_started.load(std::memory_order_acquire); + })); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + EXPECT_FALSE(render_submitted.load(std::memory_order_acquire)); + release_callback.set_value(); + renderer.join(); + scene.wait_for_render(); + EXPECT_TRUE(render_submitted.load(std::memory_order_acquire)); +} +TEST(scene_base_test, initialization_and_runtime_edit_build_equivalent_plan_shapes) { + Scene2D_Context<> initialized_scene; + auto initialized_parent = renderive_Owner::make(); + auto initialized_child = renderive_Owner::make(); + with_attach_builder(initialized_scene, [&](auto& builder) { + builder.attach(initialized_parent); + builder.attach(initialized_child); + builder.set_display_parent(initialized_child, initialized_parent); + builder.set_dependency_parent(initialized_child, initialized_parent); + }); + Scene2D_Context<> edited_scene; + auto edited_parent = renderive_Owner::make(); + auto edited_child = renderive_Owner::make(); + wait_renderable_edit(edited_scene, [edited_parent, edited_child](auto& editor) { + editor.attach(edited_parent); + editor.attach(edited_child); + editor.set_display_parent(edited_child, edited_parent); + editor.set_dependency_parent(edited_child, edited_parent); + }); + initialized_scene.render(); + edited_scene.render(); + initialized_scene.wait_for_render(); + edited_scene.wait_for_render(); + const auto initialized_plan = initialized_scene.render_plan_snapshot(); + const auto edited_plan = edited_scene.render_plan_snapshot(); + ASSERT_TRUE(initialized_plan); + ASSERT_TRUE(edited_plan); + ASSERT_EQ(initialized_plan->graph.nodes.size(), edited_plan->graph.nodes.size()); + std::unordered_map initialized_indices; + std::unordered_map edited_indices; + for (std::size_t index = 0; index < initialized_plan->graph.nodes.size(); ++index) { + const auto& initialized_node = initialized_plan->graph.nodes[index]; + const auto& edited_node = edited_plan->graph.nodes[index]; + EXPECT_EQ(initialized_node.name, edited_node.name); + EXPECT_EQ(initialized_node.kind, edited_node.kind); + initialized_indices.emplace(initialized_node.node_id, index); + edited_indices.emplace(edited_node.node_id, index); + } + std::vector> initialized_edges; + std::vector> edited_edges; + for (const auto& edge : initialized_plan->graph.edges) + initialized_edges.emplace_back(initialized_indices.at(edge.from), + initialized_indices.at(edge.to)); + for (const auto& edge : edited_plan->graph.edges) + edited_edges.emplace_back(edited_indices.at(edge.from), + edited_indices.at(edge.to)); + std::ranges::sort(initialized_edges); + std::ranges::sort(edited_edges); + EXPECT_EQ(initialized_edges, edited_edges); +} +TEST(scene_base_test, detached_topology_reference_fails_fast_after_callback) { + EXPECT_DEATH( + { + Scene2D_Context<> scene; + auto parent = renderive_Owner::make(); + auto child = renderive_Owner::make(); + with_attach_builder(scene, [&](auto& builder) { + builder.attach(parent); + builder.attach(child); + builder.set_dependency_parent(child, parent); + }); + scene.edit_renderables([parent](auto& editor) { + editor.detach(parent); + }); + scene.render(); + }, + "dependency topology still references detached renderable"); +} TEST(scene_base_test, wait_for_render_propagates_background_render_failure) { Scene2D_Context<> scene; auto renderable = renderive_Owner::make(); diff --git a/Kernel/tests/renderive/state/State_Test_Types.hpp b/Kernel/tests/renderive/state/State_Test_Types.hpp index 2627718..3bd7ba3 100644 --- a/Kernel/tests/renderive/state/State_Test_Types.hpp +++ b/Kernel/tests/renderive/state/State_Test_Types.hpp @@ -2,6 +2,7 @@ #include #include #include "renderive/base/property/Property.hpp" +#include "renderive/renderable/base/Renderable_Base_p.hpp" #include "renderive/state/State_Strategy.hpp" #include "renderive/scene/Scene.hpp" class State_Render_State_Reader { @@ -10,7 +11,7 @@ class State_Render_State_Reader { explicit Reader() : Renderable_Base() {} template auto read(const Source& source) const { - return render_state_view().get(source); + return d_func().render_state_view().get(source); } }; public: diff --git a/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp b/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp index af988fd..1c4fff6 100644 --- a/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp +++ b/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp @@ -12,6 +12,7 @@ #include "renderive/frame_control/Frame_Control.hpp" #include "renderive/real_time_data/Real_Time_Data.hpp" #include "renderive/renderable/Renderable.hpp" +#include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" #include "renderive/state/Double_State_Strategy.hpp" #include "renderive/state/Triple_State_Strategy.hpp" @@ -71,10 +72,10 @@ struct Threading_Test_State_Value { }; using Threading_Test_Double_State = Double_State_Strategy; using Threading_Test_Triple_State = Triple_State_Strategy; -struct Threading_Test_Scene_Renderable : Renderable_Base { +struct Threading_Test_Scene_Renderable : Renderable_Test_Harness { Threading_Test_Scene_Renderable(std::atomic& render_count, std::atomic& maximum_sequence) - : Renderable_Base({.cache_enabled = false}), render_count(&render_count), maximum_sequence(&maximum_sequence) {} - void prepare(const Prepare_Render_Context& context) override { + : Renderable_Test_Harness({.cache_enabled = false}), render_count(&render_count), maximum_sequence(&maximum_sequence) {} + void prepare_for_test(const Prepare_Render_Context& context) override { render_count->fetch_add(1, std::memory_order_relaxed); std::uint64_t maximum = maximum_sequence->load(std::memory_order_acquire); while (maximum < context.frame.render_sequence && !maximum_sequence->compare_exchange_weak(maximum, context.frame.render_sequence, std::memory_order_acq_rel)) {} @@ -82,16 +83,17 @@ struct Threading_Test_Scene_Renderable : Renderable_Base { std::atomic* render_count; std::atomic* maximum_sequence; }; -struct Threading_Test_Render_Graph_Renderable : Renderable_Base { - Threading_Test_Render_Graph_Renderable(std::atomic& executed) : Renderable_Base({.cache_enabled = false}), executed(&executed) {} - void build_prepare_graph(Renderable_Graph_Builder& graph) override { +struct Threading_Test_Render_Graph_Renderable : Renderable_Test_Harness { + Threading_Test_Render_Graph_Renderable(std::atomic& executed) + : Renderable_Test_Harness({.cache_enabled = false}), executed(&executed) {} + void build_prepare_graph_for_test(Renderable_Graph_Builder& graph) override { graph.emplace("prepare", "Prepare", [this](const Prepare_Render_Context&) { executed->fetch_add(1, std::memory_order_relaxed); }); } void request_graph_rebuild() { - rebuild_render_graph(); + rebuild_render_graph_for_test(); } std::atomic* executed; }; diff --git a/Qt/plot/Plot.cpp b/Qt/plot/Plot.cpp index ceb8059..4d5d441 100644 --- a/Qt/plot/Plot.cpp +++ b/Qt/plot/Plot.cpp @@ -30,11 +30,6 @@ QColor to_qcolor(Color color) { return QColor(color.r, color.g, color.b, color.a); } -std::string to_utf8_string(const QString& text) { - const QByteArray bytes = text.toUtf8(); - return {bytes.constData(), static_cast(bytes.size())}; -} - } // namespace Abs_Plot_Private::Abs_Plot_Private(bool continuous_rendering) @@ -80,19 +75,10 @@ Scene2D* Abs_Plot::scene() const { return d ? d->scene.get() : nullptr; } -std::shared_ptr Abs_Plot::root_renderable() const { +renderive_Owner Abs_Plot::root_renderable() const { return d->scene->root_renderable(); } -std::shared_ptr Abs_Plot::create_renderable_node( - const std::shared_ptr& parent, const QString& object_name) const { - return d->scene->create_renderable_node(parent, to_utf8_string(object_name)); -} - -void Abs_Plot::remove_renderable(const std::shared_ptr& renderable) { - d->scene->remove_renderable(renderable); -} - QColor Abs_Plot::background_color() const { return to_qcolor(d->scene->background_color()); } diff --git a/Qt/plot/Plot.h b/Qt/plot/Plot.h index 27fc73a..f5cca64 100644 --- a/Qt/plot/Plot.h +++ b/Qt/plot/Plot.h @@ -3,9 +3,7 @@ #include #include #include -#include #include -#include namespace renderive { struct Abs_Plot_Private; class Latency_Eager_Plot; @@ -16,11 +14,8 @@ public: virtual void init(); [[nodiscard]] QColor background_color() const; [[nodiscard]] Scene2D* scene() const; - [[nodiscard]] std::shared_ptr root_renderable() const; - [[nodiscard]] std::shared_ptr create_renderable_node( - const std::shared_ptr& parent, const QString& object_name = {}) const; + [[nodiscard]] renderive_Owner root_renderable() const; void set_background_color(const QColor& color); - void remove_renderable(const std::shared_ptr& renderable); ~Abs_Plot() override; protected: explicit Abs_Plot(Abs_Plot_Private* private_data); diff --git a/Qt/tests/Qt_Bridge_Tests.cpp b/Qt/tests/Qt_Bridge_Tests.cpp index 4c8575a..acaf506 100644 --- a/Qt/tests/Qt_Bridge_Tests.cpp +++ b/Qt/tests/Qt_Bridge_Tests.cpp @@ -12,22 +12,29 @@ TEST(Renderive_Qt, WidgetLifecycleDrivesAndStopsKernelScene) { Latency_Eager_Plot plot; plot.resize(240, 120); plot.init(); - auto x_axis = Frequency_Axis::Builder(plot.root_renderable(), Orientation::Horizontal) - .set_x(20) - .set_y(100) - .set_pixel_length(200) - .set_coord_range({0.0, 10.0}) - .build(); - auto y_axis = Axis::Builder(plot.root_renderable(), Orientation::Vertical) - .set_x(20) - .set_y(10) - .set_pixel_length(90) - .set_coord_range({1.0, 0.0}) - .build(); - auto spectrum = Spectrum::Builder{} - .set<&Spectrum::Properties::frequency_range>(Range{0.0, 10.0}) - .set<&Spectrum::Properties::frequency_point_size>(8) - .build(plot.root_renderable(), x_axis, y_axis); + const auto root = plot.root_renderable(); + renderive_Owner x_axis; + renderive_Owner y_axis; + renderive_Owner spectrum; + { + auto attach = plot.scene()->attach_builder(); + x_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach) + .set_x(20) + .set_y(100) + .set_pixel_length(200) + .set_coord_range({0.0, 10.0}) + .build(); + y_axis = Axis::Builder(root, Orientation::Vertical, &attach) + .set_x(20) + .set_y(10) + .set_pixel_length(90) + .set_coord_range({1.0, 0.0}) + .build(); + spectrum = Spectrum::Builder{&attach} + .set<&Spectrum::Properties::frequency_range>(Range{0.0, 10.0}) + .set<&Spectrum::Properties::frequency_point_size>(8) + .build(root, x_axis, y_axis); + } spectrum->update_samples(std::vector{0.1, 0.3, 0.8, 0.5, 0.9, 0.4, 0.2, 0.7}); plot.show(); QTimer::singleShot(150, application, &QCoreApplication::quit); diff --git a/render_2D/axis/Axis_Builder.h b/render_2D/axis/Axis_Builder.h index c992d32..49b139c 100644 --- a/render_2D/axis/Axis_Builder.h +++ b/render_2D/axis/Axis_Builder.h @@ -67,8 +67,8 @@ public: auto result = renderive_Owner::make(properties_); if (attach_builder_) { attach_builder_->attach(result); - attach_builder_->add_parent(::Scene_Base::Relationship::display, result, parent_); - attach_builder_->add_parent(::Scene_Base::Relationship::dependency, result, parent_); + attach_builder_->add_display_parent(result, parent_); + attach_builder_->add_dependency_parent(result, parent_); } return result; } diff --git a/render_2D/renderable/Renderable_Builder.h b/render_2D/renderable/Renderable_Builder.h index f5cf72d..d0ad6b0 100644 --- a/render_2D/renderable/Renderable_Builder.h +++ b/render_2D/renderable/Renderable_Builder.h @@ -16,11 +16,11 @@ requires std::derived_from && std::derived_from& renderable, const renderive_Owner& axis) { if (!axis) return; - builder.add_parent(::Scene_Base::Relationship::dependency, renderable, axis); + builder.add_dependency_parent(renderable, axis); if constexpr (std::derived_from) - builder.add_parent(::Scene_Base::Relationship::display, renderable, axis); + builder.add_display_parent(renderable, axis); else - builder.add_parent(::Scene_Base::Relationship::display, axis, renderable); + builder.add_display_parent(axis, renderable); } template void attach_renderable_dependency(::Scene_Base::Attach_Builder&, const renderive_Owner&, const Value&) {} @@ -28,8 +28,8 @@ template requires std::derived_from void attach_renderable(::Scene_Base::Attach_Builder& builder, const renderive_Owner& renderable, const renderive_Owner& parent, const Args&... args) { builder.attach(renderable); - builder.add_parent(::Scene_Base::Relationship::display, renderable, parent); - builder.add_parent(::Scene_Base::Relationship::dependency, renderable, parent); + builder.add_display_parent(renderable, parent); + builder.add_dependency_parent(renderable, parent); (attach_renderable_dependency(builder, renderable, args), ...); } } diff --git a/render_2D/tests/render_2D_Integration_Tests.cpp b/render_2D/tests/render_2D_Integration_Tests.cpp index e8519b5..b29c38b 100644 --- a/render_2D/tests/render_2D_Integration_Tests.cpp +++ b/render_2D/tests/render_2D_Integration_Tests.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,17 @@ decltype(auto) build_initial(Scene& scene, Build&& build) { auto attach = scene.attach_builder(); return std::invoke(std::forward(build), attach); } +template +void apply_runtime_edit(Scene& scene, Edit&& edit) { + std::promise completed; + auto future = completed.get_future(); + scene.edit_renderables( + [edit = std::forward(edit), &completed](auto& editor) mutable { + edit(editor); + completed.set_value(); + }); + future.wait(); +} template concept Legacy_Axis_Property_Api = requires(Axis_Type& axis) { axis.x(); @@ -201,8 +213,9 @@ TEST(Renderive_Core2, KernelSceneRendersBusinessObjectsIntoBlend2DFrame) { EXPECT_TRUE(saw_drawn_pixel); EXPECT_EQ(plot.diagnostics().refresh.frame_count, 1u); EXPECT_FALSE(plot.render_frame()); + samples.front() += 1.0; spectrum->update_samples(samples); - EXPECT_TRUE(plot.render_frame()); + EXPECT_TRUE(plot.render_frame(true)); frequency_axis->set<&Axis_Properties::wheel>(true); const Range before_zoom = frequency_axis->get<&Axis_Properties::coordinates>(); Wheel_Event wheel; @@ -212,11 +225,11 @@ TEST(Renderive_Core2, KernelSceneRendersBusinessObjectsIntoBlend2DFrame) { EXPECT_TRUE(wheel.is_accepted()); EXPECT_LT(frequency_axis->get<&Axis_Properties::coordinates>().size(), before_zoom.size()); EXPECT_TRUE(plot.render_frame()); - plot.edit_renderables([root, frequency_axis, power_axis, spectrum](auto& editor) { - editor.set_parent(::Scene_Base::Relationship::display, frequency_axis, root); - editor.set_parent(::Scene_Base::Relationship::display, power_axis, root); - editor.clear_parents(::Scene_Base::Relationship::display, spectrum); - editor.clear_parents(::Scene_Base::Relationship::dependency, spectrum); + apply_runtime_edit(plot, [root, frequency_axis, power_axis, spectrum](auto& editor) { + editor.set_display_parent(frequency_axis, root); + editor.set_display_parent(power_axis, root); + editor.clear_display_parent(spectrum); + editor.clear_dependency_parent(spectrum); editor.detach(spectrum); }); EXPECT_TRUE(plot.render_frame(true)); @@ -234,18 +247,25 @@ TEST(Renderive_Core2, StandaloneRenderableBuilderJoinsSceneOnlyInsideRuntimeEdit }); const auto spectrum = Spectrum::Builder{}.build(root, frequency, power); ASSERT_TRUE(spectrum); - EXPECT_FALSE(spectrum->attached()); - plot.edit_renderables([root, frequency, power, spectrum](auto& editor) { + EXPECT_TRUE(std::ranges::none_of( + plot.topology_snapshot().renderables, + [&](const auto& renderable) { + return renderable.get() == spectrum.get(); + })); + apply_runtime_edit(plot, [root, frequency, power, spectrum](auto& editor) { editor.attach(spectrum); - editor.add_parent(::Scene_Base::Relationship::display, spectrum, root); - editor.add_parent(::Scene_Base::Relationship::dependency, spectrum, root); - editor.add_parent(::Scene_Base::Relationship::dependency, spectrum, frequency); - editor.add_parent(::Scene_Base::Relationship::dependency, spectrum, power); - editor.add_parent(::Scene_Base::Relationship::display, frequency, spectrum); - editor.add_parent(::Scene_Base::Relationship::display, power, spectrum); + editor.add_display_parent(spectrum, root); + editor.add_dependency_parent(spectrum, root); + editor.add_dependency_parent(spectrum, frequency); + editor.add_dependency_parent(spectrum, power); + editor.add_display_parent(frequency, spectrum); + editor.add_display_parent(power, spectrum); }); - EXPECT_TRUE(plot.render_frame(true)); - EXPECT_TRUE(spectrum->attached()); + EXPECT_TRUE(std::ranges::any_of( + plot.topology_snapshot().renderables, + [&](const auto& renderable) { + return renderable.get() == spectrum.get(); + })); } TEST(Renderive_Core2, TimeAxisUsesOneFontStateAndFormatsConfiguredLabels) { Scene2D plot; @@ -831,7 +851,7 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { spectrum->update_samples(samples); waterfall->append_row(0, samples); afterglow->append_spectrum(samples); - spectrum->scene().publish_frame_state(); + plot.publish_frame_state(); const auto planned_node_count = [](const Render_Plan& plan, const Renderable_Base& renderable) { @@ -846,7 +866,7 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { EXPECT_EQ(waterfall->get<&Waterfall::Properties::partition_count>(), 4); EXPECT_EQ(afterglow->get<&Afterglow::Properties::partition_count>(), 4); ASSERT_TRUE(plot.render_frame(true)); - const auto partitioned_plan = spectrum->scene().render_plan_snapshot(); + const auto partitioned_plan = plot.render_plan_snapshot(); ASSERT_TRUE(partitioned_plan); EXPECT_EQ(planned_node_count(*partitioned_plan, *spectrum), 6u); EXPECT_EQ(std::count_if( @@ -863,9 +883,9 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { spectrum->set<&Spectrum::Properties::partition_count>(1); waterfall->set<&Waterfall::Properties::partition_count>(1); afterglow->set<&Afterglow::Properties::partition_count>(1); - spectrum->scene().publish_frame_state(); + plot.publish_frame_state(); ASSERT_TRUE(plot.render_frame(true)); - const auto single_plan = spectrum->scene().render_plan_snapshot(); + const auto single_plan = plot.render_plan_snapshot(); ASSERT_TRUE(single_plan); EXPECT_EQ(planned_node_count(*single_plan, *spectrum), 3u); EXPECT_EQ(planned_node_count(*single_plan, *waterfall), 3u); @@ -874,9 +894,9 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { spectrum->set<&Spectrum::Properties::partition_mode>(Render_Partition_Mode::Automatic); waterfall->set<&Waterfall::Properties::partition_mode>(Render_Partition_Mode::Automatic); afterglow->set<&Afterglow::Properties::partition_mode>(Render_Partition_Mode::Automatic); - spectrum->scene().publish_frame_state(); + plot.publish_frame_state(); ASSERT_TRUE(plot.render_frame(true)); - const auto automatic_plan = spectrum->scene().render_plan_snapshot(); + const auto automatic_plan = plot.render_plan_snapshot(); ASSERT_TRUE(automatic_plan); EXPECT_EQ(planned_node_count(*automatic_plan, *spectrum), 3u); EXPECT_EQ(planned_node_count(*automatic_plan, *waterfall), 3u); @@ -935,7 +955,7 @@ TEST(Renderive_Core2, DynamicWaterfallCaptureStressPreservesPlansSlotsAndExactSe for (std::size_t index = 0; index < samples.size(); ++index) samples[index] = static_cast(index % 256); waterfall->append_row(0, samples); - auto& scene = waterfall->scene(); + auto& scene = plot; constexpr std::array partition_counts{64, 16, 1}; std::uint64_t tick{1}; for (std::size_t round = 0; round < 3; ++round) { @@ -984,20 +1004,24 @@ TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { const std::array samples{-80.0, -70.0, -60.0, -50.0}; spectrum->update_samples(samples); ASSERT_TRUE(plot.render_frame(true)); - ASSERT_TRUE(spectrum->prepare_cache_valid()); - ASSERT_TRUE(spectrum->paint_cache_valid()); - - const auto prepare_revision = spectrum->prepare_revision(); - const auto paint_revision = spectrum->paint_revision(); + ASSERT_TRUE(plot.render_frame(true)); + const auto cached_plan = plot.render_plan_snapshot(); + ASSERT_TRUE(cached_plan); + const auto cached_has_kind = [&](Render_Node_Kind kind) { + return std::any_of(cached_plan->graph.nodes.begin(), + cached_plan->graph.nodes.end(), + [&](const Render_Node& node) { + return node.owner_id == spectrum->renderable_id() && + node.kind == kind; + }); + }; + EXPECT_FALSE(cached_has_kind(Render_Node_Kind::prepare)); + EXPECT_FALSE(cached_has_kind(Render_Node_Kind::paint)); spectrum->set<&Spectrum::Properties::current_pen>( Pen{Color{12, 34, 56, 255}, 2.0}); - EXPECT_EQ(spectrum->prepare_revision(), prepare_revision); - EXPECT_GT(spectrum->paint_revision(), paint_revision); - EXPECT_TRUE(spectrum->prepare_cache_valid()); - EXPECT_FALSE(spectrum->paint_cache_valid()); ASSERT_TRUE(plot.render_frame(true)); - const auto plan = spectrum->scene().render_plan_snapshot(); + const auto plan = plot.render_plan_snapshot(); ASSERT_TRUE(plan); const auto has_kind = [&](Render_Node_Kind kind) { return std::any_of(plan->graph.nodes.begin(), plan->graph.nodes.end(), @@ -1008,11 +1032,9 @@ TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { EXPECT_FALSE(has_kind(Render_Node_Kind::prepare)); EXPECT_TRUE(has_kind(Render_Node_Kind::paint)); - const auto spectrum_prepare = spectrum->prepare_revision(); frequency->set<&Axis_Base_Properties::color>(Color{90, 100, 110, 255}); - EXPECT_EQ(spectrum->prepare_revision(), spectrum_prepare); ASSERT_TRUE(plot.render_frame(true)); - const auto axis_style_plan = spectrum->scene().render_plan_snapshot(); + const auto axis_style_plan = plot.render_plan_snapshot(); ASSERT_TRUE(axis_style_plan); EXPECT_FALSE(std::any_of(axis_style_plan->graph.nodes.begin(), axis_style_plan->graph.nodes.end(), @@ -1032,7 +1054,7 @@ TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) { return std::tuple{frequency_axis, power_axis, value}; }); ASSERT_TRUE(spectrum); - const auto topology = spectrum->scene().topology_snapshot(); + const auto topology = plot.topology_snapshot(); const auto has_relationship = [](const auto& relationships, const auto* child, const auto* parent) { return std::any_of(relationships.begin(), relationships.end(), [child, parent](const auto& relationship) { return relationship.child.get() == child && relationship.parent.get() == parent; @@ -1042,7 +1064,7 @@ TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) { EXPECT_TRUE(has_relationship(topology.dependency, spectrum.get(), power.get())); EXPECT_TRUE(has_relationship(topology.display, frequency.get(), spectrum.get())); EXPECT_TRUE(has_relationship(topology.display, power.get(), spectrum.get())); - const auto paint_order = spectrum->scene().paint_order_snapshot(); + const auto paint_order = plot.paint_order_snapshot(); const auto spectrum_position = std::find_if(paint_order.begin(), paint_order.end(), [&spectrum](const auto& renderable) { return renderable.get() == spectrum.get(); }); @@ -1069,7 +1091,7 @@ TEST(Renderive_Core2, InteractionOverlayPaintsAboveItsAxes) { return std::tuple{horizontal_axis, vertical_axis, value}; }); ASSERT_TRUE(selection); - const auto paint_order = selection->scene().paint_order_snapshot(); + const auto paint_order = plot.paint_order_snapshot(); const auto position = [&paint_order](const auto* target) { return std::find_if(paint_order.begin(), paint_order.end(), [target](const auto& renderable) { return renderable.get() == target; diff --git a/web_server/app/Gallery_Plot_Session.cpp b/web_server/app/Gallery_Plot_Session.cpp index 393c8c9..81ae9f1 100644 --- a/web_server/app/Gallery_Plot_Session.cpp +++ b/web_server/app/Gallery_Plot_Session.cpp @@ -113,8 +113,8 @@ public: auto group = detail::make_renderable_group(true); group->set_object_name(std::move(name)); attach.attach(group); - attach.add_parent(::Scene_Base::Relationship::display, group, root_); - attach.add_parent(::Scene_Base::Relationship::dependency, group, root_); + attach.add_display_parent(group, root_); + attach.add_dependency_parent(group, root_); return group; }; axes_node_ = make_group("坐标轴层"); diff --git a/web_server/app/Web_Plot_Session.cpp b/web_server/app/Web_Plot_Session.cpp index 0e94744..0e4d50e 100644 --- a/web_server/app/Web_Plot_Session.cpp +++ b/web_server/app/Web_Plot_Session.cpp @@ -75,8 +75,8 @@ struct Web_Plot_Session::Impl { auto group = detail::make_renderable_group(true); group->set_object_name(std::move(name)); attach.attach(group); - attach.add_parent(::Scene_Base::Relationship::display, group, root); - attach.add_parent(::Scene_Base::Relationship::dependency, group, root); + attach.add_display_parent(group, root); + attach.add_dependency_parent(group, root); return group; }; const auto data = make_group("Web_Data");