/** * Integration Tests: SceneCollector * * Comprehensive tests for scene collection from IIO messages including: * - All message types (sprite, tilemap, text, particle, camera, clear, debug) * - FramePacket construction with FrameAllocator * - String/array data copying * - Multiple frame cycles * * Uses real IntraIO for message routing */ #include #include #include "../../modules/BgfxRenderer/Scene/SceneCollector.h" #include "../../modules/BgfxRenderer/Frame/FrameAllocator.h" #include "grove/IntraIO.h" #include "grove/IntraIOManager.h" #include "grove/JsonDataNode.h" #include #include #include using namespace grove; using Catch::Matchers::WithinAbs; // Helper to create unique instance IDs per test inline std::string uniqueId(const std::string& prefix) { auto now = std::chrono::high_resolution_clock::now().time_since_epoch().count(); std::ostringstream oss; oss << prefix << "_" << now; return oss.str(); } // ============================================================================ // Sprite Parsing // ============================================================================ TEST_CASE("SceneCollector - parse sprite all fields", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); // Create sprite message auto sprite = std::make_unique("sprite"); sprite->setDouble("x", 100.0); sprite->setDouble("y", 200.0); sprite->setDouble("scaleX", 2.0); sprite->setDouble("scaleY", 3.0); sprite->setDouble("rotation", 1.57); sprite->setDouble("u0", 0.0); sprite->setDouble("v0", 0.0); sprite->setDouble("u1", 1.0); sprite->setDouble("v1", 1.0); sprite->setInt("color", 0xFF00FFAA); sprite->setInt("textureId", 42); sprite->setInt("layer", 10); ioPublisher->publish("render:sprite", std::move(sprite)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.spriteCount == 1); REQUIRE(packet.sprites != nullptr); const auto& s = packet.sprites[0]; REQUIRE_THAT(s.x, WithinAbs(100.0f, 0.01f)); REQUIRE_THAT(s.y, WithinAbs(200.0f, 0.01f)); REQUIRE_THAT(s.scaleX, WithinAbs(2.0f, 0.01f)); REQUIRE_THAT(s.scaleY, WithinAbs(3.0f, 0.01f)); REQUIRE_THAT(s.rotation, WithinAbs(1.57f, 0.01f)); REQUIRE_THAT(s.textureId, WithinAbs(42.0f, 0.01f)); REQUIRE_THAT(s.layer, WithinAbs(10.0f, 0.01f)); } TEST_CASE("SceneCollector - parse multiple sprites", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); // Publish multiple sprites auto sprite1 = std::make_unique("sprite"); sprite1->setDouble("x", 10.0); sprite1->setDouble("y", 20.0); sprite1->setInt("color", 0xFFFFFFFF); ioPublisher->publish("render:sprite", std::move(sprite1)); auto sprite2 = std::make_unique("sprite"); sprite2->setDouble("x", 30.0); sprite2->setDouble("y", 40.0); sprite2->setInt("color", 0xFF0000FF); ioPublisher->publish("render:sprite", std::move(sprite2)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.spriteCount == 2); REQUIRE_THAT(packet.sprites[0].x, WithinAbs(10.0f, 0.01f)); REQUIRE_THAT(packet.sprites[1].x, WithinAbs(30.0f, 0.01f)); } // ============================================================================ // Camera Parsing // ============================================================================ TEST_CASE("SceneCollector - parse camera with matrices", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); auto camera = std::make_unique("camera"); camera->setDouble("x", 100.0); camera->setDouble("y", 200.0); camera->setDouble("zoom", 2.0); camera->setInt("viewportX", 0); camera->setInt("viewportY", 0); camera->setInt("viewportW", 1280); camera->setInt("viewportH", 720); ioPublisher->publish("render:camera", std::move(camera)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE_THAT(packet.mainView.positionX, WithinAbs(100.0f, 0.01f)); REQUIRE_THAT(packet.mainView.positionY, WithinAbs(200.0f, 0.01f)); REQUIRE_THAT(packet.mainView.zoom, WithinAbs(2.0f, 0.01f)); REQUIRE(packet.mainView.viewportW == 1280); REQUIRE(packet.mainView.viewportH == 720); // Check view matrix (translation by -camera position) REQUIRE_THAT(packet.mainView.viewMatrix[12], WithinAbs(-100.0f, 0.01f)); REQUIRE_THAT(packet.mainView.viewMatrix[13], WithinAbs(-200.0f, 0.01f)); // Check projection matrix is not zero (ortho projection) REQUIRE(packet.mainView.projMatrix[0] != 0.0f); REQUIRE(packet.mainView.projMatrix[5] != 0.0f); } // ============================================================================ // Tilemap Parsing // ============================================================================ TEST_CASE("SceneCollector - parse tilemap with tiles", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); auto tilemap = std::make_unique("tilemap"); tilemap->setDouble("x", 0.0); tilemap->setDouble("y", 0.0); tilemap->setInt("width", 10); tilemap->setInt("height", 10); tilemap->setInt("tileW", 16); tilemap->setInt("tileH", 16); tilemap->setInt("textureId", 5); tilemap->setString("tileData", "1,2,3,4,5"); ioPublisher->publish("render:tilemap", std::move(tilemap)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.tilemapCount == 1); REQUIRE(packet.tilemaps != nullptr); const auto& tm = packet.tilemaps[0]; REQUIRE(tm.width == 10); REQUIRE(tm.height == 10); REQUIRE(tm.tileWidth == 16); REQUIRE(tm.tileHeight == 16); REQUIRE(tm.textureId == 5); REQUIRE(tm.tileCount == 5); REQUIRE(tm.tiles != nullptr); // Check tile data copied correctly REQUIRE(tm.tiles[0] == 1); REQUIRE(tm.tiles[1] == 2); REQUIRE(tm.tiles[4] == 5); } // ============================================================================ // Text Parsing // ============================================================================ TEST_CASE("SceneCollector - parse text with string", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); auto text = std::make_unique("text"); text->setDouble("x", 50.0); text->setDouble("y", 100.0); text->setString("text", "Hello World"); text->setInt("fontId", 1); text->setInt("fontSize", 24); text->setInt("color", 0xFFFFFFFF); text->setInt("layer", 5); ioPublisher->publish("render:text", std::move(text)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.textCount == 1); REQUIRE(packet.texts != nullptr); const auto& t = packet.texts[0]; REQUIRE_THAT(t.x, WithinAbs(50.0f, 0.01f)); REQUIRE_THAT(t.y, WithinAbs(100.0f, 0.01f)); REQUIRE(t.fontId == 1); REQUIRE(t.fontSize == 24); REQUIRE(t.color == 0xFFFFFFFF); REQUIRE(t.layer == 5); REQUIRE(t.text != nullptr); REQUIRE(std::string(t.text) == "Hello World"); } // ============================================================================ // Particle Parsing // ============================================================================ TEST_CASE("SceneCollector - parse particle", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); auto particle = std::make_unique("particle"); particle->setDouble("x", 10.0); particle->setDouble("y", 20.0); particle->setDouble("vx", 1.0); particle->setDouble("vy", -2.0); particle->setDouble("size", 4.0); particle->setDouble("life", 0.5); particle->setInt("color", 0xFF00FF00); particle->setInt("textureId", 3); ioPublisher->publish("render:particle", std::move(particle)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.particleCount == 1); REQUIRE(packet.particles != nullptr); const auto& p = packet.particles[0]; REQUIRE_THAT(p.x, WithinAbs(10.0f, 0.01f)); REQUIRE_THAT(p.y, WithinAbs(20.0f, 0.01f)); REQUIRE_THAT(p.vx, WithinAbs(1.0f, 0.01f)); REQUIRE_THAT(p.vy, WithinAbs(-2.0f, 0.01f)); REQUIRE_THAT(p.size, WithinAbs(4.0f, 0.01f)); REQUIRE_THAT(p.life, WithinAbs(0.5f, 0.01f)); REQUIRE(p.color == 0xFF00FF00); REQUIRE(p.textureId == 3); } // ============================================================================ // Clear Color Parsing // ============================================================================ TEST_CASE("SceneCollector - parse clear color", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); auto clear = std::make_unique("clear"); clear->setInt("color", 0x12345678); ioPublisher->publish("render:clear", std::move(clear)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.clearColor == 0x12345678); } // ============================================================================ // Debug Shapes Parsing // ============================================================================ TEST_CASE("SceneCollector - parse debug line", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); auto line = std::make_unique("line"); line->setDouble("x1", 0.0); line->setDouble("y1", 0.0); line->setDouble("x2", 100.0); line->setDouble("y2", 100.0); line->setInt("color", 0xFF0000FF); ioPublisher->publish("render:debug:line", std::move(line)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.debugLineCount == 1); REQUIRE(packet.debugLines != nullptr); const auto& l = packet.debugLines[0]; REQUIRE_THAT(l.x1, WithinAbs(0.0f, 0.01f)); REQUIRE_THAT(l.y1, WithinAbs(0.0f, 0.01f)); REQUIRE_THAT(l.x2, WithinAbs(100.0f, 0.01f)); REQUIRE_THAT(l.y2, WithinAbs(100.0f, 0.01f)); REQUIRE(l.color == 0xFF0000FF); } TEST_CASE("SceneCollector - parse debug rect filled", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); auto rect = std::make_unique("rect"); rect->setDouble("x", 10.0); rect->setDouble("y", 20.0); rect->setDouble("w", 50.0); rect->setDouble("h", 30.0); rect->setInt("color", 0x00FF00FF); rect->setBool("filled", true); ioPublisher->publish("render:debug:rect", std::move(rect)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.debugRectCount == 1); const auto& r = packet.debugRects[0]; REQUIRE_THAT(r.x, WithinAbs(10.0f, 0.01f)); REQUIRE_THAT(r.y, WithinAbs(20.0f, 0.01f)); REQUIRE_THAT(r.w, WithinAbs(50.0f, 0.01f)); REQUIRE_THAT(r.h, WithinAbs(30.0f, 0.01f)); REQUIRE(r.filled == true); } TEST_CASE("SceneCollector - parse debug rect outline", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); auto rect = std::make_unique("rect"); rect->setDouble("x", 0.0); rect->setDouble("y", 0.0); rect->setDouble("w", 100.0); rect->setDouble("h", 100.0); rect->setInt("color", 0xFFFFFFFF); rect->setBool("filled", false); ioPublisher->publish("render:debug:rect", std::move(rect)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.debugRects[0].filled == false); } // ============================================================================ // FramePacket Construction // ============================================================================ TEST_CASE("SceneCollector - finalize copies to allocator", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); // Add multiple sprites for (int i = 0; i < 5; ++i) { auto sprite = std::make_unique("sprite"); sprite->setDouble("x", i * 10.0); sprite->setDouble("y", i * 20.0); ioPublisher->publish("render:sprite", std::move(sprite)); } collector.collect(ioCollector.get(), 0.016f); size_t allocatorUsedBefore = allocator.getUsed(); FramePacket packet = collector.finalize(allocator); size_t allocatorUsedAfter = allocator.getUsed(); // Allocator should have allocated memory for sprites REQUIRE(allocatorUsedAfter > allocatorUsedBefore); REQUIRE(packet.spriteCount == 5); REQUIRE(packet.sprites != nullptr); // Verify data integrity for (int i = 0; i < 5; ++i) { REQUIRE_THAT(packet.sprites[i].x, WithinAbs(i * 10.0f, 0.01f)); } } TEST_CASE("SceneCollector - finalize string pointers valid", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); auto text1 = std::make_unique("text"); text1->setString("text", "First"); ioPublisher->publish("render:text", std::move(text1)); auto text2 = std::make_unique("text"); text2->setString("text", "Second"); ioPublisher->publish("render:text", std::move(text2)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.textCount == 2); REQUIRE(std::string(packet.texts[0].text) == "First"); REQUIRE(std::string(packet.texts[1].text) == "Second"); // Pointers should be different (allocated separately) REQUIRE(packet.texts[0].text != packet.texts[1].text); } // ============================================================================ // Clear & Multiple Frames // ============================================================================ TEST_CASE("SceneCollector - clear empties collections", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); auto sprite = std::make_unique("sprite"); sprite->setDouble("x", 10.0); ioPublisher->publish("render:sprite", std::move(sprite)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet1 = collector.finalize(allocator); REQUIRE(packet1.spriteCount == 1); collector.clear(); // After clear, no sprites should be collected allocator.reset(); collector.collect(ioCollector.get(), 0.016f); FramePacket packet2 = collector.finalize(allocator); REQUIRE(packet2.spriteCount == 0); } TEST_CASE("SceneCollector - multiple frame cycles", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); // Frame 1 { auto sprite = std::make_unique("sprite"); sprite->setDouble("x", 100.0); ioPublisher->publish("render:sprite", std::move(sprite)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.spriteCount == 1); REQUIRE(packet.frameNumber == 1); collector.clear(); allocator.reset(); } // Frame 2 { auto sprite1 = std::make_unique("sprite"); sprite1->setDouble("x", 200.0); ioPublisher->publish("render:sprite", std::move(sprite1)); auto sprite2 = std::make_unique("sprite"); sprite2->setDouble("x", 300.0); ioPublisher->publish("render:sprite", std::move(sprite2)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.spriteCount == 2); REQUIRE(packet.frameNumber == 2); collector.clear(); allocator.reset(); } // Frame 3 { collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.spriteCount == 0); REQUIRE(packet.frameNumber == 3); } } // ============================================================================ // Mixed Message Types // ============================================================================ TEST_CASE("SceneCollector - collect mixed message types", "[scene_collector][integration]") { auto& ioManager = IntraIOManager::getInstance(); auto ioCollector = ioManager.createInstance(uniqueId("receiver")); auto ioPublisher = ioManager.createInstance(uniqueId("publisher")); SceneCollector collector; FrameAllocator allocator; collector.setup(ioCollector.get()); // Publish various message types auto sprite = std::make_unique("sprite"); sprite->setDouble("x", 10.0); ioPublisher->publish("render:sprite", std::move(sprite)); auto text = std::make_unique("text"); text->setString("text", "Test"); ioPublisher->publish("render:text", std::move(text)); auto particle = std::make_unique("particle"); particle->setDouble("x", 5.0); ioPublisher->publish("render:particle", std::move(particle)); auto line = std::make_unique("line"); line->setDouble("x1", 0.0); line->setDouble("y1", 0.0); line->setDouble("x2", 10.0); line->setDouble("y2", 10.0); ioPublisher->publish("render:debug:line", std::move(line)); collector.collect(ioCollector.get(), 0.016f); FramePacket packet = collector.finalize(allocator); REQUIRE(packet.spriteCount == 1); REQUIRE(packet.textCount == 1); REQUIRE(packet.particleCount == 1); REQUIRE(packet.debugLineCount == 1); }