## Breaking Change
IIO API redesigned from manual pull+if-forest to callback dispatch.
All modules must update their subscribe() calls to pass handlers.
### Before (OLD API)
```cpp
io->subscribe("input:mouse");
void process(...) {
while (io->hasMessages()) {
auto msg = io->pullMessage();
if (msg.topic == "input:mouse") {
handleMouse(msg);
} else if (msg.topic == "input:keyboard") {
handleKeyboard(msg);
}
}
}
```
### After (NEW API)
```cpp
io->subscribe("input:mouse", [this](const Message& msg) {
handleMouse(msg);
});
void process(...) {
while (io->hasMessages()) {
io->pullAndDispatch(); // Callbacks invoked automatically
}
}
```
## Changes
**Core API (include/grove/IIO.h)**
- Added: `using MessageHandler = std::function<void(const Message&)>`
- Changed: `subscribe()` now requires `MessageHandler` callback parameter
- Changed: `subscribeLowFreq()` now requires `MessageHandler` callback
- Removed: `pullMessage()`
- Added: `pullAndDispatch()` - pulls and auto-dispatches to handlers
**Implementation (src/IntraIO.cpp)**
- Store callbacks in `Subscription.handler`
- `pullAndDispatch()` matches topic against ALL subscriptions (not just first)
- Fixed: Regex pattern compilation supports both wildcards (*) and regex (.*)
- Performance: ~1000 msg/s throughput (unchanged from before)
**Files Updated**
- 31 test/module files migrated to callback API (via parallel agents)
- 8 documentation files updated (DEVELOPER_GUIDE, USER_GUIDE, module READMEs)
## Bugs Fixed During Migration
1. **pullAndDispatch() early return bug**: Was only calling FIRST matching handler
- Fix: Loop through ALL subscriptions, invoke all matching handlers
2. **Regex pattern compilation bug**: Pattern "player:.*" failed to match
- Fix: Detect ".*" in pattern → use as regex, otherwise escape and convert wildcards
## Testing
✅ test_11_io_system: PASSED (IIO pub/sub, pattern matching, batching)
✅ test_threaded_module_system: 6/6 PASSED
✅ test_threaded_stress: 5/5 PASSED (50 modules, 100x reload, concurrent ops)
✅ test_12_datanode: PASSED
✅ 10 TopicTree scenarios: 10/10 PASSED
✅ benchmark_e2e: ~1000 msg/s throughput
Total: 23+ tests passing
## Performance Impact
No performance regression from callback dispatch:
- IIO throughput: ~1000 msg/s (same as before)
- ThreadedModuleSystem: Speedup ~1.0x (barrier pattern expected)
## Migration Guide
For all modules using IIO:
1. Update subscribe() calls to include handler lambda
2. Replace pullMessage() loops with pullAndDispatch()
3. Move topic-specific logic from if-forest into callbacks
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
215 lines
8.2 KiB
C++
215 lines
8.2 KiB
C++
/**
|
|
* Test: UIButton avec texture PNG chargée depuis un fichier
|
|
* Ce test montre qu'on peut mettre une VRAIE image sur un bouton
|
|
*/
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_syswm.h>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <fstream>
|
|
|
|
#include "BgfxRendererModule.h"
|
|
#include "UIModule/UIModule.h"
|
|
#include "../modules/BgfxRenderer/Resources/ResourceCache.h"
|
|
#include "../modules/BgfxRenderer/RHI/RHITypes.h"
|
|
#include "../modules/BgfxRenderer/RHI/RHIDevice.h"
|
|
#include <grove/JsonDataNode.h>
|
|
#include <grove/IntraIOManager.h>
|
|
#include <grove/IntraIO.h>
|
|
#include <spdlog/spdlog.h>
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
#include <bgfx/bgfx.h>
|
|
#include <vector>
|
|
|
|
using namespace grove;
|
|
|
|
int main(int argc, char* argv[]) {
|
|
spdlog::set_level(spdlog::level::info);
|
|
auto logger = spdlog::stdout_color_mt("TexturedButtonTest");
|
|
|
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
|
std::cerr << "SDL_Init failed" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
SDL_Window* window = SDL_CreateWindow(
|
|
"Textured Button Test - Gradient",
|
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
|
800, 600, SDL_WINDOW_SHOWN
|
|
);
|
|
|
|
SDL_SysWMinfo wmi;
|
|
SDL_VERSION(&wmi.version);
|
|
SDL_GetWindowWMInfo(window, &wmi);
|
|
|
|
// Create IIO instances - IMPORTANT: game publishes input, ui subscribes and publishes render commands
|
|
auto gameIO = IntraIOManager::getInstance().createInstance("game");
|
|
auto uiIO = IntraIOManager::getInstance().createInstance("ui");
|
|
auto rendererIO = IntraIOManager::getInstance().createInstance("renderer");
|
|
|
|
gameIO->subscribe("ui:hover", [](const Message& msg) {
|
|
// Hover events (not logged to avoid spam)
|
|
});
|
|
gameIO->subscribe("ui:click", [&logger](const Message& msg) {
|
|
logger->info("🖱️ BOUTON CLICKÉ!");
|
|
});
|
|
gameIO->subscribe("ui:action", [&logger](const Message& msg) {
|
|
logger->info("🖱️ ACTION!");
|
|
});
|
|
|
|
// Initialize BgfxRenderer WITH 3 TEXTURES loaded via config
|
|
auto renderer = std::make_unique<BgfxRendererModule>();
|
|
{
|
|
JsonDataNode config("config");
|
|
config.setDouble("nativeWindowHandle",
|
|
static_cast<double>(reinterpret_cast<uintptr_t>(wmi.info.win.window)));
|
|
config.setInt("windowWidth", 800);
|
|
config.setInt("windowHeight", 600);
|
|
// Load 3 textures
|
|
config.setString("texture1", "../../assets/textures/5oxaxt1vo2f91.jpg"); // Car
|
|
config.setString("texture2", "../../assets/textures/1f440.png"); // Eyes
|
|
config.setString("texture3", "../../assets/textures/IconDesigner.png"); // Icon
|
|
|
|
renderer->setConfiguration(config, rendererIO.get(), nullptr);
|
|
}
|
|
|
|
logger->info("✓ Loaded 3 textures (IDs: 1, 2, 3)");
|
|
|
|
// Initialize UIModule with 3 TEXTURED BUTTONS
|
|
auto ui = std::make_unique<UIModule>();
|
|
{
|
|
JsonDataNode config("config");
|
|
config.setInt("windowWidth", 800);
|
|
config.setInt("windowHeight", 600);
|
|
|
|
nlohmann::json layoutJson = {
|
|
{"id", "root"},
|
|
{"type", "panel"},
|
|
{"x", 0}, {"y", 0},
|
|
{"width", 800}, {"height", 600}, // Full screen invisible panel (just container)
|
|
{"style", {
|
|
{"bgColor", "0x00000000"} // Fully transparent - just a container
|
|
}},
|
|
{"children", {
|
|
{
|
|
{"id", "btn_car"},
|
|
{"type", "button"},
|
|
{"x", 50},
|
|
{"y", 50},
|
|
{"width", 400},
|
|
{"height", 200},
|
|
{"text", ""},
|
|
{"onClick", "car_action"},
|
|
{"style", {
|
|
{"normal", {{"textureId", 1}, {"bgColor", "0xFFFFFFFF"}}},
|
|
{"hover", {{"textureId", 1}, {"bgColor", "0xFFFF00FF"}}},
|
|
{"pressed", {{"textureId", 1}, {"bgColor", "0x888888FF"}}}
|
|
}}
|
|
},
|
|
{
|
|
{"id", "btn_eyes"},
|
|
{"type", "button"},
|
|
{"x", 50},
|
|
{"y", 270},
|
|
{"width", 250},
|
|
{"height", 200},
|
|
{"text", ""},
|
|
{"onClick", "eyes_action"},
|
|
{"style", {
|
|
{"normal", {{"textureId", 2}, {"bgColor", "0xFFFFFFFF"}}},
|
|
{"hover", {{"textureId", 2}, {"bgColor", "0x00FFFFFF"}}},
|
|
{"pressed", {{"textureId", 2}, {"bgColor", "0x888888FF"}}}
|
|
}}
|
|
},
|
|
{
|
|
{"id", "btn_icon"},
|
|
{"type", "button"},
|
|
{"x", 320},
|
|
{"y", 270},
|
|
{"width", 250},
|
|
{"height", 200},
|
|
{"text", ""},
|
|
{"onClick", "icon_action"},
|
|
{"style", {
|
|
{"normal", {{"textureId", 3}, {"bgColor", "0xFFFFFFFF"}}},
|
|
{"hover", {{"textureId", 3}, {"bgColor", "0xFF00FFFF"}}},
|
|
{"pressed", {{"textureId", 3}, {"bgColor", "0x888888FF"}}}
|
|
}}
|
|
}
|
|
}}
|
|
};
|
|
|
|
auto layoutNode = std::make_unique<JsonDataNode>("layout", layoutJson);
|
|
config.setChild("layout", std::move(layoutNode));
|
|
|
|
ui->setConfiguration(config, uiIO.get(), nullptr);
|
|
logger->info("✓ UIModule configured with 3 textured buttons!");
|
|
}
|
|
|
|
logger->info("\n╔════════════════════════════════════════╗");
|
|
logger->info("║ 3 BOUTONS AVEC TEXTURES ║");
|
|
logger->info("╠════════════════════════════════════════╣");
|
|
logger->info("║ Button 1: Car (textureId=1) ║");
|
|
logger->info("║ Button 2: Eyes (textureId=2) ║");
|
|
logger->info("║ Button 3: Icon (textureId=3) ║");
|
|
logger->info("║ Press ESC to exit ║");
|
|
logger->info("╚════════════════════════════════════════╝\n");
|
|
|
|
bool running = true;
|
|
while (running) {
|
|
SDL_Event e;
|
|
while (SDL_PollEvent(&e)) {
|
|
if (e.type == SDL_QUIT ||
|
|
(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
|
|
running = false;
|
|
}
|
|
|
|
// Forward mouse events
|
|
if (e.type == SDL_MOUSEMOTION) {
|
|
auto mouseMsg = std::make_unique<JsonDataNode>("mouse");
|
|
mouseMsg->setDouble("x", static_cast<double>(e.motion.x));
|
|
mouseMsg->setDouble("y", static_cast<double>(e.motion.y));
|
|
gameIO->publish("input:mouse:move", std::move(mouseMsg));
|
|
}
|
|
else if (e.type == SDL_MOUSEBUTTONDOWN || e.type == SDL_MOUSEBUTTONUP) {
|
|
auto mouseMsg = std::make_unique<JsonDataNode>("mouse");
|
|
mouseMsg->setInt("button", e.button.button);
|
|
mouseMsg->setBool("pressed", e.type == SDL_MOUSEBUTTONDOWN);
|
|
mouseMsg->setDouble("x", static_cast<double>(e.button.x));
|
|
mouseMsg->setDouble("y", static_cast<double>(e.button.y));
|
|
gameIO->publish("input:mouse:button", std::move(mouseMsg));
|
|
}
|
|
}
|
|
|
|
// Dispatch UI events (callbacks handle logging)
|
|
while (gameIO->hasMessages() > 0) {
|
|
gameIO->pullAndDispatch();
|
|
}
|
|
|
|
// Update modules
|
|
JsonDataNode input("input");
|
|
input.setDouble("deltaTime", 0.016);
|
|
ui->process(input);
|
|
renderer->process(input);
|
|
|
|
SDL_Delay(16);
|
|
}
|
|
|
|
logger->info("Cleaning up...");
|
|
|
|
// Textures are managed by ResourceCache, will be cleaned up in renderer->shutdown()
|
|
ui->shutdown();
|
|
renderer->shutdown();
|
|
|
|
IntraIOManager::getInstance().removeInstance("game");
|
|
IntraIOManager::getInstance().removeInstance("ui");
|
|
IntraIOManager::getInstance().removeInstance("renderer");
|
|
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
|
|
logger->info("Test complete!");
|
|
return 0;
|
|
}
|