#include "scene.hpp" #include namespace { struct Direct_Renderable : double_buffer::Def { struct Prop : Prev_Prop {}; struct State : Prev_State { bool operator==(const State&) const = default; }; struct Private : Prev_Private { int prepare_calls{}; int paint_calls{}; void prepare_data(double_buffer::Attached auto*) { ++prepare_calls; } void paint(double_buffer::Attached auto*) { ++paint_calls; } }; Private& data_for_test(); }; struct Graph_Renderable : double_buffer::Def { struct Prop : Prev_Prop {}; struct State : Prev_State { bool operator==(const State&) const = default; }; struct Private : Prev_Private { int prepare_calls{}; int paint_calls{}; int prepare_builds{}; bool rebuild_prepare{}; tf::Taskflow build_prepare_graph(double_buffer::Attached auto*, const Prop&) { ++prepare_builds; tf::Taskflow graph; graph.emplace([this] { ++prepare_calls; }).name("test.prepare.graph.task"); return graph; } bool should_rebuild_prepare_graph(double_buffer::Attached auto*, const Prop&) { return std::exchange(rebuild_prepare, false); } void paint(double_buffer::Attached auto*) { ++paint_calls; } }; Private& data_for_test(); }; using Direct = double_buffer::Impl; using Graph = double_buffer::Impl; using Scene = double_buffer::Impl; inline Direct_Renderable::Private& Direct_Renderable::data_for_test() { return static_cast(*d); } inline Graph_Renderable::Private& Graph_Renderable::data_for_test() { return static_cast(*d); } struct Dependency_Renderable : double_buffer::Def { struct Prop : Prev_Prop {}; struct State : Prev_State { int revision{}; bool operator==(const State&) const = default; }; struct Private : Prev_Private { int prepare_calls{}; int paint_calls{}; bool publish_revision{}; void prepare_data(double_buffer::Attached auto* object) { ++prepare_calls; auto& private_data = static_cast::Private&>(*this); if (std::exchange(publish_revision, false)) object->template update_state<&State::revision>(private_data.state.pending->revision + 1); } void paint(double_buffer::Attached auto*) { ++paint_calls; } }; Private& data_for_test(); }; using Dependency = double_buffer::Impl; inline Dependency_Renderable::Private& Dependency_Renderable::data_for_test() { return static_cast(*d); } template std::unique_ptr build_object() { auto result = typename Object_Type::Builder{}.build(); if (!result) std::terminate(); return std::move(result).value(); } template void add_renderable(Scene& scene, Renderable* renderable) { ASSERT_TRUE(scene.edit_dependency_graph([&](auto& prepare) { prepare.add(renderable); }).has_value()); } } TEST(renderable_capability, direct_stages_do_not_allocate_subgraphs) { aethera::initialize_runtime(2); auto renderable = build_object(); auto scene = build_object(); add_renderable(*scene, renderable.get()); int prepare_extension_calls{}; renderable->prepare_taskflow().emplace( [&] { ++prepare_extension_calls; }).name("test.prepare.extension"); scene->process([](const auto&) {}); auto& data = renderable->data_for_test(); auto& base = static_cast(data); EXPECT_EQ(data.prepare_calls, 1); EXPECT_EQ(data.paint_calls, 0); EXPECT_EQ(prepare_extension_calls, 1); EXPECT_EQ(base.prepare_graph, nullptr); EXPECT_EQ(base.paint_graph, nullptr); scene->process([](const auto&) {}); EXPECT_EQ(data.prepare_calls, 1); EXPECT_EQ(data.paint_calls, 0); EXPECT_EQ(prepare_extension_calls, 1); } TEST(renderable_capability, graph_stage_builds_lazily_and_rebuilds_inside_condition) { aethera::initialize_runtime(2); auto renderable = build_object(); auto scene = build_object(); add_renderable(*scene, renderable.get()); auto& data = renderable->data_for_test(); auto& base = static_cast(data); EXPECT_EQ(base.prepare_graph, nullptr); scene->process([](const auto&) {}); ASSERT_NE(base.prepare_graph, nullptr); EXPECT_EQ(data.prepare_builds, 1); EXPECT_EQ(data.prepare_calls, 1); scene->process([](const auto&) {}); EXPECT_EQ(data.prepare_builds, 1); EXPECT_EQ(data.prepare_calls, 1); data.rebuild_prepare = true; renderable->mark_dirty(); scene->process([](const auto&) {}); EXPECT_EQ(data.prepare_builds, 2); EXPECT_EQ(data.prepare_calls, 2); } TEST(renderable_state, scene_and_renderable_callbacks_publish_at_stage_boundaries) { aethera::initialize_runtime(2); auto renderable = build_object(); auto scene = build_object(); add_renderable(*scene, renderable.get()); int renderable_updates = 0; int scene_updates = 0; int runtime_updates = 0; renderable->set_state_callback([&](const auto& state) { ++renderable_updates; EXPECT_TRUE(state.prepare_executed); EXPECT_FALSE(state.paint_executed); }); scene->set_state_callback([&](const auto& state) { ++scene_updates; EXPECT_GT(state.taskflow_task_count, 0u); }); aethera::set_runtime_state_callback([&](const auto& state) { ++runtime_updates; EXPECT_GE(state.completed_taskflow_count, 1u); EXPECT_GT(state.observed_task_count, 0u); }); scene->process([](const auto&) {}); EXPECT_EQ(renderable_updates, 1); EXPECT_EQ(scene_updates, 1); EXPECT_EQ(runtime_updates, 1); aethera::clear_runtime_state_callback(); } TEST(scene_state, structural_statistics_survive_a_process_without_rebuild) { aethera::initialize_runtime(2); auto renderable = build_object(); auto scene = build_object(); add_renderable(*scene, renderable.get()); std::size_t task_count{}; std::size_t dependency_count{}; int updates{}; scene->set_state_callback([&](const auto& state) { EXPECT_EQ(state.renderable_count, 1u); EXPECT_GT(state.taskflow_task_count, 0u); if (updates == 0) { EXPECT_TRUE(state.taskflow_rebuilt); task_count = state.taskflow_task_count; dependency_count = state.taskflow_dependency_count; } else { EXPECT_FALSE(state.taskflow_rebuilt); EXPECT_EQ(state.taskflow_task_count, task_count); EXPECT_EQ(state.taskflow_dependency_count, dependency_count); } ++updates; }); scene->process([](const auto&) {}); scene->process([](const auto&) {}); EXPECT_EQ(updates, 2); } TEST(scene_condition, upstream_change_makes_downstream_run_in_same_taskflow) { aethera::initialize_runtime(2); auto source = build_object(); auto target = build_object(); auto scene = build_object(); ASSERT_TRUE((scene->edit_dependency_graph( [&](auto& prepare) { prepare.add(source.get()); prepare.add(target.get()); prepare.template add_dependency<&Dependency_Renderable::State::revision>(target.get(), source.get()); } ).has_value())); scene->process([](const auto&) {}); auto& source_data = source->data_for_test(); auto& target_data = target->data_for_test(); EXPECT_EQ(source_data.prepare_calls, 1); EXPECT_EQ(target_data.prepare_calls, 1); scene->process([](const auto&) {}); EXPECT_EQ(source_data.prepare_calls, 1); EXPECT_EQ(target_data.prepare_calls, 1); source_data.publish_revision = true; source->mark_dirty(); scene->process([](const auto&) {}); EXPECT_EQ(source_data.prepare_calls, 2); EXPECT_EQ(target_data.prepare_calls, 2); }