#include #include #include #include #include #include #include #include #include namespace aethera::web::detail { namespace { std::shared_ptr solid_frame( std::uint64_t sequence, std::uint64_t correlation_id, std::uint32_t width, std::uint32_t height, std::byte red, std::byte green, std::byte blue, std::uint64_t rendered_sequence = 0, std::uint64_t rendered_correlation_id = 0) { auto pixels = std::make_shared>( static_cast(width) * height * 4U); for (std::size_t offset = 0; offset < pixels->size(); offset += 4U) { (*pixels)[offset] = red; (*pixels)[offset + 1U] = green; (*pixels)[offset + 2U] = blue; (*pixels)[offset + 3U] = std::byte{255}; } return std::make_shared(Plot_Pixel_Frame{ std::move(pixels), {}, sequence, correlation_id, rendered_sequence == 0 ? sequence : rendered_sequence, rendered_correlation_id == 0 ? correlation_id : rendered_correlation_id, width, height}); } std::size_t pixel_offset(const Gallery_Atlas_Description& description, std::uint32_t x, std::uint32_t y) { return (static_cast(y) * description.width + x) * 4U; } } TEST(Gallery_Frame_Atlas, Layout_And_Changed_Tiles_Have_One_Authority) { Gallery_Frame_Atlas atlas(2, 2, 2, {"alpha", "beta", "gamma"}); const auto description = atlas.describe(); ASSERT_EQ(description.columns, 2U); ASSERT_EQ(description.rows, 2U); ASSERT_EQ(description.width, 4U); ASSERT_EQ(description.height, 4U); ASSERT_EQ(description.sources.size(), 3U); EXPECT_EQ(description.sources[2].column, 0U); EXPECT_EQ(description.sources[2].row, 1U); EXPECT_EQ(atlas.accept_frame( 0, solid_frame(1, 11, 2, 2, std::byte{10}, std::byte{20}, std::byte{30})), Gallery_Frame_Atlas::Accept_Frame_Result::accepted); EXPECT_EQ(atlas.accept_frame( 2, solid_frame(7, 17, 2, 2, std::byte{70}, std::byte{80}, std::byte{90})), Gallery_Frame_Atlas::Accept_Frame_Result::accepted); const auto first = atlas.compose(); ASSERT_EQ(first.rgba.size(), 4U * 4U * 4U); EXPECT_EQ(first.fresh_tile_count, 2U); EXPECT_EQ(first.missing_tile_count, 1U); EXPECT_EQ(first.sources[0].rendered_correlation_id, 11U); EXPECT_EQ(first.sources[2].completion_count, 1U); EXPECT_EQ(first.sources[2].rendered_frame_count, 1U); const auto alpha = pixel_offset(description, 0, 0); const auto gamma = pixel_offset(description, 0, 2); EXPECT_EQ(first.rgba[alpha], std::byte{10}); EXPECT_EQ(first.rgba[alpha + 3U], std::byte{255}); EXPECT_EQ(first.rgba[gamma], std::byte{70}); EXPECT_EQ(first.rgba[gamma + 2U], std::byte{90}); const auto unchanged = atlas.compose(); EXPECT_EQ(unchanged.fresh_tile_count, 0U); EXPECT_EQ(unchanged.missing_tile_count, 1U); } TEST(Gallery_Frame_Atlas, Invalid_And_Stale_Frames_Do_Not_Replace_Latest) { Gallery_Frame_Atlas atlas(2, 2, 1, {"only"}); EXPECT_EQ(atlas.accept_frame( 0, solid_frame(4, 14, 2, 2, std::byte{4}, std::byte{5}, std::byte{6})), Gallery_Frame_Atlas::Accept_Frame_Result::accepted); EXPECT_EQ(atlas.accept_frame( 0, solid_frame(3, 13, 2, 2, std::byte{30}, std::byte{31}, std::byte{32})), Gallery_Frame_Atlas::Accept_Frame_Result::stale_frame); EXPECT_EQ(atlas.accept_frame( 0, solid_frame(5, 15, 4, 2, std::byte{50}, std::byte{51}, std::byte{52})), Gallery_Frame_Atlas::Accept_Frame_Result::invalid_frame); const auto composition = atlas.compose(); EXPECT_EQ(composition.rejected_frame_count, 2U); EXPECT_EQ(composition.sources[0].completion_sequence, 4U); EXPECT_EQ(composition.rgba[0], std::byte{4}); EXPECT_EQ(atlas.compose().rejected_frame_count, 0U); } TEST(Gallery_Frame_Atlas, Logical_History_Callback_Does_Not_Fake_A_New_Image) { Gallery_Frame_Atlas atlas(2, 2, 1, {"only"}); ASSERT_EQ(atlas.accept_frame( 0, solid_frame(4, 14, 2, 2, std::byte{4}, std::byte{5}, std::byte{6})), Gallery_Frame_Atlas::Accept_Frame_Result::accepted); ASSERT_EQ(atlas.compose().fresh_tile_count, 1U); ASSERT_EQ(atlas.accept_frame( 0, solid_frame(5, 15, 2, 2, std::byte{50}, std::byte{51}, std::byte{52}, 4, 14)), Gallery_Frame_Atlas::Accept_Frame_Result::accepted); const auto composition = atlas.compose(); EXPECT_EQ(composition.fresh_tile_count, 0U); EXPECT_EQ(composition.sources[0].completion_count, 2U); EXPECT_EQ(composition.sources[0].rendered_frame_count, 1U); EXPECT_EQ(composition.sources[0].completion_sequence, 5U); EXPECT_EQ(composition.sources[0].rendered_sequence, 4U); EXPECT_EQ(composition.rgba[0], std::byte{4}); } TEST(Gallery_Frame_Atlas, Diagnostics_Only_Completion_Preserves_Progress_And_Image) { Gallery_Frame_Atlas atlas(2, 2, 1, {"only"}); ASSERT_EQ(atlas.accept_frame( 0, solid_frame(1, 11, 2, 2, std::byte{7}, std::byte{8}, std::byte{9})), Gallery_Frame_Atlas::Accept_Frame_Result::accepted); ASSERT_EQ(atlas.compose().fresh_tile_count, 1U); auto diagnostics = std::make_shared( Plot_Pixel_Frame{{}, {}, 2, 12, 2, 12, 2, 2}); ASSERT_EQ(atlas.accept_frame(0, std::move(diagnostics)), Gallery_Frame_Atlas::Accept_Frame_Result::accepted); const auto composition = atlas.compose(); EXPECT_EQ(composition.fresh_tile_count, 0U); EXPECT_EQ(composition.missing_tile_count, 0U); EXPECT_EQ(composition.sources[0].completion_sequence, 2U); EXPECT_EQ(composition.sources[0].rendered_sequence, 2U); EXPECT_EQ(composition.sources[0].completion_count, 2U); EXPECT_EQ(composition.sources[0].rendered_frame_count, 2U); EXPECT_EQ(composition.rgba[0], std::byte{7}); } TEST(Gallery_Frame_Atlas, Plot_Producers_Accept_In_Parallel_While_Encoding_Composes) { constexpr std::size_t source_count{8}; constexpr std::uint64_t frame_count{500}; std::vector ids; ids.reserve(source_count); for (std::size_t slot = 0; slot < source_count; ++slot) ids.push_back("source-" + std::to_string(slot)); Gallery_Frame_Atlas atlas(2, 2, 4, std::move(ids)); std::atomic_size_t completed_producers{}; std::vector producers; producers.reserve(source_count); for (std::size_t slot = 0; slot < source_count; ++slot) { producers.emplace_back([&, slot] { for (std::uint64_t sequence = 1; sequence <= frame_count; ++sequence) { const auto color = std::byte{ static_cast((slot + sequence) % 251U)}; EXPECT_EQ(atlas.accept_frame( slot, solid_frame( sequence, sequence, 2, 2, color, std::byte{static_cast(slot)}, std::byte{0})), Gallery_Frame_Atlas::Accept_Frame_Result::accepted); } completed_producers.fetch_add(1, std::memory_order_release); }); } while (completed_producers.load(std::memory_order_acquire) < source_count) static_cast(atlas.compose()); for (auto& producer : producers) producer.join(); const auto final = atlas.compose(); ASSERT_EQ(final.sources.size(), source_count); EXPECT_EQ(final.missing_tile_count, 0U); EXPECT_EQ(final.rejected_frame_count, 0U); for (const auto& source : final.sources) { EXPECT_EQ(source.completion_sequence, frame_count); EXPECT_EQ(source.rendered_sequence, frame_count); EXPECT_EQ(source.completion_count, frame_count); EXPECT_EQ(source.rendered_frame_count, frame_count); } } }