核心模板架构
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "renderive/base/Atomic_Mutex.hpp"
|
||||
#include "renderive/base/Concepts.hpp"
|
||||
struct Base_Plain_Type {};
|
||||
static_assert(Class_Type<Base_Plain_Type>);
|
||||
static_assert(Derivable_Type<Base_Plain_Type>);
|
||||
static_assert(Mutex_Type<Atomic_Spin_Mutex>);
|
||||
static_assert(Mutex_Type<Atomic_Wait_Mutex>);
|
||||
TEST(base_concepts_test, concepts_compile) {
|
||||
SUCCEED();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <type_traits>
|
||||
#include "renderive/base/node/Node.hpp"
|
||||
struct Multiway_Node_Test_Owner {};
|
||||
struct Multiway_Node_Test_Tag {};
|
||||
using Multiway_Node_Test_Type = Multiway_Node<Multiway_Node_Test_Owner, Multiway_Node_Test_Tag>;
|
||||
static_assert(Multiway_Node_Type<Multiway_Node_Test_Type, Multiway_Node_Test_Owner, Multiway_Node_Test_Tag>);
|
||||
static_assert(!std::same_as<Multiway_Node<Multiway_Node_Test_Owner>, Multiway_Node_Test_Type>);
|
||||
TEST(multiway_node_test, reparents_child_and_preserves_order) {
|
||||
Multiway_Node_Test_Owner first_owner;
|
||||
Multiway_Node_Test_Owner second_owner;
|
||||
Multiway_Node_Test_Owner third_owner;
|
||||
Multiway_Node_Test_Type root;
|
||||
Multiway_Node_Test_Type first(&first_owner);
|
||||
Multiway_Node_Test_Type second(&second_owner);
|
||||
Multiway_Node_Test_Type third(&third_owner);
|
||||
root.append_child(first);
|
||||
root.append_child(second);
|
||||
first.append_child(third);
|
||||
EXPECT_EQ(root.children.size(), 2);
|
||||
EXPECT_EQ(first.children.front(), &third);
|
||||
EXPECT_EQ(third.parent, &first);
|
||||
second.append_child(third);
|
||||
EXPECT_TRUE(first.children.empty());
|
||||
EXPECT_EQ(third.parent, &second);
|
||||
}
|
||||
TEST(multiway_node_test, rejects_cycles) {
|
||||
Multiway_Node_Test_Type root;
|
||||
Multiway_Node_Test_Type child;
|
||||
root.append_child(child);
|
||||
EXPECT_THROW(child.append_child(root), std::invalid_argument);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "renderive/base/observer/Observer.hpp"
|
||||
struct Observer_Test_Observation {
|
||||
int value{};
|
||||
};
|
||||
struct Observer_Test_Time_Source {
|
||||
std::shared_ptr<std::atomic<std::uint64_t>> time{std::make_shared<std::atomic<std::uint64_t>>(10)};
|
||||
std::uint64_t now_ns() const noexcept {
|
||||
return time->fetch_add(5, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
struct Observer_Test_Recorder {
|
||||
static constexpr bool enabled = true;
|
||||
std::shared_ptr<int> value{std::make_shared<int>()};
|
||||
void observe(const Observer_Test_Observation& observation) noexcept {
|
||||
*value = observation.value;
|
||||
}
|
||||
};
|
||||
using Observer_Test_State = Observer_State<Observer_Test_Recorder, Observer_Test_Time_Source>;
|
||||
static_assert(Observation_Struct<Observer_Test_Observation>);
|
||||
static_assert(Observer_Time_Source<Observer_Test_Time_Source>);
|
||||
static_assert(Struct_Observer<Observer_Test_Recorder, Observer_Test_Observation>);
|
||||
static_assert(Timed_Struct_Observer<Observer_Test_State, Observer_Test_Observation>);
|
||||
TEST(observer_state_test, forwards_observation_structure) {
|
||||
Observer_Test_Recorder recorder;
|
||||
Observer_Test_State observer(recorder, Observer_Test_Time_Source{});
|
||||
observer.observe(Observer_Test_Observation{7});
|
||||
EXPECT_EQ(*recorder.value, 7);
|
||||
}
|
||||
TEST(observer_state_test, owns_time_source) {
|
||||
Observer_Test_State observer;
|
||||
EXPECT_EQ(observer.now_ns(), 10);
|
||||
EXPECT_EQ(observer.now_ns(), 15);
|
||||
}
|
||||
TEST(disabled_observer_test, accepts_any_observation_structure) {
|
||||
Observer_State<> observer;
|
||||
EXPECT_NO_THROW(observer.observe(Observer_Test_Observation{7}));
|
||||
EXPECT_GT(observer.now_ns(), 0);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "renderive/base/observer/Observer.hpp"
|
||||
static_assert(Observer_Time_Source<Steady_Time_Source>);
|
||||
TEST(steady_time_source_test, advances_monotonically) {
|
||||
Steady_Time_Source source;
|
||||
const auto first = source.now_ns();
|
||||
const auto second = source.now_ns();
|
||||
EXPECT_GE(second, first);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "Property_Test_Types.hpp"
|
||||
static_assert(Property_Set<Property_Direct_Properties>);
|
||||
static_assert(Property_Product<Property_Direct_Product>);
|
||||
static_assert(Value_Validator<Property_Checked_Value_Validator, int>);
|
||||
static_assert(Validated_Property_Value<Range_Value<int, 0, 10>>);
|
||||
static_assert(Property_Validator<Property_External_Validator, Property_External_Properties>);
|
||||
static_assert(Property_Constructible<Property_Direct_Product, Property_Direct_Properties, int>);
|
||||
TEST(property_concepts_test, concepts_compile) {
|
||||
SUCCEED();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include "Property_Test_Types.hpp"
|
||||
TEST(property_builder_test, builds_independent_product) {
|
||||
auto product = Property_Direct_Builder{Property_Direct_Properties{.value = 42}}.build(7);
|
||||
EXPECT_EQ(product->value, 42);
|
||||
EXPECT_EQ(product->id, 7);
|
||||
}
|
||||
TEST(property_builder_test, configures_properties) {
|
||||
auto product = Property_Direct_Builder{}.set<&Property_Direct_Properties::value>(10).configure([](Property_Direct_Properties& properties) {
|
||||
properties.value += 5;
|
||||
}).build(1);
|
||||
EXPECT_EQ(product->value, 15);
|
||||
}
|
||||
TEST(property_builder_test, supports_external_validator) {
|
||||
auto builder = Property_External_Product::Builder{}.set<&Property_External_Properties::minimum>(10).set<&Property_External_Properties::maximum>(5);
|
||||
EXPECT_THROW(builder.build(), std::invalid_argument);
|
||||
}
|
||||
TEST(property_builder_test, builds_unique_product) {
|
||||
auto product = Property_Direct_Builder{Property_Direct_Properties{.value = 12}}.build_unique(3);
|
||||
static_assert(std::same_as<decltype(product), std::unique_ptr<Property_Direct_Product>>);
|
||||
EXPECT_EQ(product->value, 12);
|
||||
EXPECT_EQ(product->id, 3);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <stdexcept>
|
||||
#include "renderive/base/property/Property.hpp"
|
||||
struct Property_External_Properties {
|
||||
int minimum{};
|
||||
int maximum{};
|
||||
};
|
||||
struct Property_External_Validator {
|
||||
void operator()(const Property_External_Properties& properties) const {
|
||||
if (properties.minimum > properties.maximum) {
|
||||
throw std::invalid_argument("external minimum must not exceed maximum");
|
||||
}
|
||||
}
|
||||
};
|
||||
struct Property_External_Product_Base {
|
||||
explicit Property_External_Product_Base(const Property_External_Properties& properties) : properties(properties) {}
|
||||
Property_External_Properties properties;
|
||||
};
|
||||
using Property_External_Product = Attach_Builder<Property_External_Product_Base, Property_External_Properties, Property_Builder, Property_External_Validator>;
|
||||
struct Property_Direct_Properties {
|
||||
int value{};
|
||||
};
|
||||
struct Property_Direct_Product {
|
||||
Property_Direct_Product(const Property_Direct_Properties& properties, int id) : value(properties.value), id(id) {}
|
||||
int value;
|
||||
int id;
|
||||
};
|
||||
using Property_Direct_Builder = Property_Builder<Property_Direct_Product, Property_Direct_Properties>;
|
||||
struct Property_Checked_Value_Validator {
|
||||
void operator()(const int& value) const {
|
||||
if (value % 2 != 0) {
|
||||
throw std::invalid_argument("value must be even");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "Property_Test_Types.hpp"
|
||||
TEST(validated_value_test, accepts_range_boundaries) {
|
||||
Range_Value<int, 0, 10> minimum{0};
|
||||
Range_Value<int, 0, 10> maximum{10};
|
||||
EXPECT_EQ(minimum.get(), 0);
|
||||
EXPECT_EQ(maximum.get(), 10);
|
||||
}
|
||||
TEST(validated_value_test, rejects_values_outside_range) {
|
||||
EXPECT_THROW((Range_Value<int, 0, 10>{-1}), std::out_of_range);
|
||||
EXPECT_THROW((Range_Value<int, 0, 10>{11}), std::out_of_range);
|
||||
}
|
||||
TEST(validated_value_test, keeps_old_value_when_validation_fails) {
|
||||
Range_Value<int, 0, 10> value{5};
|
||||
EXPECT_THROW(value = 20, std::out_of_range);
|
||||
EXPECT_EQ(value.get(), 5);
|
||||
}
|
||||
TEST(validated_value_test, supports_custom_validator) {
|
||||
Validated_Value<int, Property_Checked_Value_Validator> value{4};
|
||||
EXPECT_NO_THROW(value = 6);
|
||||
EXPECT_THROW(value = 7, std::invalid_argument);
|
||||
EXPECT_EQ(value.get(), 6);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cmath>
|
||||
#include "renderive/frame_control/Frame_Control.hpp"
|
||||
struct Frame_Control_Base_Test_Frame {};
|
||||
using Frame_Control_Base_Test_Strategy = Low_Latency_Strategy<Frame_Control_Base_Test_Frame>;
|
||||
TEST(frame_control_strategy_base_test, publishes_cached_state_only_on_swap) {
|
||||
Frame_Control_Base_Test_Strategy strategy;
|
||||
strategy.set_frequency_hz(120.0);
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
ASSERT_TRUE(frame);
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
}
|
||||
EXPECT_DOUBLE_EQ(strategy.state().frequency_hz, 120.0);
|
||||
EXPECT_DOUBLE_EQ(strategy.frequency_hz(), 60.0);
|
||||
strategy.swap();
|
||||
EXPECT_DOUBLE_EQ(strategy.frequency_hz(), 120.0);
|
||||
EXPECT_EQ(strategy.next_refresh_interval_ns(), strategy.state().next_refresh_interval_ns);
|
||||
}
|
||||
TEST(frame_control_strategy_base_test, manual_and_flow_publish_invalid_frequency) {
|
||||
Manual_Refresh_Strategy<Frame_Control_Base_Test_Frame> manual;
|
||||
Flow_Refresh_Strategy<Frame_Control_Base_Test_Frame> flow;
|
||||
manual.swap();
|
||||
flow.swap();
|
||||
EXPECT_TRUE(std::isnan(manual.frequency_hz()));
|
||||
EXPECT_TRUE(std::isnan(flow.frequency_hz()));
|
||||
EXPECT_EQ(manual.next_refresh_interval_ns(), 0);
|
||||
EXPECT_EQ(flow.next_refresh_interval_ns(), 0);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "../strategy/low_latency/Low_Latency_Strategy_Test_Types.hpp"
|
||||
struct Frame_Control_Concept_Frame {};
|
||||
using Frame_Control_Concept_Manual = Manual_Refresh_Strategy<Frame_Control_Concept_Frame>;
|
||||
using Frame_Control_Concept_Flow = Flow_Refresh_Strategy<Frame_Control_Concept_Frame>;
|
||||
static_assert(Timed_Struct_Observer<Low_Latency_Test_Observer_State, Low_Latency_Test_Strategy::Observation>);
|
||||
static_assert(Painter_Frame_Lease<Low_Latency_Test_Strategy::Painter_Lease, Low_Latency_Test_Strategy::Frame>);
|
||||
static_assert(Render_Frame_Lease<Low_Latency_Test_Strategy::Render_Lease, Low_Latency_Test_Strategy::Frame>);
|
||||
static_assert(Runtime_Frame_Control_Strategy<Low_Latency_Test_Strategy>);
|
||||
static_assert(Runtime_Frame_Control_Strategy<Frame_Control_Concept_Manual>);
|
||||
static_assert(Runtime_Frame_Control_Strategy<Frame_Control_Concept_Flow>);
|
||||
static_assert(Frame_Lease_Strategy<Low_Latency_Test_Strategy>);
|
||||
static_assert(Frame_Control_Strategy_For<Low_Latency_Test_Strategy, Low_Latency_Test_Frame>);
|
||||
static_assert(Frame_Refresh_Strategy<Low_Latency_Test_Strategy>);
|
||||
static_assert(Real_Time_Data_Aware_Frame_Strategy<Low_Latency_Test_Strategy>);
|
||||
static_assert(Manual_Frame_Refresh_Strategy<Frame_Control_Concept_Manual>);
|
||||
static_assert(Flow_Frame_Refresh_Strategy<Frame_Control_Concept_Flow>);
|
||||
TEST(frame_control_concepts_test, concepts_compile) {
|
||||
SUCCEED();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "renderive/frame_control/Frame_Control.hpp"
|
||||
struct Flow_Refresh_Test_Frame {
|
||||
int value{};
|
||||
};
|
||||
using Flow_Refresh_Test_Strategy = Flow_Refresh_Strategy<Flow_Refresh_Test_Frame>;
|
||||
static_assert(Flow_Frame_Refresh_Strategy<Flow_Refresh_Test_Strategy>);
|
||||
TEST(flow_refresh_strategy_test, preserves_all_frames_in_fifo_order) {
|
||||
Flow_Refresh_Test_Strategy strategy;
|
||||
for (int value = 1; value <= 3; ++value) {
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = value;
|
||||
}
|
||||
EXPECT_EQ(strategy.pending_frame_count(), 3);
|
||||
for (int value = 1; value <= 3; ++value) {
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, value);
|
||||
}
|
||||
EXPECT_EQ(strategy.pending_frame_count(), 0);
|
||||
EXPECT_EQ(strategy.state().rendered_frame_count, 3);
|
||||
}
|
||||
TEST(flow_refresh_strategy_test, returns_empty_lease_when_queue_is_empty) {
|
||||
Flow_Refresh_Test_Strategy strategy;
|
||||
auto frame = strategy.acquire_renderer();
|
||||
EXPECT_FALSE(frame);
|
||||
EXPECT_EQ(strategy.state().empty_acquire_count, 1);
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "Low_Latency_Strategy_Test_Types.hpp"
|
||||
TEST(low_latency_strategy_test, publishes_and_renders_latest_frame) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 7;
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 7);
|
||||
}
|
||||
EXPECT_EQ(strategy.state().completed_lifecycle_count, 1);
|
||||
}
|
||||
TEST(low_latency_strategy_test, abandons_unconsumed_cached_frame) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 2;
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 2);
|
||||
}
|
||||
EXPECT_EQ(strategy.counter_statistics().abandoned_frame_count, 1);
|
||||
}
|
||||
TEST(low_latency_strategy_test, manually_discards_stale_latest_data_frame) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
strategy.on_real_time_data_update({Real_Time_Data_Observation_Event::updated, {nullptr, Real_Time_Data_Retention::latest, 1, 10, 1, 1}});
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
}
|
||||
strategy.on_real_time_data_update({Real_Time_Data_Observation_Event::updated, {nullptr, Real_Time_Data_Retention::latest, 2, 20, 2, 1}});
|
||||
EXPECT_TRUE(strategy.discard_stale_latest_data_frame());
|
||||
EXPECT_EQ(strategy.counter_statistics().manually_discarded_frame_count, 1);
|
||||
auto frame = strategy.acquire_renderer();
|
||||
EXPECT_FALSE(frame);
|
||||
}
|
||||
TEST(low_latency_strategy_test, does_not_discard_current_data_frame) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
strategy.on_real_time_data_update({Real_Time_Data_Observation_Event::updated, {nullptr, Real_Time_Data_Retention::latest, 1, 10, 1, 1}});
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 9;
|
||||
}
|
||||
EXPECT_FALSE(strategy.discard_stale_latest_data_frame());
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 9);
|
||||
}
|
||||
TEST(low_latency_strategy_test, updates_frequency_after_completed_lifecycle) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
strategy.set_frequency_hz(100.0);
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 3;
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
}
|
||||
EXPECT_DOUBLE_EQ(strategy.state().frequency_hz, 100.0);
|
||||
EXPECT_DOUBLE_EQ(strategy.frequency_hz(), 60.0);
|
||||
strategy.swap();
|
||||
EXPECT_DOUBLE_EQ(strategy.frequency_hz(), 100.0);
|
||||
EXPECT_EQ(strategy.next_refresh_interval_ns(), 10'000'000);
|
||||
}
|
||||
TEST(low_latency_strategy_test, detects_render_limited_state) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
strategy.set_frequency_hz(1'000'000.0);
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 5;
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
time_source.advance_ns(5'000);
|
||||
}
|
||||
EXPECT_EQ(strategy.state().limit_state, Low_Latency_Test_Strategy::Limit_State::render_limited);
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "renderive/frame_control/Frame_Control.hpp"
|
||||
#include "renderive/real_time_data/Real_Time_Data.hpp"
|
||||
struct Low_Latency_Test_Frame {
|
||||
int value{};
|
||||
};
|
||||
struct Low_Latency_Test_Time_Source {
|
||||
std::shared_ptr<std::atomic<std::uint64_t>> time_ns{std::make_shared<std::atomic<std::uint64_t>>(100)};
|
||||
std::uint64_t now_ns() const noexcept {
|
||||
return time_ns->fetch_add(10, std::memory_order_relaxed);
|
||||
}
|
||||
void advance_ns(std::uint64_t duration_ns) const noexcept {
|
||||
time_ns->fetch_add(duration_ns, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
struct Low_Latency_Recorded_Observation {
|
||||
int event{};
|
||||
std::uint64_t sequence{};
|
||||
std::uint64_t frame_render_count{};
|
||||
std::uint64_t real_time_data_update_sequence{};
|
||||
std::uint64_t paint_duration_ns{};
|
||||
std::uint64_t render_duration_ns{};
|
||||
std::uint64_t published_frame_count{};
|
||||
std::uint64_t render_pass_count{};
|
||||
std::uint64_t abandoned_frame_count{};
|
||||
std::uint64_t manually_discarded_frame_count{};
|
||||
std::uint64_t swap_failure_count{};
|
||||
double frequency_hz{};
|
||||
int limit_state{};
|
||||
std::uint64_t next_refresh_interval_ns{};
|
||||
std::uint64_t completed_lifecycle_count{};
|
||||
};
|
||||
struct Low_Latency_Observer_Data {
|
||||
std::mutex mutex;
|
||||
std::vector<Low_Latency_Recorded_Observation> observations;
|
||||
};
|
||||
struct Low_Latency_Test_Observer {
|
||||
static constexpr bool enabled = true;
|
||||
std::shared_ptr<Low_Latency_Observer_Data> data{std::make_shared<Low_Latency_Observer_Data>()};
|
||||
template <class Observation>
|
||||
void observe(const Observation& observation) noexcept {
|
||||
const auto& statistics = observation.statistics;
|
||||
const auto& counters = statistics.counters;
|
||||
std::lock_guard lock(data->mutex);
|
||||
data->observations.push_back({
|
||||
static_cast<int>(observation.event),
|
||||
statistics.sequence,
|
||||
statistics.frame_render_count,
|
||||
statistics.real_time_data_update_sequence,
|
||||
statistics.timing.paint_duration_ns,
|
||||
statistics.timing.render_duration_ns,
|
||||
counters.published_frame_count,
|
||||
counters.render_pass_count,
|
||||
counters.abandoned_frame_count,
|
||||
counters.manually_discarded_frame_count,
|
||||
counters.swap_failure_count,
|
||||
observation.state.frequency_hz,
|
||||
static_cast<int>(observation.state.limit_state),
|
||||
observation.state.next_refresh_interval_ns,
|
||||
observation.state.completed_lifecycle_count
|
||||
});
|
||||
}
|
||||
};
|
||||
using Low_Latency_Test_Observer_State = Observer_State<Low_Latency_Test_Observer, Low_Latency_Test_Time_Source>;
|
||||
using Low_Latency_Test_Strategy = Low_Latency_Strategy<Low_Latency_Test_Frame, std::mutex, Low_Latency_Test_Observer_State>;
|
||||
inline Low_Latency_Test_Strategy make_low_latency_test_strategy(const Low_Latency_Test_Time_Source& time_source, const Low_Latency_Test_Observer& observer) {
|
||||
return Low_Latency_Test_Strategy(Low_Latency_Test_Observer_State(observer, time_source));
|
||||
}
|
||||
inline std::vector<Low_Latency_Recorded_Observation> low_latency_observations(const std::shared_ptr<Low_Latency_Observer_Data>& data) {
|
||||
std::lock_guard lock(data->mutex);
|
||||
return data->observations;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "renderive/frame_control/Frame_Control.hpp"
|
||||
struct Manual_Refresh_Test_Frame {
|
||||
int value{};
|
||||
};
|
||||
using Manual_Refresh_Test_Strategy = Manual_Refresh_Strategy<Manual_Refresh_Test_Frame>;
|
||||
static_assert(Manual_Frame_Refresh_Strategy<Manual_Refresh_Test_Strategy>);
|
||||
TEST(manual_refresh_strategy_test, publishes_only_after_manual_refresh) {
|
||||
Manual_Refresh_Test_Strategy strategy;
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
ASSERT_TRUE(frame);
|
||||
frame->value = 7;
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
EXPECT_FALSE(frame);
|
||||
}
|
||||
EXPECT_TRUE(strategy.refresh());
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 7);
|
||||
}
|
||||
}
|
||||
TEST(manual_refresh_strategy_test, keeps_latest_prepared_frame) {
|
||||
Manual_Refresh_Test_Strategy strategy;
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 2;
|
||||
}
|
||||
EXPECT_EQ(strategy.state().replaced_prepared_frame_count, 1);
|
||||
ASSERT_TRUE(strategy.refresh());
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 2);
|
||||
}
|
||||
TEST(manual_refresh_strategy_test, reports_failed_refresh_without_pending_frame) {
|
||||
Manual_Refresh_Test_Strategy strategy;
|
||||
EXPECT_FALSE(strategy.refresh());
|
||||
EXPECT_EQ(strategy.state().failed_refresh_count, 1);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "renderive/real_time_data/Real_Time_Data.hpp"
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Real_Time_Data_Test_Time_Source {
|
||||
std::shared_ptr<std::atomic<std::uint64_t>> time_ns{std::make_shared<std::atomic<std::uint64_t>>()};
|
||||
std::uint64_t now_ns() const noexcept {
|
||||
return time_ns->fetch_add(10, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
struct Real_Time_Data_Test_Renderable : Renderable_Base {
|
||||
explicit Real_Time_Data_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {}
|
||||
void render(const Scene_Render_Context&) override {
|
||||
++render_count;
|
||||
}
|
||||
std::atomic<int> render_count{};
|
||||
};
|
||||
using Real_Time_Data_Test_Bridge = Observer_State<Frame_Strategy_Real_Time_Data_Observer, Real_Time_Data_Test_Time_Source>;
|
||||
using Real_Time_Data_Test_Latest = Latest_Real_Time_Data<int, std::mutex, Real_Time_Data_Test_Bridge>;
|
||||
using Real_Time_Data_Test_History = History_Real_Time_Data<int, std::vector<int>, std::mutex, Real_Time_Data_Test_Bridge>;
|
||||
using Real_Time_Data_Test_Attachment = Attach_Real_Time_Data<Real_Time_Data_Test_Renderable, Real_Time_Data_Test_Latest, Real_Time_Data_Test_History>;
|
||||
TEST(latest_real_time_data_test, retains_only_latest_value) {
|
||||
Latest_Real_Time_Data<int> data;
|
||||
data.update(1);
|
||||
data.update(2);
|
||||
ASSERT_TRUE(data.snapshot());
|
||||
EXPECT_EQ(*data.snapshot(), 2);
|
||||
EXPECT_EQ(data.update_state().retained_value_count, 1);
|
||||
EXPECT_EQ(data.revision(), 2);
|
||||
}
|
||||
TEST(history_real_time_data_test, retains_all_values_until_explicit_discard) {
|
||||
using Observer = Observer_State<Disabled_Observer, Real_Time_Data_Test_Time_Source>;
|
||||
History_Real_Time_Data<int, std::vector<int>, std::mutex, Observer> data;
|
||||
data.update(1);
|
||||
data.update(2);
|
||||
data.update(3);
|
||||
EXPECT_EQ(data.snapshot(), (std::vector<int>{1, 2, 3}));
|
||||
EXPECT_EQ(data.discard_before_time_ns(15), 2);
|
||||
EXPECT_EQ(data.snapshot(), (std::vector<int>{3}));
|
||||
}
|
||||
TEST(real_time_data_attachment_test, binds_updates_to_renderable_frame_strategy) {
|
||||
Scene2D_Context<> scene;
|
||||
Real_Time_Data_Test_Latest latest;
|
||||
Real_Time_Data_Test_History history;
|
||||
auto renderable = std::make_shared<Real_Time_Data_Test_Attachment>(With_Real_Time_Data(latest, history), scene);
|
||||
renderable->discard_stale_frame_on_latest_data_update = true;
|
||||
{
|
||||
auto frame = scene.frame_control.acquire_painter();
|
||||
ASSERT_TRUE(frame);
|
||||
}
|
||||
latest.update(1);
|
||||
EXPECT_EQ(scene.frame_control.counter_statistics().manually_discarded_frame_count, 1);
|
||||
auto frame = scene.frame_control.acquire_renderer();
|
||||
EXPECT_FALSE(frame);
|
||||
}
|
||||
TEST(real_time_data_attachment_test, discards_history_older_than_one_frame_interval) {
|
||||
using Strategy = Low_Latency_Strategy<Scene2D_Frame_Data>;
|
||||
Scene2D_Context<Strategy> scene(Observer_State<>{}, Strategy::Configuration{100'000'000.0});
|
||||
Real_Time_Data_Test_Latest latest;
|
||||
Real_Time_Data_Test_History history;
|
||||
auto renderable = std::make_shared<Real_Time_Data_Test_Attachment>(With_Real_Time_Data(latest, history), scene);
|
||||
renderable->real_time_data_discard_mode = Real_Time_Data_Discard_Mode::retain_frame_interval;
|
||||
history.update(1);
|
||||
history.update(2);
|
||||
history.update(3);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(history.snapshot(), (std::vector<int>{2, 3}));
|
||||
EXPECT_EQ(renderable->render_count.load(), 1);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "renderive/real_time_data/Real_Time_Data.hpp"
|
||||
static_assert(std::derived_from<Latest_Real_Time_Data<int>, Real_Time_Data_Base>);
|
||||
static_assert(std::derived_from<History_Real_Time_Data<int>, Real_Time_Data_Base>);
|
||||
TEST(real_time_data_base_test, exposes_runtime_retention) {
|
||||
Latest_Real_Time_Data<int> latest;
|
||||
History_Real_Time_Data<int> history;
|
||||
Real_Time_Data_Base* latest_base = &latest;
|
||||
Real_Time_Data_Base* history_base = &history;
|
||||
EXPECT_EQ(latest_base->retention(), Real_Time_Data_Retention::latest);
|
||||
EXPECT_EQ(history_base->retention(), Real_Time_Data_Retention::history);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Renderable_Task_Graph_Test_Renderable : Renderable_Base {
|
||||
Renderable_Task_Graph_Test_Renderable(Scene_Base& scene, std::vector<int>& order, int base)
|
||||
: Renderable_Base(scene, {.cache_enabled = false}), order(&order), base(base) {}
|
||||
void build_task_graph(Renderable_Task_Graph& graph) override {
|
||||
auto first = graph.emplace([this](const Scene_Render_Context&) {
|
||||
order->push_back(base + 1);
|
||||
}, "first");
|
||||
auto second = graph.emplace([this](const Scene_Render_Context&) {
|
||||
order->push_back(base + 2);
|
||||
}, "second");
|
||||
graph.precede(first, second);
|
||||
}
|
||||
std::vector<int>* order;
|
||||
int base;
|
||||
};
|
||||
TEST(renderable_task_graph_test, combines_internal_and_external_dependencies) {
|
||||
Scene2D_Context<> scene;
|
||||
std::vector<int> order;
|
||||
auto dependency = std::make_shared<Renderable_Task_Graph_Test_Renderable>(scene, order, 0);
|
||||
auto dependent = std::make_shared<Renderable_Task_Graph_Test_Renderable>(scene, order, 10);
|
||||
scene.attach_renderable(dependency);
|
||||
scene.attach_renderable(dependent);
|
||||
dependency->dependency_node.append_child(dependent->dependency_node);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(order, (std::vector<int>{1, 2, 11, 12}));
|
||||
}
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
struct Renderable_Task_Graph_Concurrency_Test_Renderable : Renderable_Base {
|
||||
Renderable_Task_Graph_Concurrency_Test_Renderable(Scene_Base& scene, std::atomic<int>& active, std::atomic<int>& maximum)
|
||||
: Renderable_Base(scene, {.cache_enabled = false}), active(&active), maximum(&maximum) {}
|
||||
void build_task_graph(Renderable_Task_Graph& graph) override {
|
||||
auto work = [this](const Scene_Render_Context&) {
|
||||
const int value = active->fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||
int observed = maximum->load(std::memory_order_acquire);
|
||||
while (observed < value && !maximum->compare_exchange_weak(observed, value, std::memory_order_acq_rel)) {}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
active->fetch_sub(1, std::memory_order_acq_rel);
|
||||
};
|
||||
graph.emplace(work, "parallel_a");
|
||||
graph.emplace(work, "parallel_b");
|
||||
}
|
||||
std::atomic<int>* active;
|
||||
std::atomic<int>* maximum;
|
||||
};
|
||||
TEST(renderable_task_graph_concurrency_test, DISABLED_runs_independent_internal_tasks_concurrently) {
|
||||
Scene2D_Context<> scene;
|
||||
std::atomic<int> active{};
|
||||
std::atomic<int> maximum{};
|
||||
auto renderable = std::make_shared<Renderable_Task_Graph_Concurrency_Test_Renderable>(scene, active, maximum);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_GE(maximum.load(std::memory_order_acquire), 2);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Renderable_Base_Test_Renderable : Renderable_Base {
|
||||
Renderable_Base_Test_Renderable(Scene_Base& scene, bool cache_enabled) : Renderable_Base(scene, {.cache_enabled = cache_enabled}) {}
|
||||
void render(const Scene_Render_Context&) override {
|
||||
++render_count;
|
||||
}
|
||||
int render_count{};
|
||||
};
|
||||
TEST(renderable_base_test, exposes_scene_and_strategy_members_directly) {
|
||||
Scene2D_Context<> low_latency_scene;
|
||||
Scene2D_Context<Manual_Refresh_Strategy<Scene2D_Frame_Data>> manual_scene;
|
||||
Renderable_Base_Test_Renderable low_latency_renderable(low_latency_scene, true);
|
||||
Renderable_Base_Test_Renderable manual_renderable(manual_scene, true);
|
||||
EXPECT_EQ(low_latency_renderable.scene, &low_latency_scene);
|
||||
EXPECT_EQ(low_latency_renderable.scene->frame_control_strategy, &low_latency_scene.frame_control);
|
||||
EXPECT_DOUBLE_EQ(low_latency_renderable.scene->frame_control_strategy->frequency_hz(), 60.0);
|
||||
EXPECT_TRUE(std::isnan(manual_renderable.scene->frame_control_strategy->frequency_hz()));
|
||||
}
|
||||
TEST(renderable_base_test, controls_cache_with_configuration) {
|
||||
Scene2D_Context<> scene;
|
||||
auto cached = std::make_shared<Renderable_Base_Test_Renderable>(scene, true);
|
||||
auto uncached = std::make_shared<Renderable_Base_Test_Renderable>(scene, false);
|
||||
scene.attach_renderable(cached);
|
||||
scene.attach_renderable(uncached);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(cached->render_count, 1);
|
||||
EXPECT_EQ(uncached->render_count, 2);
|
||||
cached->invalidate_cache();
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(cached->render_count, 2);
|
||||
EXPECT_EQ(uncached->render_count, 3);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
static_assert(Color_Cache_Type<Recording_Color_Cache>);
|
||||
TEST(color_cache_test, composites_source_values_in_order) {
|
||||
Recording_Color_Cache first;
|
||||
Recording_Color_Cache second;
|
||||
Recording_Color_Cache result;
|
||||
first.append(1);
|
||||
second.append(2);
|
||||
result.composite(first);
|
||||
result.composite(second);
|
||||
EXPECT_EQ(result.values, (std::vector<std::uint64_t>{1, 2}));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <type_traits>
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Renderable_Concept_Test : Renderable_Base {
|
||||
explicit Renderable_Concept_Test(Scene_Base& scene) : Renderable_Base(scene) {}
|
||||
void render(const Scene_Render_Context&) override {}
|
||||
};
|
||||
static_assert(Renderable<Renderable_Concept_Test>);
|
||||
static_assert(!std::same_as<Renderable_Base::Layer_Node, Renderable_Base::Dependency_Node>);
|
||||
TEST(renderable_concept_test, exposes_tree_nodes_scene_and_configuration) {
|
||||
Scene2D_Context<> scene;
|
||||
Renderable_Concept_Test renderable(scene);
|
||||
EXPECT_EQ(renderable.scene, &scene);
|
||||
EXPECT_EQ(renderable.layer_node.owner, &renderable);
|
||||
EXPECT_EQ(renderable.dependency_node.owner, &renderable);
|
||||
EXPECT_FALSE(renderable.configuration.cache_enabled);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Scene2D_Context_Test_Renderable : Renderable_Base {
|
||||
explicit Scene2D_Context_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
|
||||
void render(const Scene_Render_Context& context) override {
|
||||
++render_count;
|
||||
scene_is_2d = dynamic_cast<Scene_2D_Base*>(context.scene) != nullptr;
|
||||
render_thread = std::this_thread::get_id();
|
||||
}
|
||||
std::atomic<int> render_count{};
|
||||
bool scene_is_2d{};
|
||||
std::thread::id render_thread;
|
||||
};
|
||||
TEST(scene2d_context_test, swaps_renderable_cache_and_renders_in_background) {
|
||||
Scene2D_Context<> scene;
|
||||
const auto caller_thread = std::this_thread::get_id();
|
||||
auto first = std::make_shared<Scene2D_Context_Test_Renderable>(scene);
|
||||
auto second = std::make_shared<Scene2D_Context_Test_Renderable>(scene);
|
||||
scene.attach_renderable(first);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(first->render_count.load(), 1);
|
||||
EXPECT_TRUE(first->scene_is_2d);
|
||||
EXPECT_NE(first->render_thread, caller_thread);
|
||||
scene.detach_renderable(*first);
|
||||
scene.attach_renderable(second);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(first->render_count.load(), 1);
|
||||
EXPECT_EQ(second->render_count.load(), 1);
|
||||
EXPECT_EQ(scene.frame_control_strategy->frame_control_state().publish_revision, 2);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Scene2D_Render_Order_State {
|
||||
std::vector<std::uint64_t> render_order;
|
||||
};
|
||||
struct Scene2D_Render_Order_Renderable : Renderable_Base {
|
||||
Scene2D_Render_Order_Renderable(Scene_Base& scene, Scene2D_Render_Order_State& state, std::uint64_t id)
|
||||
: Renderable_Base(scene, {.cache_enabled = false}), state(&state), id(id) {}
|
||||
void render(const Scene_Render_Context& context) override {
|
||||
state->render_order.push_back(id);
|
||||
dynamic_cast<Recording_Color_Cache&>(*context.color_cache).append(id);
|
||||
}
|
||||
Scene2D_Render_Order_State* state;
|
||||
std::uint64_t id;
|
||||
};
|
||||
TEST(scene2d_render_order_test, separates_dependency_order_from_display_order) {
|
||||
Scene2D_Context<> scene;
|
||||
Scene2D_Render_Order_State state;
|
||||
auto spectrum = std::make_shared<Scene2D_Render_Order_Renderable>(scene, state, 2);
|
||||
auto axis = std::make_shared<Scene2D_Render_Order_Renderable>(scene, state, 1);
|
||||
scene.attach_renderable(spectrum);
|
||||
scene.attach_renderable(axis);
|
||||
axis->dependency_node.append_child(spectrum->dependency_node);
|
||||
spectrum->layer_node.append_child(axis->layer_node);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(state.render_order, (std::vector<std::uint64_t>{1, 2}));
|
||||
EXPECT_EQ(scene.final_color_cache.values, (std::vector<std::uint64_t>{2, 1}));
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Scene3D_Context_Test_Renderable : Renderable_Base {
|
||||
explicit Scene3D_Context_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {}
|
||||
void render(const Scene_Render_Context& context) override {
|
||||
scene_is_3d = dynamic_cast<Scene_3D_Base*>(context.scene) != nullptr;
|
||||
++render_count;
|
||||
}
|
||||
std::atomic<int> render_count{};
|
||||
bool scene_is_3d{};
|
||||
};
|
||||
TEST(scene3d_context_test, uses_scene_3d_base_contract) {
|
||||
Scene3D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene3D_Context_Test_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(renderable->render_count.load(), 1);
|
||||
EXPECT_TRUE(renderable->scene_is_3d);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Scene_State_Observer_Test_State {
|
||||
int value{};
|
||||
};
|
||||
struct Scene_State_Observer_Test_Time_Source {
|
||||
std::uint64_t now_ns() const noexcept {
|
||||
return 100;
|
||||
}
|
||||
};
|
||||
struct Scene_State_Observer_Test_Data {
|
||||
std::vector<Scene_Base::Observation_Event> events;
|
||||
};
|
||||
struct Scene_State_Observer_Test_Recorder {
|
||||
static constexpr bool enabled = true;
|
||||
std::shared_ptr<Scene_State_Observer_Test_Data> data{std::make_shared<Scene_State_Observer_Test_Data>()};
|
||||
void observe(const Scene_Base::Observation& observation) noexcept {
|
||||
data->events.push_back(observation.event);
|
||||
}
|
||||
};
|
||||
using Scene_State_Observer_Test_Scene_Observer = Observer_State<Scene_State_Observer_Test_Recorder, Scene_State_Observer_Test_Time_Source>;
|
||||
using Scene_State_Observer_Test_State_Observer = Observer_State<>;
|
||||
TEST(scene_state_observer_test, scene_uses_triple_state_and_observes_lifecycle) {
|
||||
Scene_State_Observer_Test_Recorder recorder;
|
||||
using Scene = Scene2D_Context<Low_Latency_Strategy<Scene2D_Frame_Data>, Recording_Color_Cache, Scene_State_Observer_Test_State, Scene_State_Observer_Test_State_Observer, Scene_State_Observer_Test_Scene_Observer>;
|
||||
Scene scene(With_Observer(Scene_State_Observer_Test_State_Observer{}), With_Observer(Scene_State_Observer_Test_Scene_Observer(recorder, Scene_State_Observer_Test_Time_Source{})));
|
||||
scene.Scene_State_Strategy::set<&Scene_State_Observer_Test_State::value>(9);
|
||||
scene.Scene_State_Strategy::publish();
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(scene.Scene_State_Strategy::render_use_state().value, 9);
|
||||
EXPECT_EQ(recorder.data->events, (std::vector<Scene_Base::Observation_Event>{Scene_Base::Observation_Event::render_submitted, Scene_Base::Observation_Event::render_started, Scene_Base::Observation_Event::render_completed}));
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Scene_Base_Test_Renderable : Renderable_Base {
|
||||
explicit Scene_Base_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {}
|
||||
void render(const Scene_Render_Context& context) override {
|
||||
render_sequence = context.render_sequence;
|
||||
++render_count;
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
static_assert(Scene<Scene2D_Context<>>);
|
||||
static_assert(Scene<Scene3D_Context<>>);
|
||||
static_assert(Scene_2D<Scene2D_Context<>>);
|
||||
static_assert(Scene_3D<Scene3D_Context<>>);
|
||||
static_assert(!Scene_3D<Scene2D_Context<>>);
|
||||
TEST(scene_concept_test, accepts_2d_and_3d_contexts) {
|
||||
SUCCEED();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "State_Test_Types.hpp"
|
||||
static_assert(State_Value<State_Plain_Value>);
|
||||
static_assert(std::derived_from<State_Plain_Strategy, State_Strategy_Base>);
|
||||
static_assert(Double_State_Strategy_Type<State_Plain_Strategy>);
|
||||
static_assert(!Has_State_Validator<State_Plain_Strategy, State_Plain_Value>);
|
||||
static_assert(Has_State_Validator<State_Checked_Strategy, State_Checked_Value>);
|
||||
TEST(state_concepts_test, concepts_compile) {
|
||||
SUCCEED();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "State_Test_Types.hpp"
|
||||
TEST(double_state_strategy_test, builds_without_state_validator) {
|
||||
auto product = State_Plain_Product::Builder{}.set<&State_Plain_Value::count>(10).set<&State_Plain_Value::name>("alpha").build();
|
||||
EXPECT_EQ(product->get<&State_Plain_Value::count>(), 10);
|
||||
EXPECT_EQ(product->get<&State_Plain_Value::name>(), "alpha");
|
||||
}
|
||||
TEST(double_state_strategy_test, calls_optional_state_validator_when_present) {
|
||||
auto builder = State_Checked_Product::Builder{}.set<&State_Checked_Value::minimum>(10).set<&State_Checked_Value::maximum>(5);
|
||||
EXPECT_THROW(builder.build(1, "invalid"), std::invalid_argument);
|
||||
}
|
||||
TEST(double_state_strategy_test, keeps_cache_separate_until_publish) {
|
||||
auto product = State_Checked_Product::Builder{}.set<&State_Checked_Value::percent>(20).set<&State_Checked_Value::batch_size>(4).build(1, "state");
|
||||
product->set<&State_Checked_Value::percent>(60);
|
||||
EXPECT_EQ(product->get<&State_Checked_Value::percent>(), 60);
|
||||
EXPECT_EQ(product->render_use_state().percent.get(), 20);
|
||||
product->publish();
|
||||
EXPECT_EQ(product->render_use_state().percent.get(), 60);
|
||||
}
|
||||
TEST(double_state_strategy_test, keeps_cached_value_when_runtime_validation_fails) {
|
||||
auto product = State_Checked_Product::Builder{}.set<&State_Checked_Value::percent>(20).set<&State_Checked_Value::batch_size>(4).build(1, "state");
|
||||
EXPECT_THROW((product->set<&State_Checked_Value::percent>(200)), std::out_of_range);
|
||||
EXPECT_EQ(product->get<&State_Checked_Value::percent>(), 20);
|
||||
}
|
||||
TEST(double_state_strategy_test, supports_atomic_wait_mutex) {
|
||||
auto product = State_Wait_Product::Builder{State_Plain_Value{.count = 1, .name = "wait"}}.build();
|
||||
product->set<&State_Plain_Value::count>(2);
|
||||
product->publish();
|
||||
EXPECT_EQ(product->render_use_state().count, 2);
|
||||
}
|
||||
TEST(double_state_strategy_test, passes_additional_builder_template_arguments) {
|
||||
static_assert(std::same_as<typename State_Tagged_Product::Builder::Tag, State_Builder_Tag>);
|
||||
static_assert(std::same_as<typename State_Tagged_Product::Builder::Mode, State_Builder_Mode>);
|
||||
auto product = State_Tagged_Product::Builder{State_Plain_Value{.count = 3, .name = "tagged"}}.build();
|
||||
EXPECT_EQ(product->get<&State_Plain_Value::count>(), 3);
|
||||
}
|
||||
struct Double_State_Observer_Test_Time_Source {
|
||||
std::uint64_t value{100};
|
||||
std::uint64_t now_ns() const noexcept {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
struct Double_State_Observer_Test_Data {
|
||||
std::uint64_t event_count{};
|
||||
std::uint64_t publish_count{};
|
||||
int value{};
|
||||
};
|
||||
struct Double_State_Observer_Test_Recorder {
|
||||
static constexpr bool enabled = true;
|
||||
std::shared_ptr<Double_State_Observer_Test_Data> data{std::make_shared<Double_State_Observer_Test_Data>()};
|
||||
template <class Observation>
|
||||
void observe(const Observation& observation) noexcept {
|
||||
++data->event_count;
|
||||
data->publish_count = observation.publish_count;
|
||||
data->value = observation.state.count;
|
||||
}
|
||||
};
|
||||
TEST(double_state_strategy_test, notifies_observer_for_cache_update_and_publish) {
|
||||
using Observer = Observer_State<Double_State_Observer_Test_Recorder, Double_State_Observer_Test_Time_Source>;
|
||||
using Strategy = Double_State_Strategy<State_Plain_Base, State_Plain_Value, Atomic_Spin_Mutex, Observer>;
|
||||
Double_State_Observer_Test_Recorder recorder;
|
||||
Strategy strategy(State_Plain_Value{}, With_Observer(Observer(recorder, Double_State_Observer_Test_Time_Source{})));
|
||||
strategy.set<&State_Plain_Value::count>(8);
|
||||
strategy.publish();
|
||||
EXPECT_EQ(recorder.data->event_count, 2);
|
||||
EXPECT_EQ(recorder.data->publish_count, 1);
|
||||
EXPECT_EQ(recorder.data->value, 8);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include "renderive/base/property/Property.hpp"
|
||||
#include "renderive/state/State_Strategy.hpp"
|
||||
struct State_Plain_Value {
|
||||
int count{};
|
||||
std::string name;
|
||||
};
|
||||
struct State_Plain_Base {};
|
||||
using State_Plain_Strategy = Double_State_Strategy<State_Plain_Base, State_Plain_Value>;
|
||||
using State_Plain_Product = Attach_State_Builder<State_Plain_Strategy, State_Plain_Value>;
|
||||
struct State_Checked_Value {
|
||||
struct Even_Validator {
|
||||
void operator()(const int& value) const {
|
||||
if (value % 2 != 0) {
|
||||
throw std::invalid_argument("value must be even");
|
||||
}
|
||||
}
|
||||
};
|
||||
Range_Value<int, 0, 100> percent{0};
|
||||
Validated_Value<int, Even_Validator> batch_size{0};
|
||||
int minimum{};
|
||||
int maximum{};
|
||||
};
|
||||
struct State_Checked_Base {
|
||||
State_Checked_Base(int id, std::string name) : id(id), name(std::move(name)) {}
|
||||
static void validate_state(const State_Checked_Value& state) {
|
||||
if (state.minimum > state.maximum) {
|
||||
throw std::invalid_argument("minimum must not exceed maximum");
|
||||
}
|
||||
}
|
||||
int id;
|
||||
std::string name;
|
||||
};
|
||||
using State_Checked_Strategy = Double_State_Strategy<State_Checked_Base, State_Checked_Value>;
|
||||
using State_Checked_Product = Attach_State_Builder<State_Checked_Strategy, State_Checked_Value>;
|
||||
using State_Wait_Product = Attach_State_Builder<Double_State_Strategy<State_Plain_Base, State_Plain_Value, Atomic_Wait_Mutex>, State_Plain_Value>;
|
||||
struct State_Builder_Tag {};
|
||||
struct State_Builder_Mode {};
|
||||
template <Property_Product Product, Property_Set Properties, Property_Validator<Properties> Validator, class Tag_Type, class Mode_Type>
|
||||
class State_Tagged_Builder : public Property_Builder<Product, Properties, Validator> {
|
||||
public:
|
||||
using Base = Property_Builder<Product, Properties, Validator>;
|
||||
using Tag = Tag_Type;
|
||||
using Mode = Mode_Type;
|
||||
using Base::Base;
|
||||
};
|
||||
using State_Tagged_Product = Attach_State_Builder<State_Plain_Strategy, State_Plain_Value, State_Tagged_Builder, State_Builder_Tag, State_Builder_Mode>;
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "renderive/state/State_Strategy.hpp"
|
||||
struct Triple_State_Test_Base {};
|
||||
struct Triple_State_Test_State {
|
||||
int value{};
|
||||
};
|
||||
TEST(triple_state_strategy_test, publishes_then_acquires_render_state) {
|
||||
Triple_State_Strategy<Triple_State_Test_Base, Triple_State_Test_State> strategy;
|
||||
strategy.set<&Triple_State_Test_State::value>(7);
|
||||
strategy.publish();
|
||||
EXPECT_EQ(strategy.render_use_state().value, 0);
|
||||
EXPECT_EQ(strategy.acquire_render_state(), 1);
|
||||
EXPECT_EQ(strategy.render_use_state().value, 7);
|
||||
}
|
||||
struct Multi_State_A_Base {};
|
||||
struct Multi_State_B_Base {};
|
||||
struct Multi_State_A {
|
||||
int value{};
|
||||
};
|
||||
struct Multi_State_B {
|
||||
int value{};
|
||||
};
|
||||
struct Multi_State_A_Strategy : Double_State_Strategy<Multi_State_A_Base, Multi_State_A> {};
|
||||
struct Multi_State_B_Strategy : Double_State_Strategy<Multi_State_B_Base, Multi_State_B> {};
|
||||
struct Multi_State_Product : Multi_State_A_Strategy, Multi_State_B_Strategy {};
|
||||
TEST(double_state_strategy_test, supports_named_base_access_in_multiple_inheritance) {
|
||||
Multi_State_Product product;
|
||||
product.Multi_State_A_Strategy::set<&Multi_State_A::value>(3);
|
||||
product.Multi_State_B_Strategy::set<&Multi_State_B::value>(8);
|
||||
product.Multi_State_A_Strategy::publish();
|
||||
product.Multi_State_B_Strategy::publish();
|
||||
EXPECT_EQ(product.Multi_State_A_Strategy::render_use_state().value, 3);
|
||||
EXPECT_EQ(product.Multi_State_B_Strategy::render_use_state().value, 8);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "../State_Test_Types.hpp"
|
||||
TEST(state_strategy_base_test, publishes_through_virtual_interface) {
|
||||
State_Plain_Strategy strategy;
|
||||
State_Strategy_Base* base = &strategy;
|
||||
strategy.set<&State_Plain_Value::count>(7);
|
||||
base->publish();
|
||||
EXPECT_EQ(base->state_revision(), 1);
|
||||
EXPECT_EQ(strategy.render_use_state().count, 7);
|
||||
}
|
||||
Reference in New Issue
Block a user