builder 和 运行时修改隔离开
This commit is contained in:
@@ -9,11 +9,11 @@
|
||||
#include <thread>
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
#include "renderive/scene/Scene_Test_Helpers.hpp"
|
||||
namespace {
|
||||
template <class Predicate>
|
||||
bool wait_until(Predicate&& predicate) {
|
||||
const auto deadline = std::chrono::steady_clock::now() +
|
||||
std::chrono::seconds(2);
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
while (!predicate()) {
|
||||
if (std::chrono::steady_clock::now() >= deadline)
|
||||
return false;
|
||||
@@ -23,7 +23,7 @@ bool wait_until(Predicate&& predicate) {
|
||||
}
|
||||
}
|
||||
struct Scene_Base_Test_Renderable : Renderable_Base {
|
||||
explicit Scene_Base_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {}
|
||||
Scene_Base_Test_Renderable() = default;
|
||||
void prepare(const Prepare_Render_Context& context) override {
|
||||
render_sequence = context.frame.render_sequence;
|
||||
++render_count;
|
||||
@@ -31,53 +31,23 @@ struct Scene_Base_Test_Renderable : Renderable_Base {
|
||||
std::uint64_t render_sequence{};
|
||||
int render_count{};
|
||||
};
|
||||
TEST(scene_base_test, publishes_renderable_collection_before_background_render) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(renderable->render_count, 1);
|
||||
EXPECT_EQ(renderable->render_sequence, 1);
|
||||
EXPECT_EQ(scene.renderable_count(), 1);
|
||||
}
|
||||
struct Scene_Base_Throwing_Renderable : Renderable_Base {
|
||||
explicit Scene_Base_Throwing_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
|
||||
Scene_Base_Throwing_Renderable() : Renderable_Base({.cache_enabled = false}) {}
|
||||
void prepare(const Prepare_Render_Context&) override {
|
||||
throw std::runtime_error("render failed");
|
||||
}
|
||||
};
|
||||
TEST(scene_base_test, wait_for_render_propagates_background_render_failure) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
EXPECT_THROW(scene.wait_for_render(), std::runtime_error);
|
||||
}
|
||||
TEST(scene_base_test, scene_destruction_does_not_throw_after_background_render_failure) {
|
||||
{
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
}
|
||||
EXPECT_TRUE(true);
|
||||
}
|
||||
struct Scene_Base_Blocking_Renderable : Renderable_Base {
|
||||
explicit Scene_Base_Blocking_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
|
||||
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;
|
||||
});
|
||||
condition.wait(lock, [this] { return released; });
|
||||
}
|
||||
void wait_rendering() {
|
||||
std::unique_lock lock(mutex);
|
||||
condition.wait(lock, [this] {
|
||||
return rendering;
|
||||
});
|
||||
condition.wait(lock, [this] { return rendering; });
|
||||
}
|
||||
void release() {
|
||||
std::lock_guard lock(mutex);
|
||||
@@ -89,217 +59,189 @@ struct Scene_Base_Blocking_Renderable : Renderable_Base {
|
||||
bool rendering{};
|
||||
bool released{};
|
||||
};
|
||||
TEST(scene_base_test, scene_model_mutation_does_not_wait_for_background_render) {
|
||||
TEST(scene_base_test, attach_builder_is_synchronous_before_runtime) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Blocking_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
auto renderable = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
{
|
||||
auto builder = scene.attach_builder();
|
||||
builder.attach(renderable);
|
||||
EXPECT_EQ(scene.renderable_count(), 1u);
|
||||
}
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(renderable->render_count, 1);
|
||||
EXPECT_EQ(renderable->render_sequence, 1);
|
||||
}
|
||||
TEST(scene_base_test, attach_builder_can_build_complete_initial_topology) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
auto child = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
{
|
||||
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);
|
||||
}
|
||||
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() == parent.get();
|
||||
}));
|
||||
EXPECT_TRUE(std::ranges::any_of(topology.dependency, [&](const auto& relation) {
|
||||
return relation.child.get() == child.get() && relation.parent.get() == parent.get();
|
||||
}));
|
||||
}
|
||||
TEST(scene_base_test, attach_builder_rejects_null_renderable) {
|
||||
Scene2D_Context<> scene;
|
||||
auto builder = scene.attach_builder();
|
||||
EXPECT_THROW(builder.attach({}), std::invalid_argument);
|
||||
}
|
||||
TEST(scene_base_test, attach_builder_is_rejected_after_runtime_starts) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
attach_initial(scene, renderable);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_THROW(scene.attach_builder(), std::logic_error);
|
||||
}
|
||||
TEST(scene_base_test, edit_renderables_waits_for_inflight_frame) {
|
||||
Scene2D_Context<> scene;
|
||||
auto blocking = renderive_Owner<Scene_Base_Blocking_Renderable>::make();
|
||||
auto added = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
attach_initial(scene, blocking);
|
||||
scene.render();
|
||||
blocking->wait_rendering();
|
||||
std::atomic<bool> edited{};
|
||||
scene.edit_renderables([added, &edited](auto& editor) {
|
||||
editor.attach(added);
|
||||
edited.store(true, std::memory_order_release);
|
||||
});
|
||||
EXPECT_FALSE(edited.load(std::memory_order_acquire));
|
||||
blocking->release();
|
||||
scene.wait_for_render();
|
||||
EXPECT_TRUE(wait_until([&] { return edited.load(std::memory_order_acquire); }));
|
||||
EXPECT_EQ(added->render_count, 0);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(added->render_count, 1);
|
||||
}
|
||||
TEST(scene_base_test, one_runtime_edit_can_rewire_and_detach_without_internal_repair) {
|
||||
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();
|
||||
with_attach_builder(scene, [&](auto& builder) {
|
||||
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);
|
||||
});
|
||||
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.detach(first_parent);
|
||||
});
|
||||
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() == second_parent.get();
|
||||
}));
|
||||
}
|
||||
TEST(scene_base_test, detach_releases_scene_ownership_after_edit_applies) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
std::weak_ptr<Scene_Base_Test_Renderable> weak = renderable.share();
|
||||
attach_initial(scene, renderable);
|
||||
wait_renderable_edit(scene, [renderable](auto& editor) {
|
||||
editor.detach(renderable);
|
||||
});
|
||||
renderable.reset();
|
||||
EXPECT_TRUE(weak.expired());
|
||||
EXPECT_EQ(scene.renderable_count(), 0u);
|
||||
}
|
||||
TEST(scene_base_test, topology_snapshot_retains_renderable_lifetime) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
std::weak_ptr<Scene_Base_Test_Renderable> weak = renderable.share();
|
||||
attach_initial(scene, renderable);
|
||||
const auto snapshot = scene.topology_snapshot();
|
||||
wait_renderable_edit(scene, [renderable](auto& editor) {
|
||||
editor.detach(renderable);
|
||||
});
|
||||
renderable.reset();
|
||||
EXPECT_FALSE(weak.expired());
|
||||
EXPECT_EQ(snapshot.renderables.size(), 1u);
|
||||
EXPECT_EQ(snapshot.renderables.front().get(), weak.lock().get());
|
||||
}
|
||||
TEST(scene_base_test, non_structural_configuration_update_does_not_wait_for_render) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = renderive_Owner<Scene_Base_Blocking_Renderable>::make();
|
||||
attach_initial(scene, renderable);
|
||||
scene.render();
|
||||
renderable->wait_rendering();
|
||||
std::atomic<bool> mutation_finished{};
|
||||
std::thread mutation([&] {
|
||||
scene.set_renderable_configuration(renderable, {.cache_enabled = true});
|
||||
mutation_finished.store(true, std::memory_order_release);
|
||||
std::atomic<bool> updated{};
|
||||
std::thread writer([&] {
|
||||
renderable->set_configuration({.cache_enabled = true});
|
||||
updated.store(true, std::memory_order_release);
|
||||
});
|
||||
std::this_thread::yield();
|
||||
EXPECT_TRUE(wait_until([&] {
|
||||
return mutation_finished.load(std::memory_order_acquire);
|
||||
}));
|
||||
EXPECT_TRUE(wait_until([&] { return updated.load(std::memory_order_acquire); }));
|
||||
renderable->release();
|
||||
mutation.join();
|
||||
EXPECT_TRUE(mutation_finished.load(std::memory_order_acquire));
|
||||
writer.join();
|
||||
scene.wait_for_render();
|
||||
EXPECT_TRUE(renderable->configuration().cache_enabled);
|
||||
}
|
||||
TEST(scene_base_test, concurrent_render_and_topology_updates_remain_serialized) {
|
||||
TEST(scene_base_test, concurrent_render_and_runtime_structure_edits_are_serialized_by_worker) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(parent);
|
||||
scene.attach_renderable(child);
|
||||
auto parent = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
auto child = renderive_Owner<Scene_Base_Test_Renderable>::make();
|
||||
attach_initial(scene, parent, child);
|
||||
std::thread renderer([&] {
|
||||
for (int index = 0; index < 100; ++index) {
|
||||
for (int index = 0; index < 50; ++index) {
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
}
|
||||
});
|
||||
std::thread mutator([&] {
|
||||
for (int index = 0; index < 100; ++index) {
|
||||
if (index % 2 == 0) {
|
||||
scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
|
||||
scene.set_parent(Scene_Base::Relationship::display, child, parent);
|
||||
} else {
|
||||
scene.clear_parents(Scene_Base::Relationship::dependency, child);
|
||||
scene.clear_parents(Scene_Base::Relationship::display, child);
|
||||
}
|
||||
scene.set_renderable_configuration(child, {.cache_enabled = index % 2 == 0});
|
||||
std::thread editor([&] {
|
||||
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);
|
||||
} else {
|
||||
edit.clear_parents(Scene_Base::Relationship::dependency, child);
|
||||
edit.clear_parents(Scene_Base::Relationship::display, child);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
renderer.join();
|
||||
mutator.join();
|
||||
EXPECT_EQ(scene.renderable_count(), 2);
|
||||
}
|
||||
TEST(scene_base_test, detach_releases_renderable_from_the_live_model) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
std::weak_ptr<Scene_Base_Test_Renderable> weak = renderable;
|
||||
scene.attach_renderable(renderable);
|
||||
editor.join();
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
scene.detach_renderable(renderable).wait();
|
||||
renderable.reset();
|
||||
EXPECT_TRUE(weak.expired());
|
||||
EXPECT_EQ(scene.renderable_count(), 0);
|
||||
}
|
||||
TEST(scene_base_test, attach_rejects_null_renderable) {
|
||||
Scene2D_Context<> scene;
|
||||
EXPECT_THROW(scene.attach_renderable({}), std::invalid_argument);
|
||||
}
|
||||
TEST(scene_base_test, topology_rejects_unattached_renderables) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(child);
|
||||
EXPECT_THROW(scene.set_parent(Scene_Base::Relationship::dependency, child, parent).wait(), std::invalid_argument);
|
||||
EXPECT_THROW(scene.set_parent(Scene_Base::Relationship::display, child, parent).wait(), std::invalid_argument);
|
||||
scene.attach_renderable(parent);
|
||||
auto detached_child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
EXPECT_THROW(scene.set_parent(Scene_Base::Relationship::dependency, detached_child, parent).wait(), std::invalid_argument);
|
||||
}
|
||||
TEST(scene_base_test, setting_same_topology_parent_is_a_noop) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(parent);
|
||||
scene.attach_renderable(child);
|
||||
scene.set_parent(Scene_Base::Relationship::dependency, child, parent).wait();
|
||||
const auto revision = child->prepare_revision();
|
||||
EXPECT_NO_THROW(scene.set_parent(Scene_Base::Relationship::dependency, child, parent).wait());
|
||||
EXPECT_EQ(child->prepare_revision(), revision);
|
||||
const auto topology = scene.topology_snapshot();
|
||||
EXPECT_EQ(topology.dependency.size(), 2);
|
||||
bool found{};
|
||||
for (const auto& relation : topology.dependency) {
|
||||
if (relation.child.get() == child.get()) {
|
||||
EXPECT_EQ(relation.parent.get(), parent.get());
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found);
|
||||
}
|
||||
TEST(scene_base_test, display_graph_rejects_cycles) {
|
||||
Scene2D_Context<> scene;
|
||||
auto first = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto second = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto third = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(first);
|
||||
scene.attach_renderable(second);
|
||||
scene.attach_renderable(third);
|
||||
scene.set_parent(Scene_Base::Relationship::display, second, first);
|
||||
scene.add_parent(Scene_Base::Relationship::display, third, second);
|
||||
EXPECT_THROW(scene.add_parent(Scene_Base::Relationship::display, first, third).wait(), std::invalid_argument);
|
||||
}
|
||||
TEST(scene_base_test, mutation_callback_receives_apply_failure) {
|
||||
Scene2D_Context<> scene;
|
||||
auto first = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto second = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(first).wait();
|
||||
scene.attach_renderable(second).wait();
|
||||
scene.set_parent(Scene_Base::Relationship::display, second, first).wait();
|
||||
std::atomic<bool> callback_called{};
|
||||
std::atomic<bool> callback_failed{};
|
||||
auto mutation = scene.set_parent(Scene_Base::Relationship::display, first, second);
|
||||
mutation.then([&](std::exception_ptr exception) {
|
||||
callback_failed.store(static_cast<bool>(exception), std::memory_order_release);
|
||||
callback_called.store(true, std::memory_order_release);
|
||||
});
|
||||
EXPECT_THROW(mutation.wait(), std::invalid_argument);
|
||||
EXPECT_TRUE(wait_until([&] { return callback_called.load(std::memory_order_acquire); }));
|
||||
EXPECT_TRUE(callback_failed.load(std::memory_order_acquire));
|
||||
}
|
||||
TEST(scene_base_test, atomic_attach_can_place_existing_display_children) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto axis = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(parent);
|
||||
scene.attach_renderable(axis);
|
||||
auto mutation = scene.attach_renderable(renderable, {
|
||||
.display_parents = {parent},
|
||||
.display_children = {axis},
|
||||
.dependency_parents = {parent, axis}
|
||||
});
|
||||
mutation.wait();
|
||||
const auto topology = scene.topology_snapshot();
|
||||
const auto contains = [](const auto& relationships, const auto& child,
|
||||
const auto& parent_value) {
|
||||
return std::ranges::any_of(relationships, [&](const auto& relationship) {
|
||||
return relationship.child.get() == child.get() &&
|
||||
relationship.parent.get() == parent_value.get();
|
||||
});
|
||||
};
|
||||
EXPECT_TRUE(contains(topology.display, renderable, parent));
|
||||
EXPECT_TRUE(contains(topology.display, axis, renderable));
|
||||
EXPECT_TRUE(contains(topology.dependency, renderable, parent));
|
||||
EXPECT_TRUE(contains(topology.dependency, renderable, axis));
|
||||
}
|
||||
TEST(scene_base_test, failed_atomic_attach_rolls_back_every_topology) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(parent);
|
||||
|
||||
EXPECT_THROW(scene.attach_renderable(renderable, {
|
||||
.display_parents = {parent},
|
||||
.display_children = {parent},
|
||||
.dependency_parents = {parent}
|
||||
}).wait(), std::invalid_argument);
|
||||
EXPECT_EQ(scene.renderable_count(), 1u);
|
||||
EXPECT_NO_THROW(scene.attach_renderable(renderable, {
|
||||
.display_parents = {parent},
|
||||
.dependency_parents = {parent}
|
||||
}).wait());
|
||||
EXPECT_EQ(scene.renderable_count(), 2u);
|
||||
}
|
||||
TEST(scene_base_test, topology_snapshot_retains_renderable_lifetime) {
|
||||
TEST(scene_base_test, wait_for_render_propagates_background_render_failure) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
std::weak_ptr<Scene_Base_Test_Renderable> weak = renderable;
|
||||
scene.attach_renderable(renderable).wait();
|
||||
const auto snapshot = scene.topology_snapshot();
|
||||
scene.detach_renderable(renderable).wait();
|
||||
renderable.reset();
|
||||
EXPECT_FALSE(weak.expired());
|
||||
EXPECT_EQ(snapshot.renderables.size(), 1);
|
||||
EXPECT_EQ(snapshot.renderables.front().get(), weak.lock().get());
|
||||
auto renderable = renderive_Owner<Scene_Base_Throwing_Renderable>::make();
|
||||
attach_initial(scene, renderable);
|
||||
scene.render();
|
||||
EXPECT_THROW(scene.wait_for_render(), std::runtime_error);
|
||||
}
|
||||
TEST(scene_base_test, topology_snapshot_is_safe_during_topology_updates) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(parent);
|
||||
scene.attach_renderable(child).wait();
|
||||
std::thread writer([&] {
|
||||
for (int index = 0; index < 500; ++index) {
|
||||
if (index % 2 == 0) {
|
||||
scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
|
||||
scene.set_parent(Scene_Base::Relationship::display, child, parent);
|
||||
} else {
|
||||
scene.clear_parents(Scene_Base::Relationship::dependency, child);
|
||||
scene.clear_parents(Scene_Base::Relationship::display, child);
|
||||
}
|
||||
}
|
||||
});
|
||||
for (int index = 0; index < 500; ++index) {
|
||||
const auto snapshot = scene.topology_snapshot();
|
||||
EXPECT_EQ(snapshot.renderables.size(), 2);
|
||||
EXPECT_EQ(snapshot.display.size(), 2);
|
||||
EXPECT_EQ(snapshot.dependency.size(), 2);
|
||||
TEST(scene_base_test, scene_destruction_does_not_throw_after_background_render_failure) {
|
||||
{
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = renderive_Owner<Scene_Base_Throwing_Renderable>::make();
|
||||
attach_initial(scene, renderable);
|
||||
scene.render();
|
||||
}
|
||||
writer.join();
|
||||
EXPECT_TRUE(true);
|
||||
}
|
||||
TEST(scene_base_test, all_waiters_receive_the_same_render_failure) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
auto renderable = renderive_Owner<Scene_Base_Throwing_Renderable>::make();
|
||||
attach_initial(scene, renderable);
|
||||
scene.render();
|
||||
std::atomic<int> failures{};
|
||||
auto waiter = [&] {
|
||||
@@ -315,74 +257,3 @@ TEST(scene_base_test, all_waiters_receive_the_same_render_failure) {
|
||||
second.join();
|
||||
EXPECT_EQ(failures.load(), 2);
|
||||
}
|
||||
struct Scene_Base_Throw_Once_Renderable : Renderable_Base {
|
||||
explicit Scene_Base_Throw_Once_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
|
||||
void prepare(const Prepare_Render_Context&) override {
|
||||
if (render_count.fetch_add(1, std::memory_order_acq_rel) == 0) {
|
||||
throw std::runtime_error("first render failed");
|
||||
}
|
||||
}
|
||||
std::atomic<int> render_count{};
|
||||
};
|
||||
TEST(scene_base_test, unobserved_render_failure_survives_next_successful_submission) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Throw_Once_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
scene.render();
|
||||
EXPECT_THROW(scene.wait_for_render(), std::runtime_error);
|
||||
EXPECT_EQ(renderable->render_count.load(std::memory_order_acquire), 2);
|
||||
scene.render();
|
||||
EXPECT_NO_THROW(scene.wait_for_render());
|
||||
EXPECT_EQ(renderable->render_count.load(std::memory_order_acquire), 3);
|
||||
}
|
||||
struct Scene_Base_Render_Execution_Renderable : Renderable_Base {
|
||||
explicit Scene_Base_Render_Execution_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
|
||||
void prepare(const Prepare_Render_Context&) override {
|
||||
scene().wait_for_render();
|
||||
wait_returned.store(true, std::memory_order_release);
|
||||
try {
|
||||
scene().render();
|
||||
} catch (const std::logic_error&) {
|
||||
nested_render_rejected.store(true, std::memory_order_release);
|
||||
}
|
||||
try {
|
||||
scene().set_renderable_configuration(shared_from_this(),
|
||||
{.cache_enabled = true});
|
||||
mutation_completed.store(true, std::memory_order_release);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
std::atomic<bool> wait_returned{};
|
||||
std::atomic<bool> nested_render_rejected{};
|
||||
std::atomic<bool> mutation_completed{};
|
||||
};
|
||||
|
||||
TEST(scene_base_test, attach_builder_is_synchronous_before_runtime) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
{
|
||||
auto builder = scene.attach_builder();
|
||||
builder.attach(renderable);
|
||||
EXPECT_EQ(scene.renderable_count(), 1u);
|
||||
}
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(renderable->render_count, 1);
|
||||
}
|
||||
TEST(scene_base_test, attach_builder_is_rejected_after_runtime_starts) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(renderable).wait();
|
||||
EXPECT_THROW(scene.attach_builder(), std::logic_error);
|
||||
}
|
||||
TEST(scene_base_test, taskflow_execution_can_mutate_live_model_but_cannot_submit_nested_render) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Render_Execution_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_TRUE(renderable->wait_returned.load(std::memory_order_acquire));
|
||||
EXPECT_TRUE(renderable->nested_render_rejected.load(std::memory_order_acquire));
|
||||
EXPECT_TRUE(renderable->mutation_completed.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user