mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
faster
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_platform.hpp"
|
||||
#include "input_state.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -8,46 +11,18 @@
|
||||
|
||||
class AppSystem;
|
||||
|
||||
struct InputState {
|
||||
bool up = false;
|
||||
bool left = false;
|
||||
bool right = false;
|
||||
bool down = false;
|
||||
bool a = false;
|
||||
bool b = false;
|
||||
bool select = false;
|
||||
bool start = false;
|
||||
};
|
||||
template<typename FramebufferT, typename InputT, typename ClockT>
|
||||
struct BasicAppContext {
|
||||
using Framebuffer = FramebufferT;
|
||||
using Input = InputT;
|
||||
using Clock = ClockT;
|
||||
|
||||
class IFramebuffer {
|
||||
public:
|
||||
virtual ~IFramebuffer() = default;
|
||||
virtual void drawPixel(int x, int y, bool on) = 0; // on=true => black
|
||||
virtual void clear(bool on) = 0; // fill full screen to on/off
|
||||
virtual int width() const = 0;
|
||||
virtual int height() const = 0;
|
||||
};
|
||||
BasicAppContext() = delete;
|
||||
BasicAppContext(FramebufferT& fb, InputT& in, ClockT& clk) : framebuffer(fb), input(in), clock(clk) {}
|
||||
|
||||
class IInput {
|
||||
public:
|
||||
virtual ~IInput() = default;
|
||||
virtual InputState readState() = 0;
|
||||
};
|
||||
|
||||
class IClock {
|
||||
public:
|
||||
virtual ~IClock() = default;
|
||||
virtual uint32_t millis() = 0;
|
||||
virtual void sleep_ms(uint32_t ms) = 0;
|
||||
};
|
||||
|
||||
struct AppContext {
|
||||
AppContext() = delete;
|
||||
AppContext(IFramebuffer& fb, IInput& in, IClock& clk) : framebuffer(fb), input(in), clock(clk) {}
|
||||
|
||||
IFramebuffer& framebuffer;
|
||||
IInput& input;
|
||||
IClock& clock;
|
||||
FramebufferT& framebuffer;
|
||||
InputT& input;
|
||||
ClockT& clock;
|
||||
AppSystem* system = nullptr;
|
||||
|
||||
void requestAppSwitchByIndex(std::size_t index) {
|
||||
@@ -73,6 +48,8 @@ private:
|
||||
std::string pendingAppName;
|
||||
};
|
||||
|
||||
using AppContext = BasicAppContext<PlatformFramebuffer, PlatformInput, PlatformClock>;
|
||||
|
||||
struct AppSleepPlan {
|
||||
uint32_t slow_ms = 0; // long sleep allowing battery/UI periodic refresh
|
||||
uint32_t normal_ms = 0; // short sleep for responsiveness on input wake
|
||||
|
||||
64
Firmware/main/include/app_platform.hpp
Normal file
64
Firmware/main/include/app_platform.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "config.hpp"
|
||||
#include "input_state.hpp"
|
||||
|
||||
#include <buttons.hpp>
|
||||
#include <disp_tools.hpp>
|
||||
#include <power_helper.hpp>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
class PlatformFramebuffer {
|
||||
public:
|
||||
int width() const { return DISP_WIDTH; }
|
||||
int height() const { return DISP_HEIGHT; }
|
||||
|
||||
void drawPixel(int x, int y, bool on) {
|
||||
if (x < 0 || y < 0 || x >= width() || y >= height())
|
||||
return;
|
||||
DispTools::set_pixel(x, y, on);
|
||||
}
|
||||
|
||||
void clear(bool on) {
|
||||
for (int y = 0; y < height(); ++y)
|
||||
for (int x = 0; x < width(); ++x)
|
||||
DispTools::set_pixel(x, y, on);
|
||||
}
|
||||
};
|
||||
|
||||
class PlatformInput {
|
||||
public:
|
||||
InputState readState() {
|
||||
InputState state{};
|
||||
const uint8_t pressed = Buttons::get().get_pressed();
|
||||
if (pressed & BTN_UP)
|
||||
state.up = true;
|
||||
if (pressed & BTN_LEFT)
|
||||
state.left = true;
|
||||
if (pressed & BTN_RIGHT)
|
||||
state.right = true;
|
||||
if (pressed & BTN_DOWN)
|
||||
state.down = true;
|
||||
if (pressed & BTN_A)
|
||||
state.a = true;
|
||||
if (pressed & BTN_B)
|
||||
state.b = true;
|
||||
if (pressed & BTN_SELECT)
|
||||
state.select = true;
|
||||
if (pressed & BTN_START)
|
||||
state.start = true;
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
class PlatformClock {
|
||||
public:
|
||||
uint32_t millis() {
|
||||
TickType_t ticks = xTaskGetTickCount();
|
||||
return static_cast<uint32_t>((static_cast<uint64_t>(ticks) * 1000ULL) / configTICK_RATE_HZ);
|
||||
}
|
||||
|
||||
void sleep_ms(uint32_t ms) { PowerHelper::get().delay(static_cast<int>(ms), static_cast<int>(ms)); }
|
||||
};
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
namespace font16x8 {
|
||||
|
||||
constexpr int kGlyphWidth = 8;
|
||||
constexpr int kGlyphHeight = 16;
|
||||
constexpr int kGlyphWidth = 8;
|
||||
constexpr int kGlyphHeight = 16;
|
||||
constexpr unsigned char kFallbackChar = '?';
|
||||
|
||||
inline unsigned char normalizeChar(char ch) {
|
||||
@@ -27,7 +27,8 @@ inline const std::array<uint8_t, kGlyphHeight>& glyphBitmap(char ch) {
|
||||
return fonts_Terminess_Powerline[uc];
|
||||
}
|
||||
|
||||
inline void drawGlyph(IFramebuffer& fb, int x, int y, char ch, int scale = 1, bool on = true) {
|
||||
template<typename Framebuffer>
|
||||
inline void drawGlyph(Framebuffer& fb, int x, int y, char ch, int scale = 1, bool on = true) {
|
||||
const auto& rows = glyphBitmap(ch);
|
||||
for (int row = 0; row < kGlyphHeight; ++row) {
|
||||
const uint8_t rowBits = rows[row];
|
||||
@@ -49,7 +50,8 @@ inline int measureText(std::string_view text, int scale = 1, int letterSpacing =
|
||||
return static_cast<int>(text.size()) * advance - letterSpacing * scale;
|
||||
}
|
||||
|
||||
inline void drawText(IFramebuffer& fb, int x, int y, std::string_view text, int scale = 1, bool on = true,
|
||||
template<typename Framebuffer>
|
||||
inline void drawText(Framebuffer& fb, int x, int y, std::string_view text, int scale = 1, bool on = true,
|
||||
int letterSpacing = 1) {
|
||||
int cursor = x;
|
||||
for (char ch: text) {
|
||||
|
||||
12
Firmware/main/include/input_state.hpp
Normal file
12
Firmware/main/include/input_state.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
struct InputState {
|
||||
bool up = false;
|
||||
bool left = false;
|
||||
bool right = false;
|
||||
bool down = false;
|
||||
bool a = false;
|
||||
bool b = false;
|
||||
bool select = false;
|
||||
bool start = false;
|
||||
};
|
||||
@@ -14,8 +14,8 @@
|
||||
#include <buzzer.hpp>
|
||||
#include <disp_tools.hpp>
|
||||
#include <display.hpp>
|
||||
#include <i2c_global.hpp>
|
||||
#include <fs_helper.hpp>
|
||||
#include <i2c_global.hpp>
|
||||
#include <power_helper.hpp>
|
||||
#include <shutdowner.hpp>
|
||||
#include <spi_global.hpp>
|
||||
@@ -29,63 +29,6 @@
|
||||
#include "esp_sleep.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class PlatformFramebuffer final : public IFramebuffer {
|
||||
public:
|
||||
int width() const override { return DISP_WIDTH; }
|
||||
int height() const override { return DISP_HEIGHT; }
|
||||
|
||||
void drawPixel(int x, int y, bool on) override {
|
||||
if (x < 0 || y < 0 || x >= width() || y >= height())
|
||||
return;
|
||||
DispTools::set_pixel(x, y, on);
|
||||
}
|
||||
|
||||
void clear(bool on) override {
|
||||
for (int y = 0; y < height(); ++y)
|
||||
for (int x = 0; x < width(); ++x)
|
||||
DispTools::set_pixel(x, y, on);
|
||||
}
|
||||
};
|
||||
|
||||
class PlatformInput final : public IInput {
|
||||
public:
|
||||
InputState readState() override {
|
||||
InputState state{};
|
||||
const uint8_t pressed = Buttons::get().get_pressed();
|
||||
if (pressed & BTN_UP)
|
||||
state.up = true;
|
||||
if (pressed & BTN_LEFT)
|
||||
state.left = true;
|
||||
if (pressed & BTN_RIGHT)
|
||||
state.right = true;
|
||||
if (pressed & BTN_DOWN)
|
||||
state.down = true;
|
||||
if (pressed & BTN_A)
|
||||
state.a = true;
|
||||
if (pressed & BTN_B)
|
||||
state.b = true;
|
||||
if (pressed & BTN_SELECT)
|
||||
state.select = true;
|
||||
if (pressed & BTN_START)
|
||||
state.start = true;
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
class PlatformClock final : public IClock {
|
||||
public:
|
||||
uint32_t millis() override {
|
||||
TickType_t ticks = xTaskGetTickCount();
|
||||
return static_cast<uint32_t>((static_cast<uint64_t>(ticks) * 1000ULL) / configTICK_RATE_HZ);
|
||||
}
|
||||
|
||||
void sleep_ms(uint32_t ms) override { PowerHelper::get().delay(static_cast<int>(ms), static_cast<int>(ms)); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" void app_main() {
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
// const esp_pm_config_t pm_config = {
|
||||
|
||||
@@ -19,6 +19,9 @@ namespace {
|
||||
|
||||
constexpr const char* kClockAppName = "Clock";
|
||||
|
||||
using Framebuffer = typename AppContext::Framebuffer;
|
||||
using Clock = typename AppContext::Clock;
|
||||
|
||||
struct TimeSnapshot {
|
||||
bool hasWallTime = false;
|
||||
int hour24 = 0;
|
||||
@@ -76,9 +79,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
AppContext& context;
|
||||
IFramebuffer& framebuffer;
|
||||
IClock& clock;
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
Clock& clock;
|
||||
|
||||
bool use24Hour = true;
|
||||
bool dirty = false;
|
||||
@@ -119,7 +122,7 @@ private:
|
||||
return snap;
|
||||
}
|
||||
|
||||
static void drawCenteredText(IFramebuffer& fb, int y, std::string_view text, int scale, int letterSpacing = 0) {
|
||||
static void drawCenteredText(Framebuffer& fb, int y, std::string_view text, int scale, int letterSpacing = 0) {
|
||||
const int width = font16x8::measureText(text, scale, letterSpacing);
|
||||
const int x = (fb.width() - width) / 2;
|
||||
font16x8::drawText(fb, x, y, text, scale, true, letterSpacing);
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace {
|
||||
constexpr int kMenuStartY = 48;
|
||||
constexpr int kMenuSpacing = font16x8::kGlyphHeight + 6;
|
||||
|
||||
using Framebuffer = typename AppContext::Framebuffer;
|
||||
|
||||
constexpr std::array<std::string_view, 2> kRomExtensions = {".gb", ".gbc"};
|
||||
|
||||
extern "C" {
|
||||
@@ -116,7 +118,7 @@ int measureVerticalText(std::string_view text, int scale = 1, int letterSpacing
|
||||
return static_cast<int>(text.size()) * advance - letterSpacing * scale;
|
||||
}
|
||||
|
||||
void drawGlyphRotated(IFramebuffer& fb, int x, int y, char ch, bool clockwise, int scale = 1, bool on = true) {
|
||||
void drawGlyphRotated(Framebuffer& fb, int x, int y, char ch, bool clockwise, int scale = 1, bool on = true) {
|
||||
const auto& rows = font16x8::glyphBitmap(ch);
|
||||
for (int row = 0; row < font16x8::kGlyphHeight; ++row) {
|
||||
const uint8_t rowBits = rows[row];
|
||||
@@ -142,7 +144,7 @@ void drawGlyphRotated(IFramebuffer& fb, int x, int y, char ch, bool clockwise, i
|
||||
}
|
||||
}
|
||||
|
||||
void drawTextRotated(IFramebuffer& fb, int x, int y, std::string_view text, bool clockwise, int scale = 1,
|
||||
void drawTextRotated(Framebuffer& fb, int x, int y, std::string_view text, bool clockwise, int scale = 1,
|
||||
bool on = true, int letterSpacing = 1) {
|
||||
int cursor = y;
|
||||
const int advance = (font16x8::kGlyphWidth + letterSpacing) * scale;
|
||||
@@ -482,9 +484,9 @@ private:
|
||||
std::array<int, LCD_WIDTH> colXEnd{};
|
||||
};
|
||||
|
||||
AppContext& context;
|
||||
IFramebuffer& framebuffer;
|
||||
PerfTracker perf{};
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
PerfTracker perf{};
|
||||
|
||||
Mode mode = Mode::Browse;
|
||||
ScaleMode scaleMode = ScaleMode::Original;
|
||||
@@ -1129,7 +1131,7 @@ private:
|
||||
auto* self = fromGb(gb);
|
||||
if (!self)
|
||||
return 0xFF;
|
||||
ScopedCallbackTimer timer(self, PerfTracker::CallbackKind::RomRead);
|
||||
// ScopedCallbackTimer timer(self, PerfTracker::CallbackKind::RomRead);
|
||||
if (!self->romDataView || addr >= self->romDataViewSize)
|
||||
return 0xFF;
|
||||
return self->romDataView[static_cast<std::size_t>(addr)];
|
||||
@@ -1186,7 +1188,7 @@ private:
|
||||
|
||||
self->frameDirty = true;
|
||||
|
||||
IFramebuffer& fb = self->framebuffer;
|
||||
Framebuffer& fb = self->framebuffer;
|
||||
|
||||
if (geom.scaledWidth == LCD_WIDTH && geom.scaledHeight == LCD_HEIGHT) {
|
||||
const int dstY = yStart;
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace apps {
|
||||
|
||||
namespace {
|
||||
|
||||
using Framebuffer = typename AppContext::Framebuffer;
|
||||
|
||||
struct MenuEntry {
|
||||
std::string name;
|
||||
std::size_t index = 0;
|
||||
@@ -60,7 +62,7 @@ public:
|
||||
|
||||
private:
|
||||
AppContext& context;
|
||||
IFramebuffer& framebuffer;
|
||||
Framebuffer& framebuffer;
|
||||
std::vector<MenuEntry> entries;
|
||||
std::size_t selected = 0;
|
||||
|
||||
@@ -110,7 +112,7 @@ private:
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
static void drawCenteredText(IFramebuffer& fb, int y, std::string_view text, int scale, int letterSpacing = 0) {
|
||||
static void drawCenteredText(Framebuffer& fb, int y, std::string_view text, int scale, int letterSpacing = 0) {
|
||||
const int width = font16x8::measureText(text, scale, letterSpacing);
|
||||
const int x = (fb.width() - width) / 2;
|
||||
font16x8::drawText(fb, x, y, text, scale, true, letterSpacing);
|
||||
|
||||
@@ -103,6 +103,10 @@ using RotGrid = std::array<char, 16>;
|
||||
using PieceR = std::array<RotGrid, 4>;
|
||||
constexpr char _ = '.', X = '#';
|
||||
|
||||
using Framebuffer = typename AppContext::Framebuffer;
|
||||
using InputDevice = typename AppContext::Input;
|
||||
using Clock = typename AppContext::Clock;
|
||||
|
||||
static const std::array<PieceR, 7> TET = {{
|
||||
PieceR{{RotGrid{_, _, _, _, X, X, X, X, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, _, X, _, _, _, X, _, _, _, X, _, _, _, X, _},
|
||||
@@ -201,7 +205,7 @@ constexpr int kHudLabelGap = 2;
|
||||
constexpr int kHudBlockGap = 8;
|
||||
constexpr int kHudLineGapBattery = 4;
|
||||
|
||||
inline void drawHudText(IFramebuffer& fb, int x, int y, std::string_view text) {
|
||||
inline void drawHudText(Framebuffer& fb, int x, int y, std::string_view text) {
|
||||
font16x8::drawText(fb, x, y, text, kHudFontScale, true, kHudLetterSpacing);
|
||||
}
|
||||
|
||||
@@ -211,7 +215,7 @@ inline int hudFontHeight() { return font16x8::kGlyphHeight * kHudFontScale; }
|
||||
// Renderer (centered board + HUD)
|
||||
class Renderer {
|
||||
public:
|
||||
Renderer(IFramebuffer& fb) : fb(fb) {
|
||||
Renderer(Framebuffer& fb) : fb(fb) {
|
||||
bw = cfg::BoardW * cfg::CellPx; // 10 * 11 = 110
|
||||
bh = cfg::BoardH * cfg::CellPx; // 20 * 11 = 220 (leaves 10px margins vertically)
|
||||
ox = (fb.width() - bw) / 2; // centered horizontally
|
||||
@@ -375,8 +379,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
IFramebuffer& fb;
|
||||
int ox = 0, oy = 0, bw = 0, bh = 0;
|
||||
Framebuffer& fb;
|
||||
int ox = 0, oy = 0, bw = 0, bh = 0;
|
||||
|
||||
// Pattern helper: returns true if pixel (xx,yy) inside w x h interior should be filled for piece type
|
||||
bool patternOn(int type, int w, int h, int xx, int yy) const {
|
||||
@@ -926,15 +930,15 @@ private:
|
||||
static constexpr uint32_t idleActivePollMs = 40; // normal_ms provided to PowerHelper for responsiveness
|
||||
static constexpr uint32_t activeNormalCapMs = 40; // cap for normal_ms during active play
|
||||
|
||||
AppContext& appContext;
|
||||
IFramebuffer& fb;
|
||||
IInput& input;
|
||||
IClock& clock;
|
||||
Renderer renderer;
|
||||
Board board;
|
||||
Bag bag;
|
||||
ScoreState score;
|
||||
bool running = true, paused = false, touchingGround = false;
|
||||
AppContext& appContext;
|
||||
Framebuffer& fb;
|
||||
InputDevice& input;
|
||||
Clock& clock;
|
||||
Renderer renderer;
|
||||
Board board;
|
||||
Bag bag;
|
||||
ScoreState score;
|
||||
bool running = true, paused = false, touchingGround = false;
|
||||
bool rotPrev = false, lHeld = false, rHeld = false, backPrev = false, selectPrev = false, exitComboPrev = false;
|
||||
uint32_t lHoldStart = 0, rHoldStart = 0, lLastRep = 0, rLastRep = 0, lastFall = 0, touchTime = 0;
|
||||
int current = 0, nextPiece = 0, px = 3, py = -2, rot = 0;
|
||||
|
||||
Reference in New Issue
Block a user