GroveEngine/modules/InputModule
StillHammer 1b7703f07b feat(IIO)!: BREAKING CHANGE - Callback-based message dispatch
## 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>
2026-01-19 14:19:27 +07:00
..
Backends Migration Gitea 2025-12-04 20:15:53 +08:00
Core Migration Gitea 2025-12-04 20:15:53 +08:00
CMakeLists.txt fix: UIModule button interaction + JsonDataNode array children support 2026-01-05 18:23:16 +07:00
InputModule.cpp fix: Resolve bgfx Frame 1 crash on Windows DLL + MinGW GCC 15 compatibility 2025-12-30 11:03:06 +07:00
InputModule.h fix: Resolve bgfx Frame 1 crash on Windows DLL + MinGW GCC 15 compatibility 2025-12-30 11:03:06 +07:00
README.md feat(IIO)!: BREAKING CHANGE - Callback-based message dispatch 2026-01-19 14:19:27 +07:00

InputModule

⚠️ Development Stage: Phase 1-3 complete (mouse, keyboard). Gamepad support is Phase 2 (TODO). See main README.

Module de capture et conversion d'événements d'entrée (clavier, souris) vers le système IIO de GroveEngine.

Vue d'ensemble

L'InputModule permet un découplage complet entre la source d'input (SDL, GLFW, Windows, etc.) et les modules consommateurs (UI, Game Logic, etc.). Il capture les événements natifs de la plateforme, les normalise, et les publie via le système IIO pour que d'autres modules puissent y réagir.

Architecture

SDL_Event (native) → InputModule.feedEvent()
                          ↓
                    [Event Buffer]
                          ↓
                   InputModule.process()
                          ↓
                  SDLBackend.convert()
                          ↓
                   [Generic InputEvent]
                          ↓
                 InputConverter.publish()
                          ↓
                      IIO Messages

Composants

  • InputModule - Module principal IModule
  • InputState - État courant des inputs (touches pressées, position souris)
  • SDLBackend - Conversion SDL_Event → InputEvent générique
  • InputConverter - Conversion InputEvent → messages IIO

Topics IIO publiés

Mouse Events

Topic Payload Description
input:mouse:move {x, y} Position souris (coordonnées écran)
input:mouse:button {button, pressed, x, y} Click souris (button: 0=left, 1=middle, 2=right)
input:mouse:wheel {delta} Molette souris (delta: + = haut, - = bas)

Keyboard Events

Topic Payload Description
input:keyboard:key {scancode, pressed, repeat, shift, ctrl, alt} Touche clavier
input:keyboard:text {text} Saisie texte UTF-8 (pour TextInput)

Gamepad Events (Phase 2)

Topic Payload Description
input:gamepad:button {id, button, pressed} Bouton gamepad
input:gamepad:axis {id, axis, value} Axe analogique (-1.0 à 1.0)
input:gamepad:connected {id, name, connected} Gamepad connecté/déconnecté

Configuration

{
  "backend": "sdl",
  "enableMouse": true,
  "enableKeyboard": true,
  "enableGamepad": false,
  "logLevel": "info"
}

Usage

Dans un test ou jeu

#include <grove/ModuleLoader.h>
#include <grove/IntraIOManager.h>
#include "modules/InputModule/InputModule.h"

// Setup
auto& ioManager = grove::IntraIOManager::getInstance();
auto inputIO = ioManager.createInstance("input_module");
auto gameIO = ioManager.createInstance("game_logic");

// Load module
grove::ModuleLoader inputLoader;
auto inputModule = inputLoader.load("../modules/InputModule.dll", "input_module");

// Configure
grove::JsonDataNode config("config");
config.setString("backend", "sdl");
config.setBool("enableMouse", true);
config.setBool("enableKeyboard", true);
inputModule->setConfiguration(config, inputIO.get(), nullptr);

// Subscribe to events with callback handlers
gameIO->subscribe("input:mouse:button", [this](const grove::Message& msg) {
    int button = msg.data->getInt("button", 0);
    bool pressed = msg.data->getBool("pressed", false);
    double x = msg.data->getDouble("x", 0.0);
    double y = msg.data->getDouble("y", 0.0);
    handleMouseButton(button, pressed, x, y);
});

gameIO->subscribe("input:keyboard:key", [this](const grove::Message& msg) {
    int scancode = msg.data->getInt("scancode", 0);
    bool pressed = msg.data->getBool("pressed", false);
    handleKeyboard(scancode, pressed);
});

