mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 15:17:48 +01:00
faster timeout for games
This commit is contained in:
@@ -144,11 +144,14 @@ public:
|
||||
}
|
||||
|
||||
~EventBus() override { vQueueDelete(_queueHandle); }
|
||||
void post(const sdk::AppEvent& event) override { xQueueSendToBack(_queueHandle, &event, portMAX_DELAY); }
|
||||
sdk::AppEvent pop() override {
|
||||
void post(const sdk::AppEvent& event) override { xQueueSendToBack(_queueHandle, &event, portMAX_DELAY); }
|
||||
std::optional<sdk::AppEvent> pop(std::optional<std::uint32_t> timeout_ms = std::nullopt) override {
|
||||
sdk::AppEvent out;
|
||||
xQueueReceive(_queueHandle, &out, portMAX_DELAY);
|
||||
return out;
|
||||
TickType_t ticks = timeout_ms ? pdMS_TO_TICKS(*timeout_ms) : portMAX_DELAY;
|
||||
if (xQueueReceive(_queueHandle, &out, ticks) == pdTRUE) {
|
||||
return out;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace apps {
|
||||
namespace {
|
||||
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppTimeoutEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
|
||||
@@ -55,13 +56,14 @@ public:
|
||||
|
||||
void onStop() override { cancelRefreshTimer(); }
|
||||
|
||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||
[this](const AppTimerEvent& timer) {
|
||||
if (timer.handle == refreshTimer)
|
||||
updateDisplay();
|
||||
}));
|
||||
std::optional<std::uint32_t> handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
event.visit(cardboy::sdk::overload([this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||
[this](const AppTimerEvent& timer) {
|
||||
if (timer.handle == refreshTimer)
|
||||
updateDisplay();
|
||||
},
|
||||
[](const AppTimeoutEvent&) { /* ignore */ }));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -154,6 +154,7 @@ class GameboyApp;
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppEvent;
|
||||
using cardboy::sdk::AppTimeoutEvent;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
using cardboy::sdk::AppTimerHandle;
|
||||
using cardboy::sdk::InputState;
|
||||
@@ -224,7 +225,6 @@ public:
|
||||
::gAudioWriteThunk = &GameboyApp::audioWriteThunk;
|
||||
apu.attach(this);
|
||||
apu.reset();
|
||||
cancelTick();
|
||||
frameDelayCarryUs = 0;
|
||||
GB_PERF_ONLY(perf.resetAll();)
|
||||
prevInput = context.input.readState();
|
||||
@@ -233,13 +233,12 @@ public:
|
||||
scaleMode = ScaleMode::FullHeightWide;
|
||||
ensureFilesystemReady();
|
||||
refreshRomList();
|
||||
mode = Mode::Browse;
|
||||
browserDirty = true;
|
||||
scheduleNextTick(0);
|
||||
mode = Mode::Browse;
|
||||
browserDirty = true;
|
||||
nextTimeoutMs = 0;
|
||||
}
|
||||
|
||||
void onStop() override {
|
||||
cancelTick();
|
||||
frameDelayCarryUs = 0;
|
||||
GB_PERF_ONLY(perf.maybePrintAggregate(true);)
|
||||
unloadRom();
|
||||
@@ -252,14 +251,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void handleEvent(const AppEvent& event) override {
|
||||
bool handled = false;
|
||||
std::optional<std::uint32_t> handleEvent(const AppEvent& event) override {
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this, &handled](const AppTimerEvent& timer) {
|
||||
if (timer.handle != tickTimer)
|
||||
return;
|
||||
handled = true;
|
||||
tickTimer = kInvalidAppTimer;
|
||||
[this](const AppTimeoutEvent&) {
|
||||
const uint64_t frameStartUs = nowMicros();
|
||||
performStep();
|
||||
const uint64_t frameEndUs = nowMicros();
|
||||
@@ -269,10 +263,10 @@ public:
|
||||
},
|
||||
[this](const AppButtonEvent&) {
|
||||
frameDelayCarryUs = 0;
|
||||
scheduleNextTick(0);
|
||||
}));
|
||||
if (handled)
|
||||
return;
|
||||
nextTimeoutMs = 0;
|
||||
},
|
||||
[](const AppTimerEvent&) { /* ignore */ }));
|
||||
return nextTimeoutMs;
|
||||
}
|
||||
|
||||
void performStep() {
|
||||
@@ -1119,9 +1113,9 @@ public:
|
||||
cardboy::sdk::IFilesystem* filesystem = nullptr;
|
||||
cardboy::sdk::IHighResClock* highResClock = nullptr;
|
||||
PerfTracker perf{};
|
||||
AppTimerHandle tickTimer = kInvalidAppTimer;
|
||||
int64_t frameDelayCarryUs = 0;
|
||||
static constexpr uint32_t kTargetFrameUs = 1000000 / 60; // ~16.6 ms
|
||||
std::optional<std::uint32_t> nextTimeoutMs;
|
||||
static constexpr uint32_t kTargetFrameUs = 1000000 / 60; // ~16.6 ms
|
||||
|
||||
Mode mode = Mode::Browse;
|
||||
ScaleMode scaleMode = ScaleMode::FullHeightWide;
|
||||
@@ -1155,20 +1149,6 @@ public:
|
||||
uint8_t lastLoud = 0;
|
||||
uint32_t stableFrames = 0;
|
||||
|
||||
void cancelTick() {
|
||||
if (tickTimer == kInvalidAppTimer)
|
||||
return;
|
||||
if (auto* timer = context.timer())
|
||||
timer->cancelTimer(tickTimer);
|
||||
tickTimer = kInvalidAppTimer;
|
||||
}
|
||||
|
||||
void scheduleNextTick(uint32_t delayMs) {
|
||||
cancelTick();
|
||||
if (auto* timer = context.timer())
|
||||
tickTimer = timer->scheduleTimer(delayMs, false);
|
||||
}
|
||||
|
||||
uint32_t idleDelayMs() const { return browserDirty ? 50 : 140; }
|
||||
|
||||
void scheduleAfterFrame(uint64_t elapsedUs) {
|
||||
@@ -1177,17 +1157,17 @@ public:
|
||||
desiredUs += frameDelayCarryUs;
|
||||
if (desiredUs <= 0) {
|
||||
frameDelayCarryUs = desiredUs;
|
||||
scheduleNextTick(0);
|
||||
nextTimeoutMs = 0;
|
||||
return;
|
||||
}
|
||||
frameDelayCarryUs = desiredUs % 1000;
|
||||
desiredUs -= frameDelayCarryUs;
|
||||
uint32_t delayMs = static_cast<uint32_t>(desiredUs / 1000);
|
||||
scheduleNextTick(delayMs);
|
||||
nextTimeoutMs = delayMs;
|
||||
return;
|
||||
}
|
||||
frameDelayCarryUs = 0;
|
||||
scheduleNextTick(idleDelayMs());
|
||||
nextTimeoutMs = idleDelayMs();
|
||||
}
|
||||
|
||||
bool ensureFilesystemReady() {
|
||||
@@ -1838,7 +1818,7 @@ public:
|
||||
promptDirty = true;
|
||||
mode = Mode::Prompt;
|
||||
gb.direct.joypad = 0xFF;
|
||||
scheduleNextTick(0);
|
||||
// scheduleNextTick(0);
|
||||
}
|
||||
|
||||
void exitPrompt(Mode nextMode) {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include "cardboy/sdk/app_framework.hpp"
|
||||
#include "cardboy/sdk/app_system.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
@@ -18,6 +18,7 @@ namespace apps {
|
||||
namespace {
|
||||
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppTimeoutEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
|
||||
@@ -27,19 +28,13 @@ constexpr std::uint32_t kUnlockHoldMs = 1500;
|
||||
using Framebuffer = typename AppContext::Framebuffer;
|
||||
using Clock = typename AppContext::Clock;
|
||||
|
||||
constexpr std::array<std::uint8_t, font16x8::kGlyphHeight> kArrowUpGlyph{0b00011000, 0b00111100, 0b01111110,
|
||||
0b11111111, 0b00011000, 0b00011000,
|
||||
0b00011000, 0b00011000, 0b00011000,
|
||||
0b00011000, 0b00011000, 0b00011000,
|
||||
0b00011000, 0b00011000, 0b00000000,
|
||||
0b00000000};
|
||||
constexpr std::array<std::uint8_t, font16x8::kGlyphHeight> kArrowUpGlyph{
|
||||
0b00011000, 0b00111100, 0b01111110, 0b11111111, 0b00011000, 0b00011000, 0b00011000, 0b00011000,
|
||||
0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00000000, 0b00000000};
|
||||
|
||||
constexpr std::array<std::uint8_t, font16x8::kGlyphHeight> kArrowDownGlyph{0b00000000, 0b00000000, 0b00011000,
|
||||
0b00011000, 0b00011000, 0b00011000,
|
||||
0b00011000, 0b00011000, 0b00011000,
|
||||
0b00011000, 0b00011000, 0b11111111,
|
||||
0b01111110, 0b00111100, 0b00011000,
|
||||
0b00000000};
|
||||
constexpr std::array<std::uint8_t, font16x8::kGlyphHeight> kArrowDownGlyph{
|
||||
0b00000000, 0b00000000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000,
|
||||
0b00011000, 0b00011000, 0b00011000, 0b11111111, 0b01111110, 0b00111100, 0b00011000, 0b00000000};
|
||||
|
||||
struct TimeSnapshot {
|
||||
bool hasWallTime = false;
|
||||
@@ -60,10 +55,10 @@ public:
|
||||
|
||||
void onStart() override {
|
||||
cancelRefreshTimer();
|
||||
lastSnapshot = {};
|
||||
holdActive = false;
|
||||
holdProgressMs = 0;
|
||||
dirty = true;
|
||||
lastSnapshot = {};
|
||||
holdActive = false;
|
||||
holdProgressMs = 0;
|
||||
dirty = true;
|
||||
lastNotificationInteractionMs = clock.millis();
|
||||
refreshNotifications();
|
||||
const auto snap = captureTime();
|
||||
@@ -75,33 +70,34 @@ public:
|
||||
|
||||
void onStop() override { cancelRefreshTimer(); }
|
||||
|
||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||
[this](const AppTimerEvent& timer) {
|
||||
if (timer.handle == refreshTimer) {
|
||||
advanceHoldProgress();
|
||||
updateDisplay();
|
||||
}
|
||||
}));
|
||||
std::optional<std::uint32_t> handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
event.visit(cardboy::sdk::overload([this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||
[this](const AppTimerEvent& timer) {
|
||||
if (timer.handle == refreshTimer) {
|
||||
advanceHoldProgress();
|
||||
updateDisplay();
|
||||
}
|
||||
},
|
||||
[](const AppTimeoutEvent&) { /* ignore */ }));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t kMaxDisplayedNotifications = 5;
|
||||
static constexpr std::uint32_t kNotificationHideMs = 8000;
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
Clock& clock;
|
||||
cardboy::sdk::INotificationCenter* notificationCenter = nullptr;
|
||||
std::uint32_t lastNotificationRevision = 0;
|
||||
static constexpr std::size_t kMaxDisplayedNotifications = 5;
|
||||
static constexpr std::uint32_t kNotificationHideMs = 8000;
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
Clock& clock;
|
||||
cardboy::sdk::INotificationCenter* notificationCenter = nullptr;
|
||||
std::uint32_t lastNotificationRevision = 0;
|
||||
std::vector<cardboy::sdk::INotificationCenter::Notification> notifications;
|
||||
std::size_t selectedNotification = 0;
|
||||
std::size_t selectedNotification = 0;
|
||||
|
||||
bool dirty = false;
|
||||
cardboy::sdk::AppTimerHandle refreshTimer = cardboy::sdk::kInvalidAppTimer;
|
||||
TimeSnapshot lastSnapshot{};
|
||||
bool holdActive = false;
|
||||
std::uint32_t holdProgressMs = 0;
|
||||
bool holdActive = false;
|
||||
std::uint32_t holdProgressMs = 0;
|
||||
std::uint32_t lastNotificationInteractionMs = 0;
|
||||
|
||||
void cancelRefreshTimer() {
|
||||
@@ -120,7 +116,7 @@ private:
|
||||
bool navPressed = false;
|
||||
|
||||
if (!notifications.empty() && (upPressed || downPressed)) {
|
||||
const std::size_t count = notifications.size();
|
||||
const std::size_t count = notifications.size();
|
||||
lastNotificationInteractionMs = clock.millis();
|
||||
navPressed = true;
|
||||
if (count > 1) {
|
||||
@@ -133,8 +129,8 @@ private:
|
||||
|
||||
const bool deletePressed = button.current.b && !button.previous.b;
|
||||
if (deletePressed && notificationCenter && !notifications.empty()) {
|
||||
const std::size_t index = std::min<std::size_t>(selectedNotification, notifications.size() - 1);
|
||||
const auto& note = notifications[index];
|
||||
const std::size_t index = std::min<std::size_t>(selectedNotification, notifications.size() - 1);
|
||||
const auto& note = notifications[index];
|
||||
std::size_t preferredIndex = index;
|
||||
if (index + 1 < notifications.size())
|
||||
preferredIndex = index + 1;
|
||||
@@ -144,9 +140,9 @@ private:
|
||||
notificationCenter->removeByExternalId(note.externalId);
|
||||
else
|
||||
notificationCenter->removeById(note.id);
|
||||
selectedNotification = preferredIndex;
|
||||
selectedNotification = preferredIndex;
|
||||
lastNotificationInteractionMs = clock.millis();
|
||||
dirty = true;
|
||||
dirty = true;
|
||||
refreshNotifications();
|
||||
}
|
||||
|
||||
@@ -161,9 +157,9 @@ private:
|
||||
if (!notificationCenter) {
|
||||
if (!notifications.empty() || lastNotificationRevision != 0) {
|
||||
notifications.clear();
|
||||
selectedNotification = 0;
|
||||
lastNotificationRevision = 0;
|
||||
dirty = true;
|
||||
selectedNotification = 0;
|
||||
lastNotificationRevision = 0;
|
||||
dirty = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -175,7 +171,7 @@ private:
|
||||
const std::uint64_t previousId =
|
||||
(selectedNotification < notifications.size()) ? notifications[selectedNotification].id : 0;
|
||||
|
||||
auto latest = notificationCenter->recent(kMaxDisplayedNotifications);
|
||||
auto latest = notificationCenter->recent(kMaxDisplayedNotifications);
|
||||
notifications = std::move(latest);
|
||||
|
||||
if (notifications.empty()) {
|
||||
@@ -193,7 +189,7 @@ private:
|
||||
}
|
||||
|
||||
lastNotificationInteractionMs = clock.millis();
|
||||
dirty = true;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void updateHoldState(bool comboNow) {
|
||||
@@ -213,8 +209,7 @@ private:
|
||||
|
||||
void advanceHoldProgress() {
|
||||
if (holdActive) {
|
||||
const std::uint32_t next =
|
||||
std::min<std::uint32_t>(holdProgressMs + kRefreshIntervalMs, kUnlockHoldMs);
|
||||
const std::uint32_t next = std::min<std::uint32_t>(holdProgressMs + kRefreshIntervalMs, kUnlockHoldMs);
|
||||
if (next != holdProgressMs) {
|
||||
holdProgressMs = next;
|
||||
dirty = true;
|
||||
@@ -239,8 +234,8 @@ private:
|
||||
}
|
||||
|
||||
static bool sameSnapshot(const TimeSnapshot& a, const TimeSnapshot& b) {
|
||||
return a.hasWallTime == b.hasWallTime && a.hour24 == b.hour24 && a.minute == b.minute &&
|
||||
a.second == b.second && a.day == b.day && a.month == b.month && a.year == b.year;
|
||||
return a.hasWallTime == b.hasWallTime && a.hour24 == b.hour24 && a.minute == b.minute && a.second == b.second &&
|
||||
a.day == b.day && a.month == b.month && a.year == b.year;
|
||||
}
|
||||
|
||||
TimeSnapshot captureTime() const {
|
||||
@@ -327,7 +322,7 @@ private:
|
||||
if (font16x8::measureText(text, scale, letterSpacing) <= maxWidth)
|
||||
return std::string(text);
|
||||
|
||||
std::string result(text.begin(), text.end());
|
||||
std::string result(text.begin(), text.end());
|
||||
const std::string ellipsis = "...";
|
||||
while (!result.empty()) {
|
||||
result.pop_back();
|
||||
@@ -369,8 +364,7 @@ private:
|
||||
|
||||
if (!word.empty()) {
|
||||
std::string candidate = current.empty() ? word : current + " " + word;
|
||||
if (!current.empty() &&
|
||||
font16x8::measureText(candidate, scale, letterSpacing) > maxWidth) {
|
||||
if (!current.empty() && font16x8::measureText(candidate, scale, letterSpacing) > maxWidth) {
|
||||
flushCurrent();
|
||||
if (lines.size() >= static_cast<std::size_t>(maxLines)) {
|
||||
truncated = true;
|
||||
@@ -432,20 +426,20 @@ private:
|
||||
|
||||
framebuffer.frameReady();
|
||||
|
||||
const int scaleTime = 4;
|
||||
const int scaleSeconds = 2;
|
||||
const int scaleSmall = 1;
|
||||
const int scaleTime = 4;
|
||||
const int scaleSeconds = 2;
|
||||
const int scaleSmall = 1;
|
||||
const int textLineHeight = font16x8::kGlyphHeight * scaleSmall;
|
||||
|
||||
const int cardMarginTop = 4;
|
||||
const int cardMarginSide = 8;
|
||||
const int cardPadding = 6;
|
||||
const int cardMarginTop = 4;
|
||||
const int cardMarginSide = 8;
|
||||
const int cardPadding = 6;
|
||||
const int cardLineSpacing = 4;
|
||||
int cardHeight = 0;
|
||||
const int cardWidth = framebuffer.width() - cardMarginSide * 2;
|
||||
int cardHeight = 0;
|
||||
const int cardWidth = framebuffer.width() - cardMarginSide * 2;
|
||||
|
||||
const std::uint32_t nowMs = clock.millis();
|
||||
const bool hasNotifications = !notifications.empty();
|
||||
const std::uint32_t nowMs = clock.millis();
|
||||
const bool hasNotifications = !notifications.empty();
|
||||
const bool showNotificationDetails =
|
||||
hasNotifications && (nowMs - lastNotificationInteractionMs <= kNotificationHideMs);
|
||||
|
||||
@@ -481,7 +475,8 @@ private:
|
||||
std::snprintf(counter, sizeof(counter), "%zu/%zu", selectedNotification + 1, notifications.size());
|
||||
const int counterWidth = font16x8::measureText(counter, scaleSmall, 1);
|
||||
const int counterX = cardMarginSide + cardWidth - cardPadding - counterWidth;
|
||||
font16x8::drawText(framebuffer, counterX, cardMarginTop + cardPadding, counter, scaleSmall, true, 1);
|
||||
font16x8::drawText(framebuffer, counterX, cardMarginTop + cardPadding, counter, scaleSmall, true,
|
||||
1);
|
||||
const int arrowWidth = font16x8::kGlyphWidth * scaleSmall;
|
||||
const int arrowSpacing = std::max(1, scaleSmall);
|
||||
const int arrowsTotalWide = arrowWidth * 2 + arrowSpacing;
|
||||
@@ -495,7 +490,7 @@ private:
|
||||
|
||||
if (!bodyLines.empty()) {
|
||||
int bodyY = cardMarginTop + cardPadding + textLineHeight + cardLineSpacing;
|
||||
for (const auto& line : bodyLines) {
|
||||
for (const auto& line: bodyLines) {
|
||||
font16x8::drawText(framebuffer, cardMarginSide + cardPadding, bodyY, line, scaleSmall, true, 1);
|
||||
bodyY += textLineHeight + cardLineSpacing;
|
||||
}
|
||||
@@ -531,9 +526,8 @@ private:
|
||||
if (cardHeight > 0)
|
||||
timeY = cardMarginTop + cardHeight + 16;
|
||||
const int minTimeY = (cardHeight > 0) ? (cardMarginTop + cardHeight + 12) : 16;
|
||||
const int maxTimeY =
|
||||
std::max(minTimeY, framebuffer.height() - font16x8::kGlyphHeight * scaleTime - 48);
|
||||
timeY = std::clamp(timeY, minTimeY, maxTimeY);
|
||||
const int maxTimeY = std::max(minTimeY, framebuffer.height() - font16x8::kGlyphHeight * scaleTime - 48);
|
||||
timeY = std::clamp(timeY, minTimeY, maxTimeY);
|
||||
|
||||
char hoursMinutes[6];
|
||||
std::snprintf(hoursMinutes, sizeof(hoursMinutes), "%02d:%02d", snap.hour24, snap.minute);
|
||||
@@ -541,7 +535,7 @@ private:
|
||||
const int timeX = (framebuffer.width() - mainW) / 2;
|
||||
const int secX = timeX + mainW + 12;
|
||||
const int secY = timeY + font16x8::kGlyphHeight * scaleTime - font16x8::kGlyphHeight * scaleSeconds;
|
||||
char secs[3];
|
||||
char secs[3];
|
||||
std::snprintf(secs, sizeof(secs), "%02d", snap.second);
|
||||
|
||||
font16x8::drawText(framebuffer, timeX, timeY, hoursMinutes, scaleTime, true, 0);
|
||||
@@ -550,7 +544,7 @@ private:
|
||||
const std::string dateLine = formatDate(snap);
|
||||
drawCenteredText(framebuffer, timeY + font16x8::kGlyphHeight * scaleTime + 16, dateLine, scaleSmall, 1);
|
||||
|
||||
const std::string instruction = holdActive ? "KEEP HOLDING A+SELECT" : "HOLD A+SELECT";
|
||||
const std::string instruction = holdActive ? "KEEP HOLDING A+SELECT" : "HOLD A+SELECT";
|
||||
const int instructionWidth = font16x8::measureText(instruction, scaleSmall, 1);
|
||||
const int barHeight = 14;
|
||||
const int barY = framebuffer.height() - 24;
|
||||
@@ -568,11 +562,10 @@ private:
|
||||
drawRectOutline(framebuffer, barX, barY, barWidth, barHeight);
|
||||
|
||||
if (holdActive || holdProgressMs > 0) {
|
||||
const int innerWidth = barWidth - 2;
|
||||
const int innerHeight = barHeight - 2;
|
||||
const float ratio =
|
||||
std::clamp(holdProgressMs / static_cast<float>(kUnlockHoldMs), 0.0f, 1.0f);
|
||||
const int fillWidth = static_cast<int>(ratio * innerWidth + 0.5f);
|
||||
const int innerWidth = barWidth - 2;
|
||||
const int innerHeight = barHeight - 2;
|
||||
const float ratio = std::clamp(holdProgressMs / static_cast<float>(kUnlockHoldMs), 0.0f, 1.0f);
|
||||
const int fillWidth = static_cast<int>(ratio * innerWidth + 0.5f);
|
||||
if (fillWidth > 0)
|
||||
fillRect(framebuffer, barX + 1, barY + 1, fillWidth, innerHeight);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace apps {
|
||||
namespace {
|
||||
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppTimeoutEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppEvent;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
@@ -44,15 +45,16 @@ public:
|
||||
|
||||
void onStop() override { cancelInactivityTimer(); }
|
||||
|
||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||
[this](const AppTimerEvent& timer) {
|
||||
if (timer.handle == inactivityTimer) {
|
||||
cancelInactivityTimer();
|
||||
context.requestAppSwitchByName(kLockscreenAppName);
|
||||
}
|
||||
}));
|
||||
std::optional<std::uint32_t> handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
event.visit(cardboy::sdk::overload([this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||
[this](const AppTimerEvent& timer) {
|
||||
if (timer.handle == inactivityTimer) {
|
||||
cancelInactivityTimer();
|
||||
context.requestAppSwitchByName(kLockscreenAppName);
|
||||
}
|
||||
},
|
||||
[](const AppTimeoutEvent&) { /* ignore */ }));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -38,42 +38,44 @@ public:
|
||||
renderIfNeeded();
|
||||
}
|
||||
|
||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
const auto* buttonEvent = event.button();
|
||||
if (!buttonEvent)
|
||||
return;
|
||||
std::optional<std::uint32_t> handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const cardboy::sdk::AppButtonEvent& button) {
|
||||
const auto& current = button.current;
|
||||
const auto& previous = button.previous;
|
||||
|
||||
const auto& current = buttonEvent->current;
|
||||
const auto& previous = buttonEvent->previous;
|
||||
const bool previousAvailable = buzzerAvailable;
|
||||
syncBuzzerState();
|
||||
if (previousAvailable != buzzerAvailable)
|
||||
dirty = true;
|
||||
|
||||
const bool previousAvailable = buzzerAvailable;
|
||||
syncBuzzerState();
|
||||
if (previousAvailable != buzzerAvailable)
|
||||
dirty = true;
|
||||
if (current.b && !previous.b) {
|
||||
context.requestAppSwitchByName(kMenuAppName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (current.b && !previous.b) {
|
||||
context.requestAppSwitchByName(kMenuAppName);
|
||||
return;
|
||||
}
|
||||
bool moved = false;
|
||||
if (current.down && !previous.down) {
|
||||
moveSelection(+1);
|
||||
moved = true;
|
||||
} else if (current.up && !previous.up) {
|
||||
moveSelection(-1);
|
||||
moved = true;
|
||||
}
|
||||
|
||||
bool moved = false;
|
||||
if (current.down && !previous.down) {
|
||||
moveSelection(+1);
|
||||
moved = true;
|
||||
} else if (current.up && !previous.up) {
|
||||
moveSelection(-1);
|
||||
moved = true;
|
||||
}
|
||||
const bool togglePressed = (current.a && !previous.a) || (current.start && !previous.start) ||
|
||||
(current.select && !previous.select);
|
||||
if (togglePressed)
|
||||
handleToggle();
|
||||
|
||||
const bool togglePressed = (current.a && !previous.a) || (current.start && !previous.start) ||
|
||||
(current.select && !previous.select);
|
||||
if (togglePressed)
|
||||
handleToggle();
|
||||
if (moved)
|
||||
dirty = true;
|
||||
|
||||
if (moved)
|
||||
dirty = true;
|
||||
|
||||
renderIfNeeded();
|
||||
renderIfNeeded();
|
||||
},
|
||||
[](const cardboy::sdk::AppTimerEvent&) { /* ignore */ },
|
||||
[](const cardboy::sdk::AppTimeoutEvent&) { /* ignore */ }));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace apps {
|
||||
namespace {
|
||||
|
||||
using cardboy::sdk::AppButtonEvent;
|
||||
using cardboy::sdk::AppTimeoutEvent;
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppEvent;
|
||||
using cardboy::sdk::AppTimerEvent;
|
||||
@@ -28,13 +29,13 @@ using cardboy::sdk::InputState;
|
||||
|
||||
constexpr char kSnakeAppName[] = "Snake";
|
||||
|
||||
constexpr int kBoardWidth = 32;
|
||||
constexpr int kBoardHeight = 20;
|
||||
constexpr int kCellSize = 10;
|
||||
constexpr int kInitialSnakeLength = 5;
|
||||
constexpr int kScorePerFood = 10;
|
||||
constexpr int kMinMoveIntervalMs = 80;
|
||||
constexpr int kBaseMoveIntervalMs = 220;
|
||||
constexpr int kBoardWidth = 32;
|
||||
constexpr int kBoardHeight = 20;
|
||||
constexpr int kCellSize = 10;
|
||||
constexpr int kInitialSnakeLength = 5;
|
||||
constexpr int kScorePerFood = 10;
|
||||
constexpr int kMinMoveIntervalMs = 80;
|
||||
constexpr int kBaseMoveIntervalMs = 220;
|
||||
constexpr int kIntervalSpeedupPerSegment = 4;
|
||||
|
||||
struct Point {
|
||||
@@ -69,11 +70,12 @@ public:
|
||||
|
||||
void onStop() { cancelMoveTimer(); }
|
||||
|
||||
void handleEvent(const AppEvent& event) {
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const AppButtonEvent& button) { handleButtons(button); },
|
||||
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); }));
|
||||
std::optional<std::uint32_t> handleEvent(const AppEvent& event) {
|
||||
event.visit(cardboy::sdk::overload([this](const AppButtonEvent& button) { handleButtons(button); },
|
||||
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); },
|
||||
[](const AppTimeoutEvent&) { /* ignore */ }));
|
||||
renderIfNeeded();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -89,8 +91,8 @@ private:
|
||||
bool dirty = false;
|
||||
int score = 0;
|
||||
int highScore = 0;
|
||||
AppTimerHandle moveTimer = cardboy::sdk::kInvalidAppTimer;
|
||||
std::mt19937 rng;
|
||||
AppTimerHandle moveTimer = cardboy::sdk::kInvalidAppTimer;
|
||||
std::mt19937 rng;
|
||||
|
||||
void handleButtons(const AppButtonEvent& evt) {
|
||||
const auto& cur = evt.current;
|
||||
@@ -158,7 +160,7 @@ private:
|
||||
}
|
||||
|
||||
void advance() {
|
||||
direction = queuedDirection;
|
||||
direction = queuedDirection;
|
||||
Point nextHead = snake.front();
|
||||
switch (direction) {
|
||||
case Direction::Up:
|
||||
@@ -299,9 +301,7 @@ private:
|
||||
framebuffer.sendFrame();
|
||||
}
|
||||
|
||||
[[nodiscard]] int boardOriginX() const {
|
||||
return (cardboy::sdk::kDisplayWidth - kBoardWidth * kCellSize) / 2;
|
||||
}
|
||||
[[nodiscard]] int boardOriginX() const { return (cardboy::sdk::kDisplayWidth - kBoardWidth * kCellSize) / 2; }
|
||||
|
||||
[[nodiscard]] int boardOriginY() const {
|
||||
const int centered = (cardboy::sdk::kDisplayHeight - kBoardHeight * kCellSize) / 2;
|
||||
@@ -406,9 +406,9 @@ class SnakeApp final : public cardboy::sdk::IApp {
|
||||
public:
|
||||
explicit SnakeApp(AppContext& ctx) : game(ctx) {}
|
||||
|
||||
void onStart() override { game.onStart(); }
|
||||
void onStop() override { game.onStop(); }
|
||||
void handleEvent(const AppEvent& event) override { game.handleEvent(event); }
|
||||
void onStart() override { game.onStart(); }
|
||||
void onStop() override { game.onStop(); }
|
||||
std::optional<std::uint32_t> handleEvent(const AppEvent& event) override { return game.handleEvent(event); }
|
||||
|
||||
private:
|
||||
SnakeGame game;
|
||||
|
||||
@@ -148,11 +148,12 @@ public:
|
||||
|
||||
void onStop() { cancelTimers(); }
|
||||
|
||||
void handleEvent(const AppEvent& event) {
|
||||
event.visit(cardboy::sdk::overload(
|
||||
[this](const AppButtonEvent& button) { handleButtons(button); },
|
||||
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); }));
|
||||
std::optional<std::uint32_t> handleEvent(const AppEvent& event) {
|
||||
event.visit(cardboy::sdk::overload([this](const AppButtonEvent& button) { handleButtons(button); },
|
||||
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); },
|
||||
[](const cardboy::sdk::AppTimeoutEvent&) { /* ignore */ }));
|
||||
renderIfNeeded();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -634,9 +635,9 @@ class TetrisApp final : public cardboy::sdk::IApp {
|
||||
public:
|
||||
explicit TetrisApp(AppContext& ctx) : game(ctx) {}
|
||||
|
||||
void onStart() override { game.onStart(); }
|
||||
void onStop() override { game.onStop(); }
|
||||
void handleEvent(const AppEvent& event) override { game.handleEvent(event); }
|
||||
void onStart() override { game.onStart(); }
|
||||
void onStop() override { game.onStop(); }
|
||||
std::optional<std::uint32_t> handleEvent(const AppEvent& event) override { return game.handleEvent(event); }
|
||||
|
||||
private:
|
||||
TetrisGame game;
|
||||
|
||||
@@ -21,14 +21,17 @@ struct AppTimerEvent {
|
||||
AppTimerHandle handle = kInvalidAppTimer;
|
||||
};
|
||||
|
||||
struct AppTimeoutEvent {};
|
||||
|
||||
struct AppEvent {
|
||||
using Data = std::variant<AppButtonEvent, AppTimerEvent>;
|
||||
using Data = std::variant<AppButtonEvent, AppTimerEvent, AppTimeoutEvent>;
|
||||
|
||||
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]] bool isTimeout() const { return std::holds_alternative<AppTimeoutEvent>(data); }
|
||||
|
||||
[[nodiscard]] const AppButtonEvent* button() const { return std::get_if<AppButtonEvent>(&data); }
|
||||
[[nodiscard]] AppButtonEvent* button() { return std::get_if<AppButtonEvent>(&data); }
|
||||
@@ -36,6 +39,9 @@ struct AppEvent {
|
||||
[[nodiscard]] const AppTimerEvent* timer() const { return std::get_if<AppTimerEvent>(&data); }
|
||||
[[nodiscard]] AppTimerEvent* timer() { return std::get_if<AppTimerEvent>(&data); }
|
||||
|
||||
[[nodiscard]] const AppTimeoutEvent* timeout() const { return std::get_if<AppTimeoutEvent>(&data); }
|
||||
[[nodiscard]] AppTimeoutEvent* timeout() { return std::get_if<AppTimeoutEvent>(&data); }
|
||||
|
||||
template<typename Visitor>
|
||||
decltype(auto) visit(Visitor&& visitor) {
|
||||
return std::visit(std::forward<Visitor>(visitor), data);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@@ -97,8 +98,8 @@ class IEventBus {
|
||||
public:
|
||||
virtual ~IEventBus() = default;
|
||||
|
||||
virtual void post(const AppEvent& event) = 0;
|
||||
virtual AppEvent pop() = 0;
|
||||
virtual void post(const AppEvent& event) = 0;
|
||||
virtual std::optional<AppEvent> pop(std::optional<std::uint32_t> timeout_ms = std::nullopt) = 0;
|
||||
};
|
||||
|
||||
struct AppScopedServices {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@@ -76,9 +77,9 @@ private:
|
||||
class IApp {
|
||||
public:
|
||||
virtual ~IApp() = default;
|
||||
virtual void onStart() {}
|
||||
virtual void onStop() {}
|
||||
virtual void handleEvent(const AppEvent& event) = 0;
|
||||
virtual void onStart() {}
|
||||
virtual void onStop() {}
|
||||
virtual std::optional<std::uint32_t> handleEvent(const AppEvent& event) = 0;
|
||||
};
|
||||
|
||||
class IAppFactory {
|
||||
|
||||
@@ -39,6 +39,7 @@ private:
|
||||
std::size_t _activeIndex = static_cast<std::size_t>(-1);
|
||||
std::unique_ptr<AppScopedServices> _scopedServices;
|
||||
std::uint64_t _nextScopedGeneration = 1;
|
||||
std::optional<std::uint32_t> _currentTimeout;
|
||||
};
|
||||
|
||||
} // namespace cardboy::sdk
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "cardboy/sdk/status_bar.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace cardboy::sdk {
|
||||
@@ -66,6 +67,7 @@ void AppSystem::startAppByIndex(std::size_t index) {
|
||||
_context._pendingSwitchByName = false;
|
||||
_context._pendingAppName.clear();
|
||||
_current = std::move(app);
|
||||
_currentTimeout = std::nullopt;
|
||||
StatusBar::instance().setServices(_context.services);
|
||||
StatusBar::instance().setCurrentAppName(_activeFactory ? _activeFactory->name() : "");
|
||||
_current->onStart();
|
||||
@@ -81,14 +83,21 @@ void AppSystem::run() {
|
||||
if (auto* hooks = _context.loopHooks())
|
||||
hooks->onLoopIteration();
|
||||
|
||||
auto event = _context.eventBus()->pop();
|
||||
AppEvent event;
|
||||
auto event_opt = _context.eventBus()->pop(_currentTimeout);
|
||||
if (!event_opt) {
|
||||
event = AppEvent{_context.clock.millis(), AppTimeoutEvent{}};
|
||||
} else {
|
||||
event = *event_opt;
|
||||
}
|
||||
|
||||
if (const auto* btn = event.button()) {
|
||||
const bool consumedByStatusToggle = StatusBar::instance().handleToggleInput(btn->current, btn->previous);
|
||||
if (consumedByStatusToggle) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_current->handleEvent(event);
|
||||
_currentTimeout = _current->handleEvent(event);
|
||||
if (_context._pendingSwitch) {
|
||||
handlePendingSwitchRequest();
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user