some refactoring 2

This commit is contained in:
2025-10-25 12:34:53 +02:00
parent 1ee132898b
commit f8735d4bce
32 changed files with 816 additions and 818 deletions

View File

@@ -4,6 +4,11 @@ project(cardboy_sdk LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
# add_compile_options(-Werror -O0 -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-unused-variable
# -Wno-error=unused-function
# -Wshadow -Wformat=2 -Wfloat-equal -D_GLIBCXX_DEBUG -Wconversion)
#add_compile_options(-fsanitize=address -fno-sanitize-recover -D_GLIBCXX_DEBUG)
#add_link_options(-fsanitize=address -fno-sanitize-recover -D_GLIBCXX_DEBUG)
add_subdirectory(utils)

View File

@@ -49,7 +49,8 @@ public:
const auto snap = captureTime();
renderIfNeeded(snap);
lastSnapshot = snap;
refreshTimer = context.scheduleRepeatingTimer(200);
if (auto* timer = context.timer())
refreshTimer = timer->scheduleTimer(200, true);
}
void onStop() override { cancelRefreshTimer(); }
@@ -75,10 +76,11 @@ private:
TimeSnapshot lastSnapshot{};
void cancelRefreshTimer() {
if (refreshTimer != cardboy::sdk::kInvalidAppTimer) {
context.cancelTimer(refreshTimer);
refreshTimer = cardboy::sdk::kInvalidAppTimer;
}
if (refreshTimer == cardboy::sdk::kInvalidAppTimer)
return;
if (auto* timer = context.timer())
timer->cancelTimer(refreshTimer);
refreshTimer = cardboy::sdk::kInvalidAppTimer;
}
void handleButtonEvent(const cardboy::sdk::AppButtonEvent& button) {

View File

@@ -1156,15 +1156,17 @@ public:
uint32_t stableFrames = 0;
void cancelTick() {
if (tickTimer != kInvalidAppTimer) {
context.cancelTimer(tickTimer);
tickTimer = kInvalidAppTimer;
}
if (tickTimer == kInvalidAppTimer)
return;
if (auto* timer = context.timer())
timer->cancelTimer(tickTimer);
tickTimer = kInvalidAppTimer;
}
void scheduleNextTick(uint32_t delayMs) {
cancelTick();
tickTimer = context.scheduleTimer(delayMs, false);
if (auto* timer = context.timer())
tickTimer = timer->scheduleTimer(delayMs, false);
}
uint32_t idleDelayMs() const { return browserDirty ? 50 : 140; }

View File

@@ -69,7 +69,8 @@ public:
const auto snap = captureTime();
renderIfNeeded(snap);
lastSnapshot = snap;
refreshTimer = context.scheduleRepeatingTimer(kRefreshIntervalMs);
if (auto* timer = context.timer())
refreshTimer = timer->scheduleTimer(kRefreshIntervalMs, true);
}
void onStop() override { cancelRefreshTimer(); }
@@ -104,10 +105,11 @@ private:
std::uint32_t lastNotificationInteractionMs = 0;
void cancelRefreshTimer() {
if (refreshTimer != cardboy::sdk::kInvalidAppTimer) {
context.cancelTimer(refreshTimer);
refreshTimer = cardboy::sdk::kInvalidAppTimer;
}
if (refreshTimer == cardboy::sdk::kInvalidAppTimer)
return;
if (auto* timer = context.timer())
timer->cancelTimer(refreshTimer);
refreshTimer = cardboy::sdk::kInvalidAppTimer;
}
static bool comboPressed(const cardboy::sdk::InputState& state) { return state.a && state.select; }

View File

@@ -189,15 +189,17 @@ private:
}
void cancelInactivityTimer() {
if (inactivityTimer != cardboy::sdk::kInvalidAppTimer) {
context.cancelTimer(inactivityTimer);
inactivityTimer = cardboy::sdk::kInvalidAppTimer;
}
if (inactivityTimer == cardboy::sdk::kInvalidAppTimer)
return;
if (auto* timer = context.timer())
timer->cancelTimer(inactivityTimer);
inactivityTimer = cardboy::sdk::kInvalidAppTimer;
}
void resetInactivityTimer() {
cancelInactivityTimer();
inactivityTimer = context.scheduleTimer(kIdleTimeoutMs);
if (auto* timer = context.timer())
inactivityTimer = timer->scheduleTimer(kIdleTimeoutMs, false);
}
};

View File

