修改成异步的接口
This commit is contained in:
@@ -95,57 +95,50 @@ private:
|
||||
};
|
||||
|
||||
TEST(dynamic_renderable_lifecycle_test,
|
||||
attach_during_render_is_non_blocking_and_visible_on_the_next_frame) {
|
||||
attach_during_render_is_queued_until_the_current_frame_finishes) {
|
||||
Scene2D_Context<> scene;
|
||||
auto gate = std::make_shared<Gate>();
|
||||
auto current = std::make_shared<Blocking_Renderable>(scene, gate);
|
||||
auto attached = std::make_shared<Counting_Renderable>(scene);
|
||||
scene.attach_renderable(current);
|
||||
|
||||
scene.attach_renderable(current).wait();
|
||||
scene.render();
|
||||
gate->wait_until_arrived();
|
||||
std::atomic<bool> attach_finished{};
|
||||
std::thread controller([&] {
|
||||
scene.attach_renderable(attached);
|
||||
attach_finished.store(true, std::memory_order_release);
|
||||
std::atomic<bool> callback_called{};
|
||||
auto mutation = scene.attach_renderable(attached, {}, [&](std::exception_ptr exception) {
|
||||
EXPECT_FALSE(exception);
|
||||
callback_called.store(true, std::memory_order_release);
|
||||
});
|
||||
EXPECT_TRUE(wait_until([&] { return attach_finished.load(std::memory_order_acquire); }));
|
||||
EXPECT_FALSE(mutation.ready());
|
||||
EXPECT_FALSE(callback_called.load(std::memory_order_acquire));
|
||||
EXPECT_EQ(attached->prepare_count.load(std::memory_order_acquire), 0);
|
||||
|
||||
gate->open();
|
||||
controller.join();
|
||||
scene.wait_for_render();
|
||||
mutation.wait();
|
||||
EXPECT_TRUE(wait_until([&] { return callback_called.load(std::memory_order_acquire); }));
|
||||
EXPECT_EQ(attached->prepare_count.load(std::memory_order_acquire), 0);
|
||||
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(attached->prepare_count.load(std::memory_order_acquire), 1);
|
||||
}
|
||||
|
||||
TEST(dynamic_renderable_lifecycle_test,
|
||||
detach_during_render_keeps_the_frame_owner_alive_until_execution_finishes) {
|
||||
detach_during_render_waits_for_the_frame_before_releasing_scene_ownership) {
|
||||
Scene2D_Context<> scene;
|
||||
auto gate = std::make_shared<Gate>();
|
||||
std::atomic<bool> destroyed{};
|
||||
auto renderable = std::make_shared<Blocking_Renderable>(scene, gate, 1, &destroyed);
|
||||
std::weak_ptr<Blocking_Renderable> weak = renderable;
|
||||
scene.attach_renderable(renderable);
|
||||
|
||||
scene.attach_renderable(renderable).wait();
|
||||
scene.render();
|
||||
gate->wait_until_arrived();
|
||||
std::atomic<bool> detach_finished{};
|
||||
std::thread controller([&] {
|
||||
scene.detach_renderable(renderable);
|
||||
detach_finished.store(true, std::memory_order_release);
|
||||
});
|
||||
EXPECT_TRUE(wait_until([&] { return detach_finished.load(std::memory_order_acquire); }));
|
||||
controller.join();
|
||||
auto mutation = scene.detach_renderable(renderable);
|
||||
EXPECT_FALSE(mutation.ready());
|
||||
renderable.reset();
|
||||
EXPECT_FALSE(destroyed.load(std::memory_order_acquire));
|
||||
EXPECT_FALSE(weak.expired());
|
||||
|
||||
gate->open();
|
||||
scene.wait_for_render();
|
||||
mutation.wait();
|
||||
EXPECT_TRUE(wait_until([&] { return destroyed.load(std::memory_order_acquire); }));
|
||||
EXPECT_TRUE(weak.expired());
|
||||
EXPECT_EQ(scene.renderable_count(), 0u);
|
||||
@@ -164,15 +157,16 @@ TEST(dynamic_renderable_lifecycle_test,
|
||||
scene.attach_renderable(child, {
|
||||
.dependency_parents = {first_parent}
|
||||
});
|
||||
|
||||
scene.wait_for_mutations();
|
||||
scene.render();
|
||||
first_gate->wait_until_arrived();
|
||||
scene.set_dependency_parent(child, second_parent);
|
||||
auto reparent = scene.set_dependency_parent(child, second_parent);
|
||||
EXPECT_FALSE(reparent.ready());
|
||||
EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 0);
|
||||
first_gate->open();
|
||||
scene.wait_for_render();
|
||||
reparent.wait();
|
||||
EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 1);
|
||||
|
||||
scene.render();
|
||||
second_gate->wait_until_arrived();
|
||||
EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 1);
|
||||
@@ -258,9 +252,9 @@ TEST(dynamic_renderable_lifecycle_test,
|
||||
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
scene.wait_for_mutations();
|
||||
EXPECT_EQ(renderable->prepare_count.load(std::memory_order_acquire), 1);
|
||||
EXPECT_EQ(scene.renderable_count(), 0u);
|
||||
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(renderable->prepare_count.load(std::memory_order_acquire), 1);
|
||||
@@ -327,21 +321,25 @@ TEST(dynamic_renderable_lifecycle_test,
|
||||
Scene2D_Context<Low_Latency_Strategy<Scene2D_Frame_Data>, Tracking_Color_Cache> scene;
|
||||
auto gate = std::make_shared<Gate>();
|
||||
auto renderable = std::make_shared<Blocking_Painted_Renderable>(scene, gate);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.attach_renderable(renderable).wait();
|
||||
ASSERT_EQ(Tracking_Color_Cache::next_instance_id.load(), 2);
|
||||
Tracking_Color_Cache::watched_instance.store(2, std::memory_order_release);
|
||||
|
||||
scene.render();
|
||||
gate->wait_until_arrived();
|
||||
scene.detach_renderable(renderable);
|
||||
auto detach = scene.detach_renderable(renderable);
|
||||
auto attach = scene.attach_renderable(renderable);
|
||||
EXPECT_FALSE(detach.ready());
|
||||
EXPECT_FALSE(attach.ready());
|
||||
EXPECT_FALSE(Tracking_Color_Cache::watched_destroyed.load(std::memory_order_acquire));
|
||||
scene.attach_renderable(renderable);
|
||||
ASSERT_EQ(Tracking_Color_Cache::next_instance_id.load(), 3);
|
||||
EXPECT_EQ(Tracking_Color_Cache::next_instance_id.load(), 2);
|
||||
gate->open();
|
||||
scene.wait_for_render();
|
||||
detach.wait();
|
||||
attach.wait();
|
||||
EXPECT_TRUE(wait_until([] {
|
||||
return Tracking_Color_Cache::watched_destroyed.load(std::memory_order_acquire);
|
||||
}));
|
||||
ASSERT_EQ(Tracking_Color_Cache::next_instance_id.load(), 3);
|
||||
scene.with_final_color_cache([](const Tracking_Color_Cache& cache) {
|
||||
EXPECT_EQ(cache.samples, std::vector<int>({2}));
|
||||
});
|
||||
@@ -446,7 +444,7 @@ TEST(dynamic_renderable_lifecycle_test,
|
||||
controller.join();
|
||||
controls_done.store(true, std::memory_order_release);
|
||||
renderer.join();
|
||||
|
||||
scene.wait_for_mutations();
|
||||
EXPECT_EQ(failures.load(std::memory_order_acquire), 0);
|
||||
EXPECT_EQ(violations.load(std::memory_order_acquire), 0);
|
||||
EXPECT_EQ(scene.renderable_count(), 2u);
|
||||
|
||||
@@ -248,18 +248,21 @@ TEST(render_plan_execution_test, logical_node_ids_are_stable_and_retired_ids_are
|
||||
const Render_Node_Id retired_chunk = first->graph.nodes[2].node_id;
|
||||
|
||||
renderable->set_chunk_count(2);
|
||||
scene.wait_for_mutations();
|
||||
const auto unchanged = renderable->graph_snapshot();
|
||||
EXPECT_EQ(unchanged->graph.nodes[0].node_id, root);
|
||||
EXPECT_EQ(unchanged->graph.nodes[1].node_id, first_chunk);
|
||||
EXPECT_EQ(unchanged->graph.nodes[2].node_id, retired_chunk);
|
||||
|
||||
renderable->set_chunk_count(1);
|
||||
scene.wait_for_mutations();
|
||||
const auto reduced = renderable->graph_snapshot();
|
||||
ASSERT_EQ(reduced->graph.nodes.size(), 2u);
|
||||
EXPECT_EQ(reduced->graph.nodes[0].node_id, root);
|
||||
EXPECT_EQ(reduced->graph.nodes[1].node_id, first_chunk);
|
||||
|
||||
renderable->set_chunk_count(2);
|
||||
scene.wait_for_mutations();
|
||||
const auto expanded = renderable->graph_snapshot();
|
||||
ASSERT_EQ(expanded->graph.nodes.size(), 3u);
|
||||
EXPECT_EQ(expanded->graph.nodes[0].node_id, root);
|
||||
|
||||
@@ -207,11 +207,11 @@ TEST(scene2d_context_test, failed_attach_leaves_renderable_fully_detached) {
|
||||
Scene2D_Attach_Throwing_Cache::throw_on_construction = 2;
|
||||
Scene2D_Context<Low_Latency_Strategy<Scene2D_Frame_Data>, Scene2D_Attach_Throwing_Cache> scene;
|
||||
auto renderable = std::make_shared<Scene2D_Context_Test_Renderable>(scene);
|
||||
EXPECT_THROW(scene.attach_renderable(renderable), std::runtime_error);
|
||||
EXPECT_THROW(scene.attach_renderable(renderable).wait(), std::runtime_error);
|
||||
EXPECT_EQ(scene.renderable_count(), 0);
|
||||
EXPECT_TRUE(scene.topology_snapshot().renderables.empty());
|
||||
Scene2D_Attach_Throwing_Cache::throw_on_construction = 0;
|
||||
EXPECT_NO_THROW(scene.attach_renderable(renderable));
|
||||
EXPECT_NO_THROW(scene.attach_renderable(renderable).wait());
|
||||
EXPECT_EQ(scene.renderable_count(), 1);
|
||||
}
|
||||
struct Scene2D_Viewport_Snapshot_Renderable : Renderable_Base {
|
||||
|
||||
@@ -65,7 +65,7 @@ TEST(scene3d_context_test, detach_parent_promotes_children_and_invalidates_depen
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(child->render_count, 1);
|
||||
scene.detach_renderable(parent);
|
||||
scene.detach_renderable(parent).wait();
|
||||
const auto topology = scene.topology_snapshot();
|
||||
bool display_checked{};
|
||||
bool dependency_checked{};
|
||||
|
||||
@@ -144,7 +144,7 @@ TEST(scene_base_test, detach_releases_renderable_from_the_live_model) {
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
scene.detach_renderable(renderable);
|
||||
scene.detach_renderable(renderable).wait();
|
||||
renderable.reset();
|
||||
EXPECT_TRUE(weak.expired());
|
||||
EXPECT_EQ(scene.renderable_count(), 0);
|
||||
@@ -158,11 +158,11 @@ TEST(scene_base_test, topology_rejects_unattached_renderables) {
|
||||
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_dependency_parent(child, parent), std::invalid_argument);
|
||||
EXPECT_THROW(scene.set_display_parent(child, parent), std::invalid_argument);
|
||||
EXPECT_THROW(scene.set_dependency_parent(child, parent).wait(), std::invalid_argument);
|
||||
EXPECT_THROW(scene.set_display_parent(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_dependency_parent(detached_child, parent), std::invalid_argument);
|
||||
EXPECT_THROW(scene.set_dependency_parent(detached_child, parent).wait(), std::invalid_argument);
|
||||
}
|
||||
TEST(scene_base_test, setting_same_topology_parent_is_a_noop) {
|
||||
Scene2D_Context<> scene;
|
||||
@@ -170,9 +170,9 @@ TEST(scene_base_test, setting_same_topology_parent_is_a_noop) {
|
||||
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(parent);
|
||||
scene.attach_renderable(child);
|
||||
scene.set_dependency_parent(child, parent);
|
||||
scene.set_dependency_parent(child, parent).wait();
|
||||
const auto revision = child->prepare_revision();
|
||||
EXPECT_NO_THROW(scene.set_dependency_parent(child, parent));
|
||||
EXPECT_NO_THROW(scene.set_dependency_parent(child, parent).wait());
|
||||
EXPECT_EQ(child->prepare_revision(), revision);
|
||||
const auto topology = scene.topology_snapshot();
|
||||
EXPECT_EQ(topology.dependency.size(), 2);
|
||||
@@ -195,7 +195,24 @@ TEST(scene_base_test, display_graph_rejects_cycles) {
|
||||
scene.attach_renderable(third);
|
||||
scene.set_display_parent(second, first);
|
||||
scene.add_display_parent(third, second);
|
||||
EXPECT_THROW(scene.add_display_parent(first, third), std::invalid_argument);
|
||||
EXPECT_THROW(scene.add_display_parent(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_display_parent(second, first).wait();
|
||||
std::atomic<bool> callback_called{};
|
||||
std::atomic<bool> callback_failed{};
|
||||
auto mutation = scene.set_display_parent(first, second, [&](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;
|
||||
@@ -209,7 +226,7 @@ TEST(scene_base_test, atomic_attach_can_place_existing_display_children) {
|
||||
.display_children = {axis},
|
||||
.dependency_parents = {parent, axis}
|
||||
});
|
||||
|
||||
scene.wait_for_mutations();
|
||||
const auto topology = scene.topology_snapshot();
|
||||
const auto contains = [](const auto& relationships, const auto& child,
|
||||
const auto& parent_value) {
|
||||
@@ -233,21 +250,21 @@ TEST(scene_base_test, failed_atomic_attach_rolls_back_every_topology) {
|
||||
.display_parents = {parent},
|
||||
.display_children = {parent},
|
||||
.dependency_parents = {parent}
|
||||
}), std::invalid_argument);
|
||||
}).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) {
|
||||
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);
|
||||
scene.attach_renderable(renderable).wait();
|
||||
const auto snapshot = scene.topology_snapshot();
|
||||
scene.detach_renderable(renderable);
|
||||
scene.detach_renderable(renderable).wait();
|
||||
renderable.reset();
|
||||
EXPECT_FALSE(weak.expired());
|
||||
EXPECT_EQ(snapshot.renderables.size(), 1);
|
||||
@@ -259,6 +276,7 @@ TEST(scene_base_test, topology_snapshot_is_safe_during_topology_updates) {
|
||||
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(parent);
|
||||
scene.attach_renderable(child);
|
||||
scene.wait_for_mutations();
|
||||
std::thread writer([&] {
|
||||
for (int index = 0; index < 500; ++index) {
|
||||
if (index % 2 == 0) {
|
||||
@@ -339,6 +357,25 @@ struct Scene_Base_Render_Execution_Renderable : Renderable_Base {
|
||||
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(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);
|
||||
|
||||
Reference in New Issue
Block a user