mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 15:17:48 +01:00
some refactoring
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
To build:
|
To build:
|
||||||
(in zsh)
|
(in zsh, bash doesn't work)
|
||||||
. "$HOME/esp/esp-idf/export.sh"
|
. "$HOME/esp/esp-idf/export.sh"
|
||||||
idf.py build
|
idf.py build
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/event_groups.h"
|
#include "freertos/event_groups.h"
|
||||||
#include "freertos/timers.h"
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
namespace cardboy::backend::esp {
|
namespace cardboy::backend::esp {
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@ public:
|
|||||||
void signal(std::uint32_t bits) override;
|
void signal(std::uint32_t bits) override;
|
||||||
void signalFromISR(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;
|
std::uint32_t wait(std::uint32_t mask, std::uint32_t timeout_ms) override;
|
||||||
void scheduleTimerSignal(std::uint32_t delay_ms) override;
|
void postEvent(const cardboy::sdk::AppEvent& event) override;
|
||||||
void cancelTimerSignal() override;
|
bool popEvent(cardboy::sdk::AppEvent& outEvent) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EventGroupHandle_t group;
|
EventGroupHandle_t group;
|
||||||
TimerHandle_t timer;
|
QueueHandle_t eventQueue;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cardboy::backend::esp
|
} // namespace cardboy::backend::esp
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ private:
|
|||||||
class FilesystemService;
|
class FilesystemService;
|
||||||
class LoopHooksService;
|
class LoopHooksService;
|
||||||
class NotificationService;
|
class NotificationService;
|
||||||
|
class TimerService;
|
||||||
|
|
||||||
std::unique_ptr<BuzzerService> buzzerService;
|
std::unique_ptr<BuzzerService> buzzerService;
|
||||||
std::unique_ptr<BatteryService> batteryService;
|
std::unique_ptr<BatteryService> batteryService;
|
||||||
@@ -70,6 +71,7 @@ private:
|
|||||||
std::unique_ptr<HighResClockService> highResClockService;
|
std::unique_ptr<HighResClockService> highResClockService;
|
||||||
std::unique_ptr<FilesystemService> filesystemService;
|
std::unique_ptr<FilesystemService> filesystemService;
|
||||||
std::unique_ptr<EventBus> eventBus;
|
std::unique_ptr<EventBus> eventBus;
|
||||||
|
std::unique_ptr<TimerService> timerService;
|
||||||
std::unique_ptr<LoopHooksService> loopHooksService;
|
std::unique_ptr<LoopHooksService> loopHooksService;
|
||||||
std::unique_ptr<NotificationService> notificationService;
|
std::unique_ptr<NotificationService> notificationService;
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,15 @@
|
|||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/timers.h"
|
||||||
#include "nvs.h"
|
#include "nvs.h"
|
||||||
#include "nvs_flash.h"
|
#include "nvs_flash.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <list>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -131,6 +134,63 @@ public:
|
|||||||
void onLoopIteration() override { vTaskDelay(1); }
|
void onLoopIteration() override { vTaskDelay(1); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class EspRuntime::TimerService final : public cardboy::sdk::ITimerService {
|
||||||
|
public:
|
||||||
|
explicit TimerService(cardboy::sdk::IEventBus& bus);
|
||||||
|
~TimerService() 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 {
|
||||||
|
TimerService* owner = nullptr;
|
||||||
|
TimerHandle_t timer = nullptr;
|
||||||
|
cardboy::sdk::TimerClientId clientId = cardboy::sdk::kInvalidTimerClient;
|
||||||
|
cardboy::sdk::AppTimerHandle handle = cardboy::sdk::kInvalidAppTimer;
|
||||||
|
bool repeat = false;
|
||||||
|
bool active = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RecursiveLock {
|
||||||
|
public:
|
||||||
|
explicit RecursiveLock(SemaphoreHandle_t m) : mutex(m) { lock(); }
|
||||||
|
~RecursiveLock() { unlock(); }
|
||||||
|
|
||||||
|
void lock() {
|
||||||
|
if (mutex && !locked) {
|
||||||
|
if (xSemaphoreTakeRecursive(mutex, portMAX_DELAY) == pdTRUE)
|
||||||
|
locked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() {
|
||||||
|
if (mutex && locked) {
|
||||||
|
xSemaphoreGiveRecursive(mutex);
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t mutex;
|
||||||
|
bool locked = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void timerCallback(TimerHandle_t timer);
|
||||||
|
void handleTimer(TimerRecord* record);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
cardboy::sdk::IEventBus& eventBus;
|
||||||
|
SemaphoreHandle_t mutex = nullptr;
|
||||||
|
std::list<TimerRecord> timers;
|
||||||
|
cardboy::sdk::AppTimerHandle nextTimerHandle = 1;
|
||||||
|
cardboy::sdk::TimerClientId nextClientId = 1;
|
||||||
|
};
|
||||||
|
|
||||||
class EspRuntime::NotificationService final : public cardboy::sdk::INotificationCenter {
|
class EspRuntime::NotificationService final : public cardboy::sdk::INotificationCenter {
|
||||||
public:
|
public:
|
||||||
void pushNotification(Notification notification) override {
|
void pushNotification(Notification notification) override {
|
||||||
@@ -247,6 +307,216 @@ private:
|
|||||||
std::uint32_t revisionCounter = 0;
|
std::uint32_t revisionCounter = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
EspRuntime::TimerService::TimerService(cardboy::sdk::IEventBus& bus) : eventBus(bus) {
|
||||||
|
mutex = xSemaphoreCreateRecursiveMutex();
|
||||||
|
}
|
||||||
|
|
||||||
|
EspRuntime::TimerService::~TimerService() {
|
||||||
|
shutdown();
|
||||||
|
if (mutex)
|
||||||
|
vSemaphoreDelete(mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
cardboy::sdk::TimerClientId EspRuntime::TimerService::acquireClient() {
|
||||||
|
if (!mutex)
|
||||||
|
return cardboy::sdk::kInvalidTimerClient;
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
cardboy::sdk::TimerClientId id = cardboy::sdk::kInvalidTimerClient;
|
||||||
|
do {
|
||||||
|
id = nextClientId++;
|
||||||
|
} while (id == cardboy::sdk::kInvalidTimerClient);
|
||||||
|
if (nextClientId == cardboy::sdk::kInvalidTimerClient)
|
||||||
|
++nextClientId;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspRuntime::TimerService::releaseClient(cardboy::sdk::TimerClientId clientId) { cancelAllTimers(clientId); }
|
||||||
|
|
||||||
|
cardboy::sdk::AppTimerHandle EspRuntime::TimerService::scheduleTimer(cardboy::sdk::TimerClientId clientId,
|
||||||
|
std::uint32_t delay_ms, bool repeat) {
|
||||||
|
if (!mutex || clientId == cardboy::sdk::kInvalidTimerClient)
|
||||||
|
return cardboy::sdk::kInvalidAppTimer;
|
||||||
|
|
||||||
|
TimerRecord* storedRecord = nullptr;
|
||||||
|
{
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
TimerRecord record{};
|
||||||
|
record.owner = this;
|
||||||
|
record.clientId = clientId;
|
||||||
|
record.repeat = repeat;
|
||||||
|
record.active = true;
|
||||||
|
cardboy::sdk::AppTimerHandle newHandle = cardboy::sdk::kInvalidAppTimer;
|
||||||
|
do {
|
||||||
|
newHandle = nextTimerHandle++;
|
||||||
|
} while (newHandle == cardboy::sdk::kInvalidAppTimer);
|
||||||
|
if (nextTimerHandle == cardboy::sdk::kInvalidAppTimer)
|
||||||
|
++nextTimerHandle;
|
||||||
|
record.handle = newHandle;
|
||||||
|
timers.emplace_back(record);
|
||||||
|
storedRecord = &timers.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint32_t effectiveDelay = std::max<std::uint32_t>(1, delay_ms);
|
||||||
|
const TickType_t ticks = std::max<TickType_t>(pdMS_TO_TICKS(effectiveDelay), 1);
|
||||||
|
|
||||||
|
TimerHandle_t timerHandle =
|
||||||
|
xTimerCreate("AppSvcTimer", ticks, repeat ? pdTRUE : pdFALSE, storedRecord, &TimerService::timerCallback);
|
||||||
|
if (!timerHandle) {
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
for (auto it = timers.begin(); it != timers.end(); ++it) {
|
||||||
|
if (&(*it) == storedRecord) {
|
||||||
|
timers.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cardboy::sdk::kInvalidAppTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
storedRecord->timer = timerHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xTimerStart(timerHandle, 0) != pdPASS) {
|
||||||
|
cancelTimer(clientId, storedRecord->handle);
|
||||||
|
return cardboy::sdk::kInvalidAppTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return storedRecord->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspRuntime::TimerService::cancelTimer(cardboy::sdk::TimerClientId clientId,
|
||||||
|
cardboy::sdk::AppTimerHandle handle) {
|
||||||
|
if (!mutex || clientId == cardboy::sdk::kInvalidTimerClient || handle == cardboy::sdk::kInvalidAppTimer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TimerHandle_t timerHandle = nullptr;
|
||||||
|
{
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
for (auto& record: timers) {
|
||||||
|
if (record.clientId == clientId && record.handle == handle) {
|
||||||
|
record.active = false;
|
||||||
|
timerHandle = record.timer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!timerHandle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
xTimerStop(timerHandle, portMAX_DELAY);
|
||||||
|
xTimerDelete(timerHandle, portMAX_DELAY);
|
||||||
|
|
||||||
|
{
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
for (auto it = timers.begin(); it != timers.end();) {
|
||||||
|
if (it->clientId == clientId && it->handle == handle)
|
||||||
|
it = timers.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspRuntime::TimerService::cancelAllTimers(cardboy::sdk::TimerClientId clientId) {
|
||||||
|
if (!mutex || clientId == cardboy::sdk::kInvalidTimerClient)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<TimerHandle_t> handles;
|
||||||
|
{
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
for (auto& record: timers) {
|
||||||
|
if (record.clientId == clientId) {
|
||||||
|
record.active = false;
|
||||||
|
if (record.timer)
|
||||||
|
handles.push_back(record.timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto timerHandle: handles) {
|
||||||
|
xTimerStop(timerHandle, portMAX_DELAY);
|
||||||
|
xTimerDelete(timerHandle, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
for (auto it = timers.begin(); it != timers.end();) {
|
||||||
|
if (it->clientId == clientId)
|
||||||
|
it = timers.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspRuntime::TimerService::timerCallback(TimerHandle_t timer) {
|
||||||
|
auto* record = static_cast<TimerRecord*>(pvTimerGetTimerID(timer));
|
||||||
|
if (!record || !record->owner)
|
||||||
|
return;
|
||||||
|
record->owner->handleTimer(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspRuntime::TimerService::handleTimer(TimerRecord* record) {
|
||||||
|
if (!record || !record->active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const cardboy::sdk::TimerClientId clientId = record->clientId;
|
||||||
|
const cardboy::sdk::AppTimerHandle handle = record->handle;
|
||||||
|
const bool isRepeating = record->repeat;
|
||||||
|
TimerHandle_t timerHandle = record->timer;
|
||||||
|
|
||||||
|
if (!isRepeating) {
|
||||||
|
record->active = false;
|
||||||
|
{
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
for (auto it = timers.begin(); it != timers.end();) {
|
||||||
|
if (&(*it) == record)
|
||||||
|
it = timers.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timerHandle)
|
||||||
|
xTimerDelete(timerHandle, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
cardboy::sdk::AppTimerEvent timerEvent{};
|
||||||
|
timerEvent.clientId = clientId;
|
||||||
|
timerEvent.handle = handle;
|
||||||
|
|
||||||
|
cardboy::sdk::AppEvent event{};
|
||||||
|
event.timestamp_ms = static_cast<std::uint32_t>(esp_timer_get_time() / 1000ULL);
|
||||||
|
event.data = timerEvent;
|
||||||
|
eventBus.postEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspRuntime::TimerService::shutdown() {
|
||||||
|
if (!mutex)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<TimerHandle_t> handles;
|
||||||
|
{
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
for (auto& record: timers) {
|
||||||
|
record.active = false;
|
||||||
|
if (record.timer)
|
||||||
|
handles.push_back(record.timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto timerHandle: handles) {
|
||||||
|
xTimerStop(timerHandle, portMAX_DELAY);
|
||||||
|
xTimerDelete(timerHandle, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
RecursiveLock lock(mutex);
|
||||||
|
timers.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
|
EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
|
||||||
initializeHardware();
|
initializeHardware();
|
||||||
|
|
||||||
@@ -257,6 +527,7 @@ EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
|
|||||||
highResClockService = std::make_unique<HighResClockService>();
|
highResClockService = std::make_unique<HighResClockService>();
|
||||||
filesystemService = std::make_unique<FilesystemService>();
|
filesystemService = std::make_unique<FilesystemService>();
|
||||||
eventBus = std::make_unique<EventBus>();
|
eventBus = std::make_unique<EventBus>();
|
||||||
|
timerService = std::make_unique<TimerService>(*eventBus);
|
||||||
loopHooksService = std::make_unique<LoopHooksService>();
|
loopHooksService = std::make_unique<LoopHooksService>();
|
||||||
notificationService = std::make_unique<NotificationService>();
|
notificationService = std::make_unique<NotificationService>();
|
||||||
|
|
||||||
@@ -267,6 +538,7 @@ EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
|
|||||||
services.highResClock = highResClockService.get();
|
services.highResClock = highResClockService.get();
|
||||||
services.filesystem = filesystemService.get();
|
services.filesystem = filesystemService.get();
|
||||||
services.eventBus = eventBus.get();
|
services.eventBus = eventBus.get();
|
||||||
|
services.timer = timerService.get();
|
||||||
services.loopHooks = loopHooksService.get();
|
services.loopHooks = loopHooksService.get();
|
||||||
services.notifications = notificationService.get();
|
services.notifications = notificationService.get();
|
||||||
|
|
||||||
|
|||||||
@@ -14,22 +14,19 @@ namespace {
|
|||||||
return portMAX_DELAY;
|
return portMAX_DELAY;
|
||||||
return pdMS_TO_TICKS(timeout_ms);
|
return pdMS_TO_TICKS(timeout_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr UBaseType_t kEventQueueLength = 16;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
static void timerCallback(TimerHandle_t handle) {
|
|
||||||
auto* bus = static_cast<EventBus*>(pvTimerGetTimerID(handle));
|
|
||||||
if (bus)
|
|
||||||
bus->signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
|
|
||||||
}
|
|
||||||
|
|
||||||
EventBus::EventBus() :
|
EventBus::EventBus() :
|
||||||
group(xEventGroupCreate()), timer(xTimerCreate("EventBusTimer", pdMS_TO_TICKS(1), pdFALSE, this, timerCallback)) {}
|
group(xEventGroupCreate()),
|
||||||
|
eventQueue(xQueueCreate(kEventQueueLength, sizeof(cardboy::sdk::AppEvent))) {}
|
||||||
|
|
||||||
EventBus::~EventBus() {
|
EventBus::~EventBus() {
|
||||||
if (timer)
|
|
||||||
xTimerDelete(timer, portMAX_DELAY);
|
|
||||||
if (group)
|
if (group)
|
||||||
vEventGroupDelete(group);
|
vEventGroupDelete(group);
|
||||||
|
if (eventQueue)
|
||||||
|
vQueueDelete(eventQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventBus::signal(std::uint32_t bits) {
|
void EventBus::signal(std::uint32_t bits) {
|
||||||
@@ -54,28 +51,23 @@ std::uint32_t EventBus::wait(std::uint32_t mask, std::uint32_t timeout_ms) {
|
|||||||
return static_cast<std::uint32_t>(bits & mask);
|
return static_cast<std::uint32_t>(bits & mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventBus::scheduleTimerSignal(std::uint32_t delay_ms) {
|
void EventBus::postEvent(const cardboy::sdk::AppEvent& event) {
|
||||||
if (!timer)
|
if (!eventQueue)
|
||||||
return;
|
|
||||||
xTimerStop(timer, 0);
|
|
||||||
|
|
||||||
if (delay_ms == cardboy::sdk::IEventBus::kWaitForever)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (delay_ms == 0) {
|
if (xQueueSend(eventQueue, &event, 0) == errQUEUE_FULL) {
|
||||||
signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
|
cardboy::sdk::AppEvent discarded{};
|
||||||
return;
|
if (xQueueReceive(eventQueue, &discarded, 0) == pdTRUE)
|
||||||
|
xQueueSend(eventQueue, &event, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TickType_t ticks = std::max<TickType_t>(pdMS_TO_TICKS(delay_ms), 1);
|
signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
|
||||||
if (xTimerChangePeriod(timer, ticks, 0) == pdPASS)
|
|
||||||
xTimerStart(timer, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventBus::cancelTimerSignal() {
|
bool EventBus::popEvent(cardboy::sdk::AppEvent& outEvent) {
|
||||||
if (!timer)
|
if (!eventQueue)
|
||||||
return;
|
return false;
|
||||||
xTimerStop(timer, 0);
|
return xQueueReceive(eventQueue, &outEvent, 0) == pdTRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cardboy::backend::esp
|
} // namespace cardboy::backend::esp
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ namespace apps {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using cardboy::sdk::AppButtonEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
|
using cardboy::sdk::AppTimerEvent;
|
||||||
|
|
||||||
constexpr const char* kClockAppName = "Clock";
|
constexpr const char* kClockAppName = "Clock";
|
||||||
|
|
||||||
@@ -53,15 +55,12 @@ public:
|
|||||||
void onStop() override { cancelRefreshTimer(); }
|
void onStop() override { cancelRefreshTimer(); }
|
||||||
|
|
||||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||||
switch (event.type) {
|
event.visit(cardboy::sdk::overload(
|
||||||
case cardboy::sdk::AppEventType::Button:
|
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||||
handleButtonEvent(event.button);
|
[this](const AppTimerEvent& timer) {
|
||||||
break;
|
if (timer.handle == refreshTimer)
|
||||||
case cardboy::sdk::AppEventType::Timer:
|
updateDisplay();
|
||||||
if (event.timer.handle == refreshTimer)
|
}));
|
||||||
updateDisplay();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -151,9 +151,10 @@ constexpr int kMenuSpacing = font16x8::kGlyphHeight + 6;
|
|||||||
class GameboyApp;
|
class GameboyApp;
|
||||||
|
|
||||||
|
|
||||||
|
using cardboy::sdk::AppButtonEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
using cardboy::sdk::AppEvent;
|
using cardboy::sdk::AppEvent;
|
||||||
using cardboy::sdk::AppEventType;
|
using cardboy::sdk::AppTimerEvent;
|
||||||
using cardboy::sdk::AppTimerHandle;
|
using cardboy::sdk::AppTimerHandle;
|
||||||
using cardboy::sdk::InputState;
|
using cardboy::sdk::InputState;
|
||||||
using cardboy::sdk::kInvalidAppTimer;
|
using cardboy::sdk::kInvalidAppTimer;
|
||||||
@@ -252,20 +253,26 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleEvent(const AppEvent& event) override {
|
void handleEvent(const AppEvent& event) override {
|
||||||
if (event.type == AppEventType::Timer && event.timer.handle == tickTimer) {
|
bool handled = false;
|
||||||
tickTimer = kInvalidAppTimer;
|
event.visit(cardboy::sdk::overload(
|
||||||
const uint64_t frameStartUs = nowMicros();
|
[this, &handled](const AppTimerEvent& timer) {
|
||||||
performStep();
|
if (timer.handle != tickTimer)
|
||||||
const uint64_t frameEndUs = nowMicros();
|
return;
|
||||||
const uint64_t elapsedUs = (frameEndUs >= frameStartUs) ? (frameEndUs - frameStartUs) : 0;
|
handled = true;
|
||||||
GB_PERF_ONLY(printf("Step took %" PRIu64 " us\n", elapsedUs));
|
tickTimer = kInvalidAppTimer;
|
||||||
scheduleAfterFrame(elapsedUs);
|
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;
|
return;
|
||||||
}
|
|
||||||
if (event.type == AppEventType::Button) {
|
|
||||||
frameDelayCarryUs = 0;
|
|
||||||
scheduleNextTick(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void performStep() {
|
void performStep() {
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ namespace apps {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using cardboy::sdk::AppButtonEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
|
using cardboy::sdk::AppTimerEvent;
|
||||||
|
|
||||||
constexpr std::uint32_t kRefreshIntervalMs = 100;
|
constexpr std::uint32_t kRefreshIntervalMs = 100;
|
||||||
constexpr std::uint32_t kUnlockHoldMs = 1500;
|
constexpr std::uint32_t kUnlockHoldMs = 1500;
|
||||||
@@ -73,17 +75,14 @@ public:
|
|||||||
void onStop() override { cancelRefreshTimer(); }
|
void onStop() override { cancelRefreshTimer(); }
|
||||||
|
|
||||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||||
switch (event.type) {
|
event.visit(cardboy::sdk::overload(
|
||||||
case cardboy::sdk::AppEventType::Button:
|
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||||
handleButtonEvent(event.button);
|
[this](const AppTimerEvent& timer) {
|
||||||
break;
|
if (timer.handle == refreshTimer) {
|
||||||
case cardboy::sdk::AppEventType::Timer:
|
advanceHoldProgress();
|
||||||
if (event.timer.handle == refreshTimer) {
|
updateDisplay();
|
||||||
advanceHoldProgress();
|
}
|
||||||
updateDisplay();
|
}));
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ namespace apps {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using cardboy::sdk::AppButtonEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
|
using cardboy::sdk::AppEvent;
|
||||||
|
using cardboy::sdk::AppTimerEvent;
|
||||||
|
|
||||||
using Framebuffer = typename AppContext::Framebuffer;
|
using Framebuffer = typename AppContext::Framebuffer;
|
||||||
|
|
||||||
@@ -42,17 +45,14 @@ public:
|
|||||||
void onStop() override { cancelInactivityTimer(); }
|
void onStop() override { cancelInactivityTimer(); }
|
||||||
|
|
||||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||||
switch (event.type) {
|
event.visit(cardboy::sdk::overload(
|
||||||
case cardboy::sdk::AppEventType::Button:
|
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||||
handleButtonEvent(event.button);
|
[this](const AppTimerEvent& timer) {
|
||||||
break;
|
if (timer.handle == inactivityTimer) {
|
||||||
case cardboy::sdk::AppEventType::Timer:
|
cancelInactivityTimer();
|
||||||
if (event.timer.handle == inactivityTimer) {
|
context.requestAppSwitchByName(kLockscreenAppName);
|
||||||
cancelInactivityTimer();
|
}
|
||||||
context.requestAppSwitchByName(kLockscreenAppName);
|
}));
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -39,11 +39,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||||
if (event.type != cardboy::sdk::AppEventType::Button)
|
const auto* buttonEvent = event.button();
|
||||||
|
if (!buttonEvent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto& current = event.button.current;
|
const auto& current = buttonEvent->current;
|
||||||
const auto& previous = event.button.previous;
|
const auto& previous = buttonEvent->previous;
|
||||||
|
|
||||||
const bool previousAvailable = buzzerAvailable;
|
const bool previousAvailable = buzzerAvailable;
|
||||||
syncBuzzerState();
|
syncBuzzerState();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace {
|
|||||||
using cardboy::sdk::AppButtonEvent;
|
using cardboy::sdk::AppButtonEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
using cardboy::sdk::AppEvent;
|
using cardboy::sdk::AppEvent;
|
||||||
using cardboy::sdk::AppEventType;
|
using cardboy::sdk::AppTimerEvent;
|
||||||
using cardboy::sdk::AppTimerHandle;
|
using cardboy::sdk::AppTimerHandle;
|
||||||
using cardboy::sdk::InputState;
|
using cardboy::sdk::InputState;
|
||||||
|
|
||||||
@@ -70,14 +70,9 @@ public:
|
|||||||
void onStop() { cancelMoveTimer(); }
|
void onStop() { cancelMoveTimer(); }
|
||||||
|
|
||||||
void handleEvent(const AppEvent& event) {
|
void handleEvent(const AppEvent& event) {
|
||||||
switch (event.type) {
|
event.visit(cardboy::sdk::overload(
|
||||||
case AppEventType::Button:
|
[this](const AppButtonEvent& button) { handleButtons(button); },
|
||||||
handleButtons(event.button);
|
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); }));
|
||||||
break;
|
|
||||||
case AppEventType::Timer:
|
|
||||||
handleTimer(event.timer.handle);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
renderIfNeeded();
|
renderIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace {
|
|||||||
using cardboy::sdk::AppButtonEvent;
|
using cardboy::sdk::AppButtonEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
using cardboy::sdk::AppEvent;
|
using cardboy::sdk::AppEvent;
|
||||||
using cardboy::sdk::AppEventType;
|
using cardboy::sdk::AppTimerEvent;
|
||||||
using cardboy::sdk::AppTimerHandle;
|
using cardboy::sdk::AppTimerHandle;
|
||||||
using cardboy::sdk::InputState;
|
using cardboy::sdk::InputState;
|
||||||
|
|
||||||
@@ -149,14 +149,9 @@ public:
|
|||||||
void onStop() { cancelTimers(); }
|
void onStop() { cancelTimers(); }
|
||||||
|
|
||||||
void handleEvent(const AppEvent& event) {
|
void handleEvent(const AppEvent& event) {
|
||||||
switch (event.type) {
|
event.visit(cardboy::sdk::overload(
|
||||||
case AppEventType::Button:
|
[this](const AppButtonEvent& button) { handleButtons(button); },
|
||||||
handleButtons(event.button);
|
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); }));
|
||||||
break;
|
|
||||||
case AppEventType::Timer:
|
|
||||||
handleTimer(event.timer.handle);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
renderIfNeeded();
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include "cardboy/sdk/app_events.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace cardboy::sdk {
|
namespace cardboy::sdk {
|
||||||
@@ -35,8 +37,8 @@ public:
|
|||||||
virtual void signal(std::uint32_t bits) = 0;
|
virtual void signal(std::uint32_t bits) = 0;
|
||||||
virtual void signalFromISR(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 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 postEvent(const AppEvent& event) = 0;
|
||||||
virtual void cancelTimerSignal() = 0;
|
virtual bool popEvent(AppEvent& outEvent) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cardboy::sdk
|
} // namespace cardboy::sdk
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "cardboy/sdk/event_bus.hpp"
|
#include "cardboy/sdk/event_bus.hpp"
|
||||||
#include "cardboy/sdk/loop_hooks.hpp"
|
#include "cardboy/sdk/loop_hooks.hpp"
|
||||||
|
#include "cardboy/sdk/timer_service.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -100,6 +101,7 @@ struct Services {
|
|||||||
IHighResClock* highResClock = nullptr;
|
IHighResClock* highResClock = nullptr;
|
||||||
IFilesystem* filesystem = nullptr;
|
IFilesystem* filesystem = nullptr;
|
||||||
IEventBus* eventBus = nullptr;
|
IEventBus* eventBus = nullptr;
|
||||||
|
ITimerService* timer = nullptr;
|
||||||
ILoopHooks* loopHooks = nullptr;
|
ILoopHooks* loopHooks = nullptr;
|
||||||
INotificationCenter* notifications = 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 <SFML/Window/Keyboard.hpp>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -108,24 +109,57 @@ private:
|
|||||||
class DesktopEventBus final : public cardboy::sdk::IEventBus {
|
class DesktopEventBus final : public cardboy::sdk::IEventBus {
|
||||||
public:
|
public:
|
||||||
explicit DesktopEventBus(DesktopRuntime& owner);
|
explicit DesktopEventBus(DesktopRuntime& owner);
|
||||||
~DesktopEventBus() override;
|
|
||||||
|
|
||||||
void signal(std::uint32_t bits) override;
|
void signal(std::uint32_t bits) override;
|
||||||
void signalFromISR(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;
|
std::uint32_t wait(std::uint32_t mask, std::uint32_t timeout_ms) override;
|
||||||
void scheduleTimerSignal(std::uint32_t delay_ms) override;
|
void postEvent(const cardboy::sdk::AppEvent& event) override;
|
||||||
void cancelTimerSignal() override;
|
bool popEvent(cardboy::sdk::AppEvent& outEvent) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DesktopRuntime& runtime;
|
DesktopRuntime& runtime;
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
std::condition_variable cv;
|
std::condition_variable cv;
|
||||||
std::uint32_t pendingBits = 0;
|
std::uint32_t pendingBits = 0;
|
||||||
|
std::mutex eventMutex;
|
||||||
|
std::deque<cardboy::sdk::AppEvent> eventQueue;
|
||||||
|
};
|
||||||
|
|
||||||
std::mutex timerMutex;
|
class DesktopTimerService final : public cardboy::sdk::ITimerService {
|
||||||
std::condition_variable timerCv;
|
public:
|
||||||
std::thread timerThread;
|
DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus);
|
||||||
bool timerCancel = false;
|
~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> {
|
class DesktopFramebuffer final : public cardboy::sdk::FramebufferFacade<DesktopFramebuffer> {
|
||||||
@@ -206,6 +240,7 @@ private:
|
|||||||
DesktopHighResClock highResService;
|
DesktopHighResClock highResService;
|
||||||
DesktopFilesystem filesystemService;
|
DesktopFilesystem filesystemService;
|
||||||
DesktopEventBus eventBusService;
|
DesktopEventBus eventBusService;
|
||||||
|
DesktopTimerService timerService;
|
||||||
DesktopNotificationCenter notificationService;
|
DesktopNotificationCenter notificationService;
|
||||||
cardboy::sdk::Services services{};
|
cardboy::sdk::Services services{};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,9 +15,11 @@
|
|||||||
|
|
||||||
namespace cardboy::backend::desktop {
|
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) {
|
void DesktopEventBus::signal(std::uint32_t bits) {
|
||||||
if (bits == 0)
|
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) {
|
void DesktopEventBus::postEvent(const cardboy::sdk::AppEvent& event) {
|
||||||
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<std::mutex> lock(timerMutex);
|
std::lock_guard<std::mutex> lock(eventMutex);
|
||||||
timerCancel = false;
|
if (eventQueue.size() >= kDesktopEventQueueLimit)
|
||||||
|
eventQueue.pop_front();
|
||||||
|
eventQueue.push_back(event);
|
||||||
}
|
}
|
||||||
|
signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
timerCancel = true;
|
stopWorker = true;
|
||||||
}
|
}
|
||||||
timerCv.notify_all();
|
cv.notify_all();
|
||||||
if (timerThread.joinable())
|
if (worker.joinable())
|
||||||
timerThread.join();
|
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);
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
timerCancel = false;
|
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) {
|
bool DesktopStorage::readUint32(std::string_view ns, std::string_view key, std::uint32_t& out) {
|
||||||
auto it = data.find(composeKey(ns, key));
|
auto it = data.find(composeKey(ns, key));
|
||||||
if (it == data.end())
|
if (it == data.end())
|
||||||
@@ -324,7 +441,7 @@ 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) {
|
framebuffer(*this), input(*this), clock(*this), eventBusService(*this), timerService(*this, eventBusService) {
|
||||||
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");
|
||||||
@@ -340,6 +457,7 @@ DesktopRuntime::DesktopRuntime() :
|
|||||||
services.highResClock = &highResService;
|
services.highResClock = &highResService;
|
||||||
services.filesystem = &filesystemService;
|
services.filesystem = &filesystemService;
|
||||||
services.eventBus = &eventBusService;
|
services.eventBus = &eventBusService;
|
||||||
|
services.timer = &timerService;
|
||||||
services.loopHooks = nullptr;
|
services.loopHooks = nullptr;
|
||||||
services.notifications = ¬ificationService;
|
services.notifications = ¬ificationService;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cardboy/sdk/app_events.hpp>
|
||||||
#include <cardboy/sdk/backend.hpp>
|
#include <cardboy/sdk/backend.hpp>
|
||||||
#include <cardboy/sdk/platform.hpp>
|
#include <cardboy/sdk/platform.hpp>
|
||||||
#include <cardboy/sdk/services.hpp>
|
#include <cardboy/sdk/services.hpp>
|
||||||
@@ -14,30 +15,6 @@ namespace cardboy::sdk {
|
|||||||
|
|
||||||
class AppSystem;
|
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;
|
using ActiveBackend = cardboy::backend::ActiveBackend;
|
||||||
|
|
||||||
struct AppContext {
|
struct AppContext {
|
||||||
@@ -63,6 +40,7 @@ struct AppContext {
|
|||||||
[[nodiscard]] IHighResClock* highResClock() const { return services ? services->highResClock : nullptr; }
|
[[nodiscard]] IHighResClock* highResClock() const { return services ? services->highResClock : nullptr; }
|
||||||
[[nodiscard]] IFilesystem* filesystem() const { return services ? services->filesystem : nullptr; }
|
[[nodiscard]] IFilesystem* filesystem() const { return services ? services->filesystem : nullptr; }
|
||||||
[[nodiscard]] IEventBus* eventBus() const { return services ? services->eventBus : 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]] ILoopHooks* loopHooks() const { return services ? services->loopHooks : nullptr; }
|
||||||
[[nodiscard]] INotificationCenter* notificationCenter() const {
|
[[nodiscard]] INotificationCenter* notificationCenter() const {
|
||||||
return services ? services->notifications : nullptr;
|
return services ? services->notifications : nullptr;
|
||||||
@@ -84,27 +62,28 @@ struct AppContext {
|
|||||||
[[nodiscard]] bool hasPendingAppSwitch() const { return pendingSwitch; }
|
[[nodiscard]] bool hasPendingAppSwitch() const { return pendingSwitch; }
|
||||||
|
|
||||||
AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat = false) {
|
AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat = false) {
|
||||||
if (!system)
|
auto* timer = timerService();
|
||||||
|
if (!timer || timerClientId == kInvalidTimerClient)
|
||||||
return kInvalidAppTimer;
|
return kInvalidAppTimer;
|
||||||
return scheduleTimerInternal(delay_ms, repeat);
|
return timer->scheduleTimer(timerClientId, delay_ms, repeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppTimerHandle scheduleRepeatingTimer(std::uint32_t interval_ms) {
|
AppTimerHandle scheduleRepeatingTimer(std::uint32_t interval_ms) {
|
||||||
if (!system)
|
return scheduleTimer(interval_ms, true);
|
||||||
return kInvalidAppTimer;
|
|
||||||
return scheduleTimerInternal(interval_ms, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancelTimer(AppTimerHandle handle) {
|
void cancelTimer(AppTimerHandle handle) {
|
||||||
if (!system)
|
auto* timer = timerService();
|
||||||
|
if (!timer || timerClientId == kInvalidTimerClient)
|
||||||
return;
|
return;
|
||||||
cancelTimerInternal(handle);
|
timer->cancelTimer(timerClientId, handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancelAllTimers() {
|
void cancelAllTimers() {
|
||||||
if (!system)
|
auto* timer = timerService();
|
||||||
|
if (!timer || timerClientId == kInvalidTimerClient)
|
||||||
return;
|
return;
|
||||||
cancelAllTimersInternal();
|
timer->cancelAllTimers(timerClientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -113,10 +92,7 @@ private:
|
|||||||
bool pendingSwitchByName = false;
|
bool pendingSwitchByName = false;
|
||||||
std::size_t pendingAppIndex = 0;
|
std::size_t pendingAppIndex = 0;
|
||||||
std::string pendingAppName;
|
std::string pendingAppName;
|
||||||
|
TimerClientId timerClientId = kInvalidTimerClient;
|
||||||
AppTimerHandle scheduleTimerInternal(std::uint32_t delay_ms, bool repeat);
|
|
||||||
void cancelTimerInternal(AppTimerHandle handle);
|
|
||||||
void cancelAllTimersInternal();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class IApp {
|
class IApp {
|
||||||
|
|||||||
@@ -32,52 +32,20 @@ public:
|
|||||||
private:
|
private:
|
||||||
friend struct AppContext;
|
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 dispatchEvent(const AppEvent& event);
|
||||||
|
bool handlePendingSwitchRequest();
|
||||||
void processDueTimers(std::uint32_t now, std::vector<AppEvent>& outEvents);
|
void attachTimerClient();
|
||||||
std::uint32_t nextTimerDueMs(std::uint32_t now) const;
|
void detachTimerClient();
|
||||||
void clearTimersForCurrentApp();
|
void processEventBusEvents();
|
||||||
TimerRecord* findTimer(AppTimerHandle handle);
|
|
||||||
bool handlePendingSwitchRequest();
|
|
||||||
void notifyEventBus(EventBusSignal signal);
|
|
||||||
|
|
||||||
AppContext context;
|
AppContext context;
|
||||||
std::vector<std::unique_ptr<IAppFactory>> factories;
|
std::vector<std::unique_ptr<IAppFactory>> factories;
|
||||||
std::unique_ptr<IApp> current;
|
std::unique_ptr<IApp> current;
|
||||||
IAppFactory* activeFactory = nullptr;
|
IAppFactory* activeFactory = nullptr;
|
||||||
std::size_t activeIndex = static_cast<std::size_t>(-1);
|
std::size_t activeIndex = static_cast<std::size_t>(-1);
|
||||||
std::vector<TimerRecord> timers;
|
TimerClientId timerClientId = kInvalidTimerClient;
|
||||||
AppTimerHandle nextTimerId = 1;
|
|
||||||
std::uint32_t currentGeneration = 0;
|
|
||||||
InputState lastInputState{};
|
InputState lastInputState{};
|
||||||
bool suppressInputs = false;
|
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
|
} // namespace cardboy::sdk
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "cardboy/sdk/status_bar.hpp"
|
#include "cardboy/sdk/status_bar.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <limits>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace cardboy::sdk {
|
namespace cardboy::sdk {
|
||||||
@@ -34,7 +33,10 @@ AppSystem::AppSystem(AppContext ctx) : context(std::move(ctx)) {
|
|||||||
FramebufferHooks::setPreSendHook(&statusBarPreSendHook<FBType>, &statusBar);
|
FramebufferHooks::setPreSendHook(&statusBarPreSendHook<FBType>, &statusBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSystem::~AppSystem() { FramebufferHooks::clearPreSendHook(); }
|
AppSystem::~AppSystem() {
|
||||||
|
detachTimerClient();
|
||||||
|
FramebufferHooks::clearPreSendHook();
|
||||||
|
}
|
||||||
|
|
||||||
void AppSystem::registerApp(std::unique_ptr<IAppFactory> factory) {
|
void AppSystem::registerApp(std::unique_ptr<IAppFactory> factory) {
|
||||||
if (!factory)
|
if (!factory)
|
||||||
@@ -63,6 +65,7 @@ bool AppSystem::startAppByIndex(std::size_t index) {
|
|||||||
if (current) {
|
if (current) {
|
||||||
current->onStop();
|
current->onStop();
|
||||||
current.reset();
|
current.reset();
|
||||||
|
detachTimerClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
activeFactory = factory.get();
|
activeFactory = factory.get();
|
||||||
@@ -70,8 +73,8 @@ bool AppSystem::startAppByIndex(std::size_t index) {
|
|||||||
context.pendingSwitch = false;
|
context.pendingSwitch = false;
|
||||||
context.pendingSwitchByName = false;
|
context.pendingSwitchByName = false;
|
||||||
context.pendingAppName.clear();
|
context.pendingAppName.clear();
|
||||||
clearTimersForCurrentApp();
|
|
||||||
current = std::move(app);
|
current = std::move(app);
|
||||||
|
attachTimerClient();
|
||||||
lastInputState = context.input.readState();
|
lastInputState = context.input.readState();
|
||||||
suppressInputs = true;
|
suppressInputs = true;
|
||||||
StatusBar::instance().setServices(context.services);
|
StatusBar::instance().setServices(context.services);
|
||||||
@@ -86,17 +89,19 @@ void AppSystem::run() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<AppEvent> events;
|
|
||||||
events.reserve(4);
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
auto* eventBus = context.eventBus();
|
||||||
|
if (!eventBus)
|
||||||
|
return;
|
||||||
|
|
||||||
if (auto* hooks = context.loopHooks())
|
if (auto* hooks = context.loopHooks())
|
||||||
hooks->onLoopIteration();
|
hooks->onLoopIteration();
|
||||||
|
|
||||||
events.clear();
|
processEventBusEvents();
|
||||||
const std::uint32_t now = context.clock.millis();
|
if (handlePendingSwitchRequest())
|
||||||
processDueTimers(now, events);
|
continue;
|
||||||
|
|
||||||
|
const std::uint32_t now = context.clock.millis();
|
||||||
const InputState inputNow = context.input.readState();
|
const InputState inputNow = context.input.readState();
|
||||||
const bool consumedByStatusToggle = StatusBar::instance().handleToggleInput(inputNow, lastInputState);
|
const bool consumedByStatusToggle = StatusBar::instance().handleToggleInput(inputNow, lastInputState);
|
||||||
|
|
||||||
@@ -105,42 +110,25 @@ void AppSystem::run() {
|
|||||||
if (!anyButtonPressed(inputNow))
|
if (!anyButtonPressed(inputNow))
|
||||||
suppressInputs = false;
|
suppressInputs = false;
|
||||||
} else if (!consumedByStatusToggle && inputsDiffer(inputNow, lastInputState)) {
|
} else if (!consumedByStatusToggle && inputsDiffer(inputNow, lastInputState)) {
|
||||||
|
AppButtonEvent button{};
|
||||||
|
button.current = inputNow;
|
||||||
|
button.previous = lastInputState;
|
||||||
|
|
||||||
AppEvent evt{};
|
AppEvent evt{};
|
||||||
evt.type = AppEventType::Button;
|
evt.timestamp_ms = now;
|
||||||
evt.timestamp_ms = now;
|
evt.data = button;
|
||||||
evt.button.current = inputNow;
|
|
||||||
evt.button.previous = lastInputState;
|
|
||||||
events.push_back(evt);
|
|
||||||
lastInputState = inputNow;
|
lastInputState = inputNow;
|
||||||
|
dispatchEvent(evt);
|
||||||
} else if (consumedByStatusToggle) {
|
} else if (consumedByStatusToggle) {
|
||||||
lastInputState = inputNow;
|
lastInputState = inputNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& evt: events) {
|
if (handlePendingSwitchRequest())
|
||||||
dispatchEvent(evt);
|
|
||||||
if (handlePendingSwitchRequest())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::uint32_t waitBase = context.clock.millis();
|
|
||||||
std::uint32_t waitMs = nextTimerDueMs(waitBase);
|
|
||||||
|
|
||||||
if (waitMs == 0)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto* eventBus = context.eventBus();
|
|
||||||
if (!eventBus)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const std::uint32_t mask = to_event_bits(EventBusSignal::Input) | to_event_bits(EventBusSignal::Timer);
|
const std::uint32_t mask = to_event_bits(EventBusSignal::Input) | to_event_bits(EventBusSignal::Timer);
|
||||||
|
eventBus->wait(mask, IEventBus::kWaitForever);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,105 +148,16 @@ std::size_t AppSystem::indexOfFactory(const IAppFactory* factory) const {
|
|||||||
return static_cast<std::size_t>(-1);
|
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) {
|
void AppSystem::dispatchEvent(const AppEvent& event) {
|
||||||
if (current)
|
if (!current)
|
||||||
current->handleEvent(event);
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
void AppSystem::processDueTimers(std::uint32_t now, std::vector<AppEvent>& outEvents) {
|
if (const auto* timer = event.timer()) {
|
||||||
for (auto& timer: timers) {
|
if (timer->clientId != timerClientId)
|
||||||
if (!timer.active || timer.generation != currentGeneration)
|
return;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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 {
|
current->handleEvent(event);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AppSystem::handlePendingSwitchRequest() {
|
bool AppSystem::handlePendingSwitchRequest() {
|
||||||
@@ -278,11 +177,39 @@ bool AppSystem::handlePendingSwitchRequest() {
|
|||||||
return switched;
|
return switched;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppSystem::notifyEventBus(EventBusSignal signal) {
|
void AppSystem::attachTimerClient() {
|
||||||
if (signal == EventBusSignal::None)
|
auto* timer = context.timerService();
|
||||||
|
if (!timer) {
|
||||||
|
timerClientId = kInvalidTimerClient;
|
||||||
|
context.timerClientId = kInvalidTimerClient;
|
||||||
return;
|
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
|
} // namespace cardboy::sdk
|
||||||
|
|||||||
@@ -2497,7 +2497,7 @@ CONFIG_NIMBLE_ROLE_CENTRAL=y
|
|||||||
CONFIG_NIMBLE_ROLE_PERIPHERAL=y
|
CONFIG_NIMBLE_ROLE_PERIPHERAL=y
|
||||||
CONFIG_NIMBLE_ROLE_BROADCASTER=y
|
CONFIG_NIMBLE_ROLE_BROADCASTER=y
|
||||||
CONFIG_NIMBLE_ROLE_OBSERVER=y
|
CONFIG_NIMBLE_ROLE_OBSERVER=y
|
||||||
# CONFIG_NIMBLE_NVS_PERSIST is not set
|
CONFIG_NIMBLE_NVS_PERSIST=y
|
||||||
CONFIG_NIMBLE_SM_LEGACY=y
|
CONFIG_NIMBLE_SM_LEGACY=y
|
||||||
CONFIG_NIMBLE_SM_SC=y
|
CONFIG_NIMBLE_SM_SC=y
|
||||||
# CONFIG_NIMBLE_SM_SC_DEBUG_KEYS is not set
|
# CONFIG_NIMBLE_SM_SC_DEBUG_KEYS is not set
|
||||||
|
|||||||
Reference in New Issue
Block a user