diff --git a/deps/ox/src/ox/std/CMakeLists.txt b/deps/ox/src/ox/std/CMakeLists.txt index 32cdf7c..0c1ad78 100644 --- a/deps/ox/src/ox/std/CMakeLists.txt +++ b/deps/ox/src/ox/std/CMakeLists.txt @@ -113,6 +113,7 @@ install( memory.hpp new.hpp optional.hpp + pair.hpp point.hpp random.hpp ranges.hpp diff --git a/deps/ox/src/ox/std/conv.hpp b/deps/ox/src/ox/std/conv.hpp index 4329f86..f17e69f 100644 --- a/deps/ox/src/ox/std/conv.hpp +++ b/deps/ox/src/ox/std/conv.hpp @@ -14,32 +14,20 @@ namespace ox { -constexpr Vec2::operator Point() const noexcept { - return { - static_cast(x), - static_cast(y), - }; -} +constexpr Point::Point(Vec2 const&pt) noexcept: + x(static_cast(pt.x)), + y(static_cast(pt.y)) {} -constexpr Vec2::operator Size() const noexcept { - return { - static_cast(x), - static_cast(y), - }; -} +constexpr Size::Size(Vec2 const&pt) noexcept: + width(static_cast(pt.x)), + height(static_cast(pt.y)) {} -constexpr Point::operator Vec2() const noexcept { - return { - static_cast(x), - static_cast(y), - }; -} +constexpr Vec2::Vec2(Point const&pt) noexcept: + x(static_cast(pt.x)), + y(static_cast(pt.y)) {} -constexpr Size::operator Vec2() const noexcept { - return { - static_cast(width), - static_cast(height), - }; -} +constexpr Vec2::Vec2(Size const&pt) noexcept: + x(static_cast(pt.width)), + y(static_cast(pt.height)) {} } \ No newline at end of file diff --git a/deps/ox/src/ox/std/pair.hpp b/deps/ox/src/ox/std/pair.hpp new file mode 100644 index 0000000..d7424de --- /dev/null +++ b/deps/ox/src/ox/std/pair.hpp @@ -0,0 +1,19 @@ +/* + * Copyright 2015 - 2024 gary@drinkingtea.net + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +namespace ox { + +template +struct Pair { + T a{}; + U b{}; +}; + +} \ No newline at end of file diff --git a/deps/ox/src/ox/std/point.hpp b/deps/ox/src/ox/std/point.hpp index 94062e1..57737f8 100644 --- a/deps/ox/src/ox/std/point.hpp +++ b/deps/ox/src/ox/std/point.hpp @@ -24,6 +24,8 @@ class Point { constexpr Point(int x, int y) noexcept; + explicit constexpr Point(class Vec2 const&pt) noexcept; + constexpr Point operator+(const Point &p) const noexcept; constexpr Point operator-(const Point &p) const noexcept; diff --git a/deps/ox/src/ox/std/size.hpp b/deps/ox/src/ox/std/size.hpp index 6684008..b2980ed 100644 --- a/deps/ox/src/ox/std/size.hpp +++ b/deps/ox/src/ox/std/size.hpp @@ -24,6 +24,8 @@ class Size { constexpr Size(int width, int height) noexcept; + explicit constexpr Size(class Vec2 const&pt) noexcept; + constexpr Size operator+(Size p) const noexcept; constexpr Size operator-(Size p) const noexcept; diff --git a/deps/ox/src/ox/std/span.hpp b/deps/ox/src/ox/std/span.hpp index 542ac6d..9c8bb2a 100644 --- a/deps/ox/src/ox/std/span.hpp +++ b/deps/ox/src/ox/std/span.hpp @@ -228,6 +228,10 @@ class Span { return iterator(m_items, MaxValue, m_size); } + constexpr T &operator[](std::size_t i) noexcept { + return m_items[i]; + } + constexpr const T &operator[](std::size_t i) const noexcept { return m_items[i]; } diff --git a/deps/ox/src/ox/std/std.hpp b/deps/ox/src/ox/std/std.hpp index a88416d..85cbc87 100644 --- a/deps/ox/src/ox/std/std.hpp +++ b/deps/ox/src/ox/std/std.hpp @@ -35,6 +35,7 @@ #include "memory.hpp" #include "new.hpp" #include "optional.hpp" +#include "pair.hpp" #include "point.hpp" #include "random.hpp" #include "realstd.hpp" diff --git a/deps/ox/src/ox/std/vec.hpp b/deps/ox/src/ox/std/vec.hpp index 22a78ad..3c443d4 100644 --- a/deps/ox/src/ox/std/vec.hpp +++ b/deps/ox/src/ox/std/vec.hpp @@ -142,6 +142,10 @@ class Vec2 { constexpr Vec2() noexcept = default; + explicit constexpr Vec2(class Point const&pt) noexcept; + + explicit constexpr Vec2(class Size const&pt) noexcept; + template constexpr Vec2(float pX, float pY) noexcept: x(pX), y(pY) { } diff --git a/developer-handbook.md b/developer-handbook.md index 1bb1097..e73858e 100644 --- a/developer-handbook.md +++ b/developer-handbook.md @@ -265,7 +265,7 @@ ox::Error engineCode() noexcept { auto [val, err] = foo(1); oxReturnError(err); doStuff(val); - return OxError(0); + return {}; } void anyCode() { @@ -288,7 +288,7 @@ ox::Error engineCode() noexcept { auto valerr = foo(1); oxReturnError(valerr); doStuff(valerr.value); - return OxError(0); + return {}; } ``` @@ -426,7 +426,7 @@ constexpr ox::Error model(T *h, ox::CommonPtrWith auto *pal) n // it is also possible to provide the type name and type version as function arguments //h->setTypeInfo("net.drinkingtea.nostalgia.core.NostalgiaPalette", 1); oxReturnError(h->field("colors", &pal->colors)); - return OxError(0); + return {}; } template @@ -438,7 +438,7 @@ constexpr ox::Error model(T *h, ox::CommonPtrWith auto *ng) no oxReturnError(h->field("defaultPalette", &ng->defaultPalette)); oxReturnError(h->field("pal", &ng->pal)); oxReturnError(h->field("pixels", &ng->pixels)); - return OxError(0); + return {}; } ``` @@ -475,7 +475,7 @@ constexpr Error model(T *h, ox::CommonPtrWith auto *obj) noex oxReturnError(h->fieldCString("path", &obj->path)); oxReturnError(h->fieldCString("constPath", &obj->path)); oxReturnError(h->field("inode", &obj->inode)); - return OxError(0); + return {}; } template @@ -492,7 +492,7 @@ constexpr Error model(T *io, ox::CommonPtrWith auto *fa) noexcept { fa->m_type = static_cast(type); oxReturnError(io->field("data", UnionView(&fa->m_data, static_cast(fa->m_type)))); } - return OxError(0); + return {}; } ``` diff --git a/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp b/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp index 37974d6..0e104b2 100644 --- a/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp +++ b/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp @@ -11,6 +11,7 @@ #include "context.hpp" #include "palette.hpp" +#include "tilesheet.hpp" namespace nostalgia::core { @@ -134,12 +135,23 @@ ox::Error loadBgTileSheet( unsigned cbb, TileSheetSet const&set) noexcept; +ox::Error loadBgTileSheet( + Context &ctx, + unsigned cbb, + CompactTileSheet const&ts, + ox::Optional const&paletteBank) noexcept; + ox::Error loadBgTileSheet( Context &ctx, unsigned cbb, ox::FileAddress const&tilesheetAddr, ox::Optional const&paletteBank = {}) noexcept; +ox::Error loadSpriteTileSheet( + Context &ctx, + CompactTileSheet const&ts, + bool loadDefaultPalette) noexcept; + ox::Error loadSpriteTileSheet( Context &ctx, ox::FileAddress const&tilesheetAddr, diff --git a/src/nostalgia/modules/core/include/nostalgia/core/palette.hpp b/src/nostalgia/modules/core/include/nostalgia/core/palette.hpp index 99149e6..af61225 100644 --- a/src/nostalgia/modules/core/include/nostalgia/core/palette.hpp +++ b/src/nostalgia/modules/core/include/nostalgia/core/palette.hpp @@ -74,6 +74,15 @@ constexpr bool valid(Palette const&p) noexcept { }); } +[[nodiscard]] +constexpr ox::Error repair(Palette &p) noexcept { + auto const colors = p.colorInfo.size(); + for (auto &page : p.pages) { + page.resize(colors); + } + return {}; +} + [[nodiscard]] constexpr Color16 color(Palette const&pal, size_t page, size_t idx) noexcept { if (page < pal.pages.size() && idx < pal.pages[page].size()) [[likely]] { diff --git a/src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp b/src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp index cf7998c..a1012dc 100644 --- a/src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp +++ b/src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp @@ -296,9 +296,10 @@ using TileSheetV4 = TileSheet; struct CompactTileSheetV1 { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.CompactTileSheet"; static constexpr auto TypeVersion = 1; + static constexpr auto Preloadable = true; int8_t bpp = 0; ox::FileAddress defaultPalette; - ox::Vector pixels = {}; + ox::Vector pixels; }; using CompactTileSheet = CompactTileSheetV1; diff --git a/src/nostalgia/modules/core/src/gba/gfx.cpp b/src/nostalgia/modules/core/src/gba/gfx.cpp index fb73866..553cbab 100644 --- a/src/nostalgia/modules/core/src/gba/gfx.cpp +++ b/src/nostalgia/modules/core/src/gba/gfx.cpp @@ -24,104 +24,6 @@ namespace nostalgia::core { constexpr auto SpriteCount = 128; -struct GbaTileMapTarget { - static constexpr auto TypeName = CompactTileSheet::TypeName; - static constexpr auto TypeVersion = CompactTileSheet::TypeVersion; - unsigned &bpp; - ox::FileAddress defaultPalette; - volatile uint16_t *tileMap = nullptr; - // the following values are not actually in CompactTileSheet, - // and only exist to communicate with the loading process - size_t tileWriteIdx = 0; - unsigned targetBpp = 0; - TileSheetSetEntry const*setEntry = nullptr; -}; - -[[nodiscard]] -static bool loadPixel(TileSheetSetEntry const&setEntry, size_t §ionIdx, int tileIdx) noexcept { - if (setEntry.sections.size() <= sectionIdx) { - return false; - } - auto §ion = setEntry.sections[sectionIdx]; - if (tileIdx < section.begin) { - return false; - } - if (tileIdx > section.end()) { - if (sectionIdx >= setEntry.sections.size()) { - return false; - } - ++sectionIdx; - return tileIdx > section.begin && tileIdx <= section.end(); - } - return true; -} - -constexpr ox::Error model(auto *io, ox::CommonPtrWith auto *t) noexcept { - oxReturnError(io->template setTypeInfo()); - oxReturnError(io->field("bpp", &t->bpp)); - oxReturnError(io->field("defaultPalette", &t->defaultPalette)); - if (t->targetBpp == 0) { - t->targetBpp = t->bpp; - } - if (t->targetBpp != t->bpp && t->bpp == 8) { - return OxError(1, "Cannot load an 8 BPP tilesheet into a 4 BPP CBB"); - } - ox::Error out{}; - if (t->setEntry) { - // The following code is atrocious, but it works. - // It might be possible to clean it up a little, but it probably - // cannot be seriously optimized without preloading TileSheets. - size_t sectionIdx = 0; - if (t->targetBpp == t->bpp) { - uint16_t intermediate = 0; - size_t const fourBpp = t->bpp == 4; - const auto handleTileMap = [t, &intermediate, §ionIdx, fourBpp] - (std::size_t i, uint8_t const*tile) { - auto const tileIdx = static_cast((i * (2 * fourBpp)) / PixelsPerTile); - if (!loadPixel(*t->setEntry, sectionIdx, tileIdx)) { - return ox::Error{}; - } - if (i & 1) { // i is odd - intermediate |= static_cast(*tile) << 8; - t->tileMap[t->tileWriteIdx] = intermediate; - ++t->tileWriteIdx; - } else { // i is even - intermediate = *tile & 0x00ff; - } - return ox::Error{}; - }; - out = io->template field("tileMap", handleTileMap); - } else if (t->targetBpp > t->bpp) { // 4 -> 8 bits - const auto handleTileMap = [t, §ionIdx](std::size_t i, uint8_t const*tile) { - auto constexpr BytesPerTile4Bpp = 32; - auto const tileIdx = static_cast(i / BytesPerTile4Bpp); - if (!loadPixel(*t->setEntry, sectionIdx, tileIdx)) { - return ox::Error{}; - } - uint16_t const px1 = *tile & 0xf; - uint16_t const px2 = *tile >> 4; - t->tileMap[t->tileWriteIdx] = static_cast(px1 | (px2 << 8)); - ++t->tileWriteIdx; - return ox::Error{}; - }; - out = io->template field("tileMap", handleTileMap); - } - } else { - uint16_t intermediate = 0; - const auto handleTileMap = [t, &intermediate](std::size_t i, const uint8_t*tile) { - if (i & 1) { // i is odd - intermediate |= static_cast(*tile) << 8; - t->tileMap[i / 2] = intermediate; - } else { // i is even - intermediate = *tile & 0x00ff; - } - return ox::Error{}; - }; - out = io->template field("tileMap", handleTileMap); - } - return out; -} - ox::Error initGfx(Context&, InitParams const&) noexcept { for (auto bgCtl = ®_BG0CTL; bgCtl <= ®_BG3CTL; bgCtl += 2) { teagba::bgSetSbb(*bgCtl, 28); @@ -181,21 +83,48 @@ static ox::Error loadTileSheetSet( Context &ctx, uint16_t *tileMapTargetMem, TileSheetSet const&set) noexcept { - auto &rom = ctx.rom(); size_t tileWriteIdx = 0; for (auto const&entry : set.entries) { - oxRequire(tsStat, rom.stat(entry.tilesheet)); - oxRequire(ts, rom.directAccess(entry.tilesheet)); - unsigned tilesheetBpp{}; - GbaTileMapTarget target{ - .bpp = tilesheetBpp, - .defaultPalette = {}, - .tileMap = tileMapTargetMem + tileWriteIdx, - .targetBpp = static_cast(set.bpp), - .setEntry = &entry, - }; - oxReturnError(ox::readMC({ts, static_cast(tsStat.size)}, target)); - tileWriteIdx += target.tileWriteIdx; + oxRequire(ts, keel::readObj(keelCtx(ctx), entry.tilesheet)); + if (set.bpp != ts->bpp && ts->bpp == 8) { + return OxError(1, "cannot load an 8 BPP tilesheet into a 4 BPP CBB"); + } + for (auto const&s : entry.sections) { + auto const cnt = (static_cast(s.tiles) * PixelsPerTile) / (1 + (set.bpp == 4)); + for (size_t i = 0; i < cnt; ++i) { + auto const srcIdx = static_cast(s.begin) + i * 2; + auto const v = static_cast( + static_cast(ts->pixels[srcIdx]) | + (static_cast(ts->pixels[srcIdx + 1]) << 8)); + tileMapTargetMem[tileWriteIdx + i] = v; + } + tileWriteIdx += cnt; + } + } + return {}; +} + +ox::Error loadBgTileSheet( + Context &ctx, + unsigned cbb, + CompactTileSheet const&ts, + ox::Optional const&paletteBank) noexcept { + auto const cnt = (ts.pixels.size() * PixelsPerTile) / (1 + (ts.bpp == 4)); + for (size_t i = 0; i < cnt; ++i) { + auto const srcIdx = i * 2; + auto const p1 = static_cast(ts.pixels[srcIdx]); + auto const p2 = static_cast(ts.pixels[srcIdx + 1]); + MEM_BG_TILES[cbb][i] = static_cast(p1 | (p2 << 8)); + } + // update bpp of all bgs with the updated cbb + auto const bpp = ctx.cbbData[cbb].bpp; + teagba::iterateBgCtl([bpp, cbb](volatile BgCtl &bgCtl) { + if (teagba::bgCbb(bgCtl) == cbb) { + teagba::bgSetBpp(bgCtl, bpp); + } + }); + if (paletteBank.has_value() && ts.defaultPalette) { + oxReturnError(loadBgPalette(ctx, *paletteBank, ts.defaultPalette)); } return {}; } @@ -205,26 +134,8 @@ ox::Error loadBgTileSheet( unsigned cbb, ox::FileAddress const&tilesheetAddr, ox::Optional const&paletteBank) noexcept { - auto &rom = ctx.rom(); - oxRequire(tsStat, rom.stat(tilesheetAddr)); - oxRequire(ts, rom.directAccess(tilesheetAddr)); - GbaTileMapTarget target{ - .bpp = ctx.cbbData[cbb].bpp, - .defaultPalette = {}, - .tileMap = MEM_BG_TILES[cbb].data(), - }; - oxReturnError(ox::readMC({ts, static_cast(tsStat.size)}, target)); - // update bpp of all bgs with the updated cbb - const auto bpp = ctx.cbbData[cbb].bpp; - teagba::iterateBgCtl([bpp, cbb](volatile BgCtl &bgCtl) { - if (teagba::bgCbb(bgCtl) == cbb) { - teagba::bgSetBpp(bgCtl, bpp); - } - }); - if (paletteBank.has_value() && target.defaultPalette) { - oxReturnError(loadBgPalette(ctx, *paletteBank, target.defaultPalette)); - } - return {}; + oxRequire(ts, keel::readObj(keelCtx(ctx), tilesheetAddr)); + return loadBgTileSheet(ctx, cbb, *ts, paletteBank); } ox::Error loadBgTileSheet( @@ -252,25 +163,28 @@ static void setSpritesBpp(unsigned const bpp) noexcept { } } +ox::Error loadSpriteTileSheet( + Context &ctx, + CompactTileSheet const&ts, + bool loadDefaultPalette) noexcept { + for (size_t i = 0; i < ts.pixels.size(); i += 2) { + uint16_t v = ts.pixels[i]; + v |= static_cast(ts.pixels[i + 1] << 8); + MEM_SPRITE_TILES[i] = v; + } + if (loadDefaultPalette && ts.defaultPalette) { + oxReturnError(loadSpritePalette(ctx, ts.defaultPalette)); + } + setSpritesBpp(static_cast(ts.bpp)); + return {}; +} + ox::Error loadSpriteTileSheet( Context &ctx, ox::FileAddress const&tilesheetAddr, bool loadDefaultPalette) noexcept { - auto &rom = ctx.rom(); - oxRequire(tsStat, rom.stat(tilesheetAddr)); - oxRequire(ts, rom.directAccess(tilesheetAddr)); - unsigned bpp{}; - GbaTileMapTarget target{ - .bpp = bpp, - .defaultPalette = {}, - .tileMap = MEM_SPRITE_TILES, - }; - oxReturnError(ox::readMC({ts, static_cast(tsStat.size)}, target)); - if (loadDefaultPalette && target.defaultPalette) { - oxReturnError(loadSpritePalette(ctx, target.defaultPalette)); - } - setSpritesBpp(bpp); - return {}; + oxRequire(ts, keel::readObj(keelCtx(ctx), tilesheetAddr)); + return loadSpriteTileSheet(ctx, *ts, loadDefaultPalette); } ox::Error loadSpriteTileSheet( diff --git a/src/nostalgia/modules/core/src/opengl/gfx.cpp b/src/nostalgia/modules/core/src/opengl/gfx.cpp index 19f77a4..e5dee25 100644 --- a/src/nostalgia/modules/core/src/opengl/gfx.cpp +++ b/src/nostalgia/modules/core/src/opengl/gfx.cpp @@ -572,20 +572,28 @@ static ox::Result buildSetTsd( return setTsd; } +ox::Error loadBgTileSheet( + Context &ctx, + uint_t cbb, + CompactTileSheet const&ts, + ox::Optional const&paletteBank) noexcept { + oxRequire(tsd, normalizeTileSheet(ts)); + oxTracef("nostalgia.core.gfx.gl", "loadBgTexture: { cbbIdx: {}, w: {}, h: {} }", cbb, tsd.width, tsd.height); + ctx.cbbs[cbb].tex = renderer::createTexture(tsd.width, tsd.height, tsd.pixels.data()); + if (paletteBank.has_value() && ts.defaultPalette) { + oxReturnError(loadBgPalette(ctx, *paletteBank, ts.defaultPalette)); + } + return {}; +} + ox::Error loadBgTileSheet( Context &ctx, uint_t cbb, ox::FileAddress const&tilesheetAddr, ox::Optional const&paletteBank) noexcept { auto &kctx = keelCtx(ctx.turbineCtx); - oxRequire(tilesheet, readObj(kctx, tilesheetAddr)); - oxRequire(tsd, normalizeTileSheet(*tilesheet)); - oxTracef("nostalgia.core.gfx.gl", "loadBgTexture: { cbbIdx: {}, w: {}, h: {} }", cbb, tsd.width, tsd.height); - ctx.cbbs[cbb].tex = renderer::createTexture(tsd.width, tsd.height, tsd.pixels.data()); - if (paletteBank.has_value() && tilesheet->defaultPalette) { - oxReturnError(loadBgPalette(ctx, *paletteBank, tilesheet->defaultPalette)); - } - return {}; + oxRequire(ts, readObj(kctx, tilesheetAddr)); + return loadBgTileSheet(ctx, cbb, *ts, paletteBank); } ox::Error loadBgTileSheet( @@ -599,19 +607,26 @@ ox::Error loadBgTileSheet( ox::Error loadSpriteTileSheet( Context &ctx, - ox::FileAddress const&tilesheetAddr, + CompactTileSheet const&ts, bool loadDefaultPalette) noexcept { - auto &kctx = keelCtx(ctx.turbineCtx); - oxRequire(tilesheet, readObj(kctx, tilesheetAddr)); - oxRequire(tsd, normalizeTileSheet(*tilesheet)); + oxRequire(tsd, normalizeTileSheet(ts)); oxTracef("nostalgia.core.gfx.gl", "loadSpriteTexture: { w: {}, h: {} }", tsd.width, tsd.height); ctx.spriteBlocks.tex = renderer::createTexture(tsd.width, tsd.height, tsd.pixels.data()); if (loadDefaultPalette) { - oxReturnError(loadSpritePalette(ctx, tilesheet->defaultPalette)); + oxReturnError(loadSpritePalette(ctx, ts.defaultPalette)); } return {}; } +ox::Error loadSpriteTileSheet( + Context &ctx, + ox::FileAddress const&tilesheetAddr, + bool loadDefaultPalette) noexcept { + auto &kctx = keelCtx(ctx.turbineCtx); + oxRequire(ts, readObj(kctx, tilesheetAddr)); + return loadSpriteTileSheet(ctx, *ts, loadDefaultPalette); +} + ox::Error loadSpriteTileSheet( Context &ctx, TileSheetSet const&set) noexcept { diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index da406f0..f14c445 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -15,16 +15,6 @@ namespace nostalgia::core { namespace ig = studio::ig; -static ox::String configName(ox::StringView str) noexcept { - auto out = ox::String{str}; - for (auto &c : out) { - if (c == '/' || c == '\\') { - c = '%'; - } - } - return out; -} - struct TileSheetEditorConfig { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetEditorConfig"; static constexpr auto TypeVersion = 1; @@ -35,16 +25,6 @@ oxModelBegin(TileSheetEditorConfig) oxModelFieldRename(activeSubsheet, active_subsheet) oxModelEnd() -struct TileSheetEditorConfigs { - static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetEditorConfigs"; - static constexpr auto TypeVersion = 1; - ox::HashMap configs; -}; - -oxModelBegin(TileSheetEditorConfigs) - oxModelField(configs) -oxModelEnd() - static ox::Vector normalizePixelSizes( ox::Vector const&inPixels, int const bpp) noexcept { @@ -120,7 +100,7 @@ TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::Stri m_model.paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection); // load config auto const&config = studio::readConfig( - keelCtx(m_sctx), configName(itemPath())); + keelCtx(m_sctx), itemPath()); if (config.ok()) { m_model.setActiveSubsheet(validateSubSheetIdx(m_model.img(), config.value.activeSubsheet)); } @@ -537,9 +517,9 @@ ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept { void TileSheetEditorImGui::setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept { m_model.setActiveSubsheet(path); - studio::editConfig(keelCtx(m_sctx), configName(itemPath()), - [&path](TileSheetEditorConfig *config) { - config->activeSubsheet = std::move(path); + studio::editConfig(keelCtx(m_sctx), itemPath(), + [&path](TileSheetEditorConfig &config) { + config.activeSubsheet = std::move(path); }); } diff --git a/src/nostalgia/modules/core/src/tilesheet.cpp b/src/nostalgia/modules/core/src/tilesheet.cpp index 18b62d0..fb336f7 100644 --- a/src/nostalgia/modules/core/src/tilesheet.cpp +++ b/src/nostalgia/modules/core/src/tilesheet.cpp @@ -52,7 +52,7 @@ TileSheet::SubSheet const *getSubsheet(TileSheet const&ts, SubSheetId const id) static ox::Optional getPixelIdx( TileSheet::SubSheet const&ss, SubSheetId const id, - size_t idx, + size_t &idx, int8_t const bpp) noexcept { for (auto const&child: ss.subsheets) { if (child.id == id) { @@ -63,11 +63,12 @@ static ox::Optional getPixelIdx( } idx += pixelCnt(child, bpp); } - return {}; + return ox::Optional{}; } size_t getTileIdx(TileSheet const&ts, SubSheetId const id) noexcept { - auto const out = getPixelIdx(ts.subsheet, id, 0, ts.bpp); + size_t idx{}; + auto const out = getPixelIdx(ts.subsheet, id, idx, ts.bpp); return out.or_value(0) / PixelsPerTile; } diff --git a/src/olympic/keel/include/keel/assetmanager.hpp b/src/olympic/keel/include/keel/assetmanager.hpp index 99f8d6b..b17d2cf 100644 --- a/src/olympic/keel/include/keel/assetmanager.hpp +++ b/src/olympic/keel/include/keel/assetmanager.hpp @@ -21,6 +21,17 @@ class AssetManager; template class AssetRef; + +[[nodiscard]] +constexpr bool valid(auto const&) noexcept { + return true; +} + +[[nodiscard]] +constexpr ox::Error repair(auto const&) noexcept { + return {}; +} + #ifndef OX_BARE_METAL template class AssetContainer { @@ -215,6 +226,9 @@ class AssetManager { ox::Result> loadAsset(ox::StringView const assetId) noexcept { auto &p = m_cache[assetId]; oxRequireM(obj, m_loader(assetId)); + if (!valid(obj) && repair(obj)) { + return OxError(1, "asset is invalid state and could not be repaired"); + } if (!p) { p = ox::make_unique>(std::move(obj)); } else { diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index dc74dd4..607627a 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -200,8 +200,8 @@ void StudioUI::drawTabs() noexcept { if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) { if (m_activeEditor != e.get()) { m_activeEditor = e.get(); - studio::editConfig(keelCtx(m_ctx), [&](StudioConfig *config) { - config->activeTabItemName = m_activeEditor->itemPath(); + studio::editConfig(keelCtx(m_ctx), [&](StudioConfig &config) { + config.activeTabItemName = m_activeEditor->itemPath(); }); } if (m_activeEditorUpdatePending == e.get()) { @@ -255,8 +255,8 @@ void StudioUI::loadModules() noexcept { void StudioUI::toggleProjectExplorer() noexcept { m_showProjectExplorer = !m_showProjectExplorer; - studio::editConfig(keelCtx(m_ctx), [&](StudioConfig *config) { - config->showProjectExplorer = m_showProjectExplorer; + studio::editConfig(keelCtx(m_ctx), [&](StudioConfig &config) { + config.showProjectExplorer = m_showProjectExplorer; }); } @@ -339,9 +339,9 @@ ox::Error StudioUI::openProjectPath(ox::CRStringView path) noexcept { m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_openFiles.clear(); m_editors.clear(); - studio::editConfig(keelCtx(m_ctx), [&](StudioConfig *config) { - config->projectPath = ox::String(m_project->projectPath()); - config->openFiles.clear(); + studio::editConfig(keelCtx(m_ctx), [&](StudioConfig &config) { + config.projectPath = ox::String(m_project->projectPath()); + config.openFiles.clear(); }); return m_projectExplorer.refreshProjectTreeModel(); } @@ -392,9 +392,9 @@ ox::Error StudioUI::openFileActiveTab(ox::CRStringView path, bool makeActiveTab) m_activeEditorUpdatePending = editor; } // save to config - studio::editConfig(keelCtx(m_ctx), [&](StudioConfig *config) { - if (!config->openFiles.contains(path)) { - config->openFiles.emplace_back(path); + studio::editConfig(keelCtx(m_ctx), [&](StudioConfig &config) { + if (!config.openFiles.contains(path)) { + config.openFiles.emplace_back(path); } }); return {}; @@ -406,8 +406,8 @@ ox::Error StudioUI::closeFile(ox::CRStringView path) noexcept { } std::ignore = m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path)); // save to config - studio::editConfig(keelCtx(m_ctx), [&](StudioConfig *config) { - std::ignore = config->openFiles.erase(std::remove(config->openFiles.begin(), config->openFiles.end(), path)); + studio::editConfig(keelCtx(m_ctx), [&](StudioConfig &config) { + std::ignore = config.openFiles.erase(std::remove(config.openFiles.begin(), config.openFiles.end(), path)); }); return {}; } diff --git a/src/olympic/studio/modlib/include/studio/configio.hpp b/src/olympic/studio/modlib/include/studio/configio.hpp index 14d476e..69b4443 100644 --- a/src/olympic/studio/modlib/include/studio/configio.hpp +++ b/src/olympic/studio/modlib/include/studio/configio.hpp @@ -17,17 +17,29 @@ namespace studio { +namespace detail { +inline ox::String slashesToPct(ox::StringView str) noexcept { + auto out = ox::String{str}; + for (auto&c: out) { + if (c == '/' || c == '\\') { + c = '%'; + } + } + return out; +} +} + [[nodiscard]] ox::String configPath(keel::Context const&ctx) noexcept; template ox::Result readConfig(keel::Context &ctx, ox::CRStringView name) noexcept { oxAssert(name != "", "Config type has no TypeName"); - auto const path = ox::sfmt("/{}.json", name); + auto const path = ox::sfmt("/{}.json", detail::slashesToPct(name)); ox::PassThroughFS fs(configPath(ctx)); auto const [buff, err] = fs.read(path); if (err) { - oxErrf("Could not read config file: {}\n", toStr(err)); + //oxErrf("Could not read config file: {} - {}\n", path, toStr(err)); return err; } return ox::readOC(buff); @@ -40,25 +52,25 @@ ox::Result readConfig(keel::Context &ctx) noexcept { } template -ox::Error writeConfig(keel::Context &ctx, ox::CRStringView name, T *data) noexcept { +ox::Error writeConfig(keel::Context &ctx, ox::CRStringView name, T const&data) noexcept { oxAssert(name != "", "Config type has no TypeName"); - auto const path = ox::sfmt("/{}.json", name); + auto const path = ox::sfmt("/{}.json", detail::slashesToPct(name)); ox::PassThroughFS fs(configPath(ctx)); if (auto const err = fs.mkdir("/", true)) { - oxErrf("Could not create config directory: {}\n", toStr(err)); + //oxErrf("Could not create config directory: {} - {}\n", path, toStr(err)); return err; } - oxRequireM(buff, ox::writeOC(*data)); + oxRequireM(buff, ox::writeOC(data)); *buff.back().value = '\n'; if (auto const err = fs.write(path, buff.data(), buff.size())) { - oxErrf("Could not read config file: {}\n", toStr(err)); + //oxErrf("Could not read config file: {} - {}\n", path, toStr(err)); return OxError(2, "Could not read config file"); } return {}; } template -ox::Error writeConfig(keel::Context &ctx, T *data) noexcept { +ox::Error writeConfig(keel::Context &ctx, T const&data) noexcept { constexpr auto TypeName = ox::requireModelTypeName(); return writeConfig(ctx, TypeName, data); } @@ -68,7 +80,7 @@ void openConfig(keel::Context &ctx, ox::CRStringView name, Func f) noexcept { oxAssert(name != "", "Config type has no TypeName"); auto const [c, err] = readConfig(ctx, name); oxLogError(err); - f(&c); + f(c); } template @@ -82,8 +94,8 @@ void editConfig(keel::Context &ctx, ox::CRStringView name, Func f) noexcept { oxAssert(name != "", "Config type has no TypeName"); auto [c, err] = readConfig(ctx, name); oxLogError(err); - f(&c); - oxLogError(writeConfig(ctx, name, &c)); + f(c); + oxLogError(writeConfig(ctx, name, c)); } template diff --git a/src/olympic/studio/modlib/src/task.cpp b/src/olympic/studio/modlib/src/task.cpp index b966ee3..9aa202a 100644 --- a/src/olympic/studio/modlib/src/task.cpp +++ b/src/olympic/studio/modlib/src/task.cpp @@ -10,6 +10,7 @@ namespace studio { void TaskRunner::update(turbine::Context &ctx) noexcept { std::ignore = m_tasks.erase(std::remove_if(m_tasks.begin(), m_tasks.end(), [&](ox::UPtr &t) { + if (!t) { return true; } auto const done = t->update(ctx) == TaskState::Done; if (done) { t->finished.emit(); diff --git a/src/olympic/turbine/include/turbine/turbine.hpp b/src/olympic/turbine/include/turbine/turbine.hpp index f12b18e..d24897b 100644 --- a/src/olympic/turbine/include/turbine/turbine.hpp +++ b/src/olympic/turbine/include/turbine/turbine.hpp @@ -14,6 +14,8 @@ namespace turbine { +using TimeMs = uint64_t; + ox::Result init(ox::UPtr &&fs, ox::CRStringView appName) noexcept; ox::Error run(Context &ctx) noexcept; @@ -21,7 +23,7 @@ ox::Error run(Context &ctx) noexcept; // Returns the number of milliseconds that have passed since the start of the // program. [[nodiscard]] -uint64_t ticksMs(Context const&ctx) noexcept; +TimeMs ticksMs(Context const&ctx) noexcept; void requestShutdown(Context &ctx) noexcept; diff --git a/src/olympic/turbine/src/gba/turbine.cpp b/src/olympic/turbine/src/gba/turbine.cpp index aed5fb5..96432a2 100644 --- a/src/olympic/turbine/src/gba/turbine.cpp +++ b/src/olympic/turbine/src/gba/turbine.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "context.hpp" #include "turbine.hpp" @@ -71,7 +72,7 @@ ox::Result init( void shutdown(Context&) noexcept { } -uint64_t ticksMs(Context const&) noexcept { +TimeMs ticksMs(Context const&) noexcept { return g_timerMs; } diff --git a/src/olympic/turbine/src/glfw/context.hpp b/src/olympic/turbine/src/glfw/context.hpp index 6a6cd1d..522e3c1 100644 --- a/src/olympic/turbine/src/glfw/context.hpp +++ b/src/olympic/turbine/src/glfw/context.hpp @@ -8,12 +8,13 @@ #include #include #include +#include namespace turbine { class Context { public: - UpdateHandler updateHandler = [](Context&) -> int {return 0;}; + UpdateHandler updateHandler = [](Context&) -> int {return -1;}; keel::Context keelCtx; KeyEventHandler keyEventHandler = nullptr; ox::AnyPtr applicationData; @@ -24,7 +25,7 @@ class Context { struct GLFWwindow *window = nullptr; int refreshWithinMs = 0; ox::Vector drawers; - int64_t startTime = 0; + TimeMs startTime = 0; uint64_t wakeupTime = 0; uint64_t keysDown = 0; uint64_t prevFpsCheckTime = 0; diff --git a/src/olympic/turbine/src/glfw/turbine.cpp b/src/olympic/turbine/src/glfw/turbine.cpp index 2865423..fb307e6 100644 --- a/src/olympic/turbine/src/glfw/turbine.cpp +++ b/src/olympic/turbine/src/glfw/turbine.cpp @@ -45,7 +45,8 @@ ox::Result init( auto ctx = ox::make_unique(); oxReturnError(keel::init(ctx->keelCtx, std::move(fs), appName)); using namespace std::chrono; - ctx->startTime = duration_cast(system_clock::now().time_since_epoch()).count(); + ctx->startTime = static_cast( + duration_cast(system_clock::now().time_since_epoch()).count()); glfwInit(); oxReturnError(initGfx(*ctx)); glfwSetWindowSizeCallback(ctx->window, draw); @@ -67,24 +68,26 @@ static void tickFps(Context &ctx, uint64_t nowMs) noexcept { } ox::Error run(Context &ctx) noexcept { - int sleepTime = 0; + uint64_t sleepTime = 0; while (!glfwWindowShouldClose(ctx.window)) { ctx.refreshWithinMs = 10 * 1000; // refresh within 10 seconds glfwPollEvents(); auto const ticks = ticksMs(ctx); if (ctx.wakeupTime <= ticks) { - sleepTime = ctx.updateHandler(ctx); - if (sleepTime >= 0) { - ctx.wakeupTime = ticks + static_cast(sleepTime); + auto const st = ctx.updateHandler(ctx); + if (st >= 0) { + ctx.wakeupTime = ticks + static_cast(st); + sleepTime = static_cast(st); } else { ctx.wakeupTime = ~uint64_t(0); + sleepTime = ctx.wakeupTime - ticks; } } else { - sleepTime = static_cast(ctx.wakeupTime - ticks); + sleepTime = ctx.wakeupTime - ticks; } tickFps(ctx, ticks); draw(ctx); - auto const realSleepTime = ox::min(ctx.refreshWithinMs, sleepTime); + auto const realSleepTime = ox::min(static_cast(ctx.refreshWithinMs), sleepTime); if (realSleepTime) { if (ctx.uninterruptedRefreshes) { --ctx.uninterruptedRefreshes; @@ -108,10 +111,10 @@ void shutdown(Context &ctx) noexcept { } } -uint64_t ticksMs(Context const&ctx) noexcept { +TimeMs ticksMs(Context const&ctx) noexcept { using namespace std::chrono; auto const now = duration_cast(system_clock::now().time_since_epoch()).count(); - return static_cast(now - ctx.startTime); + return static_cast(now) - ctx.startTime; } bool buttonDown(Context const&ctx, Key key) noexcept {