// Main loop
while (running) {
    // 1. Poll SDL events
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        inputModule->feedEvent(&event);  // Thread-safe injection
    }

    // 2. Process InputModule (converts buffered events → IIO)
    grove::JsonDataNode input("input");
    inputModule->process(input);

    // 3. Process game logic - pull and auto-dispatch to callbacks
    while (gameIO->hasMessages() > 0) {
        gameIO->pullAndDispatch();  // Callbacks invoked automatically
    }
}

// Cleanup
inputModule->shutdown();

Avec SequentialModuleSystem

auto moduleSystem = ModuleSystemFactory::create("sequential");

// Load modules in order
auto inputModule = loadModule("InputModule.dll");
auto uiModule = loadModule("UIModule.dll");
auto gameModule = loadModule("GameLogic.dll");

moduleSystem->registerModule("input", std::move(inputModule));
moduleSystem->registerModule("ui", std::move(uiModule));
moduleSystem->registerModule("game", std::move(gameModule));

// Get InputModule for feedEvent()
auto* inputPtr = /* get pointer via queryModule or similar */;

// Main loop
while (running) {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        inputPtr->feedEvent(&event);
    }

    // Process all modules in order (input → ui → game)
    moduleSystem->processModules(deltaTime);
}

Hot-Reload Support

L'InputModule supporte le hot-reload avec préservation de l'état :

État préservé

  • Position souris (x, y)
  • État des boutons souris (left, middle, right)
  • Statistiques (frameCount, eventsProcessed)

État non préservé

  • Buffer d'événements (SDL_Event non sérialisable)
  • Touches clavier actuellement pressées

Note: Perdre au max 1 frame d'événements pendant le reload (~16ms à 60fps).

Tests

Test unitaire visuel

# Compile
cmake -B build -DGROVE_BUILD_INPUT_MODULE=ON
cmake --build build --target test_30_input_module

# Run
./build/test_30_input_module

Interactions:

  • Bouger la souris pour voir input:mouse:move
  • Cliquer pour voir input:mouse:button
  • Scroller pour voir input:mouse:wheel
  • Taper des touches pour voir input:keyboard:key
  • Taper du texte pour voir input:keyboard:text

Test d'intégration

# Compile avec UIModule
cmake -B build -DGROVE_BUILD_INPUT_MODULE=ON -DGROVE_BUILD_UI_MODULE=ON
cmake --build build

# Run integration test
cd build
ctest -R InputUIIntegration --output-on-failure

Performance

Objectifs

  • < 0.1ms par frame pour process() (100 events/frame max)
  • 0 allocation dynamique dans process() (sauf IIO messages)
  • Thread-safe feedEvent() avec lock minimal

Monitoring

auto health = inputModule->getHealthStatus();
std::cout << "Status: " << health->getString("status", "") << "\n";
std::cout << "Frames: " << health->getInt("frameCount", 0) << "\n";
std::cout << "Events processed: " << health->getInt("eventsProcessed", 0) << "\n";
std::cout << "Events/frame: " << health->getDouble("eventsPerFrame", 0.0) << "\n";

Dépendances

  • GroveEngine Core - IModule, IIO, IDataNode
  • SDL2 - Backend pour capture d'événements
  • nlohmann/json - Parsing configuration JSON
  • spdlog - Logging

Phases d'implémentation

  • Phase 1 - Souris + Clavier (SDL Backend)
  • 📋 Phase 2 - Gamepad Support (voir plans/later/PLAN_INPUT_MODULE_PHASE2_GAMEPAD.md)
  • Phase 3 - Test d'intégration avec UIModule

Fichiers

modules/InputModule/
├── README.md                  # Ce fichier
├── CMakeLists.txt             # Configuration build
├── InputModule.h              # Module principal
├── InputModule.cpp
├── Core/
│   ├── InputState.h           # État des inputs
│   ├── InputState.cpp
│   ├── InputConverter.h       # Generic → IIO
│   └── InputConverter.cpp
└── Backends/
    ├── SDLBackend.h           # SDL → Generic
    └── SDLBackend.cpp

tests/
├── visual/
│   └── test_30_input_module.cpp       # Test visuel interactif
└── integration/
    └── IT_015_input_ui_integration.cpp # Test intégration complet

Extensibilité

Pour ajouter un nouveau backend (GLFW, Win32, etc.) :

  1. Créer Backends/YourBackend.h/cpp
  2. Implémenter convert(NativeEvent, InputEvent&)
  3. Modifier InputModule::process() pour utiliser le nouveau backend
  4. Configurer via backend: "your_backend" dans la config JSON

Le reste du système (InputConverter, IIO topics) reste inchangé ! 🚀

Licence

Voir LICENSE à la racine du projet.