补充测试
This commit is contained in:
@@ -1,32 +1,22 @@
|
||||
#include "renderive/frame_control/Frame_Control.hpp"
|
||||
#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;
|
||||
EXPECT_EQ(strategy.set_frequency_hz(120.0), Frame_Control_Base_Test_Strategy::Control_Error::none);
|
||||
{
|
||||
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();
|
||||
|
||||
struct Frame_Control_Base_Test_Scene { struct Frame {}; };
|
||||
|
||||
TEST(frame_control_strategy_base_test, low_latency_exposes_runtime_refresh) {
|
||||
Low_Latency_Strategy<Frame_Control_Base_Test_Scene> strategy;
|
||||
EXPECT_EQ(strategy.set_frequency_hz(120.0),
|
||||
decltype(strategy)::Control_Error::none);
|
||||
EXPECT_DOUBLE_EQ(strategy.frequency_hz(), 120.0);
|
||||
EXPECT_EQ(strategy.next_refresh_interval_ns(), strategy.state().next_refresh_interval_ns);
|
||||
EXPECT_EQ(strategy.next_refresh_interval_ns(), 8'333'333U);
|
||||
}
|
||||
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();
|
||||
|
||||
TEST(frame_control_strategy_base_test, manual_and_flow_have_no_frequency) {
|
||||
Manual_Refresh_Strategy<Frame_Control_Base_Test_Scene> manual;
|
||||
Flow_Refresh_Strategy<Frame_Control_Base_Test_Scene> flow;
|
||||
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);
|
||||
EXPECT_EQ(manual.next_refresh_interval_ns(), 0U);
|
||||
EXPECT_EQ(flow.next_refresh_interval_ns(), 0U);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
#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>);
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
struct Frame_Control_Concept_Scene { struct Frame {}; };
|
||||
using Frame_Control_Concept_Manual = Manual_Refresh_Strategy<Frame_Control_Concept_Scene>;
|
||||
using Frame_Control_Concept_Flow = Flow_Refresh_Strategy<Frame_Control_Concept_Scene>;
|
||||
|
||||
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(Frame_Control_Strategy_For<Low_Latency_Test_Strategy,
|
||||
Low_Latency_Test_Scene>);
|
||||
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) {
|
||||
|
||||
TEST(frame_control_concepts_test, current_scene_parameter_contract_compiles) {
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
+10
-236
@@ -1,252 +1,26 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <memory_resource>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#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>;
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
struct Flow_Refresh_Test_Scene { struct Frame { int value{}; }; };
|
||||
using Flow_Refresh_Test_Strategy = Flow_Refresh_Strategy<Flow_Refresh_Test_Scene>;
|
||||
static_assert(Flow_Frame_Refresh_Strategy<Flow_Refresh_Test_Strategy>);
|
||||
TEST(flow_refresh_strategy_test, preserves_all_frames_in_fifo_order) {
|
||||
|
||||
TEST(flow_refresh_strategy_test, preserves_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);
|
||||
EXPECT_EQ(strategy.pending_frame_count(), 3U);
|
||||
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);
|
||||
}
|
||||
TEST(flow_refresh_strategy_test, accepts_multiple_concurrent_producers_without_losing_frames) {
|
||||
Flow_Refresh_Test_Strategy strategy(Observer_State<>{}, 512);
|
||||
constexpr int producer_count = 4;
|
||||
constexpr int frames_per_producer = 100;
|
||||
std::vector<std::thread> producers;
|
||||
for (int producer = 0; producer < producer_count; ++producer) {
|
||||
producers.emplace_back([&strategy, producer] {
|
||||
for (int index = 0; index < frames_per_producer; ++index) {
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = producer * 1000 + index;
|
||||
}
|
||||
});
|
||||
}
|
||||
for (auto& producer : producers) {
|
||||
producer.join();
|
||||
}
|
||||
EXPECT_EQ(strategy.pending_frame_count(), producer_count * frames_per_producer);
|
||||
std::vector<int> values;
|
||||
while (auto frame = strategy.acquire_renderer()) {
|
||||
values.push_back(frame->value);
|
||||
}
|
||||
std::sort(values.begin(), values.end());
|
||||
EXPECT_EQ(values.size(), producer_count * frames_per_producer);
|
||||
for (int producer = 0; producer < producer_count; ++producer) {
|
||||
for (int index = 0; index < frames_per_producer; ++index) {
|
||||
EXPECT_TRUE(std::binary_search(values.begin(), values.end(), producer * 1000 + index));
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(strategy.pending_frame_count(), 0U);
|
||||
}
|
||||
|
||||
TEST(flow_refresh_strategy_test, bounded_queue_applies_backpressure_until_consumer_releases_capacity) {
|
||||
Flow_Refresh_Test_Strategy strategy(Observer_State<>{}, 1);
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
}
|
||||
std::atomic<bool> second_enqueued{};
|
||||
std::thread producer([&] {
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 2;
|
||||
}
|
||||
second_enqueued.store(true, std::memory_order_release);
|
||||
second_enqueued.notify_all();
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
EXPECT_FALSE(second_enqueued.load(std::memory_order_acquire));
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 1);
|
||||
}
|
||||
second_enqueued.wait(false, std::memory_order_acquire);
|
||||
producer.join();
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 2);
|
||||
}
|
||||
TEST(flow_refresh_strategy_test, concurrent_consumer_does_not_underflow_pending_count) {
|
||||
TEST(flow_refresh_strategy_test, empty_queue_returns_empty_lease) {
|
||||
Flow_Refresh_Test_Strategy strategy;
|
||||
constexpr int producer_count = 4;
|
||||
constexpr int frames_per_producer = 200;
|
||||
constexpr int frame_count = producer_count * frames_per_producer;
|
||||
std::atomic<int> producers_done{};
|
||||
std::atomic<int> consumed{};
|
||||
std::vector<int> values;
|
||||
values.reserve(frame_count);
|
||||
std::thread consumer([&] {
|
||||
while (consumed.load(std::memory_order_acquire) != frame_count) {
|
||||
auto frame = strategy.acquire_renderer();
|
||||
if (!frame) {
|
||||
if (producers_done.load(std::memory_order_acquire) == producer_count && strategy.pending_frame_count() == 0) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
continue;
|
||||
}
|
||||
values.push_back(frame->value);
|
||||
consumed.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
});
|
||||
std::vector<std::thread> producers;
|
||||
for (int producer = 0; producer < producer_count; ++producer) {
|
||||
producers.emplace_back([&strategy, &producers_done, producer] {
|
||||
for (int index = 0; index < frames_per_producer; ++index) {
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = producer * 1000 + index;
|
||||
}
|
||||
producers_done.fetch_add(1, std::memory_order_release);
|
||||
});
|
||||
}
|
||||
for (auto& producer : producers) {
|
||||
producer.join();
|
||||
}
|
||||
consumer.join();
|
||||
EXPECT_EQ(consumed.load(std::memory_order_acquire), frame_count);
|
||||
EXPECT_EQ(strategy.pending_frame_count(), 0);
|
||||
std::sort(values.begin(), values.end());
|
||||
EXPECT_EQ(values.size(), frame_count);
|
||||
for (int producer = 0; producer < producer_count; ++producer) {
|
||||
for (int index = 0; index < frames_per_producer; ++index) {
|
||||
EXPECT_TRUE(std::binary_search(values.begin(), values.end(), producer * 1000 + index));
|
||||
}
|
||||
}
|
||||
}
|
||||
struct Flow_Refresh_Reentrant_Observer_Data {
|
||||
std::function<void(int)> callback;
|
||||
};
|
||||
struct Flow_Refresh_Reentrant_Observer {
|
||||
static constexpr bool enabled = true;
|
||||
std::shared_ptr<Flow_Refresh_Reentrant_Observer_Data> data{std::make_shared<Flow_Refresh_Reentrant_Observer_Data>()};
|
||||
template <class Observation>
|
||||
void observe(const Observation& observation) noexcept {
|
||||
if (data->callback) {
|
||||
data->callback(static_cast<int>(observation.event));
|
||||
}
|
||||
}
|
||||
};
|
||||
using Flow_Refresh_Reentrant_Observer_State = Observer_State<Flow_Refresh_Reentrant_Observer>;
|
||||
using Flow_Refresh_Reentrant_Strategy = Flow_Refresh_Strategy<Flow_Refresh_Test_Frame, std::mutex, Flow_Refresh_Reentrant_Observer_State>;
|
||||
TEST(flow_refresh_strategy_test, observer_can_reenter_observer_state_without_deadlock) {
|
||||
Flow_Refresh_Reentrant_Observer recorder;
|
||||
auto data = recorder.data;
|
||||
Flow_Refresh_Reentrant_Strategy strategy{Flow_Refresh_Reentrant_Observer_State(recorder)};
|
||||
std::atomic<bool> reentered{};
|
||||
data->callback = [&](int event) {
|
||||
if (event != static_cast<int>(Flow_Refresh_Reentrant_Strategy::Observation_Event::enqueued) || reentered.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
strategy.on_real_time_data_update({Real_Time_Data_Observation_Event::updated, {nullptr, Real_Time_Data_Retention::latest, 1, 1, 1, 1}});
|
||||
};
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
}
|
||||
EXPECT_TRUE(reentered.load());
|
||||
EXPECT_EQ(strategy.state().real_time_data_update_sequence, 1);
|
||||
}
|
||||
struct Flow_Refresh_Render_Reentrant_Observer_Data {
|
||||
std::function<void()> callback;
|
||||
};
|
||||
struct Flow_Refresh_Render_Reentrant_Observer {
|
||||
static constexpr bool enabled = true;
|
||||
std::shared_ptr<Flow_Refresh_Render_Reentrant_Observer_Data> data{std::make_shared<Flow_Refresh_Render_Reentrant_Observer_Data>()};
|
||||
template <class Observation>
|
||||
void observe(const Observation& observation) noexcept {
|
||||
if (static_cast<int>(observation.event) == 1 && data->callback) {
|
||||
data->callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
using Flow_Refresh_Render_Reentrant_Observer_State = Observer_State<Flow_Refresh_Render_Reentrant_Observer>;
|
||||
using Flow_Refresh_Render_Reentrant_Strategy = Flow_Refresh_Strategy<Flow_Refresh_Test_Frame, std::mutex, Flow_Refresh_Render_Reentrant_Observer_State>;
|
||||
TEST(flow_refresh_strategy_test, render_observer_reentry_does_not_deadlock_render_mutex) {
|
||||
Flow_Refresh_Render_Reentrant_Observer recorder;
|
||||
auto data = recorder.data;
|
||||
Flow_Refresh_Render_Reentrant_Strategy strategy{Flow_Refresh_Render_Reentrant_Observer_State(recorder)};
|
||||
for (int value = 1; value <= 2; ++value) {
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = value;
|
||||
}
|
||||
std::atomic<bool> nested_result{true};
|
||||
data->callback = [&] {
|
||||
auto nested = strategy.acquire_renderer();
|
||||
nested_result.store(static_cast<bool>(nested), std::memory_order_release);
|
||||
};
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 1);
|
||||
}
|
||||
EXPECT_FALSE(nested_result.load(std::memory_order_acquire));
|
||||
auto second = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(second);
|
||||
EXPECT_EQ(second->value, 2);
|
||||
}
|
||||
TEST(flow_refresh_strategy_test, concurrent_producers_and_consumer_support_unsynchronized_pmr_resource) {
|
||||
std::pmr::unsynchronized_pool_resource resource;
|
||||
Flow_Refresh_Test_Strategy strategy(resource);
|
||||
constexpr int producer_count = 8;
|
||||
constexpr int frames_per_producer = 200;
|
||||
constexpr int frame_count = producer_count * frames_per_producer;
|
||||
std::atomic<int> producers_done{};
|
||||
std::atomic<int> consumed{};
|
||||
std::thread consumer([&] {
|
||||
while (consumed.load(std::memory_order_acquire) < frame_count) {
|
||||
auto frame = strategy.acquire_renderer();
|
||||
if (frame) {
|
||||
consumed.fetch_add(1, std::memory_order_release);
|
||||
continue;
|
||||
}
|
||||
if (producers_done.load(std::memory_order_acquire) == producer_count && strategy.pending_frame_count() == 0) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
std::vector<std::thread> producers;
|
||||
producers.reserve(producer_count);
|
||||
for (int producer = 0; producer < producer_count; ++producer) {
|
||||
producers.emplace_back([&strategy, &producers_done, producer] {
|
||||
for (int index = 0; index < frames_per_producer; ++index) {
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = producer * frames_per_producer + index;
|
||||
}
|
||||
producers_done.fetch_add(1, std::memory_order_release);
|
||||
});
|
||||
}
|
||||
for (auto& producer : producers) {
|
||||
producer.join();
|
||||
}
|
||||
consumer.join();
|
||||
EXPECT_EQ(consumed.load(std::memory_order_acquire), frame_count);
|
||||
EXPECT_EQ(strategy.pending_frame_count(), 0);
|
||||
EXPECT_FALSE(strategy.acquire_renderer());
|
||||
}
|
||||
|
||||
+12
-237
@@ -1,248 +1,23 @@
|
||||
#include "Low_Latency_Strategy_Test_Types.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "Low_Latency_Strategy_Test_Types.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
void complete_pipeline(Low_Latency_Test_Strategy& strategy,
|
||||
const Low_Latency_Test_Time_Source& time_source,
|
||||
int value,
|
||||
std::uint64_t paint_duration_ns,
|
||||
std::uint64_t ready_wait_ns,
|
||||
std::uint64_t render_duration_ns) {
|
||||
{
|
||||
TEST(low_latency_pipeline_test, repeated_frames_preserve_latest_wins_contract) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
for (int value = 0; value != 32; ++value) {
|
||||
auto frame = strategy.acquire_painter();
|
||||
ASSERT_TRUE(frame);
|
||||
frame->value = value;
|
||||
time_source.advance_ns(paint_duration_ns);
|
||||
}
|
||||
time_source.advance_ns(ready_wait_ns);
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, value);
|
||||
time_source.advance_ns(render_duration_ns);
|
||||
}
|
||||
EXPECT_EQ(strategy.pending_frame_count(), 1U);
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 31);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(low_latency_pipeline_test, classifies_frequency_paint_and_render_limits_across_lifecycles) {
|
||||
TEST(low_latency_pipeline_test, renderer_without_published_frame_is_empty) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
|
||||
EXPECT_EQ(strategy.set_frequency_hz(100.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
complete_pipeline(strategy, time_source, 1, 2'000'000, 500'000, 3'000'000);
|
||||
auto state = strategy.state();
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
|
||||
EXPECT_EQ(state.target_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(state.bottleneck_duration_ns, 3'000'000u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 10'000'000u);
|
||||
|
||||
EXPECT_EQ(strategy.set_frequency_hz(1'000.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
complete_pipeline(strategy, time_source, 2, 4'000'000, 250'000, 2'000'000);
|
||||
state = strategy.state();
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::paint_limited);
|
||||
EXPECT_EQ(state.paint_duration_ns, 4'000'000u);
|
||||
EXPECT_EQ(state.render_duration_ns, 2'000'000u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 4'000'000u);
|
||||
|
||||
complete_pipeline(strategy, time_source, 3, 2'000'000, 125'000, 5'000'000);
|
||||
state = strategy.state();
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::render_limited);
|
||||
EXPECT_EQ(state.paint_duration_ns, 2'000'000u);
|
||||
EXPECT_EQ(state.render_duration_ns, 5'000'000u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 5'000'000u);
|
||||
EXPECT_EQ(state.completed_lifecycle_count, 3u);
|
||||
EXPECT_EQ(state.frame_sequence, 3u);
|
||||
}
|
||||
|
||||
TEST(low_latency_pipeline_test, consumer_feedback_limits_refresh_without_knowing_the_consumer_type) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(100.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
EXPECT_EQ(strategy.set_consumer_feedback({40'000'000}), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
complete_pipeline(strategy, time_source, 1, 2'000'000, 0, 3'000'000);
|
||||
auto state = strategy.state();
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::consumer_limited);
|
||||
EXPECT_EQ(state.target_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 40'000'000u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 40'000'000u);
|
||||
strategy.clear_consumer_feedback();
|
||||
state = strategy.state();
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 0u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 10'000'000u);
|
||||
}
|
||||
|
||||
TEST(low_latency_pipeline_test, applies_frequency_and_equal_bottleneck_boundaries_deterministically) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(1'000.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
|
||||
complete_pipeline(strategy, time_source, 1, 1'000'000, 0, 999'999);
|
||||
auto state = strategy.state();
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
|
||||
EXPECT_EQ(state.bottleneck_duration_ns, state.target_interval_ns);
|
||||
|
||||
complete_pipeline(strategy, time_source, 2, 2'000'000, 0, 2'000'000);
|
||||
state = strategy.state();
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::paint_limited);
|
||||
EXPECT_EQ(state.paint_duration_ns, state.render_duration_ns);
|
||||
EXPECT_EQ(state.bottleneck_duration_ns, 2'000'000u);
|
||||
}
|
||||
|
||||
TEST(low_latency_pipeline_test, accepts_one_billion_hz_and_uses_the_measured_bottleneck) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(1'000'000'000.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
|
||||
complete_pipeline(strategy, time_source, 7, 20, 10, 30);
|
||||
const auto state = strategy.state();
|
||||
EXPECT_DOUBLE_EQ(state.frequency_hz, 1'000'000'000.0);
|
||||
EXPECT_EQ(state.target_interval_ns, 1u);
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::render_limited);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 30u);
|
||||
}
|
||||
|
||||
TEST(low_latency_pipeline_test, reports_exact_pipeline_timing_and_state_to_observers) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto data = observer.data;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(500.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
|
||||
complete_pipeline(strategy, time_source, 42, 3'000'000, 700'000, 4'000'000);
|
||||
const auto observations = low_latency_observations(data);
|
||||
ASSERT_EQ(observations.size(), 3u);
|
||||
EXPECT_EQ(observations[0].event,
|
||||
static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::published));
|
||||
EXPECT_EQ(observations[1].event,
|
||||
static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::rendered));
|
||||
EXPECT_EQ(observations[2].event,
|
||||
static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::lifecycle_completed));
|
||||
|
||||
const auto& completed = observations.back();
|
||||
EXPECT_EQ(completed.sequence, 1u);
|
||||
EXPECT_EQ(completed.frame_render_count, 1u);
|
||||
EXPECT_EQ(completed.paint_duration_ns, 3'000'000u);
|
||||
EXPECT_EQ(completed.ready_wait_ns, 700'000u);
|
||||
EXPECT_EQ(completed.frame_age_at_render_ns, 700'000u);
|
||||
EXPECT_EQ(completed.render_duration_ns, 4'000'000u);
|
||||
EXPECT_EQ(completed.end_to_end_ns, 7'700'000u);
|
||||
EXPECT_EQ(completed.published_frame_count, 1u);
|
||||
EXPECT_EQ(completed.render_pass_count, 1u);
|
||||
EXPECT_EQ(completed.target_interval_ns, 2'000'000u);
|
||||
EXPECT_EQ(completed.bottleneck_duration_ns, 4'000'000u);
|
||||
EXPECT_EQ(completed.next_refresh_interval_ns, 4'000'000u);
|
||||
EXPECT_EQ(completed.limit_state,
|
||||
static_cast<int>(Low_Latency_Test_Strategy::Limit_State::render_limited));
|
||||
EXPECT_EQ(completed.completed_lifecycle_count, 1u);
|
||||
}
|
||||
|
||||
TEST(low_latency_pipeline_test, replaces_only_the_unconsumed_frame_and_renders_the_latest) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto data = observer.data;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
time_source.advance_ns(100);
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 2;
|
||||
time_source.advance_ns(200);
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 2);
|
||||
time_source.advance_ns(300);
|
||||
}
|
||||
|
||||
const auto counters = strategy.counter_statistics();
|
||||
EXPECT_EQ(counters.published_frame_count, 2u);
|
||||
EXPECT_EQ(counters.abandoned_frame_count, 1u);
|
||||
EXPECT_EQ(counters.render_pass_count, 1u);
|
||||
EXPECT_EQ(strategy.state().completed_lifecycle_count, 1u);
|
||||
EXPECT_EQ(strategy.state().frame_sequence, 2u);
|
||||
|
||||
const auto observations = low_latency_observations(data);
|
||||
ASSERT_EQ(observations.size(), 5u);
|
||||
EXPECT_EQ(observations[1].event,
|
||||
static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::abandoned));
|
||||
EXPECT_EQ(observations.back().abandoned_frame_count, 1u);
|
||||
}
|
||||
|
||||
TEST(low_latency_pipeline_test, rerender_without_new_frame_reports_failure_without_new_lifecycle) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto data = observer.data;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
complete_pipeline(strategy, time_source, 9, 100, 0, 200);
|
||||
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 9);
|
||||
time_source.advance_ns(400);
|
||||
}
|
||||
|
||||
const auto state = strategy.state();
|
||||
const auto counters = strategy.counter_statistics();
|
||||
EXPECT_EQ(state.completed_lifecycle_count, 1u);
|
||||
EXPECT_EQ(counters.render_pass_count, 2u);
|
||||
EXPECT_EQ(counters.swap_failure_count, 1u);
|
||||
|
||||
const auto observations = low_latency_observations(data);
|
||||
ASSERT_EQ(observations.size(), 5u);
|
||||
EXPECT_EQ(observations[3].event,
|
||||
static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::swap_failed));
|
||||
EXPECT_EQ(observations[4].event,
|
||||
static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::rendered));
|
||||
EXPECT_EQ(observations[4].render_pass_count, 2u);
|
||||
EXPECT_EQ(observations[4].completed_lifecycle_count, 1u);
|
||||
}
|
||||
|
||||
TEST(low_latency_pipeline_test, completed_frame_keeps_the_real_time_revision_captured_at_paint) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto data = observer.data;
|
||||
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, 10, 100, 1, 1}});
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
time_source.advance_ns(100);
|
||||
}
|
||||
strategy.on_real_time_data_update(
|
||||
{Real_Time_Data_Observation_Event::updated,
|
||||
{nullptr, Real_Time_Data_Retention::latest, 11, 110, 2, 1}});
|
||||
auto observations = low_latency_observations(data);
|
||||
ASSERT_EQ(observations.size(), 3u);
|
||||
EXPECT_EQ(observations.back().event,
|
||||
static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::real_time_data_updated));
|
||||
EXPECT_EQ(observations.back().published_frame_count, 1u);
|
||||
EXPECT_EQ(observations.back().real_time_data_update_sequence, 2u);
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
time_source.advance_ns(200);
|
||||
}
|
||||
|
||||
const auto state = strategy.state();
|
||||
EXPECT_EQ(state.real_time_data_update_sequence, 2u);
|
||||
EXPECT_EQ(state.completed_frame_real_time_data_update_sequence, 1u);
|
||||
EXPECT_FALSE(strategy.acquire_renderer());
|
||||
}
|
||||
|
||||
+27
-224
@@ -1,26 +1,10 @@
|
||||
#include "Low_Latency_Strategy_Test_Types.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#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) {
|
||||
|
||||
TEST(low_latency_strategy_test, publishes_latest_frame_and_observes_lifecycle) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
@@ -37,9 +21,13 @@ TEST(low_latency_strategy_test, abandons_unconsumed_cached_frame) {
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 2);
|
||||
}
|
||||
EXPECT_EQ(strategy.counter_statistics().abandoned_frame_count, 1);
|
||||
const auto observations = low_latency_observations(observer.data);
|
||||
ASSERT_FALSE(observations.empty());
|
||||
EXPECT_EQ(observations.back().completed_lifecycle_count, 1U);
|
||||
EXPECT_EQ(observations.back().abandoned_frame_count, 1U);
|
||||
}
|
||||
TEST(low_latency_strategy_test, replacement_disabled_preserves_pending_frame_and_wakes_after_renderer_release) {
|
||||
|
||||
TEST(low_latency_strategy_test, replacement_backpressure_releases_after_render) {
|
||||
using namespace std::chrono_literals;
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
@@ -49,18 +37,11 @@ TEST(low_latency_strategy_test, replacement_disabled_preserves_pending_frame_and
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
}
|
||||
EXPECT_EQ(strategy.pending_frame_count(), 1U);
|
||||
|
||||
std::promise<void> producer_started;
|
||||
auto producer_started_future = producer_started.get_future();
|
||||
auto producer = std::async(std::launch::async, [&] {
|
||||
producer_started.set_value();
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 2;
|
||||
});
|
||||
producer_started_future.wait();
|
||||
EXPECT_EQ(producer.wait_for(50ms), std::future_status::timeout);
|
||||
|
||||
EXPECT_EQ(producer.wait_for(30ms), std::future_status::timeout);
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
@@ -68,210 +49,32 @@ TEST(low_latency_strategy_test, replacement_disabled_preserves_pending_frame_and
|
||||
}
|
||||
ASSERT_EQ(producer.wait_for(1s), std::future_status::ready);
|
||||
producer.get();
|
||||
EXPECT_EQ(strategy.pending_frame_count(), 1U);
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 2);
|
||||
}
|
||||
EXPECT_EQ(strategy.counter_statistics().abandoned_frame_count, 0u);
|
||||
}
|
||||
TEST(low_latency_strategy_test, replacement_disabled_wakes_blocked_producer_after_explicit_discard) {
|
||||
using namespace std::chrono_literals;
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
Low_Latency_Test_Strategy strategy{
|
||||
Low_Latency_Test_Observer_State(observer, time_source), {60.0, false}};
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
}
|
||||
|
||||
std::promise<void> producer_started;
|
||||
auto producer_started_future = producer_started.get_future();
|
||||
auto producer = std::async(std::launch::async, [&] {
|
||||
producer_started.set_value();
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 2;
|
||||
});
|
||||
producer_started_future.wait();
|
||||
EXPECT_EQ(producer.wait_for(50ms), std::future_status::timeout);
|
||||
EXPECT_TRUE(strategy.discard_pending_frame());
|
||||
ASSERT_EQ(producer.wait_for(1s), std::future_status::ready);
|
||||
producer.get();
|
||||
EXPECT_EQ(strategy.counter_statistics().abandoned_frame_count, 0u);
|
||||
EXPECT_EQ(strategy.counter_statistics().manually_discarded_frame_count, 1u);
|
||||
}
|
||||
TEST(low_latency_strategy_test, manually_discards_stale_latest_data_frame) {
|
||||
TEST(low_latency_strategy_test, refresh_configuration_is_public_runtime_state) {
|
||||
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);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(100.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
EXPECT_DOUBLE_EQ(strategy.state().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_EQ(strategy.set_frequency_hz(100.0),
|
||||
Low_Latency_Test_Strategy::Control_Error::none);
|
||||
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);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(1'000'000.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
{
|
||||
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);
|
||||
}
|
||||
TEST(low_latency_strategy_test, rejects_non_finite_frequency_configuration) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
EXPECT_THROW((Low_Latency_Test_Strategy(Low_Latency_Test_Observer_State(observer, time_source), {std::numeric_limits<double>::quiet_NaN()})), std::invalid_argument);
|
||||
EXPECT_THROW((Low_Latency_Test_Strategy(Low_Latency_Test_Observer_State(observer, time_source), {std::numeric_limits<double>::infinity()})), std::invalid_argument);
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(std::numeric_limits<double>::quiet_NaN()),
|
||||
Low_Latency_Test_Strategy::Control_Error::invalid_frequency);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(std::numeric_limits<double>::infinity()),
|
||||
EXPECT_EQ(strategy.next_refresh_interval_ns(), 10'000'000U);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(
|
||||
std::numeric_limits<double>::quiet_NaN()),
|
||||
Low_Latency_Test_Strategy::Control_Error::invalid_frequency);
|
||||
}
|
||||
TEST(low_latency_strategy_test, clamps_extremely_small_frequency_interval_without_overflow) {
|
||||
|
||||
TEST(low_latency_strategy_test, stale_latest_data_frame_can_be_discarded) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(1e-300), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
}
|
||||
EXPECT_EQ(strategy.state().target_interval_ns, std::numeric_limits<std::uint64_t>::max());
|
||||
}
|
||||
TEST(low_latency_strategy_test, separates_consumer_rate_from_jitter_safety_margin) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(1000.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
EXPECT_EQ(strategy.set_consumer_feedback({10'000'000}), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
auto state = strategy.state();
|
||||
EXPECT_TRUE(state.consumer_feedback_enabled);
|
||||
EXPECT_EQ(state.consumer_sample_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(state.consumer_smoothed_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(state.consumer_variation_ns, 0u);
|
||||
EXPECT_EQ(state.consumer_safety_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(strategy.set_consumer_feedback({14'000'000}), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
state = strategy.state();
|
||||
EXPECT_EQ(state.consumer_sample_interval_ns, 14'000'000u);
|
||||
EXPECT_EQ(state.consumer_smoothed_interval_ns, 12'000'000u);
|
||||
EXPECT_EQ(state.consumer_variation_ns, 250'000u);
|
||||
EXPECT_EQ(state.consumer_safety_interval_ns, 14'000'000u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 14'000'000u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 14'000'000u);
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::consumer_limited);
|
||||
}
|
||||
TEST(low_latency_strategy_test, consumer_jitter_changes_safety_deadline_without_becoming_rate_limit) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(1000.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
EXPECT_EQ(strategy.set_consumer_feedback({20'000'000}), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
EXPECT_EQ(strategy.set_consumer_feedback({22'000'000}), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
EXPECT_EQ(strategy.set_consumer_feedback({20'000'000}), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
const auto state = strategy.state();
|
||||
EXPECT_EQ(state.consumer_sample_interval_ns, 20'000'000u);
|
||||
EXPECT_EQ(state.consumer_smoothed_interval_ns, 20'875'000u);
|
||||
EXPECT_EQ(state.consumer_variation_ns, 242'187u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 20'875'000u);
|
||||
EXPECT_EQ(state.consumer_safety_interval_ns, 21'843'748u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 20'875'000u);
|
||||
}
|
||||
TEST(low_latency_strategy_test, allows_frequency_and_consumer_limits_to_be_cleared) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
EXPECT_EQ(strategy.set_frequency_hz(1000.0), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
EXPECT_EQ(strategy.set_consumer_feedback({20'000'000}), Low_Latency_Test_Strategy::Control_Error::none);
|
||||
strategy.clear_consumer_feedback();
|
||||
auto state = strategy.state();
|
||||
EXPECT_FALSE(state.consumer_feedback_enabled);
|
||||
EXPECT_EQ(state.consumer_safety_interval_ns, 0u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 0u);
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
|
||||
strategy.clear_frequency_limit();
|
||||
state = strategy.state();
|
||||
EXPECT_FALSE(state.frequency_limit_enabled);
|
||||
EXPECT_TRUE(std::isnan(state.frequency_hz));
|
||||
EXPECT_EQ(state.target_interval_ns, 0u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 0u);
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::unlimited);
|
||||
}
|
||||
TEST(low_latency_strategy_test, unlimited_mode_runs_at_measured_render_bottleneck) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
strategy.clear_frequency_limit();
|
||||
strategy.clear_consumer_feedback();
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
time_source.advance_ns(2'000);
|
||||
frame->value = 1;
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
time_source.advance_ns(5'000);
|
||||
}
|
||||
const auto state = strategy.state();
|
||||
EXPECT_EQ(state.target_interval_ns, 0u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 0u);
|
||||
EXPECT_EQ(state.bottleneck_duration_ns, 5'000u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 5'000u);
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::render_limited);
|
||||
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.pending_frame_count(), 0U);
|
||||
}
|
||||
|
||||
+4
-1
@@ -9,6 +9,9 @@
|
||||
struct Low_Latency_Test_Frame {
|
||||
int value{};
|
||||
};
|
||||
struct Low_Latency_Test_Scene {
|
||||
using Frame = Low_Latency_Test_Frame;
|
||||
};
|
||||
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 {
|
||||
@@ -82,7 +85,7 @@ struct Low_Latency_Test_Observer {
|
||||
}
|
||||
};
|
||||
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>;
|
||||
using Low_Latency_Test_Strategy = Low_Latency_Strategy<Low_Latency_Test_Scene, 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));
|
||||
}
|
||||
|
||||
+14
-97
@@ -1,106 +1,23 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#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>;
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
struct Manual_Refresh_Test_Scene { struct Frame { int value{}; }; };
|
||||
using Manual_Refresh_Test_Strategy = Manual_Refresh_Strategy<Manual_Refresh_Test_Scene>;
|
||||
static_assert(Manual_Frame_Refresh_Strategy<Manual_Refresh_Test_Strategy>);
|
||||
TEST(manual_refresh_strategy_test, publishes_only_after_manual_refresh) {
|
||||
|
||||
TEST(manual_refresh_strategy_test, publishes_only_after_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);
|
||||
}
|
||||
{ auto frame = strategy.acquire_painter(); frame->value = 7; }
|
||||
EXPECT_FALSE(strategy.acquire_renderer());
|
||||
EXPECT_EQ(strategy.refresh(), Manual_Refresh_Result::none);
|
||||
{
|
||||
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_EQ(strategy.refresh(), Manual_Refresh_Result::none);
|
||||
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_EQ(strategy.refresh(), Manual_Refresh_Result::no_pending_frame);
|
||||
EXPECT_EQ(strategy.state().failed_refresh_count, 1);
|
||||
}
|
||||
struct Manual_Refresh_Reentrant_Observer_Data {
|
||||
std::function<void(int)> callback;
|
||||
};
|
||||
struct Manual_Refresh_Reentrant_Observer {
|
||||
static constexpr bool enabled = true;
|
||||
std::shared_ptr<Manual_Refresh_Reentrant_Observer_Data> data{std::make_shared<Manual_Refresh_Reentrant_Observer_Data>()};
|
||||
template <class Observation>
|
||||
void observe(const Observation& observation) noexcept {
|
||||
if (data->callback) {
|
||||
data->callback(static_cast<int>(observation.event));
|
||||
}
|
||||
}
|
||||
};
|
||||
using Manual_Refresh_Reentrant_Observer_State = Observer_State<Manual_Refresh_Reentrant_Observer>;
|
||||
using Manual_Refresh_Reentrant_Strategy = Manual_Refresh_Strategy<Manual_Refresh_Test_Frame, std::mutex, Manual_Refresh_Reentrant_Observer_State>;
|
||||
TEST(manual_refresh_strategy_test, refresh_observer_can_acquire_renderer_without_deadlock) {
|
||||
Manual_Refresh_Reentrant_Observer observer;
|
||||
auto data = observer.data;
|
||||
Manual_Refresh_Reentrant_Strategy strategy{Manual_Refresh_Reentrant_Observer_State(observer)};
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 42;
|
||||
}
|
||||
std::atomic<int> observed_value{};
|
||||
data->callback = [&](int event) {
|
||||
if (event != static_cast<int>(Manual_Refresh_Reentrant_Strategy::Observation_Event::refresh_succeeded)) {
|
||||
return;
|
||||
}
|
||||
auto frame = strategy.acquire_renderer();
|
||||
if (frame) {
|
||||
observed_value.store(frame->value, std::memory_order_release);
|
||||
}
|
||||
};
|
||||
EXPECT_EQ(strategy.refresh(), Manual_Refresh_Result::none);
|
||||
EXPECT_EQ(observed_value.load(std::memory_order_acquire), 42);
|
||||
EXPECT_EQ(frame->value, 7);
|
||||
}
|
||||
|
||||
TEST(manual_refresh_strategy_test, discard_is_counted_and_observed) {
|
||||
Manual_Refresh_Reentrant_Observer observer;
|
||||
auto data = observer.data;
|
||||
Manual_Refresh_Reentrant_Strategy strategy{Manual_Refresh_Reentrant_Observer_State(observer)};
|
||||
std::vector<int> events;
|
||||
data->callback = [&events](int event) { events.push_back(event); };
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 21;
|
||||
}
|
||||
ASSERT_TRUE(strategy.discard_pending_frame());
|
||||
EXPECT_FALSE(strategy.discard_pending_frame());
|
||||
const auto state = strategy.state();
|
||||
EXPECT_FALSE(state.pending_frame);
|
||||
EXPECT_EQ(state.discarded_prepared_frame_count, 1u);
|
||||
ASSERT_EQ(events.size(), 2u);
|
||||
EXPECT_EQ(events.back(), static_cast<int>(Manual_Refresh_Reentrant_Strategy::Observation_Event::manually_discarded));
|
||||
TEST(manual_refresh_strategy_test, discard_removes_pending_frame) {
|
||||
Manual_Refresh_Test_Strategy strategy;
|
||||
{ auto frame = strategy.acquire_painter(); frame->value = 1; }
|
||||
EXPECT_TRUE(strategy.discard_pending_frame());
|
||||
EXPECT_EQ(strategy.refresh(), Manual_Refresh_Result::no_pending_frame);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user