/** * TestControllerModule - Simulates game logic for integration tests * * This module: * - Subscribes to UI events (clicks, actions, value changes) * - Responds to UI interactions * - Updates UI state via IIO messages * - Demonstrates bidirectional UI ↔ Game communication */ #include #include #include #include #include #include extern "C" { class TestControllerModule : public grove::IModule { public: TestControllerModule() = default; ~TestControllerModule() override = default; void setConfiguration(const grove::IDataNode& config, grove::IIO* io, grove::ITaskScheduler* scheduler) override { m_io = io; m_uiInstanceId = config.getString("uiInstanceId", "ui_module"); std::cout << "[TestController] Initializing...\n"; // Subscribe to UI events if (m_io) { m_io->subscribe("ui:click"); m_io->subscribe("ui:action"); m_io->subscribe("ui:value_changed"); m_io->subscribe("ui:text_changed"); m_io->subscribe("ui:text_submit"); m_io->subscribe("ui:hover"); m_io->subscribe("ui:focus_gained"); m_io->subscribe("ui:focus_lost"); } std::cout << "[TestController] Subscribed to UI events\n"; } void process(const grove::IDataNode& input) override { if (!m_io) return; m_frameCount++; // Process incoming UI events while (m_io->hasMessages() > 0) { auto msg = m_io->pullMessage(); if (msg.topic == "ui:click") { handleClick(*msg.data); } else if (msg.topic == "ui:action") { handleAction(*msg.data); } else if (msg.topic == "ui:value_changed") { handleValueChanged(*msg.data); } else if (msg.topic == "ui:text_changed") { handleTextChanged(*msg.data); } else if (msg.topic == "ui:text_submit") { handleTextSubmit(*msg.data); } else if (msg.topic == "ui:hover") { handleHover(*msg.data); } else if (msg.topic == "ui:focus_gained") { handleFocusGained(*msg.data); } else if (msg.topic == "ui:focus_lost") { handleFocusLost(*msg.data); } } // Simulate some game logic if (m_frameCount % 120 == 0) { // Every 2 seconds at 60fps // Could update UI here std::cout << "[TestController] Heartbeat at frame " << m_frameCount << "\n"; } } const grove::IDataNode& getConfiguration() override { if (!m_config) { m_config = std::make_unique("config"); } return *m_config; } std::unique_ptr getHealthStatus() override { auto health = std::make_unique("health"); health->setString("status", "healthy"); health->setString("module", "TestControllerModule"); health->setInt("frameCount", static_cast(m_frameCount)); health->setInt("clicksReceived", m_clickCount); health->setInt("actionsReceived", m_actionCount); return health; } void shutdown() override { std::cout << "[TestController] Shutting down...\n"; std::cout << " Total frames: " << m_frameCount << "\n"; std::cout << " Clicks received: " << m_clickCount << "\n"; std::cout << " Actions received: " << m_actionCount << "\n"; std::cout << " Value changes: " << m_valueChangeCount << "\n"; m_io = nullptr; } std::unique_ptr getState() override { auto state = std::make_unique("state"); state->setInt("frameCount", static_cast(m_frameCount)); state->setInt("clickCount", m_clickCount); state->setInt("actionCount", m_actionCount); state->setInt("valueChangeCount", m_valueChangeCount); return state; } void setState(const grove::IDataNode& state) override { m_frameCount = static_cast(state.getInt("frameCount", 0)); m_clickCount = state.getInt("clickCount", 0); m_actionCount = state.getInt("actionCount", 0); m_valueChangeCount = state.getInt("valueChangeCount", 0); } std::string getType() const override { return "TestControllerModule"; } bool isIdle() const override { return true; } private: grove::IIO* m_io = nullptr; std::string m_uiInstanceId; std::unique_ptr m_config; // Stats uint64_t m_frameCount = 0; int m_clickCount = 0; int m_actionCount = 0; int m_valueChangeCount = 0; int m_textChangeCount = 0; void handleClick(const grove::IDataNode& data) { m_clickCount++; std::string widgetId = data.getString("widgetId", ""); double x = data.getDouble("x", 0.0); double y = data.getDouble("y", 0.0); std::cout << "[TestController] Click on '" << widgetId << "' at (" << x << ", " << y << ")\n"; // Example: Update UI visibility based on click if (widgetId == "btn_toggle") { auto visibilityMsg = std::make_unique("set_visible"); visibilityMsg->setString("id", "hidden_panel"); visibilityMsg->setBool("visible", true); m_io->publish("ui:set_visible", std::move(visibilityMsg)); } } void handleAction(const grove::IDataNode& data) { m_actionCount++; std::string action = data.getString("action", ""); std::string widgetId = data.getString("widgetId", ""); std::cout << "[TestController] Action: '" << action << "' from '" << widgetId << "'\n"; // Example: Handle game actions if (action == "game:start") { std::cout << " → Starting game...\n"; } else if (action == "game:quit") { std::cout << " → Quitting game...\n"; } else if (action == "settings:volume") { double volume = data.getDouble("value", 50.0); std::cout << " → Setting volume to " << volume << "\n"; } } void handleValueChanged(const grove::IDataNode& data) { m_valueChangeCount++; std::string widgetId = data.getString("widgetId", ""); if (data.hasChild("value")) { double value = data.getDouble("value", 0.0); std::cout << "[TestController] Value changed: '" << widgetId << "' = " << value << "\n"; } else if (data.hasChild("checked")) { bool checked = data.getBool("checked", false); std::cout << "[TestController] Checkbox changed: '" << widgetId << "' = " << (checked ? "checked" : "unchecked") << "\n"; } } void handleTextChanged(const grove::IDataNode& data) { m_textChangeCount++; std::string widgetId = data.getString("widgetId", ""); std::string text = data.getString("text", ""); std::cout << "[TestController] Text changed: '" << widgetId << "' = \"" << text << "\"\n"; } void handleTextSubmit(const grove::IDataNode& data) { std::string widgetId = data.getString("widgetId", ""); std::string text = data.getString("text", ""); std::cout << "[TestController] Text submitted: '" << widgetId << "' = \"" << text << "\"\n"; // Example: Process username input if (widgetId == "username_input") { std::cout << " → Username entered: " << text << "\n"; } } void handleHover(const grove::IDataNode& data) { std::string widgetId = data.getString("widgetId", ""); bool enter = data.getBool("enter", false); if (enter && !widgetId.empty()) { // Only log enter events to reduce spam // std::cout << "[TestController] Hover: '" << widgetId << "'\n"; } } void handleFocusGained(const grove::IDataNode& data) { std::string widgetId = data.getString("widgetId", ""); std::cout << "[TestController] Focus gained: '" << widgetId << "'\n"; } void handleFocusLost(const grove::IDataNode& data) { std::string widgetId = data.getString("widgetId", ""); std::cout << "[TestController] Focus lost: '" << widgetId << "'\n"; } }; // Module factory functions (standard GroveEngine interface) grove::IModule* createModule() { return new TestControllerModule(); } void destroyModule(grove::IModule* module) { delete module; } } // extern "C"