补充测试

This commit is contained in:
2026-08-17 14:28:19 +08:00
parent 93fd683e62
commit e793c6a278
26 changed files with 354 additions and 2927 deletions
@@ -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();
}
@@ -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());
}
@@ -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());
}
@@ -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);
}
@@ -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));
}
@@ -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);
}