结构优化
This commit is contained in:
@@ -3,11 +3,14 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#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<Scene_Base_Test_Renderable&>(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<Impl>(),
|
||||
{.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<Impl>(),
|
||||
{.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<Scene_Base_Blocking_Renderable&>(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<Impl>(),
|
||||
{.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<Scene_Base_Test_Renderable>::make();
|
||||
auto second = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
auto third = renderive_Owner<Scene_Base_Test_Renderable>::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<Scene_Base_Test_Renderable>::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<Scene_Base_Test_Renderable>::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<Scene_Base_Test_Renderable>::make();
|
||||
auto replacement = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
auto child = renderive_Owner<Scene_Base_Test_Renderable>::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<Scene_Base_Test_Renderable>::make();
|
||||
auto replacement = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
auto child = renderive_Owner<Scene_Base_Test_Renderable>::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<Scene_Base_Test_Renderable>::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<Scene_Base_Test_Renderable>::make();
|
||||
auto child = renderive_Owner<Scene_Base_Test_Renderable>::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<Scene_Base_Test_Renderable>::make();
|
||||
auto second_parent = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
auto child = renderive_Owner<Scene_Base_Test_Renderable>::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<Scene_Base_Test_Renderable>::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<bool> 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<void> callback_entered;
|
||||
auto callback_entered_future = callback_entered.get_future();
|
||||
std::promise<void> 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<bool> renderer_started{};
|
||||
std::atomic<bool> 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<Scene_Base_Test_Renderable>::make();
|
||||
auto initialized_child = renderive_Owner<Scene_Base_Test_Renderable>::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<Scene_Base_Test_Renderable>::make();
|
||||
auto edited_child = renderive_Owner<Scene_Base_Test_Renderable>::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<Render_Node_Id, std::size_t> initialized_indices;
|
||||
std::unordered_map<Render_Node_Id, std::size_t> 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<std::pair<std::size_t, std::size_t>> initialized_edges;
|
||||
std::vector<std::pair<std::size_t, std::size_t>> 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<Scene_Base_Test_Renderable>::make();
|
||||
auto child = renderive_Owner<Scene_Base_Test_Renderable>::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<Scene_Base_Throwing_Renderable>::make();
|
||||
|
||||
Reference in New Issue
Block a user