结构优化

This commit is contained in:
2026-07-30 18:09:03 +08:00
parent 9271599723
commit 13ca0f3141
107 changed files with 3749 additions and 2424 deletions
+7 -2
View File
@@ -1,7 +1,12 @@
#include <memory>
int main() {
#include <gtest/gtest.h>
TEST(ASan_Runtime_Smoke, AllocatesAndReadsArray) {
auto values = std::make_unique<int[]>(64);
for (int i = 0; i < 64; ++i)
values[i] = i;
return values[63] == 63 ? 0 : 1;
EXPECT_EQ(values[63], 63);
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+154 -72
View File
@@ -1,10 +1,12 @@
#include "../Renderive/plottable/Performance_Shower_p.h"
#include <QDir>
#include <QFile>
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <optional>
#include <thread>
@@ -28,7 +30,7 @@ struct Scenario_Config {
double maximum_display_fps{};
};
struct Scenario_Result {
renderive::Frame_Feedback_State state;
renderive::Frame_Feedback_Snapshot state;
std::uint64_t returned_frame_count{};
};
class Scenario_Simulation {
@@ -80,7 +82,7 @@ private:
void start_render() {
if (!can_start_render() || !controller.rate_limit_ready(now))
return;
renderive::Frame_Metadata frame;
renderive::Frame_Lifecycle_Record frame;
frame.frame_id = config.id * 1000000 + ++next_frame_id;
frame.input_version = latest_input_version;
frame.snapshot_version = latest_input_version;
@@ -101,7 +103,7 @@ private:
render_complete_time = now + to_nanoseconds(config.render_duration_ms);
}
void complete_render() {
renderive::Frame_Metadata frame = *rendering_frame;
renderive::Frame_Lifecycle_Record frame = *rendering_frame;
frame.draw_begin_time = frame.render_begin_time;
frame.draw_end_time = now;
frame.render_end_time = now;
@@ -148,17 +150,17 @@ private:
event_time = std::min(event_time, controller.next_render_time_ns());
return event_time;
}
void return_frame(renderive::Frame_Metadata& frame) {
void return_frame(renderive::Frame_Lifecycle_Record& frame) {
renderive::write_frame_performance_log(frame);
returned_frames.push_back(frame);
}
static constexpr std::uint64_t Infinite_Time = std::numeric_limits<std::uint64_t>::max();
Scenario_Config config;
renderive::Frame_Feedback_Controller controller;
std::optional<renderive::Frame_Metadata> rendering_frame;
std::optional<renderive::Frame_Metadata> ready_frame;
std::optional<renderive::Frame_Metadata> consuming_frame;
std::vector<renderive::Frame_Metadata> returned_frames;
std::optional<renderive::Frame_Lifecycle_Record> rendering_frame;
std::optional<renderive::Frame_Lifecycle_Record> ready_frame;
std::optional<renderive::Frame_Lifecycle_Record> consuming_frame;
std::vector<renderive::Frame_Lifecycle_Record> returned_frames;
std::uint64_t now{};
std::uint64_t end_time{};
std::uint64_t next_input_time{};
@@ -172,97 +174,177 @@ private:
bool in_range(double value, double minimum, double maximum) {
return value >= minimum && value <= maximum;
}
int validate_scenario(const Scenario_Config& config, const Scenario_Result& result, int index) {
const renderive::Frame_Feedback_State& state = result.state;
std::cout << config.name
<< " state=" << renderive::bottleneck_state_name(state.bottleneck_state)
<< " production_fps=" << state.production_rate_fps
<< " display_fps=" << state.display_rate_fps
<< " adaptive_interval_ms=" << state.adaptive_min_render_interval_ms
<< " superseded_ratio=" << state.superseded_ready_ratio
<< " display_efficiency=" << state.display_efficiency
<< " returned=" << result.returned_frame_count
<< '\n';
if (state.bottleneck_state != config.expected_state)
return index * 10 + 1;
if (!in_range(state.production_rate_fps, config.minimum_production_fps, config.maximum_production_fps))
return index * 10 + 2;
if (!in_range(state.display_rate_fps, config.minimum_display_fps, config.maximum_display_fps))
return index * 10 + 3;
std::vector<QString> read_log_lines(const QString& path) {
QFile file(path);
std::vector<QString> lines;
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return lines;
while (!file.atEnd()) {
QString line = QString::fromUtf8(file.readLine()).trimmed();
if (!line.isEmpty())
lines.push_back(line);
}
return lines;
}
bool has_line_containing(const std::vector<QString>& lines, const QString& text) {
return std::any_of(lines.begin(), lines.end(), [&text](const QString& line) {
return line.contains(text);
});
}
bool has_all_bottleneck_states(const std::vector<QString>& lines) {
return has_line_containing(lines, "Input_Limited")
&& has_line_containing(lines, "Producer_Limited")
&& has_line_containing(lines, "Consumer_Limited")
&& has_line_containing(lines, "Manual_Limited");
}
std::vector<QString> wait_log_lines(const QString& path, std::size_t minimum_line_count) {
for (int i = 0; i < 50; ++i) {
std::vector<QString> lines = read_log_lines(path);
if (lines.size() >= minimum_line_count && has_all_bottleneck_states(lines))
return lines;
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
return read_log_lines(path);
}
void expect_header_field(const QString& header, const QString& field) {
EXPECT_TRUE(header.split(',').contains(field)) << field.toStdString();
}
void validate_scenario(const Scenario_Config& config, const Scenario_Result& result) {
const renderive::Frame_Feedback_Snapshot& state = result.state;
SCOPED_TRACE(config.name);
EXPECT_EQ(state.bottleneck_state, config.expected_state);
EXPECT_TRUE(in_range(state.production_rate_fps, config.minimum_production_fps, config.maximum_production_fps)) << state.production_rate_fps;
EXPECT_TRUE(in_range(state.display_rate_fps, config.minimum_display_fps, config.maximum_display_fps)) << state.display_rate_fps;
EXPECT_GT(result.returned_frame_count, 0);
if (config.expected_state == renderive::Bottleneck_State::Consumer_Limited) {
if (std::abs(state.production_rate_fps - state.display_rate_fps) > 6.0)
return index * 10 + 4;
if (state.adaptive_min_render_interval_ms < config.paint_duration_ms * 0.8)
return index * 10 + 5;
EXPECT_LE(std::abs(state.production_rate_fps - state.display_rate_fps), 6.0);
EXPECT_GE(state.adaptive_min_render_interval_ms, config.paint_duration_ms * 0.8);
}
if (config.expected_state == renderive::Bottleneck_State::Manual_Limited) {
double expected_interval = 1000.0 / config.max_render_fps;
if (std::abs(state.effective_min_render_interval_ms - expected_interval) > 0.2)
return index * 10 + 6;
EXPECT_NEAR(state.effective_min_render_interval_ms, expected_interval, 0.2);
}
return 0;
}
int validate_frame_input_ring() {
void validate_performance_log(const QString& log_path) {
std::vector<QString> lines = wait_log_lines(log_path, 8);
ASSERT_GT(lines.size(), 1);
const QString& header = lines.front();
EXPECT_TRUE(header.startsWith("frame_id,input_version,snapshot_version,slot_generation,outcome,bottleneck"));
EXPECT_FALSE(header.contains("policy"));
expect_header_field(header, "production_fps");
expect_header_field(header, "display_fps");
expect_header_field(header, "adaptive_interval_ms");
expect_header_field(header, "effective_interval_ms");
expect_header_field(header, "superseded_ratio");
expect_header_field(header, "display_efficiency");
expect_header_field(header, "renderable_stats");
expect_header_field(header, "renderable_cache_stats");
expect_header_field(header, "cache_rebuild_count");
expect_header_field(header, "cache_rebuild_time_ns");
expect_header_field(header, "cache_compose_time_ns");
int header_column_count = header.split(',').size();
EXPECT_EQ(lines[1].split(',').size(), header_column_count);
EXPECT_TRUE(has_all_bottleneck_states(lines));
}
}
TEST(Frame_Scheduler_Feedback, KeepsPerformanceShowerInputRingBounded) {
renderive::Performance_Shower_Input_Data input;
for (std::uint64_t frame_id = 1; frame_id <= 72; ++frame_id) {
renderive::Frame_Metadata frame;
renderive::Frame_Lifecycle_Record frame;
frame.frame_id = frame_id;
input.push(frame);
}
if (input.get_pending_count() != renderive::Performance_Shower_Input_Data::Capacity)
return 1;
if (input.get_capacity_dropped_count() != 8)
return 2;
EXPECT_EQ(input.get_pending_count(), renderive::Performance_Shower_Input_Data::Capacity);
EXPECT_EQ(input.get_capacity_dropped_count(), 8);
std::vector<std::uint64_t> frame_ids;
input.read_all([&frame_ids](const renderive::Frame_Metadata& frame) {
input.read_all([&frame_ids](const renderive::Frame_Lifecycle_Record& frame) {
frame_ids.push_back(frame.frame_id);
});
if (frame_ids.front() != 9 || frame_ids.back() != 72)
return 3;
ASSERT_FALSE(frame_ids.empty());
EXPECT_EQ(frame_ids.front(), 9);
EXPECT_EQ(frame_ids.back(), 72);
input.request_reset();
if (input.get_pending_count() != 0 || !input.reset_pending)
return 4;
return 0;
EXPECT_EQ(input.get_pending_count(), 0);
EXPECT_TRUE(input.reset_pending);
}
int validate_renderable_cache_stats() {
renderive::Frame_Metadata frame;
frame.record_renderable_cache_hit("cache_a");
frame.record_renderable_cache_stale_hit("cache_a");
frame.record_renderable_cache_miss("cache_b");
if (frame.renderable_cache_hit_count != 1 || frame.renderable_cache_stale_hit_count != 1 || frame.renderable_cache_miss_count != 1)
return 1;
TEST(Frame_Scheduler_Feedback, AggregatesRenderableCacheStats) {
renderive::Frame_Lifecycle_Record frame;
frame.render_begin_time = 1000;
frame.render_end_time = 5000;
frame.record_renderable_render("root", {}, 0, renderive::Renderable_Cache_Mode::Direct, 1000);
frame.record_renderable_self_draw("root", {}, 0, renderive::Renderable_Cache_Mode::Direct, 300);
frame.record_renderable_render("cache_b", "cache_a", 1, renderive::Renderable_Cache_Mode::Local_Pixel, 2000);
frame.record_renderable_cache_hit("cache_a", {}, 0);
frame.record_renderable_cache_stale_hit("cache_a", {}, 0);
frame.record_renderable_cache_miss("cache_b", "cache_a", 1);
frame.record_renderable_cache_rebuild("cache_b", "cache_a", 1, 1000, 4096, 1024, 3, 3, false, true);
frame.record_renderable_cache_rebuild("cache_a", {}, 0, 2000, 2048, 512, 4, 4, true, false);
frame.record_renderable_cache_compose("cache_a", {}, 0, 300, 2048, 512, 4, 4);
frame.record_renderable_cache_compose("cache_b", "cache_a", 1, 700, 4096, 1024, 3, 3);
EXPECT_EQ(frame.renderable_cache_hit_count, 1);
EXPECT_EQ(frame.renderable_cache_stale_hit_count, 1);
EXPECT_EQ(frame.renderable_cache_miss_count, 1);
EXPECT_EQ(frame.renderable_cache_rebuild_count, 2);
EXPECT_EQ(frame.renderable_cache_stale_rebuild_count, 1);
EXPECT_EQ(frame.renderable_cache_miss_rebuild_count, 1);
EXPECT_EQ(frame.renderable_cache_rebuild_time_ns, 3000);
EXPECT_EQ(frame.renderable_cache_compose_time_ns, 1000);
ASSERT_EQ(frame.renderable_stats.size(), 2);
EXPECT_FALSE(frame.renderable_stats[0].cache_enabled);
EXPECT_TRUE(frame.renderable_stats[1].cache_enabled);
EXPECT_EQ(frame.renderable_stats[1].tree_depth, 1);
renderive::Frame_Performance_Aggregate aggregate;
aggregate.update(frame);
if (aggregate.cache_hit_count != 1 || aggregate.cache_stale_hit_count != 1 || aggregate.cache_miss_count != 1)
return 2;
if (aggregate.renderable_cache_stats.size() != 2)
return 3;
return 0;
EXPECT_EQ(aggregate.render_time_ns, 4000);
ASSERT_EQ(aggregate.renderable_stats.size(), 2);
EXPECT_EQ(aggregate.renderable_stats[1].parent_object_name, "cache_a");
EXPECT_EQ(aggregate.renderable_stats[1].tree_depth, 1);
EXPECT_TRUE(aggregate.renderable_stats[1].cache_enabled);
EXPECT_GT(aggregate.renderable_stats[0].total_render_ms.times, 0);
EXPECT_GT(aggregate.renderable_stats[0].self_draw_ms.times, 0);
EXPECT_GT(aggregate.renderable_stats[1].total_render_ratio.times, 0);
EXPECT_EQ(aggregate.cache_hit_count, 1);
EXPECT_EQ(aggregate.cache_stale_hit_count, 1);
EXPECT_EQ(aggregate.cache_miss_count, 1);
EXPECT_EQ(aggregate.cache_rebuild_count, 2);
EXPECT_EQ(aggregate.cache_rebuild_time_ns, 3000);
EXPECT_EQ(aggregate.cache_compose_time_ns, 1000);
EXPECT_EQ(aggregate.renderable_cache_stats.size(), 2);
EXPECT_EQ(aggregate.renderable_cache_stats[1].parent_object_name, "cache_a");
EXPECT_EQ(aggregate.renderable_cache_stats[1].tree_depth, 1);
EXPECT_GT(aggregate.renderable_cache_stats[0].rebuild_ms.times, 0);
EXPECT_GT(aggregate.renderable_cache_stats[0].compose_ms.times, 0);
EXPECT_GT(aggregate.cache_rebuild_ms.times, 0);
EXPECT_GT(aggregate.cache_compose_ms.times, 0);
EXPECT_GT(aggregate.cache_rebuild_ratio.times, 0);
EXPECT_GT(aggregate.cache_compose_ratio.times, 0);
EXPECT_EQ(aggregate.renderable_cache_stats[0].last_subtree_compose_time_ns, 1000);
EXPECT_EQ(aggregate.renderable_cache_stats[1].last_subtree_compose_time_ns, 700);
EXPECT_GT(aggregate.renderable_cache_stats[0].subtree_compose_ms.times, 0);
}
}
int main() {
TEST(Frame_Scheduler_Feedback, ClassifiesFourBottlenecksAndWritesUsableLog) {
QString log_path = qEnvironmentVariable("RENDERIVE_PERFORMANCE_LOG_PATH");
if (log_path.isEmpty()) {
log_path = QDir::current().filePath("renderive_feedback_test_log.csv");
qputenv("RENDERIVE_PERFORMANCE_LOG_PATH", log_path.toUtf8());
}
qputenv("RENDERIVE_PERFORMANCE_LOG", "1");
if (!log_path.isEmpty())
QFile::remove(log_path);
int ring_result = validate_frame_input_ring();
if (ring_result)
return ring_result;
int cache_result = validate_renderable_cache_stats();
if (cache_result)
return 100 + cache_result;
const std::array<Scenario_Config, 4> scenarios{{
{"Input_Limited", 1, 50.0, 2.0, 2.0, 0.0, renderive::Bottleneck_State::Input_Limited, 18.0, 22.0, 18.0, 22.0},
{"Producer_Limited", 2, 1.0, 20.0, 2.0, 0.0, renderive::Bottleneck_State::Producer_Limited, 45.0, 55.0, 45.0, 55.0},
{"Consumer_Limited", 3, 1.0, 2.0, 25.0, 0.0, renderive::Bottleneck_State::Consumer_Limited, 34.0, 46.0, 36.0, 44.0},
{"Manual_Limited", 4, 1.0, 2.0, 2.0, 30.0, renderive::Bottleneck_State::Manual_Limited, 28.0, 32.0, 28.0, 32.0}
}};
for (int i = 0; i < scenarios.size(); ++i) {
Scenario_Simulation simulation(scenarios[i]);
for (const Scenario_Config& scenario : scenarios) {
Scenario_Simulation simulation(scenario);
Scenario_Result result = simulation.run(8000.0);
int validation_result = validate_scenario(scenarios[i], result, i + 1);
if (validation_result)
return validation_result;
validate_scenario(scenario, result);
}
std::this_thread::sleep_for(std::chrono::milliseconds(250));
return 0;
validate_performance_log(log_path);
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+15 -27
View File
@@ -2,6 +2,7 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <gtest/gtest.h>
#include <memory_resource>
#include <stdexcept>
#include <thread>
@@ -44,33 +45,21 @@ struct Large_Payload {
std::array<std::byte, 128 * 1024> data{};
};
}
int main() {
TEST(Memory_Callbacks, ValidatesCallbacksAndTracksDeallocationThread) {
callback_state.main_thread = std::this_thread::get_id();
renderive::Memory_Callbacks invalid_callbacks;
invalid_callbacks.context = &callback_state;
invalid_callbacks.allocate = callback_allocate;
try {
renderive::set_memory_callbacks(invalid_callbacks);
return 1;
}
catch (const std::invalid_argument&) {}
EXPECT_THROW(renderive::set_memory_callbacks(invalid_callbacks), std::invalid_argument);
invalid_callbacks.allocate = nullptr;
invalid_callbacks.deallocate = callback_deallocate;
try {
renderive::set_memory_callbacks(invalid_callbacks);
return 2;
}
catch (const std::invalid_argument&) {}
EXPECT_THROW(renderive::set_memory_callbacks(invalid_callbacks), std::invalid_argument);
renderive::Memory_Callbacks callbacks;
callbacks.context = &callback_state;
callbacks.allocate = callback_allocate;
callbacks.deallocate = callback_deallocate;
renderive::set_memory_callbacks(callbacks);
try {
renderive::set_memory_callbacks(callbacks);
return 3;
}
catch (const std::logic_error&) {}
EXPECT_THROW(renderive::set_memory_callbacks(callbacks), std::logic_error);
{
std::pmr::vector<std::uint64_t> values(renderive::memory_resource());
values.resize(8192, 11);
@@ -85,16 +74,15 @@ int main() {
}
renderive::release_unused_memory();
renderive::Memory_Stats stats = renderive::memory_stats();
if (callback_state.alignment_error.load(std::memory_order_relaxed))
return 4;
if (!callback_state.allocation_count.load(std::memory_order_relaxed))
return 5;
if (stats.upstream_allocation_count != callback_state.allocation_count.load(std::memory_order_relaxed))
return 6;
if (!callback_state.deallocation_count.load(std::memory_order_relaxed) || !callback_state.deallocated_off_main_thread.load(std::memory_order_relaxed))
return 7;
EXPECT_FALSE(callback_state.alignment_error.load(std::memory_order_relaxed));
EXPECT_GT(callback_state.allocation_count.load(std::memory_order_relaxed), 0);
EXPECT_EQ(stats.upstream_allocation_count, callback_state.allocation_count.load(std::memory_order_relaxed));
EXPECT_GT(callback_state.deallocation_count.load(std::memory_order_relaxed), 0);
EXPECT_TRUE(callback_state.deallocated_off_main_thread.load(std::memory_order_relaxed));
renderive::shutdown_memory();
if (renderive::memory_stats().upstream_current_bytes)
return 8;
return 0;
EXPECT_EQ(renderive::memory_stats().upstream_current_bytes, 0);
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+148 -47
View File
@@ -3,14 +3,16 @@
#include <QEventLoop>
#include <QThread>
#include <QTime>
#include <gtest/gtest.h>
#include <cmath>
#include <unordered_map>
#include "../Renderive/Axis/Axis.h"
#include "../Renderive/Axis/Frequency_Axis.h"
#include "../Renderive/Axis/Time_Axis.h"
#include "../Renderive/Axis/Time_Axis_p.h"
#include "../Renderive/architecture/Plot.h"
#include "../Renderive/architecture/Plot_p.h"
#include "../Renderive/plot/Latency_Eager_Plot.h"
#include "../Renderive/plot/Plot_p.h"
#include "../Renderive/architecture/Timeline_Stream.h"
#include "../Renderive/base/Memory.h"
#include "../Renderive/plottable/Audio_Frequency.h"
#include "../Renderive/plottable/Audio_Frequency_p.h"
@@ -44,19 +46,30 @@ bool wait_ready(const std::shared_ptr<renderive::Waterfall>& waterfall, const st
}
return false;
}
bool wait_idle(renderive::Plot& plot) {
bool wait_ready(const std::shared_ptr<renderive::Waterfall>& waterfall, const std::shared_ptr<renderive::Time_Axis>& time_axis) {
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < 3000) {
QApplication::processEvents(QEventLoop::AllEvents, 10);
if (!plot.is_rendering() && !plot.d->active_render_tasks.load(std::memory_order_acquire))
if (waterfall->ok() && time_axis->ok())
return true;
QThread::msleep(1);
}
return false;
}
int stress_data_paths() {
renderive::Plot plot;
bool wait_idle(renderive::Latency_Eager_Plot& plot) {
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < 3000) {
QApplication::processEvents(QEventLoop::AllEvents, 10);
if (!plot.is_rendering() && !renderive::Abs_Plot_Private_Access::active_render_tasks(plot))
return true;
QThread::msleep(1);
}
return false;
}
void stress_data_paths() {
renderive::Latency_Eager_Plot plot;
plot.init();
plot.resize(720, 420);
auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
@@ -71,8 +84,7 @@ int stress_data_paths() {
auto planisphere = renderive::Planisphere::Builder(data_node, i_axis, q_axis).set_i_range({-2.0, 2.0}).set_q_range({-2.0, 2.0}).set_continue_millisecond(500).build();
plot.show();
plot.start_render(240);
if (!wait_ready(waterfall, time_axis, audio, planisphere))
return 1;
ASSERT_TRUE(wait_ready(waterfall, time_axis, audio, planisphere));
const int frequency_sizes[] = {64, 512, 8192, 8193, 128};
const int time_sizes[] = {32, 96, 48, 64, 24};
const renderive::Waterfall_Update_Mode update_modes[] = {
@@ -96,7 +108,7 @@ int stress_data_paths() {
for (int frame = 0; frame < time_sizes[stage] * 2; ++frame) {
std::vector<double> frequency_data = make_frequency_data(frequency_sizes[stage], frame);
expected_time = QTime::fromMSecsSinceStartOfDay((stage * 10000 + frame * 4) % 86400000);
int tick = time_axis->give_data(expected_time);
int tick = time_axis->timeline_stream()->push_time(expected_time);
waterfall->give_data(tick, frequency_data);
expected_frequency[tick] = frequency_data.front();
expected_power = frequency_data[frame % frequency_data.size()];
@@ -109,48 +121,44 @@ int stress_data_paths() {
}
process_events(60);
plot.pause_render();
if (!wait_idle(plot))
return 2;
auto* waterfall_data = waterfall->d();
if (waterfall_data->ring_buffer.col_count != frequency_sizes[stage] || waterfall_data->ring_buffer.row_count != time_sizes[stage])
return 3;
if (waterfall_data->image.width() != frequency_sizes[stage] || waterfall_data->image.height() != time_sizes[stage])
return 4;
ASSERT_TRUE(wait_idle(plot));
auto* waterfall_data = renderive::Waterfall_Private_Access::data(waterfall.get());
ASSERT_EQ(waterfall_data->ring_buffer.col_count, frequency_sizes[stage]);
ASSERT_EQ(waterfall_data->ring_buffer.row_count, time_sizes[stage]);
ASSERT_EQ(waterfall_data->image.width(), frequency_sizes[stage]);
ASSERT_EQ(waterfall_data->image.height(), time_sizes[stage]);
int row_count = waterfall_data->ring_buffer.snapshot();
int previous_tick{};
for (int row = 0; row < row_count; ++row) {
int tick = waterfall_data->ring_buffer.tick(row);
auto expected = expected_frequency.find(tick);
if (expected == expected_frequency.end())
return 5;
if (std::abs(waterfall_data->ring_buffer.frequency_data(row, 0, 0)[0] - expected->second) > 0.000001)
return 6;
if (row && tick >= previous_tick)
return 7;
ASSERT_NE(expected, expected_frequency.end());
EXPECT_NEAR(waterfall_data->ring_buffer.frequency_data(row, 0, 0)[0], expected->second, 0.000001);
if (row)
EXPECT_GT(tick, previous_tick);
previous_tick = tick;
}
auto* time_data = time_axis->d();
if (time_data->time_ticker.time_point_size != time_sizes[stage] || time_data->time_snapshot.size() != static_cast<std::size_t>(time_sizes[stage]))
return 8;
if (!time_data->time_data_count || time_data->time_snapshot[time_data->time_data_count - 1] != expected_time)
return 9;
int lower = qMin(time_data->time_ticker.lower, time_data->time_ticker.upper());
int upper = qMax(time_data->time_ticker.lower, time_data->time_ticker.upper());
for (const renderive::Time_Tick& tick : time_data->data) {
if (tick.tick < lower || tick.tick > upper)
return 10;
ASSERT_EQ(time_data->timeline_snapshot.time_point_size, time_sizes[stage]);
ASSERT_EQ(time_data->time_snapshot.size(), static_cast<std::size_t>(time_sizes[stage]));
ASSERT_NE(time_data->time_data_count, 0);
EXPECT_EQ(time_data->time_snapshot[time_data->time_data_count - 1], expected_time);
int lower = qMin(time_data->timeline_snapshot.lower, time_data->timeline_snapshot.upper());
int upper = qMax(time_data->timeline_snapshot.lower, time_data->timeline_snapshot.upper());
for (const renderive::Timeline_Tick& tick : time_data->data) {
EXPECT_GE(tick.tick, lower);
EXPECT_LE(tick.tick, upper);
}
auto* audio_data = audio->d();
if (audio_data->point_count != time_sizes[stage] || audio_data->power_snapshot.size() != static_cast<std::size_t>(time_sizes[stage]))
return 11;
if (!audio_data->data_count || std::abs(audio_data->power_snapshot[audio_data->data_count - 1] - expected_power) > 0.000001)
return 12;
auto* planisphere_data = planisphere->d();
if (planisphere_data->data_list.empty())
return 13;
auto* audio_data = renderive::Audio_Frequency_Private_Access::data(audio.get());
ASSERT_EQ(audio_data->point_count, time_sizes[stage]);
ASSERT_EQ(audio_data->power_snapshot.size(), static_cast<std::size_t>(time_sizes[stage]));
ASSERT_NE(audio_data->data_count, 0);
EXPECT_NEAR(audio_data->power_snapshot[audio_data->data_count - 1], expected_power, 0.000001);
auto* planisphere_data = renderive::Planisphere_Private_Access::data(planisphere.get());
ASSERT_FALSE(planisphere_data->data_list.empty());
QPointF actual_position = planisphere_data->data_list.back().pos;
if (std::abs(actual_position.x() - expected_position.x()) > 0.000001 || std::abs(actual_position.y() - expected_position.y()) > 0.000001)
return 14;
EXPECT_NEAR(actual_position.x(), expected_position.x(), 0.000001);
EXPECT_NEAR(actual_position.y(), expected_position.y(), 0.000001);
plot.start_render(240);
}
for (int frame = 0; frame < 180; ++frame) {
@@ -158,16 +166,109 @@ int stress_data_paths() {
process_events(8);
}
plot.pause_render();
if (!wait_idle(plot))
return 15;
if (!planisphere->d()->data_list.empty())
return 16;
ASSERT_TRUE(wait_idle(plot));
EXPECT_TRUE(renderive::Planisphere_Private_Access::data(planisphere.get())->data_list.empty());
renderive::Memory_Stats stats = renderive::memory_stats();
return stats.upstream_allocation_count && stats.upstream_peak_bytes ? 0 : 17;
EXPECT_GT(stats.upstream_allocation_count, 0);
EXPECT_GT(stats.upstream_peak_bytes, 0);
}
}
TEST(Memory_Data_Path_Stress, KeepsRingAndRenderableDataConsistentAcrossResizes) {
stress_data_paths();
}
TEST(Memory_Data_Path_Stress, WaterfallWritesNewestTimelineRowAtBottom) {
renderive::Latency_Eager_Plot plot;
plot.init();
plot.resize(1, 4);
auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable");
auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(1).set_coord_range({0.0, 1.0}).build();
auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(4).set_time_point_size(4).build();
renderive::Color_Map color_map({qRgb(10, 0, 0), qRgb(20, 0, 0), qRgb(30, 0, 0), qRgb(40, 0, 0)});
auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 1.0}).set_power_range({0.0, 4.0}).set_frequency_point_size(1).set_color_map(color_map).build();
plot.show();
plot.start_render(240);
ASSERT_TRUE(wait_ready(waterfall, time_axis));
for (int frame = 0; frame < 4; ++frame) {
std::pmr::vector<double> row(renderive::memory_resource(renderive::Memory_Domain::Waterfall));
row.resize(1);
row[0] = static_cast<double>(frame);
waterfall->give_data(QTime::fromMSecsSinceStartOfDay(frame), std::move(row));
}
process_events(100);
plot.pause_render();
ASSERT_TRUE(wait_idle(plot));
auto* data = renderive::Waterfall_Private_Access::data(waterfall.get());
ASSERT_EQ(data->image.width(), 1);
ASSERT_EQ(data->image.height(), 4);
EXPECT_EQ(qRed(data->image.image().pixel(0, 0)), 10);
EXPECT_EQ(qRed(data->image.image().pixel(0, 3)), 40);
}
TEST(Memory_Data_Path_Stress, WaterfallWritesNewestTimelineRowAtTopWhenTimeAxisPushesToStart) {
renderive::Latency_Eager_Plot plot;
plot.init();
plot.resize(1, 4);
auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable");
auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(1).set_coord_range({0.0, 1.0}).build();
auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(4).set_time_point_size(4).set_start_coord_to_end_coord(true).build();
renderive::Color_Map color_map({qRgb(10, 0, 0), qRgb(20, 0, 0), qRgb(30, 0, 0), qRgb(40, 0, 0)});
auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 1.0}).set_power_range({0.0, 4.0}).set_frequency_point_size(1).set_color_map(color_map).build();
plot.show();
plot.start_render(240);
ASSERT_TRUE(wait_ready(waterfall, time_axis));
for (int frame = 0; frame < 4; ++frame) {
std::pmr::vector<double> row(renderive::memory_resource(renderive::Memory_Domain::Waterfall));
row.resize(1);
row[0] = static_cast<double>(frame);
waterfall->give_data(QTime::fromMSecsSinceStartOfDay(frame), std::move(row));
}
process_events(100);
plot.pause_render();
ASSERT_TRUE(wait_idle(plot));
auto* data = renderive::Waterfall_Private_Access::data(waterfall.get());
ASSERT_EQ(data->image.width(), 1);
ASSERT_EQ(data->image.height(), 4);
EXPECT_EQ(qRed(data->image.image().pixel(0, 0)), 40);
EXPECT_EQ(qRed(data->image.image().pixel(0, 3)), 10);
}
TEST(Memory_Data_Path_Stress, DirtyLocalPixelCacheRebuildsAfterInput) {
renderive::Latency_Eager_Plot plot;
plot.init();
plot.resize(1, 4);
auto cache_node = plot.create_renderable_node(plot.get_root_renderable(), "Stream_Cache");
auto data_node = plot.create_renderable_node(cache_node, "Data_Renderable");
auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable");
cache_node->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(1).set_coord_range({0.0, 1.0}).build();
auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(4).set_time_point_size(4).build();
renderive::Color_Map color_map({qRgb(10, 0, 0), qRgb(20, 0, 0), qRgb(30, 0, 0), qRgb(40, 0, 0)});
auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 1.0}).set_power_range({0.0, 4.0}).set_frequency_point_size(1).set_color_map(color_map).build();
plot.show();
plot.start_render(240);
ASSERT_TRUE(wait_ready(waterfall, time_axis));
process_events(80);
plot.pause_render();
ASSERT_TRUE(wait_idle(plot));
ASSERT_FALSE(cache_node->cache_image.isNull());
plot.start_render(240);
for (int frame = 0; frame < 4; ++frame) {
std::pmr::vector<double> row(renderive::memory_resource(renderive::Memory_Domain::Waterfall));
row.resize(1);
row[0] = static_cast<double>(frame);
waterfall->give_data(QTime::fromMSecsSinceStartOfDay(frame), std::move(row));
}
process_events(100);
plot.pause_render();
ASSERT_TRUE(wait_idle(plot));
ASSERT_EQ(cache_node->cache_image.width(), 1);
ASSERT_EQ(cache_node->cache_image.height(), 4);
EXPECT_EQ(qRed(cache_node->cache_image.image().pixel(0, 3)), 40);
}
int main(int argc, char** argv) {
qputenv("QT_QPA_PLATFORM", "offscreen");
QApplication app(argc, argv);
testing::InitGoogleTest(&argc, argv);
renderive::start_render_scheduler();
return stress_data_paths();
return RUN_ALL_TESTS();
}
+10 -6
View File
@@ -1,4 +1,5 @@
#include <cstdint>
#include <gtest/gtest.h>
#include <thread>
#include <vector>
#include "../Renderive/base/Memory.h"
@@ -7,19 +8,22 @@ struct Payload {
std::uint64_t value{};
};
}
int main() {
TEST(Memory_Default, AllocatesAndTracksDefaultResource) {
std::pmr::vector<std::uint64_t> values(renderive::memory_resource());
values.resize(4096, 7);
auto owner = renderive::make_shared<Payload>();
owner->value = values.front();
if (owner->value != 7)
return 1;
EXPECT_EQ(owner->value, 7);
std::thread release_thread([owner = std::move(owner)]() mutable {
owner.reset();
});
release_thread.join();
renderive::Memory_Stats stats = renderive::memory_stats();
if (!stats.upstream_allocation_count || !stats.upstream_current_bytes || !stats.upstream_peak_bytes)
return 2;
return 0;
EXPECT_GT(stats.upstream_allocation_count, 0);
EXPECT_GT(stats.upstream_current_bytes, 0);
EXPECT_GT(stats.upstream_peak_bytes, 0);
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+10 -2
View File
@@ -1,4 +1,5 @@
#include <atomic>
#include <gtest/gtest.h>
#include <memory_resource>
#include <new>
#include <vector>
@@ -20,7 +21,7 @@ void callback_deallocate(void*, void* address, std::size_t size, std::size_t ali
std::pmr::new_delete_resource()->deallocate(address, size, alignment);
}
}
int main() {
TEST(Memory_Failure, ConvertsCallbackFailureToBadAlloc) {
renderive::Memory_Callbacks callbacks;
callbacks.context = &callback_state;
callbacks.allocate = callback_allocate;
@@ -71,5 +72,12 @@ int main() {
catch (const std::bad_alloc&) {
ring_buffer_failure_caught = true;
}
return direct_failure_caught && container_failure_caught && frame_failure_caught && ring_buffer_failure_caught ? 0 : 1;
EXPECT_TRUE(direct_failure_caught);
EXPECT_TRUE(container_failure_caught);
EXPECT_TRUE(frame_failure_caught);
EXPECT_TRUE(ring_buffer_failure_caught);
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+8 -9
View File
@@ -1,5 +1,6 @@
#include <stdexcept>
#include "../Renderive/architecture/Plot.h"
#include <gtest/gtest.h>
#include "../Renderive/plot/Plot.h"
#include "../Renderive/base/Memory.h"
namespace {
void* callback_allocate(void*, std::size_t size, std::size_t alignment) {
@@ -9,16 +10,14 @@ void callback_deallocate(void*, void* address, std::size_t size, std::size_t ali
std::pmr::new_delete_resource()->deallocate(address, size, alignment);
}
}
int main() {
TEST(Memory_Late_Config, RejectsCallbacksAfterSchedulerStart) {
renderive::start_render_scheduler();
renderive::Memory_Callbacks callbacks;
callbacks.allocate = callback_allocate;
callbacks.deallocate = callback_deallocate;
try {
renderive::set_memory_callbacks(callbacks);
}
catch (const std::logic_error&) {
return 0;
}
return 1;
EXPECT_THROW(renderive::set_memory_callbacks(callbacks), std::logic_error);
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+11 -11
View File
@@ -1,6 +1,7 @@
#include <QApplication>
#include <stdexcept>
#include "../Renderive/architecture/Plot.h"
#include <gtest/gtest.h>
#include "../Renderive/plot/Latency_Eager_Plot.h"
#include "../Renderive/base/Memory.h"
namespace {
void* callback_allocate(void*, std::size_t size, std::size_t alignment) {
@@ -10,19 +11,18 @@ void callback_deallocate(void*, void* address, std::size_t size, std::size_t ali
std::pmr::new_delete_resource()->deallocate(address, size, alignment);
}
}
int main(int argc, char** argv) {
QApplication app(argc, argv);
TEST(Memory_Plot_Late_Config, RejectsCallbacksAfterPlotCreatesMemory) {
renderive::start_render_scheduler();
renderive::Plot plot;
renderive::Latency_Eager_Plot plot;
plot.init();
renderive::Memory_Callbacks callbacks;
callbacks.allocate = callback_allocate;
callbacks.deallocate = callback_deallocate;
try {
renderive::set_memory_callbacks(callbacks);
}
catch (const std::logic_error&) {
return 0;
}
return 1;
EXPECT_THROW(renderive::set_memory_callbacks(callbacks), std::logic_error);
}
int main(int argc, char** argv) {
qputenv("QT_QPA_PLATFORM", "offscreen");
QApplication app(argc, argv);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+17 -13
View File
@@ -3,6 +3,7 @@
#include <QEventLoop>
#include <QThread>
#include <QTime>
#include <gtest/gtest.h>
#include <atomic>
#include <cmath>
#include <thread>
@@ -10,8 +11,9 @@
#include "../Renderive/Axis/Axis.h"
#include "../Renderive/Axis/Frequency_Axis.h"
#include "../Renderive/Axis/Time_Axis.h"
#include "../Renderive/architecture/Plot.h"
#include "../Renderive/architecture/Plot_p.h"
#include "../Renderive/architecture/Timeline_Stream.h"
#include "../Renderive/plot/Latency_Eager_Plot.h"
#include "../Renderive/plot/Plot_p.h"
#include "../Renderive/base/Memory.h"
#include "../Renderive/plottable/Audio_Frequency.h"
#include "../Renderive/plottable/Spectrum.h"
@@ -43,19 +45,19 @@ bool wait_ready(const std::shared_ptr<renderive::Waterfall>& waterfall, const st
}
return false;
}
bool wait_idle(renderive::Plot& plot) {
bool wait_idle(renderive::Latency_Eager_Plot& plot) {
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < 3000) {
QApplication::processEvents(QEventLoop::AllEvents, 10);
if (!plot.is_rendering() && !plot.d->active_render_tasks.load(std::memory_order_acquire))
if (!plot.is_rendering() && !renderive::Abs_Plot_Private_Access::active_render_tasks(plot))
return true;
QThread::msleep(1);
}
return false;
}
int run_stress() {
renderive::Plot plot;
void run_stress() {
renderive::Latency_Eager_Plot plot;
plot.init();
plot.resize(640, 360);
plot.set_max_render_fps(1000);
@@ -69,8 +71,7 @@ int run_stress() {
auto audio = renderive::Audio_Frequency::Builder(data_node, time_axis, power_axis).set_time_point_size(128).set_key_range({-120.0, 0.0}).build();
plot.show();
plot.start_render(1000);
if (!wait_ready(waterfall, spectrum, audio, time_axis))
return 1;
ASSERT_TRUE(wait_ready(waterfall, spectrum, audio, time_axis));
std::atomic_bool running{true};
std::atomic<int> pushes{0};
std::vector<std::thread> workers;
@@ -79,7 +80,7 @@ int run_stress() {
for (int frame = 0; running.load(std::memory_order_acquire) && frame < 2500; ++frame) {
int seed = frame + thread_index * 10000;
QTime time = QTime::fromMSecsSinceStartOfDay(seed % 86400000);
int tick = time_axis->give_data(time);
int tick = time_axis->timeline_stream()->push_time(time);
waterfall->give_data(tick, make_data(256, seed, renderive::Memory_Domain::Waterfall));
spectrum->give_data(make_data(256, seed, renderive::Memory_Domain::Spectrum));
audio->give_data(tick, -90.0 + static_cast<double>((seed % 120)));
@@ -98,14 +99,17 @@ int run_stress() {
worker.join();
process_events(300);
plot.pause_render();
if (!wait_idle(plot))
return 2;
return pushes.load(std::memory_order_relaxed) > 1000 ? 0 : 3;
ASSERT_TRUE(wait_idle(plot));
EXPECT_GT(pushes.load(std::memory_order_relaxed), 1000);
}
}
TEST(Multithread_Input_Stress, AcceptsConcurrentRenderableInput) {
run_stress();
}
int main(int argc, char** argv) {
qputenv("QT_QPA_PLATFORM", "offscreen");
QApplication app(argc, argv);
testing::InitGoogleTest(&argc, argv);
renderive::start_render_scheduler();
return run_stress();
return RUN_ALL_TESTS();
}
+15 -13
View File
@@ -5,6 +5,7 @@
#include <QPushButton>
#include <QThread>
#include <QWheelEvent>
#include <gtest/gtest.h>
#include <memory>
namespace {
QPushButton* find_performance_button(QWidget* control) {
@@ -41,7 +42,7 @@ void wheel_plot(QWidget* plot, const QPoint& pos, int delta) {
QWheelEvent event(pos, plot->mapToGlobal(pos), QPoint(), QPoint(0, delta), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
QApplication::sendEvent(plot, &event);
}
bool pump(QApplication& app, renderive::Plot& plot, int milliseconds) {
bool pump(QApplication& app, renderive::Latency_Eager_Plot& plot, int milliseconds) {
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < milliseconds) {
@@ -52,12 +53,10 @@ bool pump(QApplication& app, renderive::Plot& plot, int milliseconds) {
return true;
}
}
int main(int argc, char** argv) {
qputenv("QT_QPA_PLATFORM", "offscreen");
qputenv("RENDERIVE_PERFORMANCE_LOG", "0");
QApplication app(argc, argv);
TEST(Performance_Shower_Interaction, RepeatedToggleDoubleClickAndWheelStayResponsive) {
QApplication& app = *qobject_cast<QApplication*>(QApplication::instance());
renderive::start_render_scheduler();
renderive::Plot plot;
renderive::Latency_Eager_Plot plot;
plot.resize(320, 240);
plot.init();
plot.show();
@@ -66,15 +65,13 @@ int main(int argc, char** argv) {
control->show();
app.processEvents();
QPushButton* performance_button = find_performance_button(control.get());
if (!performance_button)
return 1;
ASSERT_NE(performance_button, nullptr);
QElapsedTimer total;
total.start();
for (int i = 0; i < 40; ++i) {
click_button(performance_button);
pump(app, plot, 15);
if (total.elapsed() > 5000)
return 2;
ASSERT_LE(total.elapsed(), 5000);
}
double_click_button(performance_button);
pump(app, plot, 200);
@@ -93,8 +90,13 @@ int main(int argc, char** argv) {
pump(app, plot, 10);
}
pump(app, plot, 200);
if (!plot.get_use_performance_shower())
return 3;
EXPECT_TRUE(plot.get_use_performance_shower());
plot.pause_render();
return 0;
}
int main(int argc, char** argv) {
qputenv("QT_QPA_PLATFORM", "offscreen");
qputenv("RENDERIVE_PERFORMANCE_LOG", "0");
QApplication app(argc, argv);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+13 -8
View File
@@ -2,12 +2,13 @@
#include <QElapsedTimer>
#include <QEventLoop>
#include <QThread>
#include <gtest/gtest.h>
#include <cmath>
#include <memory>
#include <thread>
#include "../Renderive/Axis/Axis.h"
#include "../Renderive/Axis/Frequency_Axis.h"
#include "../Renderive/architecture/Plot.h"
#include "../Renderive/plot/Latency_Eager_Plot.h"
#include "../Renderive/plottable/Spectrum.h"
namespace {
std::vector<double> make_spectrum_data(int size, int frame) {
@@ -25,7 +26,7 @@ void process_events(int ms) {
}
}
void stress_remove_local_cache_renderable() {
renderive::Plot plot;
renderive::Latency_Eager_Plot plot;
plot.init();
plot.resize(640, 360);
auto data = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
@@ -45,7 +46,7 @@ void stress_remove_local_cache_renderable() {
}
void stress_plot_destroy_with_pending_tasks() {
for (int round = 0; round < 32; ++round) {
auto plot = std::make_unique<renderive::Plot>();
auto plot = std::make_unique<renderive::Latency_Eager_Plot>();
plot->init();
plot->resize(480, 240);
auto data = plot->create_renderable_node(plot->get_root_renderable(), "Data_Renderable");
@@ -69,7 +70,7 @@ void stress_plot_destroy_with_pending_tasks() {
void stress_release_renderable_after_plot_destroy() {
std::shared_ptr<renderive::Renderable> survivor;
{
renderive::Plot plot;
renderive::Latency_Eager_Plot plot;
plot.init();
survivor = plot.create_renderable_node(plot.get_root_renderable(), "Survivor");
}
@@ -79,12 +80,16 @@ void stress_release_renderable_after_plot_destroy() {
release_thread.join();
}
}
int main(int argc, char** argv) {
QApplication app(argc, argv);
renderive::start_render_scheduler();
TEST(Renderable_Lifecycle_Stress, HandlesRemovalDestroyAndLateRelease) {
stress_remove_local_cache_renderable();
stress_plot_destroy_with_pending_tasks();
stress_release_renderable_after_plot_destroy();
process_events(250);
return 0;
}
int main(int argc, char** argv) {
qputenv("QT_QPA_PLATFORM", "offscreen");
QApplication app(argc, argv);
testing::InitGoogleTest(&argc, argv);
renderive::start_render_scheduler();
return RUN_ALL_TESTS();
}
+56
View File
@@ -0,0 +1,56 @@
#include <QTime>
#include <gtest/gtest.h>
#include "../Renderive/architecture/Plot_Render_Context.h"
#include "../Renderive/architecture/Timeline_Stream.h"
#include "../Renderive/base/Memory.h"
TEST(Timeline_Stream_Consistency, ReusesOneTimelineSnapshotInsideFrame) {
auto stream = renderive::make_shared<renderive::Timeline_Stream>();
stream->set_time_point_size(8);
int first_tick = stream->push_time(QTime(10, 0, 0));
renderive::Plot_Render_Snapshot frame;
auto first_snapshot = frame.timeline_snapshot(stream);
int second_tick = stream->push_time(QTime(10, 0, 1));
auto second_snapshot = frame.timeline_snapshot(stream);
EXPECT_EQ(first_snapshot, second_snapshot);
EXPECT_EQ(first_snapshot->time_by_tick(first_tick), QTime(10, 0, 0));
EXPECT_NE(first_snapshot->time_by_tick(second_tick), QTime(10, 0, 1));
renderive::Plot_Render_Snapshot next_frame;
auto next_snapshot = next_frame.timeline_snapshot(stream);
EXPECT_NE(next_snapshot->version, first_snapshot->version);
EXPECT_EQ(next_snapshot->time_by_tick(second_tick), QTime(10, 0, 1));
}
TEST(Timeline_Stream_Consistency, KeepsTickAndTimePairedInSnapshot) {
auto stream = renderive::make_shared<renderive::Timeline_Stream>();
stream->set_time_point_size(16);
stream->set_tick_space(2);
for (int i = 0; i < 12; ++i)
stream->push_time(QTime::fromMSecsSinceStartOfDay(i * 1000));
renderive::Timeline_Stream_Snapshot snapshot = stream->capture();
ASSERT_FALSE(snapshot.ticks.empty());
for (const renderive::Timeline_Tick& tick : snapshot.ticks)
EXPECT_EQ(snapshot.time_by_tick(tick.tick), tick.time);
}
TEST(Timeline_Stream_Consistency, DefaultsNewTimeToCoordEnd) {
auto stream = renderive::make_shared<renderive::Timeline_Stream>();
stream->set_time_point_size(4);
int tick = stream->push_time(QTime(10, 0, 0));
renderive::Timeline_Stream_Snapshot snapshot = stream->capture();
EXPECT_FALSE(snapshot.start_coord_to_end_coord);
EXPECT_EQ(tick, snapshot.upper());
EXPECT_EQ(snapshot.time_by_tick(snapshot.upper()), QTime(10, 0, 0));
}
TEST(Timeline_Stream_Consistency, SupportsNewTimeToCoordStart) {
auto stream = renderive::make_shared<renderive::Timeline_Stream>();
stream->set_time_point_size(4);
stream->set_start_coord_to_end_coord(true);
for (int i = 0; i < 4; ++i)
stream->push_time(QTime::fromMSecsSinceStartOfDay(i));
renderive::Timeline_Stream_Snapshot snapshot = stream->capture();
EXPECT_TRUE(snapshot.start_coord_to_end_coord);
EXPECT_EQ(snapshot.time_by_tick(snapshot.lower), QTime::fromMSecsSinceStartOfDay(3));
EXPECT_EQ(snapshot.time_by_tick(snapshot.upper()), QTime::fromMSecsSinceStartOfDay(0));
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+10 -8
View File
@@ -1,8 +1,9 @@
#include "../Renderive/architecture/Update_Completion.h"
#include "asio.hpp"
#include <gtest/gtest.h>
#include <atomic>
#include <future>
int main() {
TEST(Update_Completion, CompletesStageWaitersAndCancelledWaiters) {
asio::io_context io;
auto state = renderive::make_update_state();
renderive::Update_Ticket ticket(state);
@@ -15,13 +16,11 @@ int main() {
}));
state->complete_until(renderive::Update_Stage::Input_Released);
io.poll();
if (rendered_count.load(std::memory_order_relaxed) != 0)
return 1;
EXPECT_EQ(rendered_count.load(std::memory_order_relaxed), 0);
state->complete_until(renderive::Update_Stage::Rendered);
io.restart();
io.run();
if (rendered_count.load(std::memory_order_relaxed) != 1)
return 2;
EXPECT_EQ(rendered_count.load(std::memory_order_relaxed), 1);
ticket.async_wait(renderive::Update_Stage::Input_Released, asio::bind_executor(io.get_executor(), [&](std::error_code error, renderive::Update_Outcome outcome) {
if (error || outcome != renderive::Update_Outcome::Completed)
std::terminate();
@@ -29,8 +28,7 @@ int main() {
}));
io.restart();
io.run();
if (input_count.load(std::memory_order_relaxed) != 1)
return 3;
EXPECT_EQ(input_count.load(std::memory_order_relaxed), 1);
auto cancelled_state = renderive::make_update_state();
renderive::Update_Ticket cancelled_ticket(cancelled_state);
std::atomic_int cancel_count{};
@@ -42,5 +40,9 @@ int main() {
cancelled_state->complete_all(std::make_error_code(std::errc::operation_canceled), renderive::Update_Outcome::Cancelled);
io.restart();
io.run();
return cancel_count.load(std::memory_order_relaxed) == 1 ? 0 : 4;
EXPECT_EQ(cancel_count.load(std::memory_order_relaxed), 1);
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}