mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
some cleanup
This commit is contained in:
@@ -5,35 +5,39 @@ set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||
set(CMAKE_CXX_EXTENSIONS NO)
|
||||
|
||||
add_library(cardboy_sdk STATIC
|
||||
src/app_system.cpp
|
||||
)
|
||||
add_library(cardboy_sdk INTERFACE)
|
||||
|
||||
target_include_directories(cardboy_sdk
|
||||
PUBLIC
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_compile_features(cardboy_sdk PUBLIC cxx_std_20)
|
||||
target_compile_features(cardboy_sdk INTERFACE cxx_std_20)
|
||||
|
||||
add_library(cardboy_apps STATIC
|
||||
apps/menu_app.cpp
|
||||
apps/clock_app.cpp
|
||||
apps/tetris_app.cpp
|
||||
apps/gameboy_app.cpp
|
||||
target_sources(cardboy_sdk
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/app_system.cpp
|
||||
)
|
||||
|
||||
add_library(cardboy_apps INTERFACE)
|
||||
|
||||
target_include_directories(cardboy_apps
|
||||
PUBLIC
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries(cardboy_apps
|
||||
PUBLIC
|
||||
INTERFACE
|
||||
cardboy_sdk
|
||||
)
|
||||
|
||||
target_compile_features(cardboy_apps PUBLIC cxx_std_20)
|
||||
target_sources(cardboy_apps
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/apps/menu_app.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/apps/clock_app.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/apps/tetris_app.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/apps/gameboy_app.cpp
|
||||
)
|
||||
|
||||
option(CARDBOY_BUILD_SFML "Build SFML harness" OFF)
|
||||
|
||||
@@ -66,5 +70,16 @@ if (CARDBOY_BUILD_SFML)
|
||||
SFML::System
|
||||
)
|
||||
|
||||
target_include_directories(cardboy_desktop
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/hosts/include
|
||||
)
|
||||
|
||||
target_compile_definitions(cardboy_desktop
|
||||
PRIVATE
|
||||
CARDBOY_SDK_BACKEND_HEADER=\"cardboy/backend/desktop_backend.hpp\"
|
||||
CARDBOY_SDK_ACTIVE_BACKEND_TYPE=cardboy::backend::DesktopBackend
|
||||
)
|
||||
|
||||
target_compile_features(cardboy_desktop PRIVATE cxx_std_20)
|
||||
endif ()
|
||||
|
||||
@@ -160,8 +160,7 @@ private:
|
||||
return;
|
||||
dirty = false;
|
||||
|
||||
framebuffer.beginFrame();
|
||||
framebuffer.clear(false);
|
||||
framebuffer.frameReady();
|
||||
|
||||
const int scaleLarge = 3;
|
||||
const int scaleSeconds = 2;
|
||||
@@ -220,7 +219,7 @@ private:
|
||||
drawCenteredText(framebuffer, framebuffer.height() - 36, "SELECT TOGGLE 12/24H", 1, 1);
|
||||
drawCenteredText(framebuffer, framebuffer.height() - 18, "B BACK", 1, 1);
|
||||
|
||||
framebuffer.endFrame();
|
||||
framebuffer.sendFrame();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
#include "cardboy/apps/gameboy_app.hpp"
|
||||
#include "cardboy/apps/peanut_gb.h"
|
||||
|
||||
#include "cardboy/gfx/font16x8.hpp"
|
||||
#include "cardboy/sdk/app_framework.hpp"
|
||||
#include "cardboy/sdk/app_system.hpp"
|
||||
#include "cardboy/gfx/font16x8.hpp"
|
||||
#include "cardboy/sdk/services.hpp"
|
||||
|
||||
#include <inttypes.h>
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -21,8 +22,7 @@
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#define ESP_PLATFORM 1
|
||||
#define GAMEBOY_PERF_METRICS 0
|
||||
|
||||
#ifndef GAMEBOY_PERF_METRICS
|
||||
@@ -41,6 +41,7 @@ namespace {
|
||||
constexpr int kMenuStartY = 48;
|
||||
constexpr int kMenuSpacing = font16x8::kGlyphHeight + 6;
|
||||
|
||||
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppEvent;
|
||||
using cardboy::sdk::AppEventType;
|
||||
@@ -250,8 +251,6 @@ public:
|
||||
break;
|
||||
}
|
||||
|
||||
framebuffer.beginFrame();
|
||||
framebuffer.clear(false);
|
||||
|
||||
GB_PERF_ONLY(const uint64_t geometryStartUs = nowMicros();)
|
||||
ensureRenderGeometry();
|
||||
@@ -264,7 +263,6 @@ public:
|
||||
GB_PERF_ONLY(const uint64_t renderStartUs = nowMicros();)
|
||||
renderGameFrame();
|
||||
GB_PERF_ONLY(perf.renderUs = nowMicros() - renderStartUs;)
|
||||
framebuffer.endFrame();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -536,14 +534,14 @@ private:
|
||||
std::array<int, LCD_WIDTH> colXEnd{};
|
||||
};
|
||||
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
cardboy::sdk::IFilesystem* filesystem = nullptr;
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
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
|
||||
PerfTracker perf{};
|
||||
AppTimerHandle tickTimer = kInvalidAppTimer;
|
||||
int64_t frameDelayCarryUs = 0;
|
||||
static constexpr uint32_t kTargetFrameUs = 1000000 / 60; // ~16.6 ms
|
||||
|
||||
Mode mode = Mode::Browse;
|
||||
ScaleMode scaleMode = ScaleMode::Original;
|
||||
@@ -849,8 +847,7 @@ private:
|
||||
return;
|
||||
browserDirty = false;
|
||||
|
||||
framebuffer.beginFrame();
|
||||
framebuffer.clear(false);
|
||||
framebuffer.frameReady();
|
||||
|
||||
const std::string_view title = "GAME BOY";
|
||||
const int titleWidth = font16x8::measureText(title, 2, 1);
|
||||
@@ -893,7 +890,7 @@ private:
|
||||
font16x8::drawText(framebuffer, x, framebuffer.height() - 16, statusMessage, 1, true, 1);
|
||||
}
|
||||
|
||||
framebuffer.endFrame();
|
||||
framebuffer.sendFrame();
|
||||
}
|
||||
|
||||
bool loadRom(std::size_t index) {
|
||||
@@ -1184,7 +1181,7 @@ private:
|
||||
font16x8::drawText(framebuffer, statusX, statusY, statusMessage, 1, true, 1);
|
||||
}
|
||||
}
|
||||
|
||||
framebuffer.sendFrame();
|
||||
}
|
||||
|
||||
void maybeSaveRam() {
|
||||
@@ -1259,10 +1256,7 @@ private:
|
||||
return static_cast<GameboyApp*>(gb->direct.priv);
|
||||
}
|
||||
|
||||
static bool shouldPixelBeOn(int value, int dstX, int dstY, bool useDither) {
|
||||
value &= 0x03;
|
||||
if (!useDither)
|
||||
return value >= 2;
|
||||
__attribute__((always_inline)) static bool shouldPixelBeOn(int value, int dstX, int dstY) {
|
||||
if (value >= 3)
|
||||
return true;
|
||||
if (value <= 0)
|
||||
@@ -1335,16 +1329,13 @@ private:
|
||||
self->frameDirty = true;
|
||||
|
||||
Framebuffer& fb = self->framebuffer;
|
||||
|
||||
const bool useDither = (self->scaleMode == ScaleMode::FullHeight) &&
|
||||
(geom.scaledWidth != LCD_WIDTH || geom.scaledHeight != LCD_HEIGHT);
|
||||
fb.frameReady();
|
||||
|
||||
if (geom.scaledWidth == LCD_WIDTH && geom.scaledHeight == LCD_HEIGHT) {
|
||||
const int dstY = yStart;
|
||||
const int dstXBase = geom.offsetX;
|
||||
for (int x = 0; x < LCD_WIDTH; ++x) {
|
||||
const int val = (pixels[x] & 0x03u);
|
||||
const bool on = shouldPixelBeOn(val, dstXBase + x, dstY, useDither);
|
||||
const bool on = shouldPixelBeOn(pixels[x], dstXBase + x, dstY);
|
||||
fb.drawPixel(dstXBase + x, dstY, on);
|
||||
}
|
||||
return;
|
||||
@@ -1359,7 +1350,7 @@ private:
|
||||
continue;
|
||||
for (int dstY = yStart; dstY < yEnd; ++dstY)
|
||||
for (int dstX = drawStart; dstX < drawEnd; ++dstX) {
|
||||
const bool on = shouldPixelBeOn(pixels[x], dstX, dstY, useDither);
|
||||
const bool on = shouldPixelBeOn(pixels[x], dstX, dstY);
|
||||
fb.drawPixel(dstX, dstY, on);
|
||||
}
|
||||
}
|
||||
@@ -1398,7 +1389,7 @@ private:
|
||||
|
||||
class GameboyAppFactory final : public cardboy::sdk::IAppFactory {
|
||||
public:
|
||||
const char* name() const override { return kGameboyAppName; }
|
||||
const char* name() const override { return kGameboyAppName; }
|
||||
std::unique_ptr<cardboy::sdk::IApp> create(cardboy::sdk::AppContext& context) override {
|
||||
return std::make_unique<GameboyApp>(context);
|
||||
}
|
||||
@@ -1406,8 +1397,6 @@ public:
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<cardboy::sdk::IAppFactory> createGameboyAppFactory() {
|
||||
return std::make_unique<GameboyAppFactory>();
|
||||
}
|
||||
std::unique_ptr<cardboy::sdk::IAppFactory> createGameboyAppFactory() { return std::make_unique<GameboyAppFactory>(); }
|
||||
|
||||
} // namespace apps
|
||||
|
||||
@@ -135,8 +135,7 @@ private:
|
||||
return;
|
||||
dirty = false;
|
||||
|
||||
framebuffer.beginFrame();
|
||||
framebuffer.clear(false);
|
||||
framebuffer.frameReady();
|
||||
|
||||
drawCenteredText(framebuffer, 24, "APPS", 1, 1);
|
||||
|
||||
@@ -158,7 +157,7 @@ private:
|
||||
drawCenteredText(framebuffer, framebuffer.height() - 28, "L/R CHOOSE", 1, 1);
|
||||
}
|
||||
|
||||
framebuffer.endFrame();
|
||||
framebuffer.sendFrame();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -467,15 +467,14 @@ private:
|
||||
return;
|
||||
dirty = false;
|
||||
|
||||
framebuffer.beginFrame();
|
||||
framebuffer.clear(false);
|
||||
framebuffer.frameReady();
|
||||
|
||||
drawBoard();
|
||||
drawActivePiece();
|
||||
drawNextPreview();
|
||||
drawHUD();
|
||||
|
||||
framebuffer.endFrame();
|
||||
framebuffer.sendFrame();
|
||||
}
|
||||
|
||||
void drawBoard() {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "cardboy/sdk/platform.hpp"
|
||||
|
||||
#include <SFML/Window/Keyboard.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace cardboy::backend::desktop {
|
||||
|
||||
class DesktopRuntime;
|
||||
|
||||
class DesktopFramebuffer final : public cardboy::sdk::FramebufferFacade<DesktopFramebuffer> {
|
||||
public:
|
||||
explicit DesktopFramebuffer(DesktopRuntime& runtime);
|
||||
|
||||
[[nodiscard]] int width_impl() const;
|
||||
[[nodiscard]] int height_impl() const;
|
||||
void drawPixel_impl(int x, int y, bool on);
|
||||
void clear_impl(bool on);
|
||||
void frameReady_impl();
|
||||
void sendFrame_impl(bool clearAfterSend);
|
||||
[[nodiscard]] bool frameInFlight_impl() const { return false; }
|
||||
|
||||
private:
|
||||
DesktopRuntime& runtime;
|
||||
};
|
||||
|
||||
class DesktopInput final : public cardboy::sdk::InputFacade<DesktopInput> {
|
||||
public:
|
||||
explicit DesktopInput(DesktopRuntime& runtime);
|
||||
|
||||
cardboy::sdk::InputState readState_impl();
|
||||
void handleKey(sf::Keyboard::Key key, bool pressed);
|
||||
|
||||
private:
|
||||
DesktopRuntime& runtime;
|
||||
cardboy::sdk::InputState state{};
|
||||
};
|
||||
|
||||
class DesktopClock final : public cardboy::sdk::ClockFacade<DesktopClock> {
|
||||
public:
|
||||
explicit DesktopClock(DesktopRuntime& runtime);
|
||||
|
||||
std::uint32_t millis_impl();
|
||||
void sleep_ms_impl(std::uint32_t ms);
|
||||
|
||||
private:
|
||||
DesktopRuntime& runtime;
|
||||
const std::chrono::steady_clock::time_point start;
|
||||
};
|
||||
|
||||
struct Backend {
|
||||
using Framebuffer = DesktopFramebuffer;
|
||||
using Input = DesktopInput;
|
||||
using Clock = DesktopClock;
|
||||
};
|
||||
|
||||
} // namespace cardboy::backend::desktop
|
||||
|
||||
namespace cardboy::backend {
|
||||
using DesktopBackend = desktop::Backend;
|
||||
} // namespace cardboy::backend
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "cardboy/apps/gameboy_app.hpp"
|
||||
#include "cardboy/apps/menu_app.hpp"
|
||||
#include "cardboy/apps/tetris_app.hpp"
|
||||
#include "cardboy/backend/desktop_backend.hpp"
|
||||
#include "cardboy/sdk/app_system.hpp"
|
||||
#include "cardboy/sdk/display_spec.hpp"
|
||||
#include "cardboy/sdk/services.hpp"
|
||||
@@ -26,7 +27,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
namespace cardboy::backend::desktop {
|
||||
|
||||
constexpr int kPixelScale = 2;
|
||||
|
||||
@@ -135,54 +136,6 @@ private:
|
||||
bool mounted = false;
|
||||
};
|
||||
|
||||
class DesktopRuntime;
|
||||
|
||||
class DesktopFramebuffer final : public cardboy::sdk::IFramebuffer {
|
||||
public:
|
||||
explicit DesktopFramebuffer(DesktopRuntime& runtime) : runtime(runtime) {}
|
||||
|
||||
int width() const override;
|
||||
int height() const override;
|
||||
void drawPixel(int x, int y, bool on) override;
|
||||
void clear(bool on) override;
|
||||
void beginFrame() override {}
|
||||
void endFrame() override;
|
||||
|
||||
private:
|
||||
DesktopRuntime& runtime;
|
||||
};
|
||||
|
||||
class DesktopInput final : public cardboy::sdk::IInput {
|
||||
public:
|
||||
explicit DesktopInput(DesktopRuntime& runtime) : runtime(runtime) {}
|
||||
|
||||
cardboy::sdk::InputState readState() override { return state; }
|
||||
|
||||
void handleKey(sf::Keyboard::Key key, bool pressed);
|
||||
|
||||
private:
|
||||
DesktopRuntime& runtime;
|
||||
cardboy::sdk::InputState state{};
|
||||
};
|
||||
|
||||
class DesktopClock final : public cardboy::sdk::IClock {
|
||||
public:
|
||||
explicit DesktopClock(DesktopRuntime& runtime)
|
||||
: runtime(runtime), start(std::chrono::steady_clock::now()) {}
|
||||
|
||||
std::uint32_t millis() override {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
return static_cast<std::uint32_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count());
|
||||
}
|
||||
|
||||
void sleep_ms(std::uint32_t ms) override;
|
||||
|
||||
private:
|
||||
DesktopRuntime& runtime;
|
||||
const std::chrono::steady_clock::time_point start;
|
||||
};
|
||||
|
||||
class DesktopRuntime {
|
||||
private:
|
||||
friend class DesktopFramebuffer;
|
||||
@@ -193,8 +146,9 @@ private:
|
||||
sf::Texture texture;
|
||||
sf::Sprite sprite;
|
||||
std::vector<std::uint8_t> pixels; // RGBA buffer
|
||||
bool dirty = true;
|
||||
bool running = true;
|
||||
bool dirty = true;
|
||||
bool running = true;
|
||||
bool clearNextFrame = true;
|
||||
|
||||
DesktopBuzzer buzzerService;
|
||||
DesktopBattery batteryService;
|
||||
@@ -326,16 +280,32 @@ void DesktopRuntime::sleepFor(std::uint32_t ms) {
|
||||
std::this_thread::yield();
|
||||
} while (true);
|
||||
}
|
||||
|
||||
DesktopFramebuffer::DesktopFramebuffer(DesktopRuntime& runtime) : runtime(runtime) {}
|
||||
|
||||
int DesktopFramebuffer::width() const { return cardboy::sdk::kDisplayWidth; }
|
||||
int DesktopFramebuffer::width_impl() const { return cardboy::sdk::kDisplayWidth; }
|
||||
|
||||
int DesktopFramebuffer::height() const { return cardboy::sdk::kDisplayHeight; }
|
||||
int DesktopFramebuffer::height_impl() const { return cardboy::sdk::kDisplayHeight; }
|
||||
|
||||
void DesktopFramebuffer::drawPixel(int x, int y, bool on) { runtime.setPixel(x, y, on); }
|
||||
void DesktopFramebuffer::drawPixel_impl(int x, int y, bool on) { runtime.setPixel(x, y, on); }
|
||||
|
||||
void DesktopFramebuffer::clear(bool on) { runtime.clearPixels(on); }
|
||||
void DesktopFramebuffer::clear_impl(bool on) { runtime.clearPixels(on); }
|
||||
|
||||
void DesktopFramebuffer::endFrame() { runtime.dirty = true; }
|
||||
void DesktopFramebuffer::frameReady_impl() {
|
||||
if (runtime.clearNextFrame) {
|
||||
runtime.clearPixels(false);
|
||||
runtime.clearNextFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopFramebuffer::sendFrame_impl(bool clearAfterSend) {
|
||||
runtime.clearNextFrame = clearAfterSend;
|
||||
runtime.dirty = true;
|
||||
}
|
||||
|
||||
DesktopInput::DesktopInput(DesktopRuntime& runtime) : runtime(runtime) {}
|
||||
|
||||
cardboy::sdk::InputState DesktopInput::readState_impl() { return state; }
|
||||
|
||||
void DesktopInput::handleKey(sf::Keyboard::Key key, bool pressed) {
|
||||
switch (key) {
|
||||
@@ -370,9 +340,20 @@ void DesktopInput::handleKey(sf::Keyboard::Key key, bool pressed) {
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopClock::sleep_ms(std::uint32_t ms) { runtime.sleepFor(ms); }
|
||||
DesktopClock::DesktopClock(DesktopRuntime& runtime)
|
||||
: runtime(runtime), start(std::chrono::steady_clock::now()) {}
|
||||
|
||||
} // namespace
|
||||
std::uint32_t DesktopClock::millis_impl() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
return static_cast<std::uint32_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count());
|
||||
}
|
||||
|
||||
void DesktopClock::sleep_ms_impl(std::uint32_t ms) { runtime.sleepFor(ms); }
|
||||
|
||||
} // namespace cardboy::backend::desktop
|
||||
|
||||
using cardboy::backend::desktop::DesktopRuntime;
|
||||
|
||||
int main() {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "backend.hpp"
|
||||
#include "platform.hpp"
|
||||
#include "services.hpp"
|
||||
|
||||
@@ -37,18 +38,23 @@ struct AppEvent {
|
||||
AppTimerEvent timer{};
|
||||
};
|
||||
|
||||
template<typename FramebufferT, typename InputT, typename ClockT>
|
||||
struct BasicAppContext {
|
||||
using Framebuffer = FramebufferT;
|
||||
using Input = InputT;
|
||||
using Clock = ClockT;
|
||||
#ifdef CARDBOY_SDK_ACTIVE_BACKEND_TYPE
|
||||
using ActiveBackend = CARDBOY_SDK_ACTIVE_BACKEND_TYPE;
|
||||
#else
|
||||
#error "CARDBOY_SDK_ACTIVE_BACKEND_TYPE must name the active backend type"
|
||||
#endif
|
||||
|
||||
BasicAppContext() = delete;
|
||||
BasicAppContext(FramebufferT& fb, InputT& in, ClockT& clk) : framebuffer(fb), input(in), clock(clk) {}
|
||||
struct AppContext {
|
||||
using Framebuffer = typename ActiveBackend::Framebuffer;
|
||||
using Input = typename ActiveBackend::Input;
|
||||
using Clock = typename ActiveBackend::Clock;
|
||||
|
||||
FramebufferT& framebuffer;
|
||||
InputT& input;
|
||||
ClockT& clock;
|
||||
AppContext() = delete;
|
||||
AppContext(Framebuffer& fb, Input& in, Clock& clk) : framebuffer(fb), input(in), clock(clk) {}
|
||||
|
||||
Framebuffer& framebuffer;
|
||||
Input& input;
|
||||
Clock& clock;
|
||||
AppSystem* system = nullptr;
|
||||
Services* services = nullptr;
|
||||
|
||||
@@ -113,8 +119,6 @@ private:
|
||||
void cancelAllTimersInternal();
|
||||
};
|
||||
|
||||
using AppContext = BasicAppContext<IFramebuffer, IInput, IClock>;
|
||||
|
||||
class IApp {
|
||||
public:
|
||||
virtual ~IApp() = default;
|
||||
|
||||
@@ -28,8 +28,7 @@ public:
|
||||
[[nodiscard]] const IAppFactory* currentFactory() const { return activeFactory; }
|
||||
|
||||
private:
|
||||
template<typename FramebufferT, typename InputT, typename ClockT>
|
||||
friend struct BasicAppContext;
|
||||
friend struct AppContext;
|
||||
|
||||
struct TimerRecord {
|
||||
AppTimerHandle id = kInvalidAppTimer;
|
||||
@@ -63,23 +62,18 @@ private:
|
||||
InputState lastInputState{};
|
||||
};
|
||||
|
||||
template<typename FramebufferT, typename InputT, typename ClockT>
|
||||
AppTimerHandle BasicAppContext<FramebufferT, InputT, ClockT>::scheduleTimerInternal(std::uint32_t delay_ms,
|
||||
bool repeat) {
|
||||
inline AppTimerHandle AppContext::scheduleTimerInternal(std::uint32_t delay_ms, bool repeat) {
|
||||
return system ? system->scheduleTimer(delay_ms, repeat) : kInvalidAppTimer;
|
||||
}
|
||||
|
||||
template<typename FramebufferT, typename InputT, typename ClockT>
|
||||
void BasicAppContext<FramebufferT, InputT, ClockT>::cancelTimerInternal(AppTimerHandle handle) {
|
||||
inline void AppContext::cancelTimerInternal(AppTimerHandle handle) {
|
||||
if (system)
|
||||
system->cancelTimer(handle);
|
||||
}
|
||||
|
||||
template<typename FramebufferT, typename InputT, typename ClockT>
|
||||
void BasicAppContext<FramebufferT, InputT, ClockT>::cancelAllTimersInternal() {
|
||||
inline void AppContext::cancelAllTimersInternal() {
|
||||
if (system)
|
||||
system->cancelAllTimers();
|
||||
}
|
||||
|
||||
} // namespace cardboy::sdk
|
||||
|
||||
|
||||
10
Firmware/sdk/include/cardboy/sdk/backend.hpp
Normal file
10
Firmware/sdk/include/cardboy/sdk/backend.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "platform.hpp"
|
||||
|
||||
#ifndef CARDBOY_SDK_BACKEND_HEADER
|
||||
#error "CARDBOY_SDK_BACKEND_HEADER must expand to the active backend header path"
|
||||
#endif
|
||||
|
||||
#include CARDBOY_SDK_BACKEND_HEADER
|
||||
|
||||
@@ -2,39 +2,114 @@
|
||||
|
||||
#include "input_state.hpp"
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
|
||||
namespace cardboy::sdk {
|
||||
|
||||
class IFramebuffer {
|
||||
public:
|
||||
virtual ~IFramebuffer() = default;
|
||||
|
||||
virtual int width() const = 0;
|
||||
virtual int height() const = 0;
|
||||
virtual void drawPixel(int x, int y, bool on) = 0;
|
||||
virtual void clear(bool on) = 0;
|
||||
|
||||
// Optional hooks for async display pipelines; default to no-ops.
|
||||
virtual void beginFrame() {}
|
||||
virtual void endFrame() {}
|
||||
virtual bool isFrameInFlight() const { return false; }
|
||||
namespace detail {
|
||||
template<typename Impl>
|
||||
concept HasClearImpl = requires(Impl& impl, bool value) {
|
||||
{ impl.clear_impl(value) };
|
||||
};
|
||||
|
||||
class IInput {
|
||||
public:
|
||||
virtual ~IInput() = default;
|
||||
|
||||
virtual InputState readState() = 0;
|
||||
template<typename Impl>
|
||||
concept HasFrameReadyImpl = requires(Impl& impl) {
|
||||
{ impl.frameReady_impl() };
|
||||
};
|
||||
|
||||
class IClock {
|
||||
public:
|
||||
virtual ~IClock() = default;
|
||||
template<typename Impl>
|
||||
concept HasSendFrameImpl = requires(Impl& impl, bool flag) {
|
||||
{ impl.sendFrame_impl(flag) };
|
||||
};
|
||||
|
||||
virtual std::uint32_t millis() = 0;
|
||||
virtual void sleep_ms(std::uint32_t ms) = 0;
|
||||
template<typename Impl>
|
||||
concept HasFrameInFlightImpl = requires(const Impl& impl) {
|
||||
{ impl.frameInFlight_impl() } -> std::convertible_to<bool>;
|
||||
};
|
||||
|
||||
template<typename Impl>
|
||||
concept HasSleepMsImpl = requires(Impl& impl, std::uint32_t value) {
|
||||
{ impl.sleep_ms_impl(value) };
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template<typename Impl>
|
||||
class FramebufferFacade {
|
||||
public:
|
||||
[[nodiscard]] int width() const { return impl().width_impl(); }
|
||||
[[nodiscard]] int height() const { return impl().height_impl(); }
|
||||
|
||||
__attribute__((always_inline)) void drawPixel(int x, int y, bool on) { impl().drawPixel_impl(x, y, on); }
|
||||
|
||||
void clear(bool on) {
|
||||
if constexpr (detail::HasClearImpl<Impl>) {
|
||||
impl().clear_impl(on);
|
||||
} else {
|
||||
defaultClear(on);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) void frameReady() {
|
||||
if constexpr (detail::HasFrameReadyImpl<Impl>)
|
||||
impl().frameReady_impl();
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) void sendFrame(bool clearDrawBuffer = true) {
|
||||
if constexpr (detail::HasSendFrameImpl<Impl>)
|
||||
impl().sendFrame_impl(clearDrawBuffer);
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) [[nodiscard]] bool isFrameInFlight() const {
|
||||
if constexpr (detail::HasFrameInFlightImpl<Impl>)
|
||||
return impl().frameInFlight_impl();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
FramebufferFacade() = default;
|
||||
~FramebufferFacade() = default;
|
||||
|
||||
private:
|
||||
__attribute__((always_inline)) [[nodiscard]] Impl& impl() { return static_cast<Impl&>(*this); }
|
||||
__attribute__((always_inline)) [[nodiscard]] const Impl& impl() const { return static_cast<const Impl&>(*this); }
|
||||
|
||||
void defaultClear(bool on) {
|
||||
for (int y = 0; y < height(); ++y)
|
||||
for (int x = 0; x < width(); ++x)
|
||||
drawPixel(x, y, on);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Impl>
|
||||
class InputFacade {
|
||||
public:
|
||||
InputState readState() { return impl().readState_impl(); }
|
||||
|
||||
protected:
|
||||
InputFacade() = default;
|
||||
~InputFacade() = default;
|
||||
|
||||
private:
|
||||
[[nodiscard]] Impl& impl() { return static_cast<Impl&>(*this); }
|
||||
};
|
||||
|
||||
template<typename Impl>
|
||||
class ClockFacade {
|
||||
public:
|
||||
std::uint32_t millis() { return impl().millis_impl(); }
|
||||
|
||||
void sleep_ms(std::uint32_t ms) {
|
||||
if constexpr (detail::HasSleepMsImpl<Impl>)
|
||||
impl().sleep_ms_impl(ms);
|
||||
}
|
||||
|
||||
protected:
|
||||
ClockFacade() = default;
|
||||
~ClockFacade() = default;
|
||||
|
||||
private:
|
||||
[[nodiscard]] Impl& impl() { return static_cast<Impl&>(*this); }
|
||||
};
|
||||
|
||||
} // namespace cardboy::sdk
|
||||
|
||||
|
||||
Reference in New Issue
Block a user