#include #include #include #include #include #include #include #include #include #include #include "../Renderive/Axis/Axis.h" #include "../Renderive/Axis/Frequency_Axis.h" #include "../Renderive/Axis/Time_Axis.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" #include "../Renderive/plottable/Waterfall.h" namespace { std::pmr::vector make_data(int size, int seed, renderive::Memory_Domain domain) { std::pmr::vector data(renderive::memory_resource(domain)); data.resize(size); for (int i = 0; i < size; ++i) data[i] = -90.0 + 30.0 * std::sin(static_cast(i + seed) * 0.017); return data; } void process_events(int milliseconds) { QElapsedTimer timer; timer.start(); while (timer.elapsed() < milliseconds) { QApplication::processEvents(QEventLoop::AllEvents, 10); QThread::msleep(1); } } bool wait_ready(const std::shared_ptr& waterfall, const std::shared_ptr& spectrum, const std::shared_ptr& audio, const std::shared_ptr& time_axis) { QElapsedTimer timer; timer.start(); while (timer.elapsed() < 3000) { QApplication::processEvents(QEventLoop::AllEvents, 10); if (waterfall->ok() && spectrum->ok() && audio->ok() && time_axis->ok()) return true; QThread::msleep(1); } return false; } 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 run_stress() { renderive::Latency_Eager_Plot plot; plot.init(); plot.resize(640, 360); plot.set_max_render_fps(1000); 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(640).set_coord_range({0.0, 100.0}).build(); auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(360).set_time_point_size(128).build(); auto power_axis = renderive::Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(360).set_coord_range({0.0, -120.0}).build(); auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 100.0}).set_power_range({-120.0, 0.0}).set_frequency_point_size(256).build(); auto spectrum = renderive::Spectrum::Builder(data_node, frequency_axis, power_axis).set_frequency_range({0.0, 100.0}).set_frequency_point_size(256).build(); 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); ASSERT_TRUE(wait_ready(waterfall, spectrum, audio, time_axis)); std::atomic_bool running{true}; std::atomic pushes{0}; std::vector workers; for (int thread_index = 0; thread_index < 4; ++thread_index) { workers.emplace_back([&, thread_index]() { 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->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((seed % 120))); pushes.fetch_add(1, std::memory_order_relaxed); if ((frame & 15) == 15) std::this_thread::yield(); } }); } QElapsedTimer timer; timer.start(); while (timer.elapsed() < 8000 && pushes.load(std::memory_order_relaxed) < 10000) process_events(5); running.store(false, std::memory_order_release); for (auto& worker : workers) worker.join(); process_events(300); plot.pause_render(); 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_ALL_TESTS(); }