desktop fix

This commit is contained in:
2025-10-25 18:25:12 +02:00
parent 0e69debf39
commit 65ee33a141
3 changed files with 117 additions and 148 deletions

View File

@@ -11,6 +11,13 @@ struct InputState {
bool b = false; bool b = false;
bool select = false; bool select = false;
bool start = 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 } // namespace cardboy::sdk

View File

@@ -7,8 +7,8 @@
#include <SFML/Window/Keyboard.hpp> #include <SFML/Window/Keyboard.hpp>
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
#include <deque>
#include <cstdint> #include <cstdint>
#include <deque>
#include <filesystem> #include <filesystem>
#include <limits> #include <limits>
#include <memory> #include <memory>
@@ -106,38 +106,30 @@ private:
std::uint32_t revisionCounter = 0; std::uint32_t revisionCounter = 0;
}; };
class DesktopEventBus final : public cardboy::sdk::IEventBus { class DesktopLoopHooks final : public cardboy::sdk::ILoopHooks {
public: public:
explicit DesktopEventBus(DesktopRuntime& owner); explicit DesktopLoopHooks(DesktopRuntime& owner);
void signal(std::uint32_t bits) override; void onLoopIteration() override;
void signalFromISR(std::uint32_t bits) override;
std::uint32_t wait(std::uint32_t mask, std::uint32_t timeout_ms) override;
private: private:
DesktopRuntime& runtime; DesktopRuntime& runtime;
std::mutex mutex;
std::condition_variable cv;
std::uint32_t pendingBits = 0;
}; };
class DesktopScopedEventBus final : public cardboy::sdk::IAppEventBus { class DesktopEventBus final : public cardboy::sdk::IEventBus {
public: public:
explicit DesktopScopedEventBus(cardboy::sdk::IEventBus& bus);
void post(const cardboy::sdk::AppEvent& event) override; void post(const cardboy::sdk::AppEvent& event) override;
bool pop(cardboy::sdk::AppEvent& outEvent) override; std::optional<cardboy::sdk::AppEvent> pop(std::optional<std::uint32_t> timeout_ms = std::nullopt) override;
void clear() override;
private: private:
cardboy::sdk::IEventBus& globalBus;
std::mutex mutex; std::mutex mutex;
std::condition_variable cv;
std::deque<cardboy::sdk::AppEvent> queue; std::deque<cardboy::sdk::AppEvent> queue;
}; };
class DesktopTimerService final : public cardboy::sdk::ITimerService { class DesktopTimerService final : public cardboy::sdk::ITimerService {
public: public:
DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IAppEventBus& appBus); DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& eventBus);
~DesktopTimerService() override; ~DesktopTimerService() override;
cardboy::sdk::AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat) override; cardboy::sdk::AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat) override;
@@ -158,7 +150,7 @@ private:
void cleanupInactive(); void cleanupInactive();
DesktopRuntime& runtime; DesktopRuntime& runtime;
cardboy::sdk::IAppEventBus& appEventBus; cardboy::sdk::IEventBus& eventBus;
std::mutex mutex; std::mutex mutex;
std::condition_variable cv; std::condition_variable cv;
std::vector<TimerRecord> timers; std::vector<TimerRecord> timers;
@@ -171,11 +163,11 @@ class DesktopAppServiceProvider final : public cardboy::sdk::IAppServiceProvider
public: public:
DesktopAppServiceProvider(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus); DesktopAppServiceProvider(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus);
[[nodiscard]] std::unique_ptr<cardboy::sdk::AppScopedServices> createScopedServices(std::uint64_t generation) override; [[nodiscard]] std::unique_ptr<cardboy::sdk::AppScopedServices>
createScopedServices(std::uint64_t generation) override;
private: private:
struct ScopedServices final : cardboy::sdk::AppScopedServices { struct ScopedServices final : cardboy::sdk::AppScopedServices {
std::unique_ptr<DesktopScopedEventBus> ownedEventBus;
std::unique_ptr<DesktopTimerService> ownedTimer; std::unique_ptr<DesktopTimerService> ownedTimer;
}; };
@@ -263,6 +255,7 @@ private:
DesktopEventBus eventBusService; DesktopEventBus eventBusService;
DesktopAppServiceProvider appServiceProvider; DesktopAppServiceProvider appServiceProvider;
DesktopNotificationCenter notificationService; DesktopNotificationCenter notificationService;
DesktopLoopHooks loopHooksService;
cardboy::sdk::Services services{}; cardboy::sdk::Services services{};
}; };

View File

@@ -9,6 +9,7 @@
#include <chrono> #include <chrono>
#include <cstdlib> #include <cstdlib>
#include <ctime> #include <ctime>
#include <iostream>
#include <stdexcept> #include <stdexcept>
#include <system_error> #include <system_error>
#include <utility> #include <utility>
@@ -19,78 +20,31 @@ namespace {
constexpr std::size_t kDesktopEventQueueLimit = 64; constexpr std::size_t kDesktopEventQueueLimit = 64;
} // namespace } // namespace
DesktopEventBus::DesktopEventBus(DesktopRuntime& owner) : runtime(owner) {} void DesktopEventBus::post(const cardboy::sdk::AppEvent& event) {
void DesktopEventBus::signal(std::uint32_t bits) {
if (bits == 0)
return;
{
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> 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<std::mutex> 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<std::chrono::milliseconds>(now - start).count();
if (elapsedMs >= static_cast<std::int64_t>(timeout_ms))
return 0;
const auto remaining = timeout_ms - static_cast<std::uint32_t>(elapsedMs);
runtime.sleepFor(std::min<std::uint32_t>(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<std::mutex> lock(mutex);
if (queue.size() >= kDesktopEventQueueLimit)
queue.pop_front();
queue.push_back(event); queue.push_back(event);
cv.notify_one();
}
std::optional<cardboy::sdk::AppEvent> DesktopEventBus::pop(std::optional<std::uint32_t> timeout_ms) {
std::unique_lock<std::mutex> lock(mutex);
if (queue.empty()) {
if (!timeout_ms) {
return std::nullopt;
} }
globalBus.signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer)); auto timeout = std::chrono::milliseconds(*timeout_ms);
} cv.wait_for(lock, timeout, [this] { return !queue.empty(); });
if (queue.empty()) {
bool DesktopScopedEventBus::pop(cardboy::sdk::AppEvent& outEvent) { return std::nullopt;
std::lock_guard<std::mutex> lock(mutex); }
if (queue.empty()) }
return false; auto event = queue.front();
outEvent = queue.front();
queue.pop_front(); queue.pop_front();
return true; return event;
} }
void DesktopScopedEventBus::clear() { DesktopTimerService::DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& eventBus) :
std::lock_guard<std::mutex> lock(mutex); runtime(owner), eventBus(eventBus) {
queue.clear();
}
DesktopTimerService::DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IAppEventBus& appBus) :
runtime(owner), appEventBus(appBus) {
worker = std::thread(&DesktopTimerService::workerLoop, this); 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 now = std::chrono::steady_clock::now();
const auto effectiveDelayMs = std::chrono::milliseconds(delay_ms); const auto effectiveDelayMs = std::chrono::milliseconds(delay_ms);
const auto dueTime = delay_ms == 0 ? now : now + effectiveDelayMs; const auto dueTime = delay_ms == 0 ? now : now + effectiveDelayMs;
const auto interval = std::chrono::milliseconds(std::max<std::uint32_t>(1, repeat ? std::max(delay_ms, 1u) const auto interval = std::chrono::milliseconds(
: std::max(delay_ms, 1u))); std::max<std::uint32_t>(1, repeat ? std::max(delay_ms, 1u) : std::max(delay_ms, 1u)));
TimerRecord record{}; TimerRecord record{};
record.repeat = repeat; record.repeat = repeat;
@@ -163,9 +117,8 @@ void DesktopTimerService::workerLoop() {
continue; continue;
} }
auto nextIt = std::min_element(timers.begin(), timers.end(), [](const TimerRecord& a, const TimerRecord& b) { auto nextIt = std::min_element(timers.begin(), timers.end(),
return a.due < b.due; [](const TimerRecord& a, const TimerRecord& b) { return a.due < b.due; });
});
if (nextIt == timers.end()) if (nextIt == timers.end())
continue; continue;
@@ -189,7 +142,7 @@ void DesktopTimerService::workerLoop() {
cardboy::sdk::AppEvent event{}; cardboy::sdk::AppEvent event{};
event.timestamp_ms = runtime.clock.millis(); event.timestamp_ms = runtime.clock.millis();
event.data = timerEvent; event.data = timerEvent;
appEventBus.post(event); eventBus.post(event);
lock.lock(); lock.lock();
continue; continue;
@@ -209,12 +162,11 @@ void DesktopTimerService::cleanupInactive() {
DesktopAppServiceProvider::DesktopAppServiceProvider(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus) : DesktopAppServiceProvider::DesktopAppServiceProvider(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus) :
runtime(owner), eventBus(bus) {} runtime(owner), eventBus(bus) {}
std::unique_ptr<cardboy::sdk::AppScopedServices> DesktopAppServiceProvider::createScopedServices(std::uint64_t generation) { std::unique_ptr<cardboy::sdk::AppScopedServices>
(void)generation; DesktopAppServiceProvider::createScopedServices(std::uint64_t generation) {
(void) generation;
auto scoped = std::make_unique<ScopedServices>(); auto scoped = std::make_unique<ScopedServices>();
scoped->ownedEventBus = std::make_unique<DesktopScopedEventBus>(eventBus); scoped->ownedTimer = std::make_unique<DesktopTimerService>(runtime, eventBus);
scoped->events = scoped->ownedEventBus.get();
scoped->ownedTimer = std::make_unique<DesktopTimerService>(runtime, *scoped->ownedEventBus);
scoped->timer = scoped->ownedTimer.get(); scoped->timer = scoped->ownedTimer.get();
return scoped; return scoped;
} }
@@ -312,7 +264,7 @@ std::vector<DesktopNotificationCenter::Notification> DesktopNotificationCenter::
void DesktopNotificationCenter::markAllRead() { void DesktopNotificationCenter::markAllRead() {
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);
bool changed = false; bool changed = false;
for (auto& entry : entries) { for (auto& entry: entries) {
if (entry.unread) { if (entry.unread) {
entry.unread = false; entry.unread = false;
changed = true; changed = true;
@@ -364,6 +316,13 @@ void DesktopNotificationCenter::removeByExternalId(std::uint64_t externalId) {
++revisionCounter; ++revisionCounter;
} }
DesktopLoopHooks::DesktopLoopHooks(DesktopRuntime& owner) : runtime(owner) {}
void DesktopLoopHooks::onLoopIteration() {
runtime.processEvents();
runtime.presentIfNeeded();
}
DesktopFramebuffer::DesktopFramebuffer(DesktopRuntime& runtime) : runtime(runtime) {} DesktopFramebuffer::DesktopFramebuffer(DesktopRuntime& runtime) : runtime(runtime) {}
int DesktopFramebuffer::width_impl() const { return cardboy::sdk::kDisplayWidth; } 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; } cardboy::sdk::InputState DesktopInput::readState_impl() { return state; }
void DesktopInput::handleKey(sf::Keyboard::Key key, bool pressed) { void DesktopInput::handleKey(sf::Keyboard::Key key, bool pressed) {
const auto oldState = state;
bool handled = true; bool handled = true;
switch (key) { switch (key) {
case sf::Keyboard::Key::Up: case sf::Keyboard::Key::Up:
case sf::Keyboard::Key::W:
state.up = pressed; state.up = pressed;
break; break;
case sf::Keyboard::Key::Down: case sf::Keyboard::Key::Down:
case sf::Keyboard::Key::S:
state.down = pressed; state.down = pressed;
break; break;
case sf::Keyboard::Key::Left: case sf::Keyboard::Key::Left:
case sf::Keyboard::Key::A:
state.left = pressed; state.left = pressed;
break; break;
case sf::Keyboard::Key::Right: case sf::Keyboard::Key::Right:
case sf::Keyboard::Key::D:
state.right = pressed; state.right = pressed;
break; break;
case sf::Keyboard::Key::Z: case sf::Keyboard::Key::Z:
case sf::Keyboard::Key::A:
state.a = pressed; state.a = pressed;
break; break;
case sf::Keyboard::Key::X: case sf::Keyboard::Key::X:
case sf::Keyboard::Key::S:
state.b = pressed; state.b = pressed;
break; break;
case sf::Keyboard::Key::Space: case sf::Keyboard::Key::Space:
@@ -423,8 +385,11 @@ void DesktopInput::handleKey(sf::Keyboard::Key key, bool pressed) {
handled = false; handled = false;
break; break;
} }
if (handled) if (handled && oldState != state) {
runtime.eventBusService.signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Input)); 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()) {} DesktopClock::DesktopClock(DesktopRuntime& runtime) : runtime(runtime), start(std::chrono::steady_clock::now()) {}
@@ -442,14 +407,18 @@ DesktopRuntime::DesktopRuntime() :
"Cardboy Desktop"), "Cardboy Desktop"),
texture(), sprite(texture), texture(), sprite(texture),
pixels(static_cast<std::size_t>(cardboy::sdk::kDisplayWidth * cardboy::sdk::kDisplayHeight) * 4, 0), pixels(static_cast<std::size_t>(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); window.setFramerateLimit(60);
if (!texture.resize(sf::Vector2u{cardboy::sdk::kDisplayWidth, cardboy::sdk::kDisplayHeight})) if (!texture.resize(sf::Vector2u{cardboy::sdk::kDisplayWidth, cardboy::sdk::kDisplayHeight}))
throw std::runtime_error("Failed to allocate texture for desktop framebuffer"); throw std::runtime_error("Failed to allocate texture for desktop framebuffer");
sprite.setTexture(texture, true); sprite.setTexture(texture, true);
sprite.setScale(sf::Vector2f{static_cast<float>(kPixelScale), static_cast<float>(kPixelScale)}); sprite.setScale(sf::Vector2f{static_cast<float>(kPixelScale), static_cast<float>(kPixelScale)});
clearPixels(true); clearPixels(false);
presentIfNeeded(); presentIfNeeded();
window.requestFocus();
std::cout << "Desktop window initialized and presented." << std::endl;
services.buzzer = &buzzerService; services.buzzer = &buzzerService;
services.battery = &batteryService; services.battery = &batteryService;
@@ -459,7 +428,7 @@ DesktopRuntime::DesktopRuntime() :
services.filesystem = &filesystemService; services.filesystem = &filesystemService;
services.eventBus = &eventBusService; services.eventBus = &eventBusService;
services.appServices = &appServiceProvider; services.appServices = &appServiceProvider;
services.loopHooks = nullptr; services.loopHooks = &loopHooksService;
services.notifications = &notificationService; services.notifications = &notificationService;
} }