From 088c6e47bde9d331246803c70d3ce882dd428e4f Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Sun, 12 Oct 2025 20:34:05 +0200 Subject: [PATCH] better timers --- .../components/backend-esp/CMakeLists.txt | 1 + .../include/cardboy/backend/esp/buttons.hpp | 16 ++-- .../include/cardboy/backend/esp/event_bus.hpp | 27 ++++++ .../include/cardboy/backend/esp_backend.hpp | 2 + .../components/backend-esp/src/buttons.cpp | 7 +- .../backend-esp/src/esp_backend.cpp | 4 + .../components/backend-esp/src/event_bus.cpp | 81 ++++++++++++++++ Firmware/sdk/backend_interface/CMakeLists.txt | 1 + .../include/cardboy/sdk/event_bus.hpp | 42 +++++++++ .../include/cardboy/sdk/services.hpp | 3 + .../cardboy/backend/desktop_backend.hpp | 27 ++++++ .../backends/desktop/src/desktop_backend.cpp | 93 ++++++++++++++++++- .../include/cardboy/sdk/app_framework.hpp | 1 + .../core/include/cardboy/sdk/app_system.hpp | 2 + Firmware/sdk/core/src/app_system.cpp | 44 ++++++--- 15 files changed, 332 insertions(+), 19 deletions(-) create mode 100644 Firmware/components/backend-esp/include/cardboy/backend/esp/event_bus.hpp create mode 100644 Firmware/components/backend-esp/src/event_bus.cpp create mode 100644 Firmware/sdk/backend_interface/include/cardboy/sdk/event_bus.hpp diff --git a/Firmware/components/backend-esp/CMakeLists.txt b/Firmware/components/backend-esp/CMakeLists.txt index 7575df0..a297a08 100644 --- a/Firmware/components/backend-esp/CMakeLists.txt +++ b/Firmware/components/backend-esp/CMakeLists.txt @@ -4,6 +4,7 @@ idf_component_register( "src/buttons.cpp" "src/buzzer.cpp" "src/esp_backend.cpp" + "src/event_bus.cpp" "src/display.cpp" "src/fs_helper.cpp" "src/i2c_global.cpp" diff --git a/Firmware/components/backend-esp/include/cardboy/backend/esp/buttons.hpp b/Firmware/components/backend-esp/include/cardboy/backend/esp/buttons.hpp index bdfd519..625a9b7 100644 --- a/Firmware/components/backend-esp/include/cardboy/backend/esp/buttons.hpp +++ b/Firmware/components/backend-esp/include/cardboy/backend/esp/buttons.hpp @@ -5,9 +5,11 @@ #ifndef BUTTONS_HPP #define BUTTONS_HPP +#include "cardboy/sdk/event_bus.hpp" + +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include typedef enum { BTN_START = 1 << 1, @@ -25,16 +27,18 @@ public: static Buttons& get(); void pooler(); // FIXME: uint8_t get_pressed(); - void install_isr(); - void register_listener(TaskHandle_t task); + void install_isr(); + void register_listener(TaskHandle_t task); + void setEventBus(cardboy::sdk::IEventBus* bus); - TaskHandle_t _pooler_task; + TaskHandle_t _pooler_task; private: Buttons(); - volatile uint8_t _current; - volatile TaskHandle_t _listener = nullptr; + volatile uint8_t _current; + volatile TaskHandle_t _listener = nullptr; + cardboy::sdk::IEventBus* _eventBus = nullptr; }; diff --git a/Firmware/components/backend-esp/include/cardboy/backend/esp/event_bus.hpp b/Firmware/components/backend-esp/include/cardboy/backend/esp/event_bus.hpp new file mode 100644 index 0000000..b7b1430 --- /dev/null +++ b/Firmware/components/backend-esp/include/cardboy/backend/esp/event_bus.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/timers.h" + +namespace cardboy::backend::esp { + +class EventBus final : public cardboy::sdk::IEventBus { +public: + EventBus(); + ~EventBus() override; + + void signal(std::uint32_t bits) override; + void signalFromISR(std::uint32_t bits) override; + std::uint32_t wait(std::uint32_t mask, std::uint32_t timeout_ms) override; + void scheduleTimerSignal(std::uint32_t delay_ms) override; + void cancelTimerSignal() override; + +private: + EventGroupHandle_t group; + TimerHandle_t timer; +}; + +} // namespace cardboy::backend::esp diff --git a/Firmware/components/backend-esp/include/cardboy/backend/esp_backend.hpp b/Firmware/components/backend-esp/include/cardboy/backend/esp_backend.hpp index 4982b2d..3071d85 100644 --- a/Firmware/components/backend-esp/include/cardboy/backend/esp_backend.hpp +++ b/Firmware/components/backend-esp/include/cardboy/backend/esp_backend.hpp @@ -2,6 +2,7 @@ #include #include "cardboy/backend/esp/display.hpp" +#include "cardboy/backend/esp/event_bus.hpp" #include "cardboy/sdk/platform.hpp" #include "cardboy/sdk/services.hpp" @@ -68,6 +69,7 @@ private: std::unique_ptr highResClockService; std::unique_ptr powerService; std::unique_ptr filesystemService; + std::unique_ptr eventBus; cardboy::sdk::Services services{}; }; diff --git a/Firmware/components/backend-esp/src/buttons.cpp b/Firmware/components/backend-esp/src/buttons.cpp index 5e64f2a..c952097 100644 --- a/Firmware/components/backend-esp/src/buttons.cpp +++ b/Firmware/components/backend-esp/src/buttons.cpp @@ -6,14 +6,15 @@ #include #include -#include "cardboy/backend/esp/power_helper.hpp" #include +#include "cardboy/backend/esp/power_helper.hpp" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "cardboy/backend/esp/config.hpp" #include "cardboy/backend/esp/i2c_global.hpp" +#include "cardboy/sdk/event_bus.hpp" static i2c_master_dev_handle_t dev_handle; static inline i2c_device_config_t dev_cfg = { @@ -92,9 +93,13 @@ void Buttons::pooler() { i2c_master_transmit_receive(dev_handle, ®, sizeof(reg), reinterpret_cast(&buffer), 1, -1)); if (_listener) xTaskNotifyGive(_listener); + if (_eventBus) + _eventBus->signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Input)); } } uint8_t Buttons::get_pressed() { return _current; } void Buttons::install_isr() { gpio_isr_handler_add(EXP_INT, wakeup, nullptr); } void Buttons::register_listener(TaskHandle_t task) { _listener = task; } + +void Buttons::setEventBus(cardboy::sdk::IEventBus* bus) { _eventBus = bus; } diff --git a/Firmware/components/backend-esp/src/esp_backend.cpp b/Firmware/components/backend-esp/src/esp_backend.cpp index 3ccc70c..60d4d13 100644 --- a/Firmware/components/backend-esp/src/esp_backend.cpp +++ b/Firmware/components/backend-esp/src/esp_backend.cpp @@ -138,6 +138,7 @@ EspRuntime::EspRuntime() : framebuffer(), input(), clock() { highResClockService = std::make_unique(); powerService = std::make_unique(); filesystemService = std::make_unique(); + eventBus = std::make_unique(); services.buzzer = buzzerService.get(); services.battery = batteryService.get(); @@ -146,6 +147,9 @@ EspRuntime::EspRuntime() : framebuffer(), input(), clock() { services.highResClock = highResClockService.get(); services.powerManager = powerService.get(); services.filesystem = filesystemService.get(); + services.eventBus = eventBus.get(); + + Buttons::get().setEventBus(eventBus.get()); } EspRuntime::~EspRuntime() = default; diff --git a/Firmware/components/backend-esp/src/event_bus.cpp b/Firmware/components/backend-esp/src/event_bus.cpp new file mode 100644 index 0000000..54171cd --- /dev/null +++ b/Firmware/components/backend-esp/src/event_bus.cpp @@ -0,0 +1,81 @@ +#include "cardboy/backend/esp/event_bus.hpp" + +#include "cardboy/sdk/event_bus.hpp" + +#include "freertos/portmacro.h" + +#include + +namespace cardboy::backend::esp { + +namespace { +[[nodiscard]] TickType_t toTicks(std::uint32_t timeout_ms) { + if (timeout_ms == cardboy::sdk::IEventBus::kWaitForever) + return portMAX_DELAY; + return pdMS_TO_TICKS(timeout_ms); +} +} // namespace + +static void timerCallback(TimerHandle_t handle) { + auto* bus = static_cast(pvTimerGetTimerID(handle)); + if (bus) + bus->signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer)); +} + +EventBus::EventBus() : + group(xEventGroupCreate()), timer(xTimerCreate("EventBusTimer", pdMS_TO_TICKS(1), pdFALSE, this, timerCallback)) {} + +EventBus::~EventBus() { + if (timer) + xTimerDelete(timer, portMAX_DELAY); + if (group) + vEventGroupDelete(group); +} + +void EventBus::signal(std::uint32_t bits) { + if (!group || bits == 0) + return; + xEventGroupSetBits(group, bits); +} + +void EventBus::signalFromISR(std::uint32_t bits) { + if (!group || bits == 0) + return; + BaseType_t higherPriorityTaskWoken = pdFALSE; + xEventGroupSetBitsFromISR(group, bits, &higherPriorityTaskWoken); + if (higherPriorityTaskWoken == pdTRUE) + portYIELD_FROM_ISR(higherPriorityTaskWoken); +} + +std::uint32_t EventBus::wait(std::uint32_t mask, std::uint32_t timeout_ms) { + if (!group || mask == 0) + return 0; + const EventBits_t bits = xEventGroupWaitBits(group, mask, pdTRUE, pdFALSE, toTicks(timeout_ms)); + return static_cast(bits & mask); +} + +void EventBus::scheduleTimerSignal(std::uint32_t delay_ms) { + if (!timer) + return; + xTimerStop(timer, 0); + + if (delay_ms == cardboy::sdk::IEventBus::kWaitForever) + return; + + if (delay_ms == 0) { + signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer)); + return; + } + + const TickType_t ticks = std::max(pdMS_TO_TICKS(delay_ms), 1); + if (xTimerChangePeriod(timer, ticks, 0) == pdPASS) + xTimerStart(timer, 0); +} + +void EventBus::cancelTimerSignal() { + if (!timer) + return; + xTimerStop(timer, 0); +} + +} // namespace cardboy::backend::esp diff --git a/Firmware/sdk/backend_interface/CMakeLists.txt b/Firmware/sdk/backend_interface/CMakeLists.txt index 8223082..b226b3e 100644 --- a/Firmware/sdk/backend_interface/CMakeLists.txt +++ b/Firmware/sdk/backend_interface/CMakeLists.txt @@ -16,6 +16,7 @@ target_sources(cardboy_backend_interface ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/backend/backend_interface.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/backend.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/display_spec.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/event_bus.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/input_state.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/platform.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/services.hpp diff --git a/Firmware/sdk/backend_interface/include/cardboy/sdk/event_bus.hpp b/Firmware/sdk/backend_interface/include/cardboy/sdk/event_bus.hpp new file mode 100644 index 0000000..4149e38 --- /dev/null +++ b/Firmware/sdk/backend_interface/include/cardboy/sdk/event_bus.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace cardboy::sdk { + +enum class EventBusSignal : std::uint32_t { + None = 0, + Input = 1u << 0, + Timer = 1u << 1, + External = 1u << 2, +}; + +inline EventBusSignal operator|(EventBusSignal lhs, EventBusSignal rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +inline EventBusSignal& operator|=(EventBusSignal& lhs, EventBusSignal rhs) { + lhs = lhs | rhs; + return lhs; +} + +inline EventBusSignal operator&(EventBusSignal lhs, EventBusSignal rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +inline std::uint32_t to_event_bits(EventBusSignal signal) { return static_cast(signal); } + +class IEventBus { +public: + static constexpr std::uint32_t kWaitForever = 0xFFFFFFFFu; + + virtual ~IEventBus() = default; + + virtual void signal(std::uint32_t bits) = 0; + virtual void signalFromISR(std::uint32_t bits) = 0; + virtual std::uint32_t wait(std::uint32_t mask, std::uint32_t timeout_ms) = 0; + virtual void scheduleTimerSignal(std::uint32_t delay_ms) = 0; + virtual void cancelTimerSignal() = 0; +}; + +} // namespace cardboy::sdk diff --git a/Firmware/sdk/backend_interface/include/cardboy/sdk/services.hpp b/Firmware/sdk/backend_interface/include/cardboy/sdk/services.hpp index 2c84bd6..c03d546 100644 --- a/Firmware/sdk/backend_interface/include/cardboy/sdk/services.hpp +++ b/Firmware/sdk/backend_interface/include/cardboy/sdk/services.hpp @@ -1,5 +1,7 @@ #pragma once +#include "cardboy/sdk/event_bus.hpp" + #include #include #include @@ -82,6 +84,7 @@ struct Services { IHighResClock* highResClock = nullptr; IPowerManager* powerManager = nullptr; IFilesystem* filesystem = nullptr; + IEventBus* eventBus = nullptr; }; } // namespace cardboy::sdk diff --git a/Firmware/sdk/backends/desktop/include/cardboy/backend/desktop_backend.hpp b/Firmware/sdk/backends/desktop/include/cardboy/backend/desktop_backend.hpp index 816c745..cb36180 100644 --- a/Firmware/sdk/backends/desktop/include/cardboy/backend/desktop_backend.hpp +++ b/Firmware/sdk/backends/desktop/include/cardboy/backend/desktop_backend.hpp @@ -1,14 +1,17 @@ #pragma once +#include "cardboy/sdk/event_bus.hpp" #include "cardboy/sdk/platform.hpp" #include "cardboy/sdk/services.hpp" #include #include #include +#include #include #include #include +#include #include #include #include @@ -92,6 +95,29 @@ private: bool mounted = false; }; +class DesktopEventBus final : public cardboy::sdk::IEventBus { +public: + explicit DesktopEventBus(DesktopRuntime& owner); + ~DesktopEventBus() override; + + void signal(std::uint32_t bits) override; + void signalFromISR(std::uint32_t bits) override; + std::uint32_t wait(std::uint32_t mask, std::uint32_t timeout_ms) override; + void scheduleTimerSignal(std::uint32_t delay_ms) override; + void cancelTimerSignal() override; + +private: + DesktopRuntime& runtime; + std::mutex mutex; + std::condition_variable cv; + std::uint32_t pendingBits = 0; + + std::mutex timerMutex; + std::condition_variable timerCv; + std::thread timerThread; + bool timerCancel = false; +}; + class DesktopFramebuffer final : public cardboy::sdk::FramebufferFacade { public: explicit DesktopFramebuffer(DesktopRuntime& runtime); @@ -170,6 +196,7 @@ private: DesktopHighResClock highResService; DesktopPowerManager powerService; DesktopFilesystem filesystemService; + DesktopEventBus eventBusService; cardboy::sdk::Services services{}; }; diff --git a/Firmware/sdk/backends/desktop/src/desktop_backend.cpp b/Firmware/sdk/backends/desktop/src/desktop_backend.cpp index 64a83f2..d0918ef 100644 --- a/Firmware/sdk/backends/desktop/src/desktop_backend.cpp +++ b/Firmware/sdk/backends/desktop/src/desktop_backend.cpp @@ -13,6 +13,92 @@ namespace cardboy::backend::desktop { +DesktopEventBus::DesktopEventBus(DesktopRuntime& owner) : runtime(owner) {} + +DesktopEventBus::~DesktopEventBus() { cancelTimerSignal(); } + +void DesktopEventBus::signal(std::uint32_t bits) { + if (bits == 0) + return; + { + std::lock_guard lock(mutex); + pendingBits |= bits; + } + cv.notify_all(); +} + +void DesktopEventBus::signalFromISR(std::uint32_t bits) { signal(bits); } + +std::uint32_t DesktopEventBus::wait(std::uint32_t mask, std::uint32_t timeout_ms) { + if (mask == 0) + return 0; + + const auto start = std::chrono::steady_clock::now(); + const bool infinite = timeout_ms == cardboy::sdk::IEventBus::kWaitForever; + + while (true) { + { + std::lock_guard lock(mutex); + const std::uint32_t ready = pendingBits & mask; + if (ready != 0) { + pendingBits &= ~mask; + return ready; + } + } + + if (!infinite) { + const auto now = std::chrono::steady_clock::now(); + const auto elapsedMs = std::chrono::duration_cast(now - start).count(); + if (elapsedMs >= static_cast(timeout_ms)) + return 0; + const auto remaining = timeout_ms - static_cast(elapsedMs); + runtime.sleepFor(std::min(remaining, 8)); + } else { + runtime.sleepFor(8); + } + } +} + +void DesktopEventBus::scheduleTimerSignal(std::uint32_t delay_ms) { + cancelTimerSignal(); + + if (delay_ms == cardboy::sdk::IEventBus::kWaitForever) + return; + + if (delay_ms == 0) { + signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer)); + return; + } + + { + std::lock_guard lock(timerMutex); + timerCancel = false; + } + + timerThread = std::thread([this, delay_ms]() { + std::unique_lock lock(timerMutex); + const bool cancelled = + timerCv.wait_for(lock, std::chrono::milliseconds(delay_ms), [this] { return timerCancel; }); + lock.unlock(); + if (!cancelled) + signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer)); + }); +} + +void DesktopEventBus::cancelTimerSignal() { + { + std::lock_guard lock(timerMutex); + timerCancel = true; + } + timerCv.notify_all(); + if (timerThread.joinable()) + timerThread.join(); + { + std::lock_guard lock(timerMutex); + timerCancel = false; + } +} + bool DesktopStorage::readUint32(std::string_view ns, std::string_view key, std::uint32_t& out) { auto it = data.find(composeKey(ns, key)); if (it == data.end()) @@ -90,6 +176,7 @@ DesktopInput::DesktopInput(DesktopRuntime& runtime) : runtime(runtime) {} cardboy::sdk::InputState DesktopInput::readState_impl() { return state; } void DesktopInput::handleKey(sf::Keyboard::Key key, bool pressed) { + bool handled = true; switch (key) { case sf::Keyboard::Key::Up: state.up = pressed; @@ -118,8 +205,11 @@ void DesktopInput::handleKey(sf::Keyboard::Key key, bool pressed) { state.start = pressed; break; default: + handled = false; break; } + if (handled) + runtime.eventBusService.signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Input)); } DesktopClock::DesktopClock(DesktopRuntime& runtime) : runtime(runtime), start(std::chrono::steady_clock::now()) {} @@ -137,7 +227,7 @@ DesktopRuntime::DesktopRuntime() : "Cardboy Desktop"), texture(), sprite(texture), pixels(static_cast(cardboy::sdk::kDisplayWidth * cardboy::sdk::kDisplayHeight) * 4, 0), - framebuffer(*this), input(*this), clock(*this) { + framebuffer(*this), input(*this), clock(*this), eventBusService(*this) { window.setFramerateLimit(60); if (!texture.resize(sf::Vector2u{cardboy::sdk::kDisplayWidth, cardboy::sdk::kDisplayHeight})) throw std::runtime_error("Failed to allocate texture for desktop framebuffer"); @@ -153,6 +243,7 @@ DesktopRuntime::DesktopRuntime() : services.highResClock = &highResService; services.powerManager = &powerService; services.filesystem = &filesystemService; + services.eventBus = &eventBusService; } cardboy::sdk::Services& DesktopRuntime::serviceRegistry() { return services; } diff --git a/Firmware/sdk/core/include/cardboy/sdk/app_framework.hpp b/Firmware/sdk/core/include/cardboy/sdk/app_framework.hpp index ff7f923..4b21338 100644 --- a/Firmware/sdk/core/include/cardboy/sdk/app_framework.hpp +++ b/Firmware/sdk/core/include/cardboy/sdk/app_framework.hpp @@ -63,6 +63,7 @@ struct AppContext { [[nodiscard]] IHighResClock* highResClock() const { return services ? services->highResClock : nullptr; } [[nodiscard]] IPowerManager* powerManager() const { return services ? services->powerManager : nullptr; } [[nodiscard]] IFilesystem* filesystem() const { return services ? services->filesystem : nullptr; } + [[nodiscard]] IEventBus* eventBus() const { return services ? services->eventBus : nullptr; } void requestAppSwitchByIndex(std::size_t index) { pendingAppIndex = index; diff --git a/Firmware/sdk/core/include/cardboy/sdk/app_system.hpp b/Firmware/sdk/core/include/cardboy/sdk/app_system.hpp index 7654488..f363ee0 100644 --- a/Firmware/sdk/core/include/cardboy/sdk/app_system.hpp +++ b/Firmware/sdk/core/include/cardboy/sdk/app_system.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include "app_framework.hpp" #include @@ -51,6 +52,7 @@ private: void clearTimersForCurrentApp(); TimerRecord* findTimer(AppTimerHandle handle); bool handlePendingSwitchRequest(); + void notifyEventBus(EventBusSignal signal); AppContext context; std::vector> factories; diff --git a/Firmware/sdk/core/src/app_system.cpp b/Firmware/sdk/core/src/app_system.cpp index 5a737b7..4f7a3dd 100644 --- a/Firmware/sdk/core/src/app_system.cpp +++ b/Firmware/sdk/core/src/app_system.cpp @@ -17,8 +17,6 @@ namespace { return state.up || state.down || state.left || state.right || state.a || state.b || state.select || state.start; } -constexpr std::uint32_t kIdlePollMs = 16; - template void statusBarPreSendHook(void* framebuffer, void* userData) { auto* fb = static_cast(framebuffer); @@ -127,13 +125,19 @@ void AppSystem::run() { if (waitMs == 0) continue; - if (waitMs == std::numeric_limits::max()) - waitMs = kIdlePollMs; - else - waitMs = std::min(waitMs, kIdlePollMs); + auto* eventBus = context.eventBus(); + if (!eventBus) + return; - if (waitMs > 0) - context.clock.sleep_ms(waitMs); + const std::uint32_t mask = to_event_bits(EventBusSignal::Input) | to_event_bits(EventBusSignal::Timer); + + if (waitMs == std::numeric_limits::max()) { + eventBus->cancelTimerSignal(); + eventBus->wait(mask, IEventBus::kWaitForever); + } else { + eventBus->scheduleTimerSignal(waitMs); + eventBus->wait(mask, IEventBus::kWaitForever); + } } } @@ -167,24 +171,32 @@ AppTimerHandle AppSystem::scheduleTimer(std::uint32_t delay_ms, bool repeat) { record.repeat = repeat; record.active = true; timers.push_back(record); + notifyEventBus(EventBusSignal::Timer); return record.id; } void AppSystem::cancelTimer(AppTimerHandle handle) { auto* timer = findTimer(handle); - if (timer) - timer->active = false; + if (!timer) + return; + timer->active = false; timers.erase(std::remove_if(timers.begin(), timers.end(), [](const TimerRecord& rec) { return !rec.active; }), timers.end()); + notifyEventBus(EventBusSignal::Timer); } void AppSystem::cancelAllTimers() { + bool changed = false; for (auto& timer: timers) { - if (timer.generation == currentGeneration) + if (timer.generation == currentGeneration && timer.active) { timer.active = false; + changed = true; + } } timers.erase(std::remove_if(timers.begin(), timers.end(), [](const TimerRecord& rec) { return !rec.active; }), timers.end()); + if (changed) + notifyEventBus(EventBusSignal::Timer); } void AppSystem::dispatchEvent(const AppEvent& event) { @@ -229,8 +241,11 @@ std::uint32_t AppSystem::nextTimerDueMs(std::uint32_t now) const { } void AppSystem::clearTimersForCurrentApp() { + const bool hadTimers = !timers.empty(); ++currentGeneration; timers.clear(); + if (hadTimers) + notifyEventBus(EventBusSignal::Timer); } AppSystem::TimerRecord* AppSystem::findTimer(AppTimerHandle handle) { @@ -260,4 +275,11 @@ bool AppSystem::handlePendingSwitchRequest() { return switched; } +void AppSystem::notifyEventBus(EventBusSignal signal) { + if (signal == EventBusSignal::None) + return; + if (auto* bus = context.eventBus()) + bus->signal(to_event_bits(signal)); +} + } // namespace cardboy::sdk