188 lines
8.3 KiB
C++
188 lines
8.3 KiB
C++
#include <gtest/gtest.h>
|
|
#include <web_server/src/detail/Gallery_Frame_Atlas.hpp>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
namespace aethera::web::detail {
|
|
namespace {
|
|
std::shared_ptr<const Plot_Pixel_Frame> 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<std::vector<std::byte>>(
|
|
static_cast<std::size_t>(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<const Plot_Pixel_Frame>(Plot_Pixel_Frame{
|
|
std::move(pixels), Plot_Pixel_Layout::rgba8, {}, 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<std::size_t>(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.pixels.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.pixels[alpha], std::byte{10});
|
|
EXPECT_EQ(first.pixels[alpha + 3U], std::byte{255});
|
|
EXPECT_EQ(first.pixels[gamma], std::byte{70});
|
|
EXPECT_EQ(first.pixels[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.pixels[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.pixels[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<const Plot_Pixel_Frame>(
|
|
Plot_Pixel_Frame{{}, Plot_Pixel_Layout::rgba8, {}, 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.pixels[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<std::string> 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<std::jthread> 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<unsigned char>((slot + sequence) % 251U)};
|
|
EXPECT_EQ(atlas.accept_frame(
|
|
slot, solid_frame(
|
|
sequence, sequence, 2, 2, color,
|
|
std::byte{static_cast<unsigned char>(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<void>(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);
|
|
}
|
|
}
|
|
}
|