# GroveEngine - Developer Guide **Comprehensive guide for building applications with GroveEngine** ⚠️ **IMPORTANT**: GroveEngine is currently in **development stage** - suitable for prototyping and experimentation, **not production games**. The engine is non-deterministic and optimized for rapid iteration, not stability. See [Current Limitations](#current-limitations) below. ## Table of Contents 1. [Current Limitations](#current-limitations) 2. [Getting Started](#getting-started) 3. [Core System](#core-system) 4. [Available Modules](#available-modules) - [BgfxRenderer - 2D Rendering](#bgfxrenderer---2d-rendering) - [UIModule - User Interface](#uimodule---user-interface) - [InputModule - Input Handling](#inputmodule---input-handling) 5. [IIO Topics Reference](#iio-topics-reference) 6. [Complete Application Example](#complete-application-example) 7. [Building Your First Game](#building-your-first-game) 8. [Advanced Topics](#advanced-topics) --- ## Current Limitations ⚠️ **GroveEngine is EXPERIMENTAL and NOT production-ready.** Understand these limitations before building with it: ### Non-Deterministic Execution - **Module execution order is NOT guaranteed** - modules may run in different orders between frames - **Not suitable for networked games** - no deterministic replay or synchronization - **Race conditions possible** - only SequentialModuleSystem is currently implemented (single-threaded) ### Development Stage - **Optimized for rapid iteration**, not stability - **No error recovery** - crashes are not handled gracefully - **Limited performance optimizations** - no profiling, memory pooling, or SIMD - **Single-threaded only** - ThreadedModuleSystem and MultithreadedModuleSystem are TODO ### Module Limitations - **InputModule**: Mouse and keyboard only (gamepad Phase 2 not implemented) - **BgfxRenderer**: Basic text rendering only (8x8 bitmap font for debug) - **UIModule**: Functional but no advanced layout constraints ### What GroveEngine IS Good For ✅ **Rapid prototyping** - 0.4ms hot-reload for instant iteration ✅ **Learning modular architecture** - clean interface-based design ✅ **AI-assisted development** - 200-300 line modules optimized for Claude Code ✅ **Experimentation** - test game ideas quickly ### Production Roadmap To make GroveEngine production-ready, the following is needed: - Deterministic execution guarantees - Error recovery and graceful degradation - Multi-threaded module systems - Performance profiling and optimization - Network IO and distributed messaging - Complete gamepad support - Advanced text rendering --- ## Getting Started ### Prerequisites - **C++17** compiler (GCC, Clang, or MSVC) - **CMake** 3.20+ - **Git** for dependency management ### Quick Start ```bash # Clone GroveEngine git clone GroveEngine cd GroveEngine # Build with all modules cmake -B build -DGROVE_BUILD_BGFX_RENDERER=ON -DGROVE_BUILD_UI_MODULE=ON -DGROVE_BUILD_INPUT_MODULE=ON cmake --build build -j4 # Run tests cd build && ctest --output-on-failure ``` ### Documentation Structure - **[USER_GUIDE.md](USER_GUIDE.md)** - Module system basics, hot-reload, IIO communication - **[BgfxRenderer README](../modules/BgfxRenderer/README.md)** - 2D rendering module details - **[InputModule README](../modules/InputModule/README.md)** - Input handling details - **This document** - Complete integration guide and examples --- ## Core System ### Architecture Overview GroveEngine uses a **module-based architecture** with hot-reload support: ``` ┌─────────────────────────────────────────────────────────────┐ │ Your Application │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Input │ │ UI │ │ Renderer │ │ Game │ │ │ │ Module │ │ Module │ │ Module │ │ Logic │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ └─────────────┼─────────────┼─────────────┘ │ │ │ IIO Pub/Sub System │ └─────────────────────┼───────────────────────────────────────┘ │ IntraIOManager ``` ### Key Concepts | Component | Purpose | Documentation | |-----------|---------|---------------| | **IModule** | Module interface | [USER_GUIDE.md](USER_GUIDE.md#imodule) | | **IIO** | Pub/Sub messaging | [USER_GUIDE.md](USER_GUIDE.md#iio) | | **IDataNode** | Configuration & data | [USER_GUIDE.md](USER_GUIDE.md#idatanode) | | **ModuleLoader** | Hot-reload system | [USER_GUIDE.md](USER_GUIDE.md#moduleloader) | --- ## Available Modules ### BgfxRenderer - 2D Rendering **Status:** ✅ Development Ready (Phase 8 complete) | ⚠️ Non-deterministic, experimental Multi-backend 2D renderer using bgfx (DirectX 11/12, OpenGL, Vulkan, Metal). #### Features - Sprite rendering with batching - Text rendering with bitmap fonts - Tilemap support - Particle effects - Debug shapes (lines, rectangles) - Layer-based Z-ordering - Multi-texture batching - Headless mode for testing #### Configuration ```cpp JsonDataNode config("config"); config.setInt("windowWidth", 1920); config.setInt("windowHeight", 1080); config.setString("backend", "auto"); // auto, opengl, vulkan, dx11, dx12, metal, noop config.setString("shaderPath", "./shaders"); config.setBool("vsync", true); config.setInt("maxSpritesPerBatch", 10000); config.setInt("nativeWindowHandle", (int)(intptr_t)hwnd); // Platform window handle renderer->setConfiguration(config, rendererIO.get(), nullptr); ``` #### Rendering a Sprite ```cpp // Publish sprite to render auto sprite = std::make_unique("sprite"); sprite->setDouble("x", 100.0); sprite->setDouble("y", 200.0); sprite->setDouble("scaleX", 1.0); sprite->setDouble("scaleY", 1.0); sprite->setDouble("rotation", 0.0); // Radians sprite->setInt("color", 0xFFFFFFFF); // RGBA sprite->setInt("textureId", playerTexture); sprite->setInt("layer", 10); // Z-order (higher = front) io->publish("render:sprite", std::move(sprite)); ``` #### Rendering Text ```cpp auto text = std::make_unique("text"); text->setDouble("x", 50.0); text->setDouble("y", 50.0); text->setString("text", "Score: 100"); text->setDouble("fontSize", 24.0); text->setInt("color", 0xFFFFFFFF); text->setInt("layer", 100); // Text on top io->publish("render:text", std::move(text)); ``` #### Camera Control ```cpp auto camera = std::make_unique("camera"); camera->setDouble("x", playerX); // Center camera on player camera->setDouble("y", playerY); camera->setDouble("zoom", 1.0); camera->setInt("viewportX", 0); camera->setInt("viewportY", 0); camera->setInt("viewportW", 1920); camera->setInt("viewportH", 1080); io->publish("render:camera", std::move(camera)); ``` **Full Topic Reference:** See [IIO Topics - Rendering](#rendering-topics) --- ### UIModule - User Interface **Status:** ✅ Development Ready (Phase 7 complete) | ⚠️ Experimental Complete UI widget system with layout, scrolling, and tooltips. #### Available Widgets | Widget | Purpose | Events | |--------|---------|--------| | **UIButton** | Clickable button | `ui:click`, `ui:action` | | **UILabel** | Static text | - | | **UIPanel** | Container | - | | **UICheckbox** | Toggle checkbox | `ui:value_changed` | | **UISlider** | Value slider | `ui:value_changed` | | **UITextInput** | Text input field | `ui:value_changed`, `ui:text_submitted` | | **UIProgressBar** | Progress display | - | | **UIImage** | Image/sprite | - | | **UIScrollPanel** | Scrollable container | `ui:scroll` | | **UITooltip** | Hover tooltip | - | #### Configuration ```cpp JsonDataNode uiConfig("config"); uiConfig.setInt("windowWidth", 1920); uiConfig.setInt("windowHeight", 1080); uiConfig.setString("layoutFile", "./ui/main_menu.json"); // JSON layout uiConfig.setInt("baseLayer", 1000); // UI renders above game (layer 1000+) uiModule->setConfiguration(uiConfig, uiIO.get(), nullptr); ``` #### Creating a Button ```cpp // In your layout JSON file (ui/main_menu.json) { "type": "UIButton", "id": "play_button", "x": 100, "y": 200, "width": 200, "height": 50, "text": "Play Game", "action": "start_game" } ``` ```cpp // In your game module - subscribe to button events gameIO->subscribe("ui:click"); gameIO->subscribe("ui:action"); // In process() while (gameIO->hasMessages() > 0) { auto msg = gameIO->pullMessage(); if (msg.topic == "ui:action") { std::string action = msg.data->getString("action", ""); std::string widgetId = msg.data->getString("widgetId", ""); if (action == "start_game" && widgetId == "play_button") { startGame(); } } } ``` #### Handling Input Events UIModule automatically consumes input events from InputModule: ```cpp // UIModule subscribes to: // - input:mouse:move // - input:mouse:button // - input:mouse:wheel // - input:keyboard:key // - input:keyboard:text // Your game module just subscribes to UI events: gameIO->subscribe("ui:*"); // All UI events ``` #### UI Rendering UIModule publishes render commands to BgfxRenderer via `UIRenderer`: ```cpp // UIModule uses retained mode rendering (only publishes on change): // - render:sprite:add/update/remove (for UI rectangles/images) // - render:text:add/update/remove (for labels/buttons) // BgfxRenderer consumes these and renders the UI // Layer management ensures UI renders on top (layer 1000+) ``` **Retained Mode:** Widgets cache render state and only publish IIO messages when visual properties change. This reduces message traffic by 85%+ for typical UIs. See [UI Rendering Documentation](UI_RENDERING.md) for details. **Full Topic Reference:** See [IIO Topics - UI Events](#ui-events) --- ### InputModule - Input Handling **Status:** ✅ Development Ready (Phase 1-3 complete) | ⚠️ Gamepad Phase 2 TODO Cross-platform input handling with SDL2 backend (mouse, keyboard). #### Features - Mouse (move, button, wheel) - Keyboard (key events, text input) - Thread-safe event buffering - Multiple backend support (SDL2, extensible) - Hot-reload support #### Configuration ```cpp JsonDataNode inputConfig("config"); inputConfig.setString("backend", "sdl"); inputConfig.setBool("enableMouse", true); inputConfig.setBool("enableKeyboard", true); inputConfig.setBool("enableGamepad", false); // Phase 2 inputModule->setConfiguration(inputConfig, inputIO.get(), nullptr); ``` #### Feeding Events (SDL2) ```cpp // In your main loop SDL_Event event; while (SDL_PollEvent(&event)) { // Feed to InputModule (thread-safe) inputModule->feedEvent(&event); // Also handle window events if (event.type == SDL_QUIT) { running = false; } } // Process InputModule (converts buffered events → IIO messages) JsonDataNode input("input"); inputModule->process(input); ``` #### Consuming Input Events ```cpp // Subscribe to input topics gameIO->subscribe("input:mouse:button"); gameIO->subscribe("input:keyboard:key"); // In process() while (gameIO->hasMessages() > 0) { auto msg = gameIO->pullMessage(); if (msg.topic == "input:mouse:button") { int button = msg.data->getInt("button", 0); // 0=left, 1=middle, 2=right bool pressed = msg.data->getBool("pressed", false); double x = msg.data->getDouble("x", 0.0); double y = msg.data->getDouble("y", 0.0); if (button == 0 && pressed) { // Left mouse button pressed at (x, y) handleClick(x, y); } } if (msg.topic == "input:keyboard:key") { int scancode = msg.data->getInt("scancode", 0); // SDL_SCANCODE_* bool pressed = msg.data->getBool("pressed", false); if (scancode == SDL_SCANCODE_SPACE && pressed) { playerJump(); } } } ``` **Full Topic Reference:** See [IIO Topics - Input Events](#input-events) --- ## IIO Topics Reference ### Input Events Published by **InputModule**, consumed by **UIModule** or **game logic**. #### Mouse | Topic | Payload | Description | |-------|---------|-------------| | `input:mouse:move` | `{x: double, y: double}` | Mouse position (screen coords) | | `input:mouse:button` | `{button: int, pressed: bool, x: double, y: double}` | Mouse click (0=left, 1=middle, 2=right) | | `input:mouse:wheel` | `{delta: double}` | Mouse wheel (+up, -down) | #### Keyboard | Topic | Payload | Description | |-------|---------|-------------| | `input:keyboard:key` | `{scancode: int, pressed: bool, repeat: bool, shift: bool, ctrl: bool, alt: bool}` | Key event (scancode = SDL_SCANCODE_*) | | `input:keyboard:text` | `{text: string}` | Text input (UTF-8, for TextInput widgets) | --- ### UI Events Published by **UIModule**, consumed by **game logic**. | Topic | Payload | Description | |-------|---------|-------------| | `ui:click` | `{widgetId: string, x: double, y: double}` | Widget clicked | | `ui:action` | `{widgetId: string, action: string}` | Button action triggered | | `ui:value_changed` | `{widgetId: string, value: variant}` | Slider, checkbox, or text input changed | | `ui:text_submitted` | `{widgetId: string, text: string}` | Text input submitted (Enter key) | | `ui:hover` | `{widgetId: string, enter: bool}` | Mouse entered/left widget | | `ui:scroll` | `{widgetId: string, scrollX: double, scrollY: double}` | Scroll panel scrolled | --- ### Rendering Topics Consumed by **BgfxRenderer**, published by **UIModule** or **game logic**. #### Sprites **Retained Mode (UIModule current):** | Topic | Payload | Description | |-------|---------|-------------| | `render:sprite:add` | `{renderId, x, y, scaleX, scaleY, color, textureId, layer}` | Register new sprite (retained) | | `render:sprite:update` | `{renderId, x, y, scaleX, scaleY, color, textureId, layer}` | Update existing sprite | | `render:sprite:remove` | `{renderId}` | Unregister sprite | **Immediate Mode (legacy, still supported):** | Topic | Payload | Description | |-------|---------|-------------| | `render:sprite` | `{x, y, scaleX, scaleY, rotation, u0, v0, u1, v1, color, textureId, layer}` | Render single sprite (ephemeral) | | `render:sprite:batch` | `{sprites: [array]}` | Render sprite batch (optimized) | #### Text **Retained Mode (UIModule current):** | Topic | Payload | Description | |-------|---------|-------------| | `render:text:add` | `{renderId, x, y, text, fontSize, color, layer}` | Register new text (retained) | | `render:text:update` | `{renderId, x, y, text, fontSize, color, layer}` | Update existing text | | `render:text:remove` | `{renderId}` | Unregister text | **Immediate Mode (legacy, still supported):** | Topic | Payload | Description | |-------|---------|-------------| | `render:text` | `{x, y, text, fontSize, color, layer}` | Render text (ephemeral) | **Note:** See [UI Rendering Documentation](UI_RENDERING.md) for details on retained mode rendering. #### Tilemap | Topic | Payload | Description | |-------|---------|-------------| | `render:tilemap` | `{chunkX, chunkY, tiles: [array], tileSize, textureId, layer}` | Render tilemap chunk | #### Particles | Topic | Payload | Description | |-------|---------|-------------| | `render:particle` | `{x, y, velocityX, velocityY, color, lifetime, textureId, layer}` | Render particle | #### Camera | Topic | Payload | Description | |-------|---------|-------------| | `render:camera` | `{x, y, zoom, viewportX, viewportY, viewportW, viewportH}` | Set camera transform | #### Clear | Topic | Payload | Description | |-------|---------|-------------| | `render:clear` | `{color: int}` | Set clear color (RGBA) | #### Debug | Topic | Payload | Description | |-------|---------|-------------| | `render:debug:line` | `{x1, y1, x2, y2, color}` | Draw debug line | | `render:debug:rect` | `{x, y, w, h, color, filled}` | Draw debug rectangle | --- ## Complete Application Example ### Directory Structure ``` MyGame/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp │ └── modules/ │ ├── GameLogic.h │ └── GameLogic.cpp ├── assets/ │ ├── ui/ │ │ └── main_menu.json │ └── sprites/ │ └── player.png └── external/ └── GroveEngine/ # Git submodule ``` ### CMakeLists.txt ```cmake cmake_minimum_required(VERSION 3.20) project(MyGame VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # GroveEngine + Modules add_subdirectory(external/GroveEngine) set(GROVE_BUILD_BGFX_RENDERER ON CACHE BOOL "" FORCE) set(GROVE_BUILD_UI_MODULE ON CACHE BOOL "" FORCE) set(GROVE_BUILD_INPUT_MODULE ON CACHE BOOL "" FORCE) # Main executable add_executable(mygame src/main.cpp) target_link_libraries(mygame PRIVATE GroveEngine::impl SDL2::SDL2 spdlog::spdlog ) # Game logic module add_library(GameLogic SHARED src/modules/GameLogic.cpp ) target_link_libraries(GameLogic PRIVATE GroveEngine::impl spdlog::spdlog ) set_target_properties(GameLogic PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/modules ) ``` ### main.cpp ```cpp #include #include #include #include #include int main(int argc, char* argv[]) { // Initialize SDL SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); SDL_Window* window = SDL_CreateWindow("MyGame", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1920, 1080, SDL_WINDOW_SHOWN); // Get native window handle SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); SDL_GetWindowWMInfo(window, &wmInfo); void* nativeHandle = nullptr; #ifdef _WIN32 nativeHandle = wmInfo.info.win.window; // HWND #elif __linux__ nativeHandle = (void*)(uintptr_t)wmInfo.info.x11.window; #endif // Create IIO instances auto& ioManager = grove::IntraIOManager::getInstance(); auto rendererIO = ioManager.createInstance("renderer"); auto uiIO = ioManager.createInstance("ui"); auto inputIO = ioManager.createInstance("input"); auto gameIO = ioManager.createInstance("game"); // Load modules grove::ModuleLoader rendererLoader, uiLoader, inputLoader, gameLoader; auto renderer = rendererLoader.load("./modules/BgfxRenderer.dll", "renderer"); auto uiModule = uiLoader.load("./modules/UIModule.dll", "ui"); auto inputModule = inputLoader.load("./modules/InputModule.dll", "input"); auto gameModule = gameLoader.load("./modules/GameLogic.dll", "game"); // Configure BgfxRenderer grove::JsonDataNode rendererConfig("config"); rendererConfig.setInt("windowWidth", 1920); rendererConfig.setInt("windowHeight", 1080); rendererConfig.setString("backend", "auto"); rendererConfig.setInt("nativeWindowHandle", (int)(intptr_t)nativeHandle); renderer->setConfiguration(rendererConfig, rendererIO.get(), nullptr); // Configure UIModule grove::JsonDataNode uiConfig("config"); uiConfig.setInt("windowWidth", 1920); uiConfig.setInt("windowHeight", 1080); uiConfig.setString("layoutFile", "./assets/ui/main_menu.json"); uiConfig.setInt("baseLayer", 1000); uiModule->setConfiguration(uiConfig, uiIO.get(), nullptr); // Configure InputModule grove::JsonDataNode inputConfig("config"); inputConfig.setString("backend", "sdl"); inputModule->setConfiguration(inputConfig, inputIO.get(), nullptr); // Configure GameLogic grove::JsonDataNode gameConfig("config"); gameModule->setConfiguration(gameConfig, gameIO.get(), nullptr); // Main loop bool running = true; Uint64 lastTime = SDL_GetPerformanceCounter(); while (running) { // 1. Handle SDL events SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { running = false; } inputModule->feedEvent(&event); // Feed to InputModule } // 2. Calculate deltaTime Uint64 now = SDL_GetPerformanceCounter(); double deltaTime = (now - lastTime) / (double)SDL_GetPerformanceFrequency(); lastTime = now; // 3. Process all modules grove::JsonDataNode input("input"); input.setDouble("deltaTime", deltaTime); inputModule->process(input); // Input → IIO messages uiModule->process(input); // UI → IIO messages gameModule->process(input); // Game logic renderer->process(input); // Render frame // 4. Optional: Hot-reload check // (file watcher code here) } // Cleanup renderer->shutdown(); uiModule->shutdown(); inputModule->shutdown(); gameModule->shutdown(); SDL_DestroyWindow(window); SDL_Quit(); return 0; } ``` ### GameLogic.cpp ```cpp #include #include #include #include class GameLogic : public grove::IModule { public: GameLogic() { m_logger = spdlog::stdout_color_mt("GameLogic"); } void setConfiguration(const grove::IDataNode& config, grove::IIO* io, grove::ITaskScheduler* scheduler) override { m_io = io; // Subscribe to UI events m_io->subscribe("ui:action"); m_io->subscribe("ui:click"); } void process(const grove::IDataNode& input) override { double deltaTime = input.getDouble("deltaTime", 0.016); // Process UI events while (m_io->hasMessages() > 0) { auto msg = m_io->pullMessage(); if (msg.topic == "ui:action") { std::string action = msg.data->getString("action", ""); if (action == "start_game") { startGame(); } } } // Update game logic if (m_gameStarted) { updatePlayer(deltaTime); renderPlayer(); } } // ... other IModule methods ... private: void startGame() { m_gameStarted = true; m_playerX = 960.0; m_playerY = 540.0; m_logger->info("Game started!"); } void updatePlayer(double deltaTime) { // Update player position, etc. } void renderPlayer() { // Publish sprite to renderer auto sprite = std::make_unique("sprite"); sprite->setDouble("x", m_playerX); sprite->setDouble("y", m_playerY); sprite->setInt("textureId", 0); // Player texture sprite->setInt("layer", 10); m_io->publish("render:sprite", std::move(sprite)); } std::shared_ptr m_logger; grove::IIO* m_io = nullptr; bool m_gameStarted = false; double m_playerX = 0.0; double m_playerY = 0.0; }; extern "C" { grove::IModule* createModule() { return new GameLogic(); } void destroyModule(grove::IModule* m) { delete m; } } ``` --- ## Interactive Demo - Try It First! **Before reading further**, try the full stack interactive demo to see everything in action: ```bash # Windows run_full_stack_demo.bat # Linux ./build/tests/test_full_stack_interactive ``` **What it demonstrates:** - ✅ BgfxRenderer rendering sprites and text - ✅ UIModule with buttons, sliders, panels - ✅ InputModule capturing mouse and keyboard - ✅ Complete IIO message flow (input → UI → game → render) - ✅ Hit testing and click detection (raycasting 2D) - ✅ Game logic responding to UI events **Interactive controls:** - Click buttons to spawn/clear sprites - Drag slider to change speed - Press SPACE to spawn from keyboard - Press ESC to exit **See:** [tests/visual/README_FULL_STACK.md](../tests/visual/README_FULL_STACK.md) for details. --- ## Building Your First Game ### Step-by-Step Tutorial #### 1. Create Project Structure ```bash mkdir MyGame && cd MyGame git init git submodule add external/GroveEngine mkdir -p src/modules assets/ui ``` #### 2. Create CMakeLists.txt (See [Complete Application Example](#complete-application-example)) #### 3. Create UI Layout `assets/ui/main_menu.json`: ```json { "widgets": [ { "type": "UIPanel", "id": "main_panel", "x": 0, "y": 0, "width": 1920, "height": 1080, "color": 2155905279 }, { "type": "UIButton", "id": "play_button", "x": 860, "y": 500, "width": 200, "height": 60, "text": "Play", "action": "start_game" }, { "type": "UIButton", "id": "quit_button", "x": 860, "y": 580, "width": 200, "height": 60, "text": "Quit", "action": "quit_game" } ] } ``` #### 4. Build and Run ```bash cmake -B build cmake --build build -j4 ./build/mygame ``` --- ## Advanced Topics ### Hot-Reload Workflow ```bash # Terminal 1: Run game ./build/mygame # Terminal 2: Edit and rebuild module vim src/modules/GameLogic.cpp cmake --build build --target GameLogic # Game automatically reloads GameLogic with state preserved! ``` ### Performance Optimization #### Sprite Batching ```cpp // Instead of publishing 100 individual sprites: for (auto& enemy : enemies) { auto sprite = std::make_unique("sprite"); sprite->setDouble("x", enemy.x); // ... io->publish("render:sprite", std::move(sprite)); // 100 IIO messages } // Publish as batch: auto batch = std::make_unique("batch"); auto sprites = std::make_unique("sprites"); for (auto& enemy : enemies) { auto sprite = std::make_unique("sprite"); sprite->setDouble("x", enemy.x); // ... sprites->setChild(enemy.id, std::move(sprite)); } batch->setChild("sprites", std::move(sprites)); io->publish("render:sprite:batch", std::move(batch)); // 1 IIO message ``` #### Low-Frequency Subscriptions ```cpp // For non-critical analytics/logging grove::SubscriptionConfig config; config.batchInterval = 1000; // Batch messages for 1 second io->subscribeLowFreq("analytics:*", config); ``` ### Multi-Module Communication Patterns #### Request-Response Pattern ```cpp // Module A: Request pathfinding auto request = std::make_unique("request"); request->setString("requestId", "path_123"); request->setDouble("startX", 10.0); request->setDouble("startY", 20.0); io->publish("pathfinding:request", std::move(request)); // Module B: Respond with path moduleB_io->subscribe("pathfinding:request"); // ... compute path ... auto response = std::make_unique("response"); response->setString("requestId", "path_123"); // ... add path data ... moduleB_io->publish("pathfinding:response", std::move(response)); // Module A: Receive response moduleA_io->subscribe("pathfinding:response"); ``` #### Event Aggregation ```cpp // Multiple modules publish events io->publish("combat:damage", damageData); io->publish("combat:kill", killData); io->publish("combat:levelup", levelupData); // Analytics module aggregates all combat events analyticsIO->subscribe("combat:*"); ``` ### Testing Strategies #### Headless Testing ```cpp // Configure renderer in headless mode JsonDataNode config("config"); config.setString("backend", "noop"); // No actual rendering config.setBool("vsync", false); renderer->setConfiguration(config, io, nullptr); // Run tests without window for (int i = 0; i < 1000; i++) { // Simulate game logic renderer->process(input); } ``` #### Integration Tests See `tests/integration/IT_014_ui_module_integration.cpp` for complete example. --- ## Troubleshooting ### Common Issues #### Module not loading ```bash # Check module exports nm -D build/modules/GameLogic.so | grep createModule # Should show: createModule and destroyModule # Check dependencies ldd build/modules/GameLogic.so ``` #### IIO messages not received ```cpp // Verify subscription BEFORE publishing io->subscribe("render:sprite"); // Must be before publish // Check topic patterns io->subscribe("render:*"); // Matches render:sprite, render:text io->subscribe("render:sprite:*"); // Only matches render:sprite:batch ``` #### Hot-reload state loss ```cpp // Ensure ALL state is serialized in getState() std::unique_ptr MyModule::getState() { auto state = std::make_unique("state"); // DON'T FORGET any member variables! state->setInt("score", m_score); state->setDouble("playerX", m_playerX); state->setDouble("playerY", m_playerY); // ... return state; } ``` --- ## Additional Resources - **[USER_GUIDE.md](USER_GUIDE.md)** - Core module system documentation - **[BgfxRenderer README](../modules/BgfxRenderer/README.md)** - Renderer details - **[InputModule README](../modules/InputModule/README.md)** - Input details - **[CLAUDE.md](../CLAUDE.md)** - Development context for Claude Code - **Integration Tests** - `tests/integration/IT_014_*.cpp`, `IT_015_*.cpp` --- **GroveEngine - Build modular, hot-reloadable games with ease** 🌳