mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
some refactoring
This commit is contained in:
@@ -17,7 +17,9 @@ namespace apps {
|
||||
|
||||
namespace {
|
||||
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
|
||||
constexpr const char* kClockAppName = "Clock";
|
||||
|
||||
@@ -53,15 +55,12 @@ public:
|
||||
void onStop() override { cancelRefreshTimer(); }
|
||||
|
||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
switch (event.type) {
|
||||
case cardboy::sdk::AppEventType::Button:
|
||||
handleButtonEvent(event.button);
|
||||
break;
|
||||
case cardboy::sdk::AppEventType::Timer:
|
||||
if (event.timer.handle == refreshTimer)
|
||||
updateDisplay();
|
||||
break;
|
||||
}
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||
[this](const AppTimerEvent& timer) {
|
||||
if (timer.handle == refreshTimer)
|
||||
updateDisplay();
|
||||
}));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -151,9 +151,10 @@ constexpr int kMenuSpacing = font16x8::kGlyphHeight + 6;
|
||||
class GameboyApp;
|
||||
|
||||
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppEvent;
|
||||
using cardboy::sdk::AppEventType;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
using cardboy::sdk::AppTimerHandle;
|
||||
using cardboy::sdk::InputState;
|
||||
using cardboy::sdk::kInvalidAppTimer;
|
||||
@@ -252,20 +253,26 @@ public:
|
||||
}
|
||||
|
||||
void handleEvent(const AppEvent& event) override {
|
||||
if (event.type == AppEventType::Timer && event.timer.handle == tickTimer) {
|
||||
tickTimer = kInvalidAppTimer;
|
||||
const uint64_t frameStartUs = nowMicros();
|
||||
performStep();
|
||||
const uint64_t frameEndUs = nowMicros();
|
||||
const uint64_t elapsedUs = (frameEndUs >= frameStartUs) ? (frameEndUs - frameStartUs) : 0;
|
||||
GB_PERF_ONLY(printf("Step took %" PRIu64 " us\n", elapsedUs));
|
||||
scheduleAfterFrame(elapsedUs);
|
||||
bool handled = false;
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this, &handled](const AppTimerEvent& timer) {
|
||||
if (timer.handle != tickTimer)
|
||||
return;
|
||||
handled = true;
|
||||
tickTimer = kInvalidAppTimer;
|
||||
const uint64_t frameStartUs = nowMicros();
|
||||
performStep();
|
||||
const uint64_t frameEndUs = nowMicros();
|
||||
const uint64_t elapsedUs = (frameEndUs >= frameStartUs) ? (frameEndUs - frameStartUs) : 0;
|
||||
GB_PERF_ONLY(printf("Step took %" PRIu64 " us\n", elapsedUs));
|
||||
scheduleAfterFrame(elapsedUs);
|
||||
},
|
||||
[this](const AppButtonEvent&) {
|
||||
frameDelayCarryUs = 0;
|
||||
scheduleNextTick(0);
|
||||
}));
|
||||
if (handled)
|
||||
return;
|
||||
}
|
||||
if (event.type == AppEventType::Button) {
|
||||
frameDelayCarryUs = 0;
|
||||
scheduleNextTick(0);
|
||||
}
|
||||
}
|
||||
|
||||
void performStep() {
|
||||
|
||||
@@ -17,7 +17,9 @@ namespace apps {
|
||||
|
||||
namespace {
|
||||
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
|
||||
constexpr std::uint32_t kRefreshIntervalMs = 100;
|
||||
constexpr std::uint32_t kUnlockHoldMs = 1500;
|
||||
@@ -73,17 +75,14 @@ public:
|
||||
void onStop() override { cancelRefreshTimer(); }
|
||||
|
||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
switch (event.type) {
|
||||
case cardboy::sdk::AppEventType::Button:
|
||||
handleButtonEvent(event.button);
|
||||
break;
|
||||
case cardboy::sdk::AppEventType::Timer:
|
||||
if (event.timer.handle == refreshTimer) {
|
||||
advanceHoldProgress();
|
||||
updateDisplay();
|
||||
}
|
||||
break;
|
||||
}
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||
[this](const AppTimerEvent& timer) {
|
||||
if (timer.handle == refreshTimer) {
|
||||
advanceHoldProgress();
|
||||
updateDisplay();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -17,7 +17,10 @@ namespace apps {
|
||||
|
||||
namespace {
|
||||
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppEvent;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
|
||||
using Framebuffer = typename AppContext::Framebuffer;
|
||||
|
||||
@@ -42,17 +45,14 @@ public:
|
||||
void onStop() override { cancelInactivityTimer(); }
|
||||
|
||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
switch (event.type) {
|
||||
case cardboy::sdk::AppEventType::Button:
|
||||
handleButtonEvent(event.button);
|
||||
break;
|
||||
case cardboy::sdk::AppEventType::Timer:
|
||||
if (event.timer.handle == inactivityTimer) {
|
||||
cancelInactivityTimer();
|
||||
context.requestAppSwitchByName(kLockscreenAppName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||
[this](const AppTimerEvent& timer) {
|
||||
if (timer.handle == inactivityTimer) {
|
||||
cancelInactivityTimer();
|
||||
context.requestAppSwitchByName(kLockscreenAppName);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -39,11 +39,12 @@ public:
|
||||
}
|
||||
|
||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
if (event.type != cardboy::sdk::AppEventType::Button)
|
||||
const auto* buttonEvent = event.button();
|
||||
if (!buttonEvent)
|
||||
return;
|
||||
|
||||
const auto& current = event.button.current;
|
||||
const auto& previous = event.button.previous;
|
||||
const auto& current = buttonEvent->current;
|
||||
const auto& previous = buttonEvent->previous;
|
||||
|
||||
const bool previousAvailable = buzzerAvailable;
|
||||
syncBuzzerState();
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace {
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppEvent;
|
||||
using cardboy::sdk::AppEventType;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
using cardboy::sdk::AppTimerHandle;
|
||||
using cardboy::sdk::InputState;
|
||||
|
||||
@@ -70,14 +70,9 @@ public:
|
||||
void onStop() { cancelMoveTimer(); }
|
||||
|
||||
void handleEvent(const AppEvent& event) {
|
||||
switch (event.type) {
|
||||
case AppEventType::Button:
|
||||
handleButtons(event.button);
|
||||
break;
|
||||
case AppEventType::Timer:
|
||||
handleTimer(event.timer.handle);
|
||||
break;
|
||||
}
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const AppButtonEvent& button) { handleButtons(button); },
|
||||
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); }));
|
||||
renderIfNeeded();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace {
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppEvent;
|
||||
using cardboy::sdk::AppEventType;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
using cardboy::sdk::AppTimerHandle;
|
||||
using cardboy::sdk::InputState;
|
||||
|
||||
@@ -149,14 +149,9 @@ public:
|
||||
void onStop() { cancelTimers(); }
|
||||
|
||||
void handleEvent(const AppEvent& event) {
|
||||
switch (event.type) {
|
||||
case AppEventType::Button:
|
||||
handleButtons(event.button);
|
||||
break;
|
||||
case AppEventType::Timer:
|
||||
handleTimer(event.timer.handle);
|
||||
break;
|
||||
}
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const AppButtonEvent& button) { handleButtons(button); },
|
||||
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); }));
|
||||
renderIfNeeded();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "cardboy/sdk/input_state.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace cardboy::sdk {
|
||||
|
||||
using AppTimerHandle = std::uint32_t;
|
||||
constexpr AppTimerHandle kInvalidAppTimer = 0;
|
||||
|
||||
using TimerClientId = std::uint32_t;
|
||||
constexpr TimerClientId kInvalidTimerClient = 0;
|
||||
|
||||
struct AppButtonEvent {
|
||||
InputState current{};
|
||||
InputState previous{};
|
||||
};
|
||||
|
||||
struct AppTimerEvent {
|
||||
TimerClientId clientId = kInvalidTimerClient;
|
||||
AppTimerHandle handle = kInvalidAppTimer;
|
||||
};
|
||||
|
||||
struct AppEvent {
|
||||
using Data = std::variant<AppButtonEvent, AppTimerEvent>;
|
||||
|
||||
std::uint32_t timestamp_ms = 0;
|
||||
Data data{AppButtonEvent{}};
|
||||
|
||||
[[nodiscard]] bool isButton() const { return std::holds_alternative<AppButtonEvent>(data); }
|
||||
[[nodiscard]] bool isTimer() const { return std::holds_alternative<AppTimerEvent>(data); }
|
||||
|
||||
[[nodiscard]] const AppButtonEvent* button() const { return std::get_if<AppButtonEvent>(&data); }
|
||||
[[nodiscard]] AppButtonEvent* button() { return std::get_if<AppButtonEvent>(&data); }
|
||||
|
||||
[[nodiscard]] const AppTimerEvent* timer() const { return std::get_if<AppTimerEvent>(&data); }
|
||||
[[nodiscard]] AppTimerEvent* timer() { return std::get_if<AppTimerEvent>(&data); }
|
||||
|
||||
template<typename Visitor>
|
||||
decltype(auto) visit(Visitor&& visitor) {
|
||||
return std::visit(std::forward<Visitor>(visitor), data);
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
decltype(auto) visit(Visitor&& visitor) const {
|
||||
return std::visit(std::forward<Visitor>(visitor), data);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
struct Overload : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
Overload(Ts...) -> Overload<Ts...>;
|
||||
|
||||
template<typename... Ts>
|
||||
constexpr auto overload(Ts&&... ts) {
|
||||
return Overload<std::decay_t<Ts>...>{std::forward<Ts>(ts)...};
|
||||
}
|
||||
|
||||
} // namespace cardboy::sdk
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "cardboy/sdk/app_events.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace cardboy::sdk {
|
||||
@@ -35,8 +37,8 @@ public:
|
||||
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;
|
||||
virtual void postEvent(const AppEvent& event) = 0;
|
||||
virtual bool popEvent(AppEvent& outEvent) = 0;
|
||||
};
|
||||
|
||||
} // namespace cardboy::sdk
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "cardboy/sdk/event_bus.hpp"
|
||||
#include "cardboy/sdk/loop_hooks.hpp"
|
||||
#include "cardboy/sdk/timer_service.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -100,6 +101,7 @@ struct Services {
|
||||
IHighResClock* highResClock = nullptr;
|
||||
IFilesystem* filesystem = nullptr;
|
||||
IEventBus* eventBus = nullptr;
|
||||
ITimerService* timer = nullptr;
|
||||
ILoopHooks* loopHooks = nullptr;
|
||||
INotificationCenter* notifications = nullptr;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "cardboy/sdk/app_events.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace cardboy::sdk {
|
||||
|
||||
class ITimerService {
|
||||
public:
|
||||
virtual ~ITimerService() = default;
|
||||
|
||||
virtual TimerClientId acquireClient() = 0;
|
||||
virtual void releaseClient(TimerClientId clientId) = 0;
|
||||
virtual AppTimerHandle scheduleTimer(TimerClientId clientId, std::uint32_t delay_ms, bool repeat) = 0;
|
||||
virtual void cancelTimer(TimerClientId clientId, AppTimerHandle handle) = 0;
|
||||
virtual void cancelAllTimers(TimerClientId clientId) = 0;
|
||||
};
|
||||
|
||||
} // namespace cardboy::sdk
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <SFML/Window/Keyboard.hpp>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
@@ -108,24 +109,57 @@ private:
|
||||
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;
|
||||
void postEvent(const cardboy::sdk::AppEvent& event) override;
|
||||
bool popEvent(cardboy::sdk::AppEvent& outEvent) override;
|
||||
|
||||
private:
|
||||
DesktopRuntime& runtime;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
std::uint32_t pendingBits = 0;
|
||||
std::mutex eventMutex;
|
||||
std::deque<cardboy::sdk::AppEvent> eventQueue;
|
||||
};
|
||||
|
||||
std::mutex timerMutex;
|
||||
std::condition_variable timerCv;
|
||||
std::thread timerThread;
|
||||
bool timerCancel = false;
|
||||
class DesktopTimerService final : public cardboy::sdk::ITimerService {
|
||||
public:
|
||||
DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus);
|
||||
~DesktopTimerService() override;
|
||||
|
||||
cardboy::sdk::TimerClientId acquireClient() override;
|
||||
void releaseClient(cardboy::sdk::TimerClientId clientId) override;
|
||||
cardboy::sdk::AppTimerHandle scheduleTimer(cardboy::sdk::TimerClientId clientId, std::uint32_t delay_ms,
|
||||
bool repeat) override;
|
||||
void cancelTimer(cardboy::sdk::TimerClientId clientId, cardboy::sdk::AppTimerHandle handle) override;
|
||||
void cancelAllTimers(cardboy::sdk::TimerClientId clientId) override;
|
||||
|
||||
private:
|
||||
struct TimerRecord {
|
||||
cardboy::sdk::TimerClientId clientId = cardboy::sdk::kInvalidTimerClient;
|
||||
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;
|
||||
};
|
||||
|
||||
void workerLoop();
|
||||
void wakeWorker();
|
||||
void cleanupInactive();
|
||||
|
||||
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;
|
||||
cardboy::sdk::TimerClientId nextClient = 1;
|
||||
};
|
||||
|
||||
class DesktopFramebuffer final : public cardboy::sdk::FramebufferFacade<DesktopFramebuffer> {
|
||||
@@ -206,6 +240,7 @@ private:
|
||||
DesktopHighResClock highResService;
|
||||
DesktopFilesystem filesystemService;
|
||||
DesktopEventBus eventBusService;
|
||||
DesktopTimerService timerService;
|
||||
DesktopNotificationCenter notificationService;
|
||||
cardboy::sdk::Services services{};
|
||||
};
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
|
||||
namespace cardboy::backend::desktop {
|
||||
|
||||
DesktopEventBus::DesktopEventBus(DesktopRuntime& owner) : runtime(owner) {}
|
||||
namespace {
|
||||
constexpr std::size_t kDesktopEventQueueLimit = 64;
|
||||
} // namespace
|
||||
|
||||
DesktopEventBus::~DesktopEventBus() { cancelTimerSignal(); }
|
||||
DesktopEventBus::DesktopEventBus(DesktopRuntime& owner) : runtime(owner) {}
|
||||
|
||||
void DesktopEventBus::signal(std::uint32_t bits) {
|
||||
if (bits == 0)
|
||||
@@ -61,46 +63,161 @@ std::uint32_t DesktopEventBus::wait(std::uint32_t mask, std::uint32_t timeout_ms
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void DesktopEventBus::postEvent(const cardboy::sdk::AppEvent& event) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(timerMutex);
|
||||
timerCancel = false;
|
||||
std::lock_guard<std::mutex> lock(eventMutex);
|
||||
if (eventQueue.size() >= kDesktopEventQueueLimit)
|
||||
eventQueue.pop_front();
|
||||
eventQueue.push_back(event);
|
||||
}
|
||||
|
||||
timerThread = std::thread([this, delay_ms]() {
|
||||
std::unique_lock<std::mutex> 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));
|
||||
});
|
||||
signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
|
||||
}
|
||||
|
||||
void DesktopEventBus::cancelTimerSignal() {
|
||||
bool DesktopEventBus::popEvent(cardboy::sdk::AppEvent& outEvent) {
|
||||
std::lock_guard<std::mutex> lock(eventMutex);
|
||||
if (eventQueue.empty())
|
||||
return false;
|
||||
outEvent = eventQueue.front();
|
||||
eventQueue.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
DesktopTimerService::DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus) : runtime(owner), eventBus(bus) {
|
||||
worker = std::thread(&DesktopTimerService::workerLoop, this);
|
||||
}
|
||||
|
||||
DesktopTimerService::~DesktopTimerService() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(timerMutex);
|
||||
timerCancel = true;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
stopWorker = true;
|
||||
}
|
||||
timerCv.notify_all();
|
||||
if (timerThread.joinable())
|
||||
timerThread.join();
|
||||
cv.notify_all();
|
||||
if (worker.joinable())
|
||||
worker.join();
|
||||
}
|
||||
|
||||
cardboy::sdk::TimerClientId DesktopTimerService::acquireClient() {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
cardboy::sdk::TimerClientId id = cardboy::sdk::kInvalidTimerClient;
|
||||
do {
|
||||
id = nextClient++;
|
||||
} while (id == cardboy::sdk::kInvalidTimerClient);
|
||||
if (nextClient == cardboy::sdk::kInvalidTimerClient)
|
||||
++nextClient;
|
||||
return id;
|
||||
}
|
||||
|
||||
void DesktopTimerService::releaseClient(cardboy::sdk::TimerClientId clientId) { cancelAllTimers(clientId); }
|
||||
|
||||
cardboy::sdk::AppTimerHandle DesktopTimerService::scheduleTimer(cardboy::sdk::TimerClientId clientId,
|
||||
std::uint32_t delay_ms, bool repeat) {
|
||||
if (clientId == cardboy::sdk::kInvalidTimerClient)
|
||||
return cardboy::sdk::kInvalidAppTimer;
|
||||
|
||||
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 ? delay_ms : std::max(delay_ms, 1u)));
|
||||
|
||||
TimerRecord record{};
|
||||
record.clientId = clientId;
|
||||
record.repeat = repeat;
|
||||
record.interval = interval;
|
||||
record.due = dueTime;
|
||||
record.active = true;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(timerMutex);
|
||||
timerCancel = false;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
cardboy::sdk::AppTimerHandle handle = cardboy::sdk::kInvalidAppTimer;
|
||||
do {
|
||||
handle = nextHandle++;
|
||||
} while (handle == cardboy::sdk::kInvalidAppTimer);
|
||||
if (nextHandle == cardboy::sdk::kInvalidAppTimer)
|
||||
++nextHandle;
|
||||
record.handle = handle;
|
||||
timers.push_back(record);
|
||||
wakeWorker();
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopTimerService::cancelTimer(cardboy::sdk::TimerClientId clientId, cardboy::sdk::AppTimerHandle handle) {
|
||||
if (clientId == cardboy::sdk::kInvalidTimerClient || handle == cardboy::sdk::kInvalidAppTimer)
|
||||
return;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (auto& record: timers) {
|
||||
if (record.clientId == clientId && record.handle == handle)
|
||||
record.active = false;
|
||||
}
|
||||
cleanupInactive();
|
||||
wakeWorker();
|
||||
}
|
||||
|
||||
void DesktopTimerService::cancelAllTimers(cardboy::sdk::TimerClientId clientId) {
|
||||
if (clientId == cardboy::sdk::kInvalidTimerClient)
|
||||
return;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (auto& record: timers) {
|
||||
if (record.clientId == clientId)
|
||||
record.active = false;
|
||||
}
|
||||
cleanupInactive();
|
||||
wakeWorker();
|
||||
}
|
||||
|
||||
void DesktopTimerService::workerLoop() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
while (!stopWorker) {
|
||||
cleanupInactive();
|
||||
if (timers.empty()) {
|
||||
cv.wait(lock, [this] { return stopWorker || !timers.empty(); });
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!nextIt->active)
|
||||
continue;
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now >= nextIt->due) {
|
||||
TimerRecord record = *nextIt;
|
||||
if (record.repeat) {
|
||||
nextIt->due = now + record.interval;
|
||||
} else {
|
||||
nextIt->active = false;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
cardboy::sdk::AppTimerEvent timerEvent{};
|
||||
timerEvent.clientId = record.clientId;
|
||||
timerEvent.handle = record.handle;
|
||||
|
||||
cardboy::sdk::AppEvent event{};
|
||||
event.timestamp_ms = runtime.clock.millis();
|
||||
event.data = timerEvent;
|
||||
eventBus.postEvent(event);
|
||||
|
||||
lock.lock();
|
||||
continue;
|
||||
}
|
||||
|
||||
cv.wait_until(lock, nextIt->due, [this] { return stopWorker; });
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopTimerService::wakeWorker() { cv.notify_all(); }
|
||||
|
||||
void DesktopTimerService::cleanupInactive() {
|
||||
timers.erase(std::remove_if(timers.begin(), timers.end(), [](const TimerRecord& record) { return !record.active; }),
|
||||
timers.end());
|
||||
}
|
||||
|
||||
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())
|
||||
@@ -324,7 +441,7 @@ 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) {
|
||||
framebuffer(*this), input(*this), clock(*this), eventBusService(*this), timerService(*this, eventBusService) {
|
||||
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");
|
||||
@@ -340,6 +457,7 @@ DesktopRuntime::DesktopRuntime() :
|
||||
services.highResClock = &highResService;
|
||||
services.filesystem = &filesystemService;
|
||||
services.eventBus = &eventBusService;
|
||||
services.timer = &timerService;
|
||||
services.loopHooks = nullptr;
|
||||
services.notifications = ¬ificationService;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cardboy/sdk/app_events.hpp>
|
||||
#include <cardboy/sdk/backend.hpp>
|
||||
#include <cardboy/sdk/platform.hpp>
|
||||
#include <cardboy/sdk/services.hpp>
|
||||
@@ -14,30 +15,6 @@ namespace cardboy::sdk {
|
||||
|
||||
class AppSystem;
|
||||
|
||||
using AppTimerHandle = std::uint32_t;
|
||||
constexpr AppTimerHandle kInvalidAppTimer = 0;
|
||||
|
||||
enum class AppEventType {
|
||||
Button,
|
||||
Timer,
|
||||
};
|
||||
|
||||
struct AppButtonEvent {
|
||||
InputState current{};
|
||||
InputState previous{};
|
||||
};
|
||||
|
||||
struct AppTimerEvent {
|
||||
AppTimerHandle handle = kInvalidAppTimer;
|
||||
};
|
||||
|
||||
struct AppEvent {
|
||||
AppEventType type;
|
||||
std::uint32_t timestamp_ms = 0;
|
||||
AppButtonEvent button{};
|
||||
AppTimerEvent timer{};
|
||||
};
|
||||
|
||||
using ActiveBackend = cardboy::backend::ActiveBackend;
|
||||
|
||||
struct AppContext {
|
||||
@@ -63,6 +40,7 @@ struct AppContext {
|
||||
[[nodiscard]] IHighResClock* highResClock() const { return services ? services->highResClock : nullptr; }
|
||||
[[nodiscard]] IFilesystem* filesystem() const { return services ? services->filesystem : nullptr; }
|
||||
[[nodiscard]] IEventBus* eventBus() const { return services ? services->eventBus : nullptr; }
|
||||
[[nodiscard]] ITimerService* timerService() const { return services ? services->timer : nullptr; }
|
||||
[[nodiscard]] ILoopHooks* loopHooks() const { return services ? services->loopHooks : nullptr; }
|
||||
[[nodiscard]] INotificationCenter* notificationCenter() const {
|
||||
return services ? services->notifications : nullptr;
|
||||
@@ -84,27 +62,28 @@ struct AppContext {
|
||||
[[nodiscard]] bool hasPendingAppSwitch() const { return pendingSwitch; }
|
||||
|
||||
AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat = false) {
|
||||
if (!system)
|
||||
auto* timer = timerService();
|
||||
if (!timer || timerClientId == kInvalidTimerClient)
|
||||
return kInvalidAppTimer;
|
||||
return scheduleTimerInternal(delay_ms, repeat);
|
||||
return timer->scheduleTimer(timerClientId, delay_ms, repeat);
|
||||
}
|
||||
|
||||
AppTimerHandle scheduleRepeatingTimer(std::uint32_t interval_ms) {
|
||||
if (!system)
|
||||
return kInvalidAppTimer;
|
||||
return scheduleTimerInternal(interval_ms, true);
|
||||
return scheduleTimer(interval_ms, true);
|
||||
}
|
||||
|
||||
void cancelTimer(AppTimerHandle handle) {
|
||||
if (!system)
|
||||
auto* timer = timerService();
|
||||
if (!timer || timerClientId == kInvalidTimerClient)
|
||||
return;
|
||||
cancelTimerInternal(handle);
|
||||
timer->cancelTimer(timerClientId, handle);
|
||||
}
|
||||
|
||||
void cancelAllTimers() {
|
||||
if (!system)
|
||||
auto* timer = timerService();
|
||||
if (!timer || timerClientId == kInvalidTimerClient)
|
||||
return;
|
||||
cancelAllTimersInternal();
|
||||
timer->cancelAllTimers(timerClientId);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -113,10 +92,7 @@ private:
|
||||
bool pendingSwitchByName = false;
|
||||
std::size_t pendingAppIndex = 0;
|
||||
std::string pendingAppName;
|
||||
|
||||
AppTimerHandle scheduleTimerInternal(std::uint32_t delay_ms, bool repeat);
|
||||
void cancelTimerInternal(AppTimerHandle handle);
|
||||
void cancelAllTimersInternal();
|
||||
TimerClientId timerClientId = kInvalidTimerClient;
|
||||
};
|
||||
|
||||
class IApp {
|
||||
|
||||
@@ -32,52 +32,20 @@ public:
|
||||
private:
|
||||
friend struct AppContext;
|
||||
|
||||
struct TimerRecord {
|
||||
AppTimerHandle id = kInvalidAppTimer;
|
||||
std::uint32_t generation = 0;
|
||||
std::uint32_t due_ms = 0;
|
||||
std::uint32_t interval_ms = 0;
|
||||
bool repeat = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat);
|
||||
void cancelTimer(AppTimerHandle handle);
|
||||
void cancelAllTimers();
|
||||
|
||||
void dispatchEvent(const AppEvent& event);
|
||||
|
||||
void processDueTimers(std::uint32_t now, std::vector<AppEvent>& outEvents);
|
||||
std::uint32_t nextTimerDueMs(std::uint32_t now) const;
|
||||
void clearTimersForCurrentApp();
|
||||
TimerRecord* findTimer(AppTimerHandle handle);
|
||||
bool handlePendingSwitchRequest();
|
||||
void notifyEventBus(EventBusSignal signal);
|
||||
bool handlePendingSwitchRequest();
|
||||
void attachTimerClient();
|
||||
void detachTimerClient();
|
||||
void processEventBusEvents();
|
||||
|
||||
AppContext context;
|
||||
std::vector<std::unique_ptr<IAppFactory>> factories;
|
||||
std::unique_ptr<IApp> current;
|
||||
IAppFactory* activeFactory = nullptr;
|
||||
std::size_t activeIndex = static_cast<std::size_t>(-1);
|
||||
std::vector<TimerRecord> timers;
|
||||
AppTimerHandle nextTimerId = 1;
|
||||
std::uint32_t currentGeneration = 0;
|
||||
TimerClientId timerClientId = kInvalidTimerClient;
|
||||
InputState lastInputState{};
|
||||
bool suppressInputs = false;
|
||||
};
|
||||
|
||||
inline AppTimerHandle AppContext::scheduleTimerInternal(std::uint32_t delay_ms, bool repeat) {
|
||||
return system ? system->scheduleTimer(delay_ms, repeat) : kInvalidAppTimer;
|
||||
}
|
||||
|
||||
inline void AppContext::cancelTimerInternal(AppTimerHandle handle) {
|
||||
if (system)
|
||||
system->cancelTimer(handle);
|
||||
}
|
||||
|
||||
inline void AppContext::cancelAllTimersInternal() {
|
||||
if (system)
|
||||
system->cancelAllTimers();
|
||||
}
|
||||
|
||||
} // namespace cardboy::sdk
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "cardboy/sdk/status_bar.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
namespace cardboy::sdk {
|
||||
@@ -34,7 +33,10 @@ AppSystem::AppSystem(AppContext ctx) : context(std::move(ctx)) {
|
||||
FramebufferHooks::setPreSendHook(&statusBarPreSendHook<FBType>, &statusBar);
|
||||
}
|
||||
|
||||
AppSystem::~AppSystem() { FramebufferHooks::clearPreSendHook(); }
|
||||
AppSystem::~AppSystem() {
|
||||
detachTimerClient();
|
||||
FramebufferHooks::clearPreSendHook();
|
||||
}
|
||||
|
||||
void AppSystem::registerApp(std::unique_ptr<IAppFactory> factory) {
|
||||
if (!factory)
|
||||
@@ -63,6 +65,7 @@ bool AppSystem::startAppByIndex(std::size_t index) {
|
||||
if (current) {
|
||||
current->onStop();
|
||||
current.reset();
|
||||
detachTimerClient();
|
||||
}
|
||||
|
||||
activeFactory = factory.get();
|
||||
@@ -70,8 +73,8 @@ bool AppSystem::startAppByIndex(std::size_t index) {
|
||||
context.pendingSwitch = false;
|
||||
context.pendingSwitchByName = false;
|
||||
context.pendingAppName.clear();
|
||||
clearTimersForCurrentApp();
|
||||
current = std::move(app);
|
||||
attachTimerClient();
|
||||
lastInputState = context.input.readState();
|
||||
suppressInputs = true;
|
||||
StatusBar::instance().setServices(context.services);
|
||||
@@ -86,17 +89,19 @@ void AppSystem::run() {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<AppEvent> events;
|
||||
events.reserve(4);
|
||||
|
||||
while (true) {
|
||||
auto* eventBus = context.eventBus();
|
||||
if (!eventBus)
|
||||
return;
|
||||
|
||||
if (auto* hooks = context.loopHooks())
|
||||
hooks->onLoopIteration();
|
||||
|
||||
events.clear();
|
||||
const std::uint32_t now = context.clock.millis();
|
||||
processDueTimers(now, events);
|
||||
processEventBusEvents();
|
||||
if (handlePendingSwitchRequest())
|
||||
continue;
|
||||
|
||||
const std::uint32_t now = context.clock.millis();
|
||||
const InputState inputNow = context.input.readState();
|
||||
const bool consumedByStatusToggle = StatusBar::instance().handleToggleInput(inputNow, lastInputState);
|
||||
|
||||
@@ -105,42 +110,25 @@ void AppSystem::run() {
|
||||
if (!anyButtonPressed(inputNow))
|
||||
suppressInputs = false;
|
||||
} else if (!consumedByStatusToggle && inputsDiffer(inputNow, lastInputState)) {
|
||||
AppButtonEvent button{};
|
||||
button.current = inputNow;
|
||||
button.previous = lastInputState;
|
||||
|
||||
AppEvent evt{};
|
||||
evt.type = AppEventType::Button;
|
||||
evt.timestamp_ms = now;
|
||||
evt.button.current = inputNow;
|
||||
evt.button.previous = lastInputState;
|
||||
events.push_back(evt);
|
||||
evt.timestamp_ms = now;
|
||||
evt.data = button;
|
||||
|
||||
lastInputState = inputNow;
|
||||
dispatchEvent(evt);
|
||||
} else if (consumedByStatusToggle) {
|
||||
lastInputState = inputNow;
|
||||
}
|
||||
|
||||
for (const auto& evt: events) {
|
||||
dispatchEvent(evt);
|
||||
if (handlePendingSwitchRequest())
|
||||
break;
|
||||
}
|
||||
|
||||
const std::uint32_t waitBase = context.clock.millis();
|
||||
std::uint32_t waitMs = nextTimerDueMs(waitBase);
|
||||
|
||||
if (waitMs == 0)
|
||||
if (handlePendingSwitchRequest())
|
||||
continue;
|
||||
|
||||
auto* eventBus = context.eventBus();
|
||||
if (!eventBus)
|
||||
return;
|
||||
|
||||
const std::uint32_t mask = to_event_bits(EventBusSignal::Input) | to_event_bits(EventBusSignal::Timer);
|
||||
|
||||
if (waitMs == std::numeric_limits<std::uint32_t>::max()) {
|
||||
eventBus->cancelTimerSignal();
|
||||
eventBus->wait(mask, IEventBus::kWaitForever);
|
||||
} else {
|
||||
eventBus->scheduleTimerSignal(waitMs);
|
||||
eventBus->wait(mask, IEventBus::kWaitForever);
|
||||
}
|
||||
eventBus->wait(mask, IEventBus::kWaitForever);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,105 +148,16 @@ std::size_t AppSystem::indexOfFactory(const IAppFactory* factory) const {
|
||||
return static_cast<std::size_t>(-1);
|
||||
}
|
||||
|
||||
AppTimerHandle AppSystem::scheduleTimer(std::uint32_t delay_ms, bool repeat) {
|
||||
if (!current)
|
||||
return kInvalidAppTimer;
|
||||
TimerRecord record;
|
||||
record.id = nextTimerId++;
|
||||
if (record.id == kInvalidAppTimer)
|
||||
record.id = nextTimerId++;
|
||||
record.generation = currentGeneration;
|
||||
const auto now = context.clock.millis();
|
||||
record.due_ms = now + delay_ms;
|
||||
record.interval_ms = repeat ? std::max<std::uint32_t>(1, delay_ms) : 0;
|
||||
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)
|
||||
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 && 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) {
|
||||
if (current)
|
||||
current->handleEvent(event);
|
||||
}
|
||||
if (!current)
|
||||
return;
|
||||
|
||||
void AppSystem::processDueTimers(std::uint32_t now, std::vector<AppEvent>& outEvents) {
|
||||
for (auto& timer: timers) {
|
||||
if (!timer.active || timer.generation != currentGeneration)
|
||||
continue;
|
||||
if (static_cast<std::int32_t>(now - timer.due_ms) >= 0) {
|
||||
AppEvent ev{};
|
||||
ev.type = AppEventType::Timer;
|
||||
ev.timestamp_ms = now;
|
||||
ev.timer.handle = timer.id;
|
||||
outEvents.push_back(ev);
|
||||
if (timer.repeat) {
|
||||
const std::uint32_t interval = timer.interval_ms ? timer.interval_ms : 1;
|
||||
timer.due_ms = now + interval;
|
||||
} else {
|
||||
timer.active = false;
|
||||
}
|
||||
}
|
||||
if (const auto* timer = event.timer()) {
|
||||
if (timer->clientId != timerClientId)
|
||||
return;
|
||||
}
|
||||
timers.erase(std::remove_if(timers.begin(), timers.end(), [](const TimerRecord& rec) { return !rec.active; }),
|
||||
timers.end());
|
||||
}
|
||||
|
||||
std::uint32_t AppSystem::nextTimerDueMs(std::uint32_t now) const {
|
||||
std::uint32_t minWait = std::numeric_limits<std::uint32_t>::max();
|
||||
for (const auto& timer: timers) {
|
||||
if (!timer.active || timer.generation != currentGeneration)
|
||||
continue;
|
||||
if (static_cast<std::int32_t>(now - timer.due_ms) >= 0)
|
||||
return 0;
|
||||
const std::uint32_t delta = timer.due_ms - now;
|
||||
if (delta < minWait)
|
||||
minWait = delta;
|
||||
}
|
||||
return minWait;
|
||||
}
|
||||
|
||||
void AppSystem::clearTimersForCurrentApp() {
|
||||
const bool hadTimers = !timers.empty();
|
||||
++currentGeneration;
|
||||
timers.clear();
|
||||
if (hadTimers)
|
||||
notifyEventBus(EventBusSignal::Timer);
|
||||
}
|
||||
|
||||
AppSystem::TimerRecord* AppSystem::findTimer(AppTimerHandle handle) {
|
||||
for (auto& timer: timers) {
|
||||
if (!timer.active || timer.generation != currentGeneration)
|
||||
continue;
|
||||
if (timer.id == handle)
|
||||
return &timer;
|
||||
}
|
||||
return nullptr;
|
||||
current->handleEvent(event);
|
||||
}
|
||||
|
||||
bool AppSystem::handlePendingSwitchRequest() {
|
||||
@@ -278,11 +177,39 @@ bool AppSystem::handlePendingSwitchRequest() {
|
||||
return switched;
|
||||
}
|
||||
|
||||
void AppSystem::notifyEventBus(EventBusSignal signal) {
|
||||
if (signal == EventBusSignal::None)
|
||||
void AppSystem::attachTimerClient() {
|
||||
auto* timer = context.timerService();
|
||||
if (!timer) {
|
||||
timerClientId = kInvalidTimerClient;
|
||||
context.timerClientId = kInvalidTimerClient;
|
||||
return;
|
||||
if (auto* bus = context.eventBus())
|
||||
bus->signal(to_event_bits(signal));
|
||||
}
|
||||
|
||||
timerClientId = timer->acquireClient();
|
||||
context.timerClientId = timerClientId;
|
||||
}
|
||||
|
||||
void AppSystem::detachTimerClient() {
|
||||
auto* timer = context.timerService();
|
||||
if (!timer || timerClientId == kInvalidTimerClient)
|
||||
return;
|
||||
|
||||
timer->releaseClient(timerClientId);
|
||||
timerClientId = kInvalidTimerClient;
|
||||
context.timerClientId = kInvalidTimerClient;
|
||||
}
|
||||
|
||||
void AppSystem::processEventBusEvents() {
|
||||
auto* eventBus = context.eventBus();
|
||||
if (!eventBus)
|
||||
return;
|
||||
|
||||
AppEvent event{};
|
||||
while (eventBus->popEvent(event)) {
|
||||
dispatchEvent(event);
|
||||
if (context.pendingSwitch)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cardboy::sdk
|
||||
|
||||
Reference in New Issue
Block a user