mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
independnet gameboy
This commit is contained in:
@@ -3,7 +3,7 @@ idf_component_register(SRCS
|
||||
../sdk/apps/menu_app.cpp
|
||||
../sdk/apps/clock_app.cpp
|
||||
../sdk/apps/tetris_app.cpp
|
||||
src/apps/gameboy_app.cpp
|
||||
../sdk/apps/gameboy_app.cpp
|
||||
src/display.cpp
|
||||
src/bat_mon.cpp
|
||||
src/spi_global.cpp
|
||||
|
||||
@@ -25,3 +25,4 @@ using IStorage = cardboy::sdk::IStorage;
|
||||
using IRandom = cardboy::sdk::IRandom;
|
||||
using IHighResClock = cardboy::sdk::IHighResClock;
|
||||
using IPowerManager = cardboy::sdk::IPowerManager;
|
||||
using IFilesystem = cardboy::sdk::IFilesystem;
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_framework.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace apps {
|
||||
|
||||
inline constexpr char kGameboyAppName[] = "Game Boy";
|
||||
inline constexpr std::string_view kGameboyAppNameView = kGameboyAppName;
|
||||
|
||||
std::unique_ptr<IAppFactory> createGameboyAppFactory();
|
||||
|
||||
} // namespace apps
|
||||
#include "cardboy/apps/gameboy_app.hpp"
|
||||
|
||||
@@ -118,6 +118,16 @@ public:
|
||||
[[nodiscard]] bool isSlowMode() const override { return PowerHelper::get().is_slow(); }
|
||||
};
|
||||
|
||||
class EspFilesystem final : public cardboy::sdk::IFilesystem {
|
||||
public:
|
||||
bool mount() override { return FsHelper::get().mount() == ESP_OK; }
|
||||
[[nodiscard]] bool isMounted() const override { return FsHelper::get().isMounted(); }
|
||||
[[nodiscard]] std::string basePath() const override {
|
||||
const char* path = FsHelper::get().basePath();
|
||||
return path ? std::string(path) : std::string{};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#if CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS && CONFIG_FREERTOS_USE_TRACE_FACILITY
|
||||
@@ -305,6 +315,7 @@ extern "C" void app_main() {
|
||||
static EspRandom randomService;
|
||||
static EspHighResClock highResClockService;
|
||||
static EspPowerManager powerService;
|
||||
static EspFilesystem filesystemService;
|
||||
|
||||
static cardboy::sdk::Services services{};
|
||||
services.buzzer = &buzzerService;
|
||||
@@ -313,6 +324,7 @@ extern "C" void app_main() {
|
||||
services.random = &randomService;
|
||||
services.highResClock = &highResClockService;
|
||||
services.powerManager = &powerService;
|
||||
services.filesystem = &filesystemService;
|
||||
|
||||
AppContext context(framebuffer, input, clock);
|
||||
context.services = &services;
|
||||
|
||||
@@ -20,6 +20,7 @@ add_library(cardboy_apps STATIC
|
||||
apps/menu_app.cpp
|
||||
apps/clock_app.cpp
|
||||
apps/tetris_app.cpp
|
||||
apps/gameboy_app.cpp
|
||||
)
|
||||
|
||||
target_include_directories(cardboy_apps
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
#pragma GCC optimize("Ofast")
|
||||
#include "apps/gameboy_app.hpp"
|
||||
#include "apps/peanut_gb.h"
|
||||
#include "cardboy/apps/gameboy_app.hpp"
|
||||
#include "cardboy/apps/peanut_gb.h"
|
||||
|
||||
#include "app_framework.hpp"
|
||||
#include "app_system.hpp"
|
||||
#include "cardboy/sdk/app_framework.hpp"
|
||||
#include "cardboy/sdk/app_system.hpp"
|
||||
#include "cardboy/gfx/font16x8.hpp"
|
||||
#include "input_state.hpp"
|
||||
|
||||
#include <disp_tools.hpp>
|
||||
#include <fs_helper.hpp>
|
||||
|
||||
|
||||
#include "esp_timer.h"
|
||||
#include "cardboy/sdk/services.hpp"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
@@ -27,6 +21,7 @@
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#define GAMEBOY_PERF_METRICS 0
|
||||
|
||||
@@ -46,16 +41,16 @@ namespace {
|
||||
constexpr int kMenuStartY = 48;
|
||||
constexpr int kMenuSpacing = font16x8::kGlyphHeight + 6;
|
||||
|
||||
using cardboy::sdk::AppContext;
|
||||
using cardboy::sdk::AppEvent;
|
||||
using cardboy::sdk::AppEventType;
|
||||
using cardboy::sdk::AppTimerHandle;
|
||||
using cardboy::sdk::InputState;
|
||||
using cardboy::sdk::kInvalidAppTimer;
|
||||
using Framebuffer = typename AppContext::Framebuffer;
|
||||
|
||||
constexpr std::array<std::string_view, 2> kRomExtensions = {".gb", ".gbc"};
|
||||
|
||||
extern "C" {
|
||||
extern const uint8_t _binary_builtin_demo1_gb_start[];
|
||||
extern const uint8_t _binary_builtin_demo1_gb_end[];
|
||||
extern const uint8_t _binary_builtin_demo2_gb_start[];
|
||||
extern const uint8_t _binary_builtin_demo2_gb_end[];
|
||||
}
|
||||
struct EmbeddedRomDescriptor {
|
||||
std::string_view name;
|
||||
std::string_view saveSlug;
|
||||
@@ -63,6 +58,14 @@ struct EmbeddedRomDescriptor {
|
||||
const uint8_t* end;
|
||||
};
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
extern "C" {
|
||||
extern const uint8_t _binary_builtin_demo1_gb_start[];
|
||||
extern const uint8_t _binary_builtin_demo1_gb_end[];
|
||||
extern const uint8_t _binary_builtin_demo2_gb_start[];
|
||||
extern const uint8_t _binary_builtin_demo2_gb_end[];
|
||||
}
|
||||
|
||||
static const std::array<EmbeddedRomDescriptor, 2> kEmbeddedRomDescriptors = {{{
|
||||
"Builtin Demo 1",
|
||||
"builtin_demo1",
|
||||
@@ -75,6 +78,9 @@ static const std::array<EmbeddedRomDescriptor, 2> kEmbeddedRomDescriptors = {{{
|
||||
_binary_builtin_demo2_gb_start,
|
||||
_binary_builtin_demo2_gb_end,
|
||||
}}};
|
||||
#else
|
||||
static const std::array<EmbeddedRomDescriptor, 0> kEmbeddedRomDescriptors{};
|
||||
#endif
|
||||
|
||||
struct RomEntry {
|
||||
std::string name; // short display name
|
||||
@@ -168,9 +174,10 @@ void drawTextRotated(Framebuffer& fb, int x, int y, std::string_view text, bool
|
||||
}
|
||||
}
|
||||
|
||||
class GameboyApp final : public IApp {
|
||||
class GameboyApp final : public cardboy::sdk::IApp {
|
||||
public:
|
||||
explicit GameboyApp(AppContext& ctx) : context(ctx), framebuffer(ctx.framebuffer) {}
|
||||
explicit GameboyApp(AppContext& ctx) :
|
||||
context(ctx), framebuffer(ctx.framebuffer), filesystem(ctx.filesystem()), highResClock(ctx.highResClock()) {}
|
||||
|
||||
void onStart() override {
|
||||
cancelTick();
|
||||
@@ -198,9 +205,9 @@ public:
|
||||
void handleEvent(const AppEvent& event) override {
|
||||
if (event.type == AppEventType::Timer && event.timer.handle == tickTimer) {
|
||||
tickTimer = kInvalidAppTimer;
|
||||
const uint64_t frameStartUs = esp_timer_get_time();
|
||||
const uint64_t frameStartUs = nowMicros();
|
||||
performStep();
|
||||
const uint64_t frameEndUs = esp_timer_get_time();
|
||||
const uint64_t frameEndUs = nowMicros();
|
||||
const uint64_t elapsedUs = (frameEndUs >= frameStartUs) ? (frameEndUs - frameStartUs) : 0;
|
||||
GB_PERF_ONLY(printf("Step took %" PRIu64 " us\n", elapsedUs));
|
||||
scheduleAfterFrame(elapsedUs);
|
||||
@@ -215,27 +222,27 @@ public:
|
||||
void performStep() {
|
||||
GB_PERF_ONLY(perf.resetForStep();)
|
||||
|
||||
GB_PERF_ONLY(const uint64_t inputStartUs = esp_timer_get_time();)
|
||||
GB_PERF_ONLY(const uint64_t inputStartUs = nowMicros();)
|
||||
const InputState input = context.input.readState();
|
||||
GB_PERF_ONLY(perf.inputUs = esp_timer_get_time() - inputStartUs;)
|
||||
GB_PERF_ONLY(perf.inputUs = nowMicros() - inputStartUs;)
|
||||
|
||||
const Mode stepMode = mode;
|
||||
|
||||
switch (stepMode) {
|
||||
case Mode::Browse: {
|
||||
GB_PERF_ONLY(const uint64_t handleStartUs = esp_timer_get_time();)
|
||||
GB_PERF_ONLY(const uint64_t handleStartUs = nowMicros();)
|
||||
handleBrowserInput(input);
|
||||
GB_PERF_ONLY(perf.handleUs = esp_timer_get_time() - handleStartUs;)
|
||||
GB_PERF_ONLY(perf.handleUs = nowMicros() - handleStartUs;)
|
||||
|
||||
GB_PERF_ONLY(const uint64_t renderStartUs = esp_timer_get_time();)
|
||||
GB_PERF_ONLY(const uint64_t renderStartUs = nowMicros();)
|
||||
renderBrowser();
|
||||
GB_PERF_ONLY(perf.renderUs = esp_timer_get_time() - renderStartUs;)
|
||||
GB_PERF_ONLY(perf.renderUs = nowMicros() - renderStartUs;)
|
||||
break;
|
||||
}
|
||||
case Mode::Running: {
|
||||
GB_PERF_ONLY(const uint64_t handleStartUs = esp_timer_get_time();)
|
||||
GB_PERF_ONLY(const uint64_t handleStartUs = nowMicros();)
|
||||
handleGameInput(input);
|
||||
GB_PERF_ONLY(perf.handleUs = esp_timer_get_time() - handleStartUs;)
|
||||
GB_PERF_ONLY(perf.handleUs = nowMicros() - handleStartUs;)
|
||||
|
||||
if (!gbReady) {
|
||||
mode = Mode::Browse;
|
||||
@@ -243,17 +250,21 @@ public:
|
||||
break;
|
||||
}
|
||||
|
||||
GB_PERF_ONLY(const uint64_t geometryStartUs = esp_timer_get_time();)
|
||||
framebuffer.beginFrame();
|
||||
framebuffer.clear(false);
|
||||
|
||||
GB_PERF_ONLY(const uint64_t geometryStartUs = nowMicros();)
|
||||
ensureRenderGeometry();
|
||||
GB_PERF_ONLY(perf.geometryUs = esp_timer_get_time() - geometryStartUs;)
|
||||
GB_PERF_ONLY(perf.geometryUs = nowMicros() - geometryStartUs;)
|
||||
|
||||
GB_PERF_ONLY(const uint64_t runStartUs = esp_timer_get_time();)
|
||||
GB_PERF_ONLY(const uint64_t runStartUs = nowMicros();)
|
||||
gb_run_frame(&gb);
|
||||
GB_PERF_ONLY(perf.runUs = esp_timer_get_time() - runStartUs;)
|
||||
GB_PERF_ONLY(perf.runUs = nowMicros() - runStartUs;)
|
||||
|
||||
GB_PERF_ONLY(const uint64_t renderStartUs = esp_timer_get_time();)
|
||||
GB_PERF_ONLY(const uint64_t renderStartUs = nowMicros();)
|
||||
renderGameFrame();
|
||||
GB_PERF_ONLY(perf.renderUs = esp_timer_get_time() - renderStartUs;)
|
||||
GB_PERF_ONLY(perf.renderUs = nowMicros() - renderStartUs;)
|
||||
framebuffer.endFrame();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -330,14 +341,14 @@ private:
|
||||
}
|
||||
|
||||
void resetForStep() {
|
||||
stepStartUs = esp_timer_get_time();
|
||||
stepStartUs = clockMicros();
|
||||
inputUs = handleUs = geometryUs = waitUs = runUs = renderUs = totalUs = otherUs = 0;
|
||||
cbRomReadUs = cbCartReadUs = cbCartWriteUs = cbLcdUs = cbErrorUs = 0;
|
||||
cbRomReadCalls = cbCartReadCalls = cbCartWriteCalls = cbLcdCalls = cbErrorCalls = 0;
|
||||
}
|
||||
|
||||
void finishStep() {
|
||||
const uint64_t now = esp_timer_get_time();
|
||||
const uint64_t now = clockMicros();
|
||||
totalUs = now - stepStartUs;
|
||||
lastStepEndUs = now;
|
||||
const uint64_t accounted = inputUs + handleUs + geometryUs + waitUs + runUs + renderUs;
|
||||
@@ -419,7 +430,7 @@ private:
|
||||
return;
|
||||
if (!aggStartUs)
|
||||
aggStartUs = stepStartUs;
|
||||
const uint64_t now = lastStepEndUs ? lastStepEndUs : esp_timer_get_time();
|
||||
const uint64_t now = lastStepEndUs ? lastStepEndUs : clockMicros();
|
||||
const uint64_t span = now - aggStartUs;
|
||||
if (!force && span < 1000000ULL)
|
||||
return;
|
||||
@@ -470,6 +481,12 @@ private:
|
||||
aggCbLcdCalls = 0;
|
||||
aggCbErrorCalls = 0;
|
||||
}
|
||||
|
||||
static uint64_t clockMicros() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
return static_cast<uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count());
|
||||
}
|
||||
};
|
||||
|
||||
class ScopedCallbackTimer {
|
||||
@@ -479,7 +496,7 @@ private:
|
||||
if (instance) {
|
||||
app = instance;
|
||||
cbKind = kind;
|
||||
startUs = esp_timer_get_time();
|
||||
startUs = instance->nowMicros();
|
||||
}
|
||||
#else
|
||||
(void) instance;
|
||||
@@ -491,7 +508,7 @@ private:
|
||||
#if GAMEBOY_PERF_METRICS
|
||||
if (!app)
|
||||
return;
|
||||
const uint64_t end = esp_timer_get_time();
|
||||
const uint64_t end = app->nowMicros();
|
||||
app->perf.addCallback(cbKind, end - startUs);
|
||||
#endif
|
||||
}
|
||||
@@ -521,6 +538,8 @@ private:
|
||||
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
cardboy::sdk::IFilesystem* filesystem = nullptr;
|
||||
cardboy::sdk::IHighResClock* highResClock = nullptr;
|
||||
PerfTracker perf{};
|
||||
AppTimerHandle tickTimer = kInvalidAppTimer;
|
||||
int64_t frameDelayCarryUs = 0;
|
||||
@@ -585,9 +604,13 @@ private:
|
||||
}
|
||||
|
||||
bool ensureFilesystemReady() {
|
||||
esp_err_t err = FsHelper::get().mount();
|
||||
if (err != ESP_OK) {
|
||||
setStatus("LittleFS mount failed");
|
||||
if (!filesystem) {
|
||||
setStatus("Storage unavailable");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!filesystem->isMounted() && !filesystem->mount()) {
|
||||
setStatus("Storage mount failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -611,7 +634,9 @@ private:
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string romDirectory() const {
|
||||
std::string result(FsHelper::get().basePath());
|
||||
std::string result;
|
||||
if (filesystem)
|
||||
result = filesystem->basePath();
|
||||
if (!result.empty() && result.back() != '/')
|
||||
result.push_back('/');
|
||||
result.append("roms");
|
||||
@@ -632,7 +657,7 @@ private:
|
||||
void refreshRomList() {
|
||||
roms.clear();
|
||||
|
||||
bool fsMounted = FsHelper::get().isMounted();
|
||||
bool fsMounted = filesystem ? filesystem->isMounted() : false;
|
||||
std::string statusHint;
|
||||
const auto updateStatusHintIfEmpty = [&](std::string value) {
|
||||
if (statusHint.empty())
|
||||
@@ -642,7 +667,7 @@ private:
|
||||
if (!fsMounted) {
|
||||
fsMounted = ensureFilesystemReady();
|
||||
if (!fsMounted)
|
||||
updateStatusHintIfEmpty("Built-in ROMs only (LittleFS unavailable)");
|
||||
updateStatusHintIfEmpty("Built-in ROMs only (filesystem unavailable)");
|
||||
}
|
||||
|
||||
if (fsMounted) {
|
||||
@@ -677,9 +702,9 @@ private:
|
||||
}
|
||||
closedir(dir);
|
||||
if (roms.empty())
|
||||
updateStatusHintIfEmpty("Copy .gb/.gbc to /lfs/roms");
|
||||
updateStatusHintIfEmpty("Copy .gb/.gbc into ROMS/");
|
||||
} else {
|
||||
updateStatusHintIfEmpty("No /lfs/roms directory");
|
||||
updateStatusHintIfEmpty("ROM directory missing");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -824,7 +849,8 @@ private:
|
||||
return;
|
||||
browserDirty = false;
|
||||
|
||||
DispTools::draw_to_display_async_wait();
|
||||
framebuffer.beginFrame();
|
||||
framebuffer.clear(false);
|
||||
|
||||
const std::string_view title = "GAME BOY";
|
||||
const int titleWidth = font16x8::measureText(title, 2, 1);
|
||||
@@ -833,7 +859,7 @@ private:
|
||||
|
||||
if (roms.empty()) {
|
||||
font16x8::drawText(framebuffer, 24, kMenuStartY + 12, "NO ROMS FOUND", 1, true, 1);
|
||||
font16x8::drawText(framebuffer, 24, kMenuStartY + kMenuSpacing + 12, "/LFS/ROMS", 1, true, 1);
|
||||
font16x8::drawText(framebuffer, 24, kMenuStartY + kMenuSpacing + 12, "ADD FILES TO ROMS/", 1, true, 1);
|
||||
} else {
|
||||
const std::size_t visibleCount =
|
||||
static_cast<std::size_t>(std::max(1, (framebuffer.height() - kMenuStartY - 64) / kMenuSpacing));
|
||||
@@ -867,7 +893,7 @@ private:
|
||||
font16x8::drawText(framebuffer, x, framebuffer.height() - 16, statusMessage, 1, true, 1);
|
||||
}
|
||||
|
||||
DispTools::draw_to_display_async_start();
|
||||
framebuffer.endFrame();
|
||||
}
|
||||
|
||||
bool loadRom(std::size_t index) {
|
||||
@@ -949,7 +975,7 @@ private:
|
||||
const uint_fast32_t saveSize = gb_get_save_size(&gb);
|
||||
cartRam.assign(static_cast<std::size_t>(saveSize), 0);
|
||||
std::string savePath;
|
||||
const bool fsReady = FsHelper::get().isMounted() || ensureFilesystemReady();
|
||||
const bool fsReady = (filesystem && filesystem->isMounted()) || ensureFilesystemReady();
|
||||
if (fsReady)
|
||||
savePath = buildSavePath(rom, romDirectory());
|
||||
activeRomSavePath = savePath;
|
||||
@@ -1159,7 +1185,6 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
DispTools::draw_to_display_async_start();
|
||||
}
|
||||
|
||||
void maybeSaveRam() {
|
||||
@@ -1199,6 +1224,14 @@ private:
|
||||
browserDirty = true;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t nowMicros() const {
|
||||
if (highResClock)
|
||||
return highResClock->micros();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
return static_cast<uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count());
|
||||
}
|
||||
|
||||
void resetFpsStats() {
|
||||
fpsLastSampleMs = 0;
|
||||
fpsFrameCounter = 0;
|
||||
@@ -1303,10 +1336,6 @@ private:
|
||||
|
||||
Framebuffer& fb = self->framebuffer;
|
||||
|
||||
GB_PERF_ONLY(const uint64_t waitStartUs = esp_timer_get_time();)
|
||||
DispTools::draw_to_display_async_wait();
|
||||
GB_PERF_ONLY(self->perf.waitUs = esp_timer_get_time() - waitStartUs;)
|
||||
|
||||
const bool useDither = (self->scaleMode == ScaleMode::FullHeight) &&
|
||||
(geom.scaledWidth != LCD_WIDTH || geom.scaledHeight != LCD_HEIGHT);
|
||||
|
||||
@@ -1367,14 +1396,18 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
class GameboyAppFactory final : public IAppFactory {
|
||||
class GameboyAppFactory final : public cardboy::sdk::IAppFactory {
|
||||
public:
|
||||
const char* name() const override { return kGameboyAppName; }
|
||||
std::unique_ptr<IApp> create(AppContext& context) override { return std::make_unique<GameboyApp>(context); }
|
||||
std::unique_ptr<cardboy::sdk::IApp> create(cardboy::sdk::AppContext& context) override {
|
||||
return std::make_unique<GameboyApp>(context);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<IAppFactory> createGameboyAppFactory() { return std::make_unique<GameboyAppFactory>(); }
|
||||
std::unique_ptr<cardboy::sdk::IAppFactory> createGameboyAppFactory() {
|
||||
return std::make_unique<GameboyAppFactory>();
|
||||
}
|
||||
|
||||
} // namespace apps
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "cardboy/apps/clock_app.hpp"
|
||||
#include "cardboy/apps/gameboy_app.hpp"
|
||||
#include "cardboy/apps/menu_app.hpp"
|
||||
#include "cardboy/apps/tetris_app.hpp"
|
||||
#include "cardboy/sdk/app_system.hpp"
|
||||
@@ -14,12 +15,13 @@
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -105,6 +107,34 @@ private:
|
||||
bool slowMode = false;
|
||||
};
|
||||
|
||||
class DesktopFilesystem final : public cardboy::sdk::IFilesystem {
|
||||
public:
|
||||
DesktopFilesystem() {
|
||||
if (const char* env = std::getenv("CARDBOY_ROM_DIR"); env && *env) {
|
||||
basePathPath = std::filesystem::path(env);
|
||||
} else {
|
||||
basePathPath = std::filesystem::current_path() / "roms";
|
||||
}
|
||||
}
|
||||
|
||||
bool mount() override {
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(basePathPath, ec)) {
|
||||
mounted = std::filesystem::is_directory(basePathPath, ec);
|
||||
} else {
|
||||
mounted = std::filesystem::create_directories(basePathPath, ec);
|
||||
}
|
||||
return mounted;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isMounted() const override { return mounted; }
|
||||
[[nodiscard]] std::string basePath() const override { return basePathPath.string(); }
|
||||
|
||||
private:
|
||||
std::filesystem::path basePathPath;
|
||||
bool mounted = false;
|
||||
};
|
||||
|
||||
class DesktopRuntime;
|
||||
|
||||
class DesktopFramebuffer final : public cardboy::sdk::IFramebuffer {
|
||||
@@ -172,6 +202,7 @@ private:
|
||||
DesktopRandom randomService;
|
||||
DesktopHighResClock highResService;
|
||||
DesktopPowerManager powerService;
|
||||
DesktopFilesystem filesystemService;
|
||||
cardboy::sdk::Services services{};
|
||||
|
||||
public:
|
||||
@@ -218,6 +249,7 @@ DesktopRuntime::DesktopRuntime()
|
||||
services.random = &randomService;
|
||||
services.highResClock = &highResService;
|
||||
services.powerManager = &powerService;
|
||||
services.filesystem = &filesystemService;
|
||||
}
|
||||
|
||||
void DesktopRuntime::setPixel(int x, int y, bool on) {
|
||||
@@ -352,6 +384,7 @@ int main() {
|
||||
|
||||
system.registerApp(apps::createMenuAppFactory());
|
||||
system.registerApp(apps::createClockAppFactory());
|
||||
system.registerApp(apps::createGameboyAppFactory());
|
||||
system.registerApp(apps::createTetrisAppFactory());
|
||||
|
||||
system.run();
|
||||
|
||||
16
Firmware/sdk/include/cardboy/apps/gameboy_app.hpp
Normal file
16
Firmware/sdk/include/cardboy/apps/gameboy_app.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "cardboy/sdk/app_framework.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace apps {
|
||||
|
||||
inline constexpr char kGameboyAppName[] = "Game Boy";
|
||||
inline constexpr std::string_view kGameboyAppNameView = kGameboyAppName;
|
||||
|
||||
std::unique_ptr<cardboy::sdk::IAppFactory> createGameboyAppFactory();
|
||||
|
||||
} // namespace apps
|
||||
|
||||
@@ -92,12 +92,12 @@
|
||||
/* Enable 16 bit colour palette. If disabled, only four colour shades are set in
|
||||
* pixel data. */
|
||||
#ifndef PEANUT_GB_12_COLOUR
|
||||
# define PEANUT_GB_12_COLOUR 1
|
||||
# define PEANUT_GB_12_COLOUR 0
|
||||
#endif
|
||||
|
||||
/* Adds more code to improve LCD rendering accuracy. */
|
||||
#ifndef PEANUT_GB_HIGH_LCD_ACCURACY
|
||||
# define PEANUT_GB_HIGH_LCD_ACCURACY 1
|
||||
# define PEANUT_GB_HIGH_LCD_ACCURACY 0
|
||||
#endif
|
||||
|
||||
/* Use intrinsic functions. This may produce smaller and faster code. */
|
||||
@@ -60,6 +60,7 @@ struct BasicAppContext {
|
||||
[[nodiscard]] IRandom* random() const { return services ? services->random : nullptr; }
|
||||
[[nodiscard]] IHighResClock* highResClock() const { return services ? services->highResClock : nullptr; }
|
||||
[[nodiscard]] IPowerManager* powerManager() const { return services ? services->powerManager : nullptr; }
|
||||
[[nodiscard]] IFilesystem* filesystem() const { return services ? services->filesystem : nullptr; }
|
||||
|
||||
void requestAppSwitchByIndex(std::size_t index) {
|
||||
pendingAppIndex = index;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace cardboy::sdk {
|
||||
@@ -64,6 +65,15 @@ public:
|
||||
[[nodiscard]] virtual bool isSlowMode() const = 0;
|
||||
};
|
||||
|
||||
class IFilesystem {
|
||||
public:
|
||||
virtual ~IFilesystem() = default;
|
||||
|
||||
virtual bool mount() = 0;
|
||||
[[nodiscard]] virtual bool isMounted() const = 0;
|
||||
[[nodiscard]] virtual std::string basePath() const = 0;
|
||||
};
|
||||
|
||||
struct Services {
|
||||
IBuzzer* buzzer = nullptr;
|
||||
IBatteryMonitor* battery = nullptr;
|
||||
@@ -71,7 +81,7 @@ struct Services {
|
||||
IRandom* random = nullptr;
|
||||
IHighResClock* highResClock = nullptr;
|
||||
IPowerManager* powerManager = nullptr;
|
||||
IFilesystem* filesystem = nullptr;
|
||||
};
|
||||
|
||||
} // namespace cardboy::sdk
|
||||
|
||||
|
||||
Reference in New Issue
Block a user