From 65ee33a141c8a50dca2b0b7812e32167e6991f6e Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Sat, 25 Oct 2025 18:25:12 +0200 Subject: [PATCH] desktop fix --- .../include/cardboy/sdk/input_state.hpp | 7 + .../cardboy/backend/desktop_backend.hpp | 83 ++++----- .../backends/desktop/src/desktop_backend.cpp | 175 +++++++----------- 3 files changed, 117 insertions(+), 148 deletions(-) diff --git a/Firmware/sdk/backend_interface/include/cardboy/sdk/input_state.hpp b/Firmware/sdk/backend_interface/include/cardboy/sdk/input_state.hpp index d9a5071..f9d617f 100644 --- a/Firmware/sdk/backend_interface/include/cardboy/sdk/input_state.hpp +++ b/Firmware/sdk/backend_interface/include/cardboy/sdk/input_state.hpp @@ -11,6 +11,13 @@ struct InputState { bool b = false; bool select = false; bool start = false; + + bool operator==(const InputState& other) const { + return up == other.up && left == other.left && right == other.right && down == other.down && a == other.a && + b == other.b && select == other.select && start == other.start; + } + + bool operator!=(const InputState& other) const { return !(*this == other); } }; } // 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 f131e53..c656ec1 100644 --- a/Firmware/sdk/backends/desktop/include/cardboy/backend/desktop_backend.hpp +++ b/Firmware/sdk/backends/desktop/include/cardboy/backend/desktop_backend.hpp @@ -7,8 +7,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -89,13 +89,13 @@ private: class DesktopNotificationCenter final : public cardboy::sdk::INotificationCenter { public: - void pushNotification(Notification notification) override; + void pushNotification(Notification notification) override; [[nodiscard]] std::uint32_t revision() const override; [[nodiscard]] std::vector recent(std::size_t limit) const override; - void markAllRead() override; - void clear() override; - void removeById(std::uint64_t id) override; - void removeByExternalId(std::uint64_t externalId) override; + void markAllRead() override; + void clear() override; + void removeById(std::uint64_t id) override; + void removeByExternalId(std::uint64_t externalId) override; private: static constexpr std::size_t kMaxEntries = 8; @@ -106,38 +106,30 @@ private: std::uint32_t revisionCounter = 0; }; -class DesktopEventBus final : public cardboy::sdk::IEventBus { +class DesktopLoopHooks final : public cardboy::sdk::ILoopHooks { public: - explicit DesktopEventBus(DesktopRuntime& owner); + explicit DesktopLoopHooks(DesktopRuntime& owner); - 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 onLoopIteration() override; private: - DesktopRuntime& runtime; - std::mutex mutex; - std::condition_variable cv; - std::uint32_t pendingBits = 0; + DesktopRuntime& runtime; }; -class DesktopScopedEventBus final : public cardboy::sdk::IAppEventBus { +class DesktopEventBus final : public cardboy::sdk::IEventBus { public: - explicit DesktopScopedEventBus(cardboy::sdk::IEventBus& bus); - - void post(const cardboy::sdk::AppEvent& event) override; - bool pop(cardboy::sdk::AppEvent& outEvent) override; - void clear() override; + void post(const cardboy::sdk::AppEvent& event) override; + std::optional pop(std::optional timeout_ms = std::nullopt) override; private: - cardboy::sdk::IEventBus& globalBus; - std::mutex mutex; + std::mutex mutex; + std::condition_variable cv; std::deque queue; }; class DesktopTimerService final : public cardboy::sdk::ITimerService { public: - DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IAppEventBus& appBus); + DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& eventBus); ~DesktopTimerService() override; cardboy::sdk::AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat) override; @@ -146,24 +138,24 @@ public: private: struct TimerRecord { - cardboy::sdk::AppTimerHandle handle = cardboy::sdk::kInvalidAppTimer; + cardboy::sdk::AppTimerHandle handle = cardboy::sdk::kInvalidAppTimer; std::chrono::steady_clock::time_point due; - std::chrono::milliseconds interval{0}; - bool repeat = false; - bool active = true; + std::chrono::milliseconds interval{0}; + bool repeat = false; + bool active = true; }; void workerLoop(); void wakeWorker(); void cleanupInactive(); - DesktopRuntime& runtime; - cardboy::sdk::IAppEventBus& appEventBus; - std::mutex mutex; - std::condition_variable cv; - std::vector timers; - bool stopWorker = false; - std::thread worker; + DesktopRuntime& runtime; + cardboy::sdk::IEventBus& eventBus; + std::mutex mutex; + std::condition_variable cv; + std::vector timers; + bool stopWorker = false; + std::thread worker; cardboy::sdk::AppTimerHandle nextHandle = 1; }; @@ -171,11 +163,11 @@ class DesktopAppServiceProvider final : public cardboy::sdk::IAppServiceProvider public: DesktopAppServiceProvider(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus); - [[nodiscard]] std::unique_ptr createScopedServices(std::uint64_t generation) override; + [[nodiscard]] std::unique_ptr + createScopedServices(std::uint64_t generation) override; private: struct ScopedServices final : cardboy::sdk::AppScopedServices { - std::unique_ptr ownedEventBus; std::unique_ptr ownedTimer; }; @@ -254,16 +246,17 @@ private: bool running = true; bool clearNextFrame = true; - DesktopBuzzer buzzerService; - DesktopBattery batteryService; - DesktopStorage storageService; - DesktopRandom randomService; - DesktopHighResClock highResService; - DesktopFilesystem filesystemService; - DesktopEventBus eventBusService; + DesktopBuzzer buzzerService; + DesktopBattery batteryService; + DesktopStorage storageService; + DesktopRandom randomService; + DesktopHighResClock highResService; + DesktopFilesystem filesystemService; + DesktopEventBus eventBusService; DesktopAppServiceProvider appServiceProvider; DesktopNotificationCenter notificationService; - cardboy::sdk::Services services{}; + DesktopLoopHooks loopHooksService; + cardboy::sdk::Services services{}; }; struct Backend { diff --git a/Firmware/sdk/backends/desktop/src/desktop_backend.cpp b/Firmware/sdk/backends/desktop/src/desktop_backend.cpp index a2abb36..a228875 100644 --- a/Firmware/sdk/backends/desktop/src/desktop_backend.cpp +++ b/Firmware/sdk/backends/desktop/src/desktop_backend.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -19,78 +20,31 @@ namespace { constexpr std::size_t kDesktopEventQueueLimit = 64; } // namespace -DesktopEventBus::DesktopEventBus(DesktopRuntime& owner) : runtime(owner) {} - -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); - } - } -} - -DesktopScopedEventBus::DesktopScopedEventBus(cardboy::sdk::IEventBus& bus) : globalBus(bus) {} - -void DesktopScopedEventBus::post(const cardboy::sdk::AppEvent& event) { - { - std::lock_guard lock(mutex); - if (queue.size() >= kDesktopEventQueueLimit) - queue.pop_front(); - queue.push_back(event); - } - globalBus.signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer)); -} - -bool DesktopScopedEventBus::pop(cardboy::sdk::AppEvent& outEvent) { +void DesktopEventBus::post(const cardboy::sdk::AppEvent& event) { std::lock_guard lock(mutex); - if (queue.empty()) - return false; - outEvent = queue.front(); + queue.push_back(event); + cv.notify_one(); +} + +std::optional DesktopEventBus::pop(std::optional timeout_ms) { + std::unique_lock lock(mutex); + if (queue.empty()) { + if (!timeout_ms) { + return std::nullopt; + } + auto timeout = std::chrono::milliseconds(*timeout_ms); + cv.wait_for(lock, timeout, [this] { return !queue.empty(); }); + if (queue.empty()) { + return std::nullopt; + } + } + auto event = queue.front(); queue.pop_front(); - return true; + return event; } -void DesktopScopedEventBus::clear() { - std::lock_guard lock(mutex); - queue.clear(); -} - -DesktopTimerService::DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IAppEventBus& appBus) : - runtime(owner), appEventBus(appBus) { +DesktopTimerService::DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& eventBus) : + runtime(owner), eventBus(eventBus) { worker = std::thread(&DesktopTimerService::workerLoop, this); } @@ -109,8 +63,8 @@ cardboy::sdk::AppTimerHandle DesktopTimerService::scheduleTimer(std::uint32_t de const auto now = std::chrono::steady_clock::now(); const auto effectiveDelayMs = std::chrono::milliseconds(delay_ms); const auto dueTime = delay_ms == 0 ? now : now + effectiveDelayMs; - const auto interval = std::chrono::milliseconds(std::max(1, repeat ? std::max(delay_ms, 1u) - : std::max(delay_ms, 1u))); + const auto interval = std::chrono::milliseconds( + std::max(1, repeat ? std::max(delay_ms, 1u) : std::max(delay_ms, 1u))); TimerRecord record{}; record.repeat = repeat; @@ -119,7 +73,7 @@ cardboy::sdk::AppTimerHandle DesktopTimerService::scheduleTimer(std::uint32_t de record.active = true; { - std::lock_guard lock(mutex); + std::lock_guard lock(mutex); cardboy::sdk::AppTimerHandle handle = cardboy::sdk::kInvalidAppTimer; do { handle = nextHandle++; @@ -163,9 +117,8 @@ void DesktopTimerService::workerLoop() { continue; } - auto nextIt = std::min_element(timers.begin(), timers.end(), [](const TimerRecord& a, const TimerRecord& b) { - return a.due < b.due; - }); + auto nextIt = std::min_element(timers.begin(), timers.end(), + [](const TimerRecord& a, const TimerRecord& b) { return a.due < b.due; }); if (nextIt == timers.end()) continue; @@ -189,7 +142,7 @@ void DesktopTimerService::workerLoop() { cardboy::sdk::AppEvent event{}; event.timestamp_ms = runtime.clock.millis(); event.data = timerEvent; - appEventBus.post(event); + eventBus.post(event); lock.lock(); continue; @@ -209,13 +162,12 @@ void DesktopTimerService::cleanupInactive() { DesktopAppServiceProvider::DesktopAppServiceProvider(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus) : runtime(owner), eventBus(bus) {} -std::unique_ptr DesktopAppServiceProvider::createScopedServices(std::uint64_t generation) { - (void)generation; - auto scoped = std::make_unique(); - scoped->ownedEventBus = std::make_unique(eventBus); - scoped->events = scoped->ownedEventBus.get(); - scoped->ownedTimer = std::make_unique(runtime, *scoped->ownedEventBus); - scoped->timer = scoped->ownedTimer.get(); +std::unique_ptr +DesktopAppServiceProvider::createScopedServices(std::uint64_t generation) { + (void) generation; + auto scoped = std::make_unique(); + scoped->ownedTimer = std::make_unique(runtime, eventBus); + scoped->timer = scoped->ownedTimer.get(); return scoped; } @@ -312,7 +264,7 @@ std::vector DesktopNotificationCenter:: void DesktopNotificationCenter::markAllRead() { std::lock_guard lock(mutex); bool changed = false; - for (auto& entry : entries) { + for (auto& entry: entries) { if (entry.unread) { entry.unread = false; changed = true; @@ -337,8 +289,8 @@ void DesktopNotificationCenter::removeById(std::uint64_t id) { bool removed = false; for (auto it = entries.begin(); it != entries.end();) { if (it->id == id) { - it = entries.erase(it); - removed = true; + it = entries.erase(it); + removed = true; } else { ++it; } @@ -354,8 +306,8 @@ void DesktopNotificationCenter::removeByExternalId(std::uint64_t externalId) { bool removed = false; for (auto it = entries.begin(); it != entries.end();) { if (it->externalId == externalId) { - it = entries.erase(it); - removed = true; + it = entries.erase(it); + removed = true; } else { ++it; } @@ -364,6 +316,13 @@ void DesktopNotificationCenter::removeByExternalId(std::uint64_t externalId) { ++revisionCounter; } +DesktopLoopHooks::DesktopLoopHooks(DesktopRuntime& owner) : runtime(owner) {} + +void DesktopLoopHooks::onLoopIteration() { + runtime.processEvents(); + runtime.presentIfNeeded(); +} + DesktopFramebuffer::DesktopFramebuffer(DesktopRuntime& runtime) : runtime(runtime) {} int DesktopFramebuffer::width_impl() const { return cardboy::sdk::kDisplayWidth; } @@ -391,26 +350,29 @@ 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; + const auto oldState = state; + bool handled = true; switch (key) { case sf::Keyboard::Key::Up: + case sf::Keyboard::Key::W: state.up = pressed; break; case sf::Keyboard::Key::Down: + case sf::Keyboard::Key::S: state.down = pressed; break; case sf::Keyboard::Key::Left: + case sf::Keyboard::Key::A: state.left = pressed; break; case sf::Keyboard::Key::Right: + case sf::Keyboard::Key::D: state.right = pressed; break; case sf::Keyboard::Key::Z: - case sf::Keyboard::Key::A: state.a = pressed; break; case sf::Keyboard::Key::X: - case sf::Keyboard::Key::S: state.b = pressed; break; case sf::Keyboard::Key::Space: @@ -423,8 +385,11 @@ void DesktopInput::handleKey(sf::Keyboard::Key key, bool pressed) { handled = false; break; } - if (handled) - runtime.eventBusService.signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Input)); + if (handled && oldState != state) { + cardboy::sdk::AppButtonEvent btnEvent{oldState, state}; + cardboy::sdk::AppEvent event{runtime.clock.millis(), btnEvent}; + runtime.eventBusService.post(event); + } } DesktopClock::DesktopClock(DesktopRuntime& runtime) : runtime(runtime), start(std::chrono::steady_clock::now()) {} @@ -442,24 +407,28 @@ DesktopRuntime::DesktopRuntime() : "Cardboy Desktop"), texture(), sprite(texture), pixels(static_cast(cardboy::sdk::kDisplayWidth * cardboy::sdk::kDisplayHeight) * 4, 0), - framebuffer(*this), input(*this), clock(*this), eventBusService(*this), appServiceProvider(*this, eventBusService) { + framebuffer(*this), input(*this), clock(*this), eventBusService(), appServiceProvider(*this, eventBusService), + loopHooksService(*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"); sprite.setTexture(texture, true); sprite.setScale(sf::Vector2f{static_cast(kPixelScale), static_cast(kPixelScale)}); - clearPixels(true); + clearPixels(false); presentIfNeeded(); + window.requestFocus(); - services.buzzer = &buzzerService; - services.battery = &batteryService; - services.storage = &storageService; - services.random = &randomService; - services.highResClock = &highResService; - services.filesystem = &filesystemService; - services.eventBus = &eventBusService; - services.appServices = &appServiceProvider; - services.loopHooks = nullptr; + std::cout << "Desktop window initialized and presented." << std::endl; + + services.buzzer = &buzzerService; + services.battery = &batteryService; + services.storage = &storageService; + services.random = &randomService; + services.highResClock = &highResService; + services.filesystem = &filesystemService; + services.eventBus = &eventBusService; + services.appServices = &appServiceProvider; + services.loopHooks = &loopHooksService; services.notifications = ¬ificationService; }