#include "render_3D/Point_Visual.h" #include "render_3D/detail/Point_Core.h" #include #include #include #include #include #include namespace renderive::render_3d { namespace { TEST(PointState, UsesKernelDoubleStateAtFrameBoundary) { auto data = std::make_shared(); data->update(std::vector{{{1.0F, 2.0F, 3.0F}, {1, 2, 3, 255}, 7.0F}}); Point_Visual visual(data); Point_State configured; configured.visible = false; configured.style.stroke_width_px = 3.0F; visual.configure(configured); const auto published = detail::Point_State_Access::publish(visual); EXPECT_EQ(published.state, configured); ASSERT_EQ(published.data.size(), 1U); EXPECT_EQ(published.data.front().position, (Vec3{1.0F, 2.0F, 3.0F})); EXPECT_EQ(published.state_revision, 1U); EXPECT_EQ(published.data_revision, 1U); } TEST(PointState, UsesKernelLatestRealTimeDataForBulkPayloads) { auto data = std::make_shared(); Point_Visual visual(data); constexpr int update_count = 500; std::atomic done{}; std::thread writer([&] { for (int value = 1; value <= update_count; ++value) { std::vector points(static_cast(value % 31 + 1)); for (auto& point : points) { point.position.x = static_cast(value); point.diameter_px = static_cast(value % 12 + 1); } data->update(std::move(points)); } done.store(true, std::memory_order_release); }); do { const auto published = detail::Point_State_Access::publish(visual); if (!published.data.empty()) { const float expected = published.data.front().position.x; for (const auto& point : published.data) EXPECT_EQ(point.position.x, expected); } } while (!done.load(std::memory_order_acquire)); writer.join(); const auto published = detail::Point_State_Access::publish(visual); EXPECT_EQ(published.data_revision, update_count); ASSERT_FALSE(published.data.empty()); EXPECT_EQ(published.data.front().position.x, static_cast(update_count)); } TEST(PointState, RejectsInvalidStateAndPayloadAtTheOwningBoundary) { auto data = std::make_shared(); Point_Visual visual(data); Point_State invalid; invalid.style.stroke_width_px = -1.0F; EXPECT_THROW(visual.configure(invalid), std::invalid_argument); data->update(std::vector{{{}, {}, 0.0F}}); EXPECT_THROW((void)detail::Point_State_Access::publish(visual), std::invalid_argument); } TEST(PointFrameControl, ReusesKernelManualAndPlaybackStrategies) { auto data = std::make_shared(); data->update(std::vector{{{}, {}, 8.0F}}); Point_Visual visual(data); detail::Scene_State_Buffer scene({{320, 180}, {}}); detail::Input_Collector input; detail::Frame_Scheduler manual(Frame_Mode::Manual, 60.0); EXPECT_TRUE(manual.prepare(scene, visual, input)); auto manual_status = manual.status(); EXPECT_EQ(manual_status.mode, Frame_Mode::Manual); EXPECT_EQ(manual_status.produced_frame_count, 1U); EXPECT_EQ(manual_status.pending_frame_count, 1U); bool rendered{}; EXPECT_FALSE(manual.render([&](const detail::Point_Frame_Data&) { rendered = true; return true; })); EXPECT_FALSE(rendered); EXPECT_TRUE(manual.refresh()); EXPECT_TRUE(manual.render([&](const detail::Point_Frame_Data& frame) { rendered = true; EXPECT_EQ(frame.point.data.size(), 1U); return true; })); EXPECT_TRUE(rendered); manual_status = manual.status(); EXPECT_EQ(manual_status.consumed_frame_count, 1U); EXPECT_EQ(manual_status.pending_frame_count, 0U); detail::Frame_Scheduler playback(Frame_Mode::Playback, 60.0); EXPECT_TRUE(playback.prepare(scene, visual, input)); data->update(std::vector(2, Point{{}, {}, 8.0F})); EXPECT_TRUE(playback.prepare(scene, visual, input)); auto playback_status = playback.status(); EXPECT_EQ(playback_status.produced_frame_count, 2U); EXPECT_EQ(playback_status.pending_frame_count, 2U); EXPECT_TRUE(playback.render([](const detail::Point_Frame_Data& frame) { return frame.point.data.size() == 1U; })); EXPECT_TRUE(playback.render([](const detail::Point_Frame_Data& frame) { return frame.point.data.size() == 2U; })); playback_status = playback.status(); EXPECT_EQ(playback_status.consumed_frame_count, 2U); EXPECT_EQ(playback_status.pending_frame_count, 0U); } } // namespace } // namespace renderive::render_3d