214 lines
8.4 KiB
C++
214 lines
8.4 KiB
C++
#include "scene.hpp"
|
|
#include <gtest/gtest.h>
|
|
namespace {
|
|
struct Direct_Renderable : double_buffer::Def<Direct_Renderable, aethera::Renderable> {
|
|
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<Graph_Renderable, aethera::Renderable> {
|
|
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<Direct_Renderable>;
|
|
using Graph = double_buffer::Impl<Graph_Renderable>;
|
|
using Scene = double_buffer::Impl<aethera::Scene>;
|
|
inline Direct_Renderable::Private& Direct_Renderable::data_for_test() {
|
|
return static_cast<Private&>(*d);
|
|
}
|
|
inline Graph_Renderable::Private& Graph_Renderable::data_for_test() {
|
|
return static_cast<Private&>(*d);
|
|
}
|
|
struct Dependency_Renderable : double_buffer::Def<Dependency_Renderable, aethera::Renderable> {
|
|
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<typename std::remove_pointer_t<decltype(object)>::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<Dependency_Renderable>;
|
|
inline Dependency_Renderable::Private& Dependency_Renderable::data_for_test() {
|
|
return static_cast<Private&>(*d);
|
|
}
|
|
template <typename Object_Type>
|
|
std::unique_ptr<Object_Type> build_object() {
|
|
auto result = typename Object_Type::Builder{}.build();
|
|
if (!result) std::terminate();
|
|
return std::move(result).value();
|
|
}
|
|
template <typename Renderable>
|
|
void add_renderable(Scene& scene, Renderable* renderable) {
|
|
ASSERT_TRUE(scene.edit_dependency_graph<aethera::Prepare_Data_Tag>([&](auto& prepare) { prepare.add(renderable); }).has_value());
|
|
}
|
|
}
|
|
TEST(renderable_capability, direct_stages_do_not_allocate_subgraphs) {
|
|
aethera::initialize_runtime(2);
|
|
auto renderable = build_object<Direct>();
|
|
auto scene = build_object<Scene>();
|
|
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<aethera::Renderable::Private&>(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<Graph>();
|
|
auto scene = build_object<Scene>();
|
|
add_renderable(*scene, renderable.get());
|
|
auto& data = renderable->data_for_test();
|
|
auto& base = static_cast<aethera::Renderable::Private&>(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<aethera::Prepare_Data_Tag>();
|
|
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<Direct>();
|
|
auto scene = build_object<Scene>();
|
|
add_renderable(*scene, renderable.get());
|
|
int renderable_updates = 0;
|
|
int scene_updates = 0;
|
|
int runtime_updates = 0;
|
|
renderable->set_state_callback<aethera::Renderable::Base_Tag>([&](const auto& state) {
|
|
++renderable_updates;
|
|
EXPECT_TRUE(state.prepare_executed);
|
|
EXPECT_FALSE(state.paint_executed);
|
|
});
|
|
scene->set_state_callback<aethera::Scene::Base_Tag>([&](const auto& state) {
|
|
++scene_updates;
|
|
EXPECT_GT(state.taskflow_task_count, 0u);
|
|
});
|
|
aethera::set_runtime_state_callback<aethera::Task_Runtime_State_Tag>([&](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<aethera::Task_Runtime_State_Tag>();
|
|
}
|
|
TEST(scene_state, structural_statistics_survive_a_process_without_rebuild) {
|
|
aethera::initialize_runtime(2);
|
|
auto renderable = build_object<Direct>();
|
|
auto scene = build_object<Scene>();
|
|
add_renderable(*scene, renderable.get());
|
|
std::size_t task_count{};
|
|
std::size_t dependency_count{};
|
|
int updates{};
|
|
scene->set_state_callback<aethera::Scene::Base_Tag>([&](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<Dependency>();
|
|
auto target = build_object<Dependency>();
|
|
auto scene = build_object<Scene>();
|
|
ASSERT_TRUE((scene->edit_dependency_graph<aethera::Prepare_Data_Tag>(
|
|
[&](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<aethera::Prepare_Data_Tag>();
|
|
scene->process([](const auto&) {});
|
|
EXPECT_EQ(source_data.prepare_calls, 2);
|
|
EXPECT_EQ(target_data.prepare_calls, 2);
|
|
}
|