@@ -248,14 +248,16 @@ private:
void scheduleMoveTimer() {
cancelMoveTimer();
const std::uint32_t interval = currentInterval();
moveTimer = context.scheduleRepeatingTimer(interval);
if (auto* timer = context.timer())
moveTimer = timer->scheduleTimer(interval, true);
}
void cancelMoveTimer() {
if (moveTimer != cardboy::sdk::kInvalidAppTimer) {
context.cancelTimer(moveTimer);
moveTimer = cardboy::sdk::kInvalidAppTimer;
}
if (moveTimer == cardboy::sdk::kInvalidAppTimer)
return;
if (auto* timer = context.timer())
timer->cancelTimer(moveTimer);
moveTimer = cardboy::sdk::kInvalidAppTimer;
}
[[nodiscard]] std::uint32_t currentInterval() const {

View File

@@ -235,35 +235,40 @@ private:
void cancelTimers() {
if (dropTimer != cardboy::sdk::kInvalidAppTimer) {
context.cancelTimer(dropTimer);
if (auto* timer = context.timer())
timer->cancelTimer(dropTimer);
dropTimer = cardboy::sdk::kInvalidAppTimer;
}
cancelSoftDropTimer();
}
void cancelSoftDropTimer() {
if (softTimer != cardboy::sdk::kInvalidAppTimer) {
context.cancelTimer(softTimer);
softTimer = cardboy::sdk::kInvalidAppTimer;
}
if (softTimer == cardboy::sdk::kInvalidAppTimer)
return;
if (auto* timer = context.timer())
timer->cancelTimer(softTimer);
softTimer = cardboy::sdk::kInvalidAppTimer;
}
void scheduleDropTimer() {
cancelDropTimer();
const std::uint32_t interval = dropIntervalMs();
dropTimer = context.scheduleRepeatingTimer(interval);
if (auto* timer = context.timer())
dropTimer = timer->scheduleTimer(interval, true);
}
void cancelDropTimer() {
if (dropTimer != cardboy::sdk::kInvalidAppTimer) {
context.cancelTimer(dropTimer);
dropTimer = cardboy::sdk::kInvalidAppTimer;
}
if (dropTimer == cardboy::sdk::kInvalidAppTimer)
return;
if (auto* timer = context.timer())
timer->cancelTimer(dropTimer);
dropTimer = cardboy::sdk::kInvalidAppTimer;
}
void scheduleSoftDropTimer() {
cancelSoftDropTimer();
softTimer = context.scheduleRepeatingTimer(60);
if (auto* timer = context.timer())
softTimer = timer->scheduleTimer(60, true);
}
[[nodiscard]] std::uint32_t dropIntervalMs() const {

View File

@@ -16,7 +16,6 @@ target_sources(cardboy_backend_interface
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/backend/backend_interface.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/backend.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/display_spec.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/event_bus.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/loop_hooks.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/input_state.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/platform.hpp

View File

@@ -12,17 +12,13 @@ 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;
AppTimerHandle handle = kInvalidAppTimer;
};
struct AppEvent {
@@ -51,6 +47,8 @@ struct AppEvent {
}
};
static_assert(std::is_trivially_copyable_v<AppEvent>);
template<typename... Ts>
struct Overload : Ts... {
using Ts::operator()...;

View File

@@ -1,44 +0,0 @@
#pragma once
#include "cardboy/sdk/app_events.hpp"
#include <cstdint>
namespace cardboy::sdk {
enum class EventBusSignal : std::uint32_t {
None = 0,
Input = 1u << 0,
Timer = 1u << 1,
External = 1u << 2,
};
inline EventBusSignal operator|(EventBusSignal lhs, EventBusSignal rhs) {
return static_cast<EventBusSignal>(static_cast<std::uint32_t>(lhs) | static_cast<std::uint32_t>(rhs));
}
inline EventBusSignal& operator|=(EventBusSignal& lhs, EventBusSignal rhs) {
lhs = lhs | rhs;
return lhs;
}
inline EventBusSignal operator&(EventBusSignal lhs, EventBusSignal rhs) {
return static_cast<EventBusSignal>(static_cast<std::uint32_t>(lhs) & static_cast<std::uint32_t>(rhs));
}
inline std::uint32_t to_event_bits(EventBusSignal signal) { return static_cast<std::uint32_t>(signal); }
class IEventBus {
public:
static constexpr std::uint32_t kWaitForever = 0xFFFFFFFFu;
virtual ~IEventBus() = default;
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 postEvent(const AppEvent& event) = 0;
virtual bool popEvent(AppEvent& outEvent) = 0;
};
} // namespace cardboy::sdk

View File

@@ -11,8 +11,8 @@ public:
static void invokePreSend(void* framebuffer);
private:
static PreSendHook hook_;
static void* userData_;
static PreSendHook _hook;
static void* _userData;
};
} // namespace cardboy::sdk

View File

@@ -1,10 +1,10 @@
#pragma once
#include "cardboy/sdk/event_bus.hpp"
#include "cardboy/sdk/loop_hooks.hpp"
#include "cardboy/sdk/timer_service.hpp"
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
@@ -74,36 +74,56 @@ public:
class INotificationCenter {
public:
struct Notification {
std::uint64_t id = 0;
std::uint64_t timestamp = 0;
std::uint64_t id = 0;
std::uint64_t timestamp = 0;
std::uint64_t externalId = 0;
std::string title;
std::string body;
bool unread = true;
bool unread = true;
};
virtual ~INotificationCenter() = default;
virtual void pushNotification(Notification notification) = 0;
[[nodiscard]] virtual std::uint32_t revision() const = 0;
[[nodiscard]] virtual std::vector<Notification> recent(std::size_t limit) const = 0;
virtual void markAllRead() = 0;
virtual void clear() = 0;
virtual void removeById(std::uint64_t id) = 0;
virtual void removeByExternalId(std::uint64_t externalId) = 0;
virtual void pushNotification(Notification notification) = 0;
[[nodiscard]] virtual std::uint32_t revision() const = 0;
[[nodiscard]] virtual std::vector<Notification> recent(std::size_t limit) const = 0;
virtual void markAllRead() = 0;
virtual void clear() = 0;
virtual void removeById(std::uint64_t id) = 0;
virtual void removeByExternalId(std::uint64_t externalId) = 0;
};
class IEventBus {
public:
virtual ~IEventBus() = default;
virtual void post(const AppEvent& event) = 0;
virtual AppEvent pop() = 0;
};
struct AppScopedServices {
ITimerService* timer = nullptr;
virtual ~AppScopedServices() = default;
};
class IAppServiceProvider {
public:
virtual ~IAppServiceProvider() = default;
[[nodiscard]] virtual std::unique_ptr<AppScopedServices> createScopedServices(std::uint64_t generation) = 0;
};
struct Services {
IBuzzer* buzzer = nullptr;
IBatteryMonitor* battery = nullptr;
IStorage* storage = nullptr;
IRandom* random = nullptr;
IHighResClock* highResClock = nullptr;
IFilesystem* filesystem = nullptr;
IEventBus* eventBus = nullptr;
ITimerService* timer = nullptr;
ILoopHooks* loopHooks = nullptr;
IBuzzer* buzzer = nullptr;
IBatteryMonitor* battery = nullptr;
IStorage* storage = nullptr;
IRandom* random = nullptr;
IHighResClock* highResClock = nullptr;
IFilesystem* filesystem = nullptr;
IEventBus* eventBus = nullptr;
ILoopHooks* loopHooks = nullptr;
INotificationCenter* notifications = nullptr;
IAppServiceProvider* appServices = nullptr;
};
} // namespace cardboy::sdk

