mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 15:17:48 +01:00
desktop fix
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
#include <SFML/Window/Keyboard.hpp>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
@@ -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<Notification> 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<cardboy::sdk::AppEvent> pop(std::optional<std::uint32_t> timeout_ms = std::nullopt) override;
|
||||
|
||||
private:
|
||||
cardboy::sdk::IEventBus& globalBus;
|
||||
std::mutex mutex;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
std::deque<cardboy::sdk::AppEvent> 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<TimerRecord> timers;
|
||||
bool stopWorker = false;
|
||||
std::thread worker;
|
||||
DesktopRuntime& runtime;
|
||||
cardboy::sdk::IEventBus& eventBus;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
std::vector<TimerRecord> 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<cardboy::sdk::AppScopedServices> createScopedServices(std::uint64_t generation) override;
|
||||
[[nodiscard]] std::unique_ptr<cardboy::sdk::AppScopedServices>
|
||||
createScopedServices(std::uint64_t generation) override;
|
||||
|
||||
private:
|
||||
struct ScopedServices final : cardboy::sdk::AppScopedServices {
|
||||
std::unique_ptr<DesktopScopedEventBus> ownedEventBus;
|
||||
std::unique_ptr<DesktopTimerService> 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 {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
@@ -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<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);
|
||||
}
|
||||
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<std::mutex> lock(mutex);
|
||||
if (queue.empty())
|
||||
return false;
|
||||
outEvent = queue.front();
|
||||
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;
|
||||
}
|
||||
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<std::mutex> 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<std::uint32_t>(1, repeat ? std::max(delay_ms, 1u)
|
||||
: std::max(delay_ms, 1u)));
|
||||
const auto interval = std::chrono::milliseconds(
|
||||
std::max<std::uint32_t>(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<std::mutex> lock(mutex);
|
||||
std::lock_guard<std::mutex> 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<cardboy::sdk::AppScopedServices> DesktopAppServiceProvider::createScopedServices(std::uint64_t generation) {
|
||||
(void)generation;
|
||||
auto scoped = std::make_unique<ScopedServices>();
|
||||
scoped->ownedEventBus = std::make_unique<DesktopScopedEventBus>(eventBus);
|
||||
scoped->events = scoped->ownedEventBus.get();
|
||||
scoped->ownedTimer = std::make_unique<DesktopTimerService>(runtime, *scoped->ownedEventBus);
|
||||
scoped->timer = scoped->ownedTimer.get();
|
||||
std::unique_ptr<cardboy::sdk::AppScopedServices>
|
||||
DesktopAppServiceProvider::createScopedServices(std::uint64_t generation) {
|
||||
(void) generation;
|
||||
auto scoped = std::make_unique<ScopedServices>();
|
||||
scoped->ownedTimer = std::make_unique<DesktopTimerService>(runtime, eventBus);
|
||||
scoped->timer = scoped->ownedTimer.get();
|
||||
return scoped;
|
||||
}
|
||||
|
||||
@@ -312,7 +264,7 @@ std::vector<DesktopNotificationCenter::Notification> DesktopNotificationCenter::
|
||||
void DesktopNotificationCenter::markAllRead() {
|
||||
std::lock_guard<std::mutex> 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<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);
|
||||
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<float>(kPixelScale), static_cast<float>(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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user