View File

@@ -10,12 +10,9 @@ 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;
virtual AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat) = 0;
virtual void cancelTimer(AppTimerHandle handle) = 0;
virtual void cancelAllTimers() = 0;
};
} // namespace cardboy::sdk

View File

@@ -1,6 +1,5 @@
#pragma once
#include "cardboy/sdk/event_bus.hpp"
#include "cardboy/sdk/platform.hpp"
#include "cardboy/sdk/services.hpp"
@@ -12,6 +11,7 @@
#include <cstdint>
#include <filesystem>
#include <limits>
#include <memory>
#include <mutex>
#include <random>
#include <string>
@@ -113,33 +113,39 @@ public:
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 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;
};
class DesktopScopedEventBus final : public cardboy::sdk::IAppEventBus {
public:
explicit DesktopScopedEventBus(cardboy::sdk::IEventBus& bus);
void post(const cardboy::sdk::AppEvent& event) override;
bool pop(cardboy::sdk::AppEvent& outEvent) override;
void clear() override;
private:
cardboy::sdk::IEventBus& globalBus;
std::mutex mutex;
std::deque<cardboy::sdk::AppEvent> queue;
};
class DesktopTimerService final : public cardboy::sdk::ITimerService {
public:
DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus);
DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IAppEventBus& appBus);
~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;
cardboy::sdk::AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat) override;
void cancelTimer(cardboy::sdk::AppTimerHandle handle) override;
void cancelAllTimers() 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};
@@ -151,15 +157,30 @@ private:
void wakeWorker();
void cleanupInactive();
DesktopRuntime& runtime;
cardboy::sdk::IEventBus& eventBus;
DesktopRuntime& runtime;
cardboy::sdk::IAppEventBus& appEventBus;
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 DesktopAppServiceProvider final : public cardboy::sdk::IAppServiceProvider {
public:
DesktopAppServiceProvider(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus);
[[nodiscard]] std::unique_ptr<cardboy::sdk::AppScopedServices> createScopedServices(std::uint64_t generation) override;
private:
struct ScopedServices final : cardboy::sdk::AppScopedServices {
std::unique_ptr<DesktopScopedEventBus> ownedEventBus;
std::unique_ptr<DesktopTimerService> ownedTimer;
};
DesktopRuntime& runtime;
cardboy::sdk::IEventBus& eventBus;
};
class DesktopFramebuffer final : public cardboy::sdk::FramebufferFacade<DesktopFramebuffer> {
@@ -240,7 +261,7 @@ private:
DesktopHighResClock highResService;
DesktopFilesystem filesystemService;
DesktopEventBus eventBusService;
DesktopTimerService timerService;
DesktopAppServiceProvider appServiceProvider;
DesktopNotificationCenter notificationService;
cardboy::sdk::Services services{};
};

View File

@@ -63,26 +63,34 @@ std::uint32_t DesktopEventBus::wait(std::uint32_t mask, std::uint32_t timeout_ms
}
}
void DesktopEventBus::postEvent(const cardboy::sdk::AppEvent& event) {
DesktopScopedEventBus::DesktopScopedEventBus(cardboy::sdk::IEventBus& bus) : globalBus(bus) {}
void DesktopScopedEventBus::post(const cardboy::sdk::AppEvent& event) {
{
std::lock_guard<std::mutex> lock(eventMutex);
if (eventQueue.size() >= kDesktopEventQueueLimit)
eventQueue.pop_front();
eventQueue.push_back(event);
std::lock_guard<std::mutex> lock(mutex);
if (queue.size() >= kDesktopEventQueueLimit)
queue.pop_front();
queue.push_back(event);
}
signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
globalBus.signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
}
bool DesktopEventBus::popEvent(cardboy::sdk::AppEvent& outEvent) {
std::lock_guard<std::mutex> lock(eventMutex);
if (eventQueue.empty())
bool DesktopScopedEventBus::pop(cardboy::sdk::AppEvent& outEvent) {
std::lock_guard<std::mutex> lock(mutex);
if (queue.empty())
return false;
outEvent = eventQueue.front();
eventQueue.pop_front();
outEvent = queue.front();
queue.pop_front();
return true;
}
DesktopTimerService::DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus) : runtime(owner), eventBus(bus) {
void DesktopScopedEventBus::clear() {
std::lock_guard<std::mutex> lock(mutex);
queue.clear();
}
DesktopTimerService::DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IAppEventBus& appBus) :
runtime(owner), appEventBus(appBus) {
worker = std::thread(&DesktopTimerService::workerLoop, this);
}
@@ -94,33 +102,17 @@ DesktopTimerService::~DesktopTimerService() {
cv.notify_all();
if (worker.joinable())
worker.join();
cancelAllTimers();
}
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;
cardboy::sdk::AppTimerHandle DesktopTimerService::scheduleTimer(std::uint32_t delay_ms, bool repeat) {
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)));
const auto interval = std::chrono::milliseconds(std::max<std::uint32_t>(1, repeat ? std::max(delay_ms, 1u)
: std::max(delay_ms, 1u)));
TimerRecord record{};
record.clientId = clientId;
record.repeat = repeat;
record.interval = interval;
record.due = dueTime;
@@ -141,25 +133,22 @@ cardboy::sdk::AppTimerHandle DesktopTimerService::scheduleTimer(cardboy::sdk::Ti
}
}
void DesktopTimerService::cancelTimer(cardboy::sdk::TimerClientId clientId, cardboy::sdk::AppTimerHandle handle) {
if (clientId == cardboy::sdk::kInvalidTimerClient || handle == cardboy::sdk::kInvalidAppTimer)
void DesktopTimerService::cancelTimer(cardboy::sdk::AppTimerHandle handle) {
if (handle == cardboy::sdk::kInvalidAppTimer)
return;
std::lock_guard<std::mutex> lock(mutex);
for (auto& record: timers) {
if (record.clientId == clientId && record.handle == handle)
if (record.handle == handle)
record.active = false;
}
cleanupInactive();
wakeWorker();
}
void DesktopTimerService::cancelAllTimers(cardboy::sdk::TimerClientId clientId) {
if (clientId == cardboy::sdk::kInvalidTimerClient)
return;
void DesktopTimerService::cancelAllTimers() {
std::lock_guard<std::mutex> lock(mutex);
for (auto& record: timers) {
if (record.clientId == clientId)
record.active = false;
record.active = false;
}
cleanupInactive();
wakeWorker();
@@ -195,13 +184,12 @@ void DesktopTimerService::workerLoop() {
lock.unlock();
cardboy::sdk::AppTimerEvent timerEvent{};
timerEvent.clientId = record.clientId;
timerEvent.handle = record.handle;
timerEvent.handle = record.handle;
cardboy::sdk::AppEvent event{};
event.timestamp_ms = runtime.clock.millis();
event.data = timerEvent;
eventBus.postEvent(event);
appEventBus.post(event);
lock.lock();
continue;
@@ -218,6 +206,19 @@ void DesktopTimerService::cleanupInactive() {
timers.end());
}
DesktopAppServiceProvider::DesktopAppServiceProvider(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus) :
runtime(owner), eventBus(bus) {}
std::unique_ptr<cardboy::sdk::AppScopedServices> DesktopAppServiceProvider::createScopedServices(std::uint64_t generation) {
(void)generation;
auto scoped = std::make_unique<ScopedServices>();
scoped->ownedEventBus = std::make_unique<DesktopScopedEventBus>(eventBus);
scoped->events = scoped->ownedEventBus.get();
scoped->ownedTimer = std::make_unique<DesktopTimerService>(runtime, *scoped->ownedEventBus);
scoped->timer = scoped->ownedTimer.get();
return scoped;
}
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())
@@ -441,7 +442,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), timerService(*this, eventBusService) {
framebuffer(*this), input(*this), clock(*this), eventBusService(*this), appServiceProvider(*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");
@@ -457,7 +458,7 @@ DesktopRuntime::DesktopRuntime() :
services.highResClock = &highResService;
services.filesystem = &filesystemService;
services.eventBus = &eventBusService;
services.timer = &timerService;
services.appServices = &appServiceProvider;
services.loopHooks = nullptr;
services.notifications = &notificationService;
}

View File

@@ -33,66 +33,44 @@ struct AppContext {
[[nodiscard]] Services* getServices() const { return services; }
[[nodiscard]] IBuzzer* buzzer() const { return services ? services->buzzer : nullptr; }
[[nodiscard]] IBatteryMonitor* battery() const { return services ? services->battery : nullptr; }
[[nodiscard]] IStorage* storage() const { return services ? services->storage : nullptr; }
[[nodiscard]] IRandom* random() const { return services ? services->random : nullptr; }
[[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]] IBuzzer* buzzer() const { return services ? services->buzzer : nullptr; }
[[nodiscard]] IBatteryMonitor* battery() const { return services ? services->battery : nullptr; }
[[nodiscard]] IStorage* storage() const { return services ? services->storage : nullptr; }
[[nodiscard]] IRandom* random() const { return services ? services->random : nullptr; }
[[nodiscard]] IHighResClock* highResClock() const { return services ? services->highResClock : nullptr; }
[[nodiscard]] IFilesystem* filesystem() const { return services ? services->filesystem : nullptr; }
[[nodiscard]] AppScopedServices* appServices() const { return _scopedServices; }
[[nodiscard]] IEventBus* eventBus() const { return services ? services->eventBus : nullptr; }
[[nodiscard]] ITimerService* timer() const { return _scopedServices ? _scopedServices->timer : nullptr; }
[[nodiscard]] ILoopHooks* loopHooks() const { return services ? services->loopHooks : nullptr; }
[[nodiscard]] INotificationCenter* notificationCenter() const {
return services ? services->notifications : nullptr;
}
void requestAppSwitchByIndex(std::size_t index) {
pendingAppIndex = index;
pendingAppName.clear();
pendingSwitchByName = false;
pendingSwitch = true;
_pendingAppIndex = index;
_pendingAppName.clear();
_pendingSwitchByName = false;
_pendingSwitch = true;
}
void requestAppSwitchByName(std::string_view name) {
pendingAppName.assign(name.begin(), name.end());
pendingSwitchByName = true;
pendingSwitch = true;
_pendingAppName.assign(name.begin(), name.end());
_pendingSwitchByName = true;
_pendingSwitch = true;
}
[[nodiscard]] bool hasPendingAppSwitch() const { return pendingSwitch; }
AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat = false) {
auto* timer = timerService();
if (!timer || timerClientId == kInvalidTimerClient)
return kInvalidAppTimer;
return timer->scheduleTimer(timerClientId, delay_ms, repeat);
}
AppTimerHandle scheduleRepeatingTimer(std::uint32_t interval_ms) {
return scheduleTimer(interval_ms, true);
}
void cancelTimer(AppTimerHandle handle) {
auto* timer = timerService();
if (!timer || timerClientId == kInvalidTimerClient)
return;
timer->cancelTimer(timerClientId, handle);
}
void cancelAllTimers() {
auto* timer = timerService();
if (!timer || timerClientId == kInvalidTimerClient)
return;
timer->cancelAllTimers(timerClientId);
}
[[nodiscard]] bool hasPendingAppSwitch() const { return _pendingSwitch; }
private:
friend class AppSystem;
bool pendingSwitch = false;
bool pendingSwitchByName = false;
std::size_t pendingAppIndex = 0;
std::string pendingAppName;
TimerClientId timerClientId = kInvalidTimerClient;
bool _pendingSwitch = false;
bool _pendingSwitchByName = false;
std::size_t _pendingAppIndex = 0;
std::string _pendingAppName;
AppScopedServices* _scopedServices = nullptr;
void setScopedServices(AppScopedServices* services) { _scopedServices = services; }
};
class IApp {

View File

@@ -1,6 +1,5 @@
#pragma once
#include <cardboy/sdk/event_bus.hpp>
#include "app_framework.hpp"
#include <cstdint>
@@ -16,36 +15,30 @@ public:
~AppSystem();
void registerApp(std::unique_ptr<IAppFactory> factory);
bool startApp(const std::string& name);
bool startAppByIndex(std::size_t index);
void startApp(const std::string& name);
void startAppByIndex(std::size_t index);
void run();
[[nodiscard]] std::size_t appCount() const { return factories.size(); }
[[nodiscard]] std::size_t appCount() const { return _factories.size(); }
[[nodiscard]] const IAppFactory* factoryAt(std::size_t index) const;
[[nodiscard]] std::size_t indexOfFactory(const IAppFactory* factory) const;
[[nodiscard]] std::size_t currentFactoryIndex() const { return activeIndex; }
[[nodiscard]] std::size_t currentFactoryIndex() const { return _activeIndex; }
[[nodiscard]] const IApp* currentApp() const { return current.get(); }
[[nodiscard]] const IAppFactory* currentFactory() const { return activeFactory; }
[[nodiscard]] const IApp* currentApp() const { return _current.get(); }
[[nodiscard]] const IAppFactory* currentFactory() const { return _activeFactory; }
private:
friend struct AppContext;
void handlePendingSwitchRequest();
std::unique_ptr<AppScopedServices> createAppScopedServices(std::uint64_t generation);
void dispatchEvent(const AppEvent& event);
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);
TimerClientId timerClientId = kInvalidTimerClient;
InputState lastInputState{};
bool suppressInputs = false;
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::unique_ptr<AppScopedServices> _scopedServices;
std::uint64_t _nextScopedGeneration = 1;
};
} // namespace cardboy::sdk

View File

@@ -13,8 +13,8 @@ public:
static void invokePreSend(void* framebuffer);
private:
static PreSendHook hook_;
static void* userData_;
static PreSendHook _hook;
static void* _userData;
};
} // namespace cardboy::sdk

View File

@@ -17,11 +17,11 @@ class StatusBar {
public:
static StatusBar& instance();
void setServices(Services* services) { services_ = services; }
void setServices(Services* services) { _services = services; }
void setEnabled(bool value);
void toggle();
[[nodiscard]] bool isEnabled() const { return enabled_; }
[[nodiscard]] bool isEnabled() const { return _enabled; }
void setCurrentAppName(std::string_view name);
@@ -29,7 +29,7 @@ public:
template<typename Framebuffer>
void renderIfEnabled(Framebuffer& fb) {
if (!enabled_)
if (!_enabled)
return;
renderBar(fb);
}
@@ -84,9 +84,9 @@ private:
[[nodiscard]] std::string prepareLeftText(int displayWidth) const;
[[nodiscard]] std::string prepareRightText() const;
bool enabled_ = false;
Services* services_ = nullptr;
std::string appName_{};
bool _enabled = false;
Services* _services = nullptr;
std::string _appName{};
};
} // namespace cardboy::sdk

View File

@@ -7,14 +7,6 @@
namespace cardboy::sdk {
namespace {
[[nodiscard]] bool inputsDiffer(const InputState& a, const InputState& b) {
return a.up != b.up || a.down != b.down || a.left != b.left || a.right != b.right || a.a != b.a || a.b != b.b ||
a.select != b.select || a.start != b.start;
}
[[nodiscard]] bool anyButtonPressed(const InputState& state) {
return state.up || state.down || state.left || state.right || state.a || state.b || state.select || state.start;
}
template<typename Framebuffer>
void statusBarPreSendHook(void* framebuffer, void* userData) {
@@ -25,191 +17,120 @@ void statusBarPreSendHook(void* framebuffer, void* userData) {
}
} // namespace
AppSystem::AppSystem(AppContext ctx) : context(std::move(ctx)) {
context.system = this;
AppSystem::AppSystem(AppContext ctx) : _context(std::move(ctx)) {
_context.system = this;
auto& statusBar = StatusBar::instance();
statusBar.setServices(context.services);
using FBType = typename AppContext::Framebuffer;
FramebufferHooks::setPreSendHook(&statusBarPreSendHook<FBType>, &statusBar);
statusBar.setServices(_context.services);
FramebufferHooks::setPreSendHook(&statusBarPreSendHook<AppContext::Framebuffer>, &statusBar);
}
AppSystem::~AppSystem() {
detachTimerClient();
FramebufferHooks::clearPreSendHook();
}
AppSystem::~AppSystem() { FramebufferHooks::clearPreSendHook(); }
void AppSystem::registerApp(std::unique_ptr<IAppFactory> factory) {
if (!factory)
return;
factories.emplace_back(std::move(factory));
assert(factory);
_factories.emplace_back(std::move(factory));
}
bool AppSystem::startApp(const std::string& name) {
for (std::size_t i = 0; i < factories.size(); ++i) {
if (factories[i]->name() == name)
return startAppByIndex(i);
void AppSystem::startApp(const std::string& name) {
for (std::size_t i = 0; i < _factories.size(); ++i) {
if (_factories[i]->name() == name)
startAppByIndex(i);
}
return false;
}
bool AppSystem::startAppByIndex(std::size_t index) {
if (index >= factories.size())
return false;
void AppSystem::startAppByIndex(std::size_t index) {
assert(index < _factories.size());
context.system = this;
auto& factory = factories[index];
auto app = factory->create(context);
if (!app)
return false;
if (current) {
current->onStop();
current.reset();
detachTimerClient();
if (_current) {
_current->onStop();
_current.reset();
}
activeFactory = factory.get();
activeIndex = index;
context.pendingSwitch = false;
context.pendingSwitchByName = false;
context.pendingAppName.clear();
current = std::move(app);
attachTimerClient();
lastInputState = context.input.readState();
suppressInputs = true;
StatusBar::instance().setServices(context.services);
StatusBar::instance().setCurrentAppName(activeFactory ? activeFactory->name() : "");
current->onStart();
return true;
_context.system = this;
auto& factory = _factories[index];
const std::uint64_t newGeneration = _nextScopedGeneration++;
auto app = factory->create(_context);
assert(app);
_scopedServices.reset();
auto scoped = createAppScopedServices(newGeneration);
_scopedServices = std::move(scoped);
_context.setScopedServices(_scopedServices.get());
_activeFactory = factory.get();
_activeIndex = index;
_context._pendingSwitch = false;
_context._pendingSwitchByName = false;
_context._pendingAppName.clear();
_current = std::move(app);
StatusBar::instance().setServices(_context.services);
StatusBar::instance().setCurrentAppName(_activeFactory ? _activeFactory->name() : "");
_current->onStart();
}
void AppSystem::run() {
if (!current) {
if (factories.empty() || !startAppByIndex(0))
return;
if (!_current) {
assert(!_factories.empty());
startAppByIndex(0);
}
while (true) {
auto* eventBus = context.eventBus();
if (!eventBus)
return;
if (auto* hooks = context.loopHooks())
if (auto* hooks = _context.loopHooks())
hooks->onLoopIteration();
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);
if (suppressInputs) {
lastInputState = inputNow;
if (!anyButtonPressed(inputNow))
suppressInputs = false;
} else if (!consumedByStatusToggle && inputsDiffer(inputNow, lastInputState)) {
AppButtonEvent button{};
button.current = inputNow;
button.previous = lastInputState;
AppEvent evt{};
evt.timestamp_ms = now;
evt.data = button;
lastInputState = inputNow;
dispatchEvent(evt);
} else if (consumedByStatusToggle) {
lastInputState = inputNow;
auto event = _context.eventBus()->pop();
if (const auto* btn = event.button()) {
const bool consumedByStatusToggle = StatusBar::instance().handleToggleInput(btn->current, btn->previous);
if (consumedByStatusToggle) {
continue;
}
}
if (handlePendingSwitchRequest())
_current->handleEvent(event);
if (_context._pendingSwitch) {
handlePendingSwitchRequest();
continue;
const std::uint32_t mask = to_event_bits(EventBusSignal::Input) | to_event_bits(EventBusSignal::Timer);
eventBus->wait(mask, IEventBus::kWaitForever);
}
}
}
const IAppFactory* AppSystem::factoryAt(std::size_t index) const {
if (index >= factories.size())
if (index >= _factories.size())
return nullptr;
return factories[index].get();
return _factories[index].get();
}
std::size_t AppSystem::indexOfFactory(const IAppFactory* factory) const {
if (!factory)
return static_cast<std::size_t>(-1);
for (std::size_t i = 0; i < factories.size(); ++i) {
if (factories[i].get() == factory)
for (std::size_t i = 0; i < _factories.size(); ++i) {
if (_factories[i].get() == factory)
return i;
}
return static_cast<std::size_t>(-1);
}
void AppSystem::dispatchEvent(const AppEvent& event) {
if (!current)
return;
if (const auto* timer = event.timer()) {
if (timer->clientId != timerClientId)
return;
}
current->handleEvent(event);
}
bool AppSystem::handlePendingSwitchRequest() {
if (!context.pendingSwitch)
return false;
const bool byName = context.pendingSwitchByName;
const std::size_t reqIndex = context.pendingAppIndex;
const std::string reqName = context.pendingAppName;
context.pendingSwitch = false;
context.pendingSwitchByName = false;
context.pendingAppName.clear();
void AppSystem::handlePendingSwitchRequest() {
assert(_context._pendingSwitch);
const bool byName = _context._pendingSwitchByName;
const std::size_t reqIndex = _context._pendingAppIndex;
const std::string reqName = _context._pendingAppName;
_context._pendingSwitch = false;
_context._pendingSwitchByName = false;
_context._pendingAppName.clear();
bool switched = false;
if (byName)
switched = startApp(reqName);
startApp(reqName);
else
switched = startAppByIndex(reqIndex);
return switched;
startAppByIndex(reqIndex);
}
void AppSystem::attachTimerClient() {
auto* timer = context.timerService();
if (!timer) {
timerClientId = kInvalidTimerClient;
context.timerClientId = kInvalidTimerClient;
return;
}
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;
}
std::unique_ptr<AppScopedServices> AppSystem::createAppScopedServices(std::uint64_t generation) {
if (!_context.services || !_context.services->appServices)
return nullptr;
return _context.services->appServices->createScopedServices(generation);
}
} // namespace cardboy::sdk

View File

@@ -2,22 +2,22 @@
namespace cardboy::sdk {
FramebufferHooks::PreSendHook FramebufferHooks::hook_ = nullptr;
void* FramebufferHooks::userData_ = nullptr;
FramebufferHooks::PreSendHook FramebufferHooks::_hook = nullptr;
void* FramebufferHooks::_userData = nullptr;
void FramebufferHooks::setPreSendHook(PreSendHook hook, void* userData) {
hook_ = hook;
userData_ = userData;
_hook = hook;
_userData = userData;
}
void FramebufferHooks::clearPreSendHook() {
hook_ = nullptr;
userData_ = nullptr;
_hook = nullptr;
_userData = nullptr;
}
void FramebufferHooks::invokePreSend(void* framebuffer) {
if (hook_)
hook_(framebuffer, userData_);
if (_hook)
_hook(framebuffer, _userData);
}
} // namespace cardboy::sdk

View File

@@ -12,17 +12,17 @@ StatusBar& StatusBar::instance() {
return bar;
}
void StatusBar::setEnabled(bool value) { enabled_ = value; }
void StatusBar::setEnabled(bool value) { _enabled = value; }
void StatusBar::toggle() {
enabled_ = !enabled_;
if (services_ && services_->buzzer)
services_->buzzer->beepMove();
_enabled = !_enabled;
if (_services && _services->buzzer)
_services->buzzer->beepMove();
}
void StatusBar::setCurrentAppName(std::string_view name) {
appName_.assign(name.begin(), name.end());
std::transform(appName_.begin(), appName_.end(), appName_.begin(),
_appName.assign(name.begin(), name.end());
std::transform(_appName.begin(), _appName.end(), _appName.begin(),
[](unsigned char ch) { return static_cast<char>(std::toupper(ch)); });
}
@@ -37,7 +37,7 @@ bool StatusBar::handleToggleInput(const InputState& current, const InputState& p
}
std::string StatusBar::prepareLeftText(int displayWidth) const {
std::string text = appName_.empty() ? std::string("CARDBOY") : appName_;
std::string text = _appName.empty() ? std::string("CARDBOY") : _appName;
int maxWidth = std::max(0, displayWidth - 32);
while (!text.empty() && font16x8::measureText(text, 1, 1) > maxWidth)
text.pop_back();
@@ -45,14 +45,14 @@ std::string StatusBar::prepareLeftText(int displayWidth) const {
}
std::string StatusBar::prepareRightText() const {
if (!services_)
if (!_services)
return {};
std::string right;
if (services_->battery && services_->battery->hasData()) {
const float current = services_->battery->current();
const float chargeMah = services_->battery->charge();
const float fallbackV = services_->battery->voltage();
if (_services->battery && _services->battery->hasData()) {
const float current = _services->battery->current();
const float chargeMah = _services->battery->charge();
const float fallbackV = _services->battery->voltage();
char buf[64];
if (std::isfinite(current) && std::isfinite(chargeMah)) {
std::snprintf(buf, sizeof(buf), "cur %.2fmA chr %.2fmAh", static_cast<double>(current),
@@ -63,7 +63,7 @@ std::string StatusBar::prepareRightText() const {
right.assign(buf);
}
if (services_->buzzer && services_->buzzer->isMuted()) {
if (_services->buzzer && _services->buzzer->isMuted()) {
if (!right.empty())
right.append(" ");
right.append("MUTE");