[jasper/world] Add support for incremental updating of WorldStatic

This commit is contained in:
Gary Talent 2025-01-11 03:38:05 -06:00
parent cdd6ac9a4d
commit 2103a03a15
15 changed files with 195 additions and 339 deletions

View File

@ -1,54 +0,0 @@
/*
* Copyright 2023 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <turbine/context.hpp>
#include <nostalgia/core/gfx.hpp>
#include <jasper/world/worlddoc.hpp>
#include <jasper/world/worldobject.hpp>
namespace jasper::world {
class ObjectCache {
public:
struct Obj {
ObjectId id{};
uint16_t palBank{};
size_t tilesheetIdx{};
};
private:
struct ObjSet {
uint64_t setId{};
ox::Vector<Obj> objects;
};
ox::Vector<ox::FileAddress> m_tilesheets;
ox::Vector<ox::FileAddress> m_palettes;
size_t m_tileIdx{};
size_t m_palBank{};
ox::Vector<ObjSet> m_objSets;
ncore::TileSheetSet m_tilesheetSet;
public:
void clear() noexcept;
ox::Error indexSet(
keel::Context &kctx,
uint64_t setId,
WorldObjectSet const&objSet) noexcept;
[[nodiscard]]
ox::Optional<Obj> obj(uint64_t setId, ObjectId objId) const noexcept;
[[nodiscard]]
ncore::TileSheetSet const&tilesheetSet() const noexcept;
[[nodiscard]]
ox::Vector<ox::FileAddress> const&tilesheets() const noexcept;
[[nodiscard]]
ox::Vector<ox::FileAddress> const&palettes() const noexcept;
private:
void addTileSheetSetEntry(ox::FileAddress path, int32_t tiles) noexcept;
};
ox::Result<ObjectCache> buildObjCache(keel::Context &kctx, WorldDoc const&doc) noexcept;
}

View File

@ -166,7 +166,7 @@ void resize(WorldDoc &doc, ox::Size const&sz) noexcept;
}
template<>
struct std::hash<jasper::world::DocObjRef> {
struct ox::hash<jasper::world::DocObjRef> {
[[nodiscard]]
constexpr size_t operator()(jasper::world::DocObjRef const&v) const noexcept {
return static_cast<uint32_t>(v.worldObjectSetId)

View File

@ -10,8 +10,6 @@
#include <ox/std/types.hpp>
#include <ox/std/vector.hpp>
#include <jasper/world/objectcache.hpp>
#include "worlddoc.hpp"
namespace jasper::world {
@ -144,10 +142,6 @@ constexpr bool valid(WorldStatic const&ws) noexcept {
});
}
void loadTile(TileStatic &dst, TileDoc const&src) noexcept;
ox::Result<WorldStatic> loadWorldStatic(keel::Context &kctx, WorldDoc const&doc) noexcept;
[[nodiscard]]
constexpr size_t layers(WorldStatic const&ws) noexcept {
return ws.map.size();
@ -164,8 +158,46 @@ auto &tile(
}
[[nodiscard]]
auto &cbb(ox::CommonRefWith<WorldStatic> auto&ws, size_t lyr) noexcept {
auto &cbb(ox::CommonRefWith<WorldStatic> auto&ws, size_t const lyr) noexcept {
return ws.map[lyr].cbb;
}
class WorldStaticLoader {
private:
keel::Context &m_kctx;
WorldStatic &m_worldStatic;
WorldDoc const&m_doc;
struct CacheEntry {
uint8_t value{};
int refCnt{};
};
ox::SmallMap<DocObjRef, CacheEntry> m_cache;
ox::SmallMap<uint64_t, WorldObjectSet> m_objSets;
uint16_t m_cbbIt{4};
bool m_loaded = false;
public:
WorldStaticLoader(keel::Context &kctx, WorldStatic &worldStatic, WorldDoc const&doc);
ox::Error loadWorldStatic() noexcept;
ox::Error updateTile(
uint32_t layer,
uint32_t col,
uint32_t row,
TileDoc const&oldSrc) noexcept;
void loadTile(
TileStatic &dst,
TileDoc const&src) noexcept;
void reset();
private:
ox::Result<uint8_t> setupTileResrc(DocObjRef const&docObjRef) noexcept;
void deleteRefSet(uint8_t refIdx, DocObjRef const&oldObjRef) noexcept;
};
}

View File

@ -1,7 +1,6 @@
add_library(
JasperWorld
objectcache.cpp
world.cpp
worlddoc.cpp
worldobject.cpp

View File

@ -5,8 +5,6 @@
#include <nostalgia/core/gfx.hpp>
#include <keel/media.hpp>
#include <jasper/world/objectcache.hpp>
#include "typeconv.hpp"
namespace jasper::world {
@ -15,7 +13,7 @@ ox::Error WorldDocToWorldStaticConverter::convert(
keel::Context &kctx,
WorldDoc &src,
WorldStatic &dst) const noexcept {
return loadWorldStatic(kctx, src).moveTo(dst);
return WorldStaticLoader{kctx, dst, src}.loadWorldStatic();
}
}

View File

@ -1,98 +0,0 @@
/*
* Copyright 2023 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <keel/media.hpp>
#include <nostalgia/core/gfx.hpp>
#include <jasper/world/objectcache.hpp>
namespace jasper::world {
void ObjectCache::clear() noexcept {
m_tileIdx = 0;
m_palBank = 0;
m_tilesheets.clear();
m_palettes.clear();
m_objSets.clear();
}
ox::Error ObjectCache::indexSet(
keel::Context &kctx,
uint64_t const setId,
WorldObjectSet const&objSet) noexcept {
auto &set = m_objSets.emplace_back(ObjSet{
.setId = setId,
.objects = {},
});
OX_REQUIRE(ts, readObj<ncore::TileSheet>(kctx, objSet.tilesheet));
auto tsId = ox::find(m_tilesheets.begin(), m_tilesheets.end(), objSet.tilesheet);
if (tsId == m_tilesheets.end()) {
m_tilesheets.emplace_back(objSet.tilesheet);
}
for (auto const&obj : objSet.objects) {
set.objects.emplace_back(Obj{
.id = obj.id,
.palBank = static_cast<uint16_t>(obj.palBank + m_palBank),
.tilesheetIdx = getTileIdx(*ts, obj.subsheetId).or_value(0) + m_tileIdx,
});
}
auto const tileCnt = getTileCnt(*ts);
m_tileIdx += tileCnt;
m_tilesheetSet.bpp = 4;
addTileSheetSetEntry(objSet.tilesheet, static_cast<int32_t>(tileCnt));
for (auto const&pal : objSet.palettes) {
m_palettes.emplace_back(pal);
OX_REQUIRE(p, readObj<ncore::Palette>(kctx, pal));
m_palBank += largestPage(*p);
}
return {};
}
ox::Optional<ObjectCache::Obj> ObjectCache::obj(uint64_t setId, ObjectId objId) const noexcept {
for (auto &set : m_objSets) {
if (set.setId == setId) {
for (auto &obj : set.objects) {
if (obj.id == objId) {
return ox::Optional<ObjectCache::Obj>{ox::in_place, obj};
}
}
break;
}
}
return {};
}
ncore::TileSheetSet const&ObjectCache::tilesheetSet() const noexcept {
return m_tilesheetSet;
}
ox::Vector<ox::FileAddress> const&ObjectCache::tilesheets() const noexcept {
return m_tilesheets;
}
ox::Vector<ox::FileAddress> const&ObjectCache::palettes() const noexcept {
return m_palettes;
}
void ObjectCache::addTileSheetSetEntry(ox::FileAddress path, int32_t tiles) noexcept {
ncore::TileSheetSetEntry entry;
entry.tilesheet = std::move(path);
entry.sections.push_back({
.begin = 0,
.tiles = tiles,
});
m_tilesheetSet.entries.emplace_back(std::move(entry));
}
ox::Result<ObjectCache> buildObjCache(keel::Context &kctx, WorldDoc const&doc) noexcept {
ObjectCache cache;
for (auto &set : doc.objSets) {
OX_REQUIRE(s, readObj<WorldObjectSet>(kctx, set.path));
OX_RETURN_ERROR(cache.indexSet(kctx, set.id, *s));
}
return cache;
}
}

View File

@ -10,12 +10,12 @@
namespace jasper::world {
ModifyTilesCommand::ModifyTilesCommand(
WorldStaticLoader &loader,
WorldDoc &doc,
WorldStatic &worldStatic,
ox::Vector<Mod> mods):
m_doc(doc),
m_worldStatic(worldStatic),
m_mods(std::move(mods)) {
m_loader{loader},
m_doc{doc},
m_mods{std::move(mods)} {
auto const unchanged = [this](Mod const&mod) {
auto const&docTile = tile(m_doc, mod.layer, mod.tileAddr.x, mod.tileAddr.y);
return docTile.obj.worldObjectId == mod.objId && docTile.obj.worldObjectSetId == mod.setId;
@ -26,27 +26,30 @@ ModifyTilesCommand::ModifyTilesCommand(
}
ox::Error ModifyTilesCommand::redo() noexcept {
swap();
return {};
return swap();
}
ox::Error ModifyTilesCommand::undo() noexcept {
swap();
return {};
return swap();
}
int ModifyTilesCommand::commandId() const noexcept {
return static_cast<int>(WorldEditorCommand::ModifyTiles);
}
void ModifyTilesCommand::swap() noexcept {
ox::Error ModifyTilesCommand::swap() noexcept {
for (auto &mod : m_mods) {
auto &docTile = tile(m_doc, mod.layer, mod.tileAddr.x, mod.tileAddr.y);
auto &activeTile = tile(m_worldStatic, mod.layer, mod.tileAddr.x, mod.tileAddr.y);
auto const oldDocTileVal = docTile;
std::swap(docTile.obj.worldObjectId, mod.objId);
std::swap(docTile.obj.worldObjectSetId, mod.setId);
loadTile(activeTile, docTile);
OX_RETURN_ERROR(m_loader.updateTile(
mod.layer,
static_cast<uint32_t>(mod.tileAddr.x),
static_cast<uint32_t>(mod.tileAddr.y),
oldDocTileVal));
}
return {};
}
}

View File

@ -19,20 +19,20 @@ class ModifyTilesCommand: public studio::UndoCommand {
uint64_t setId{};
};
private:
WorldStaticLoader &m_loader;
WorldDoc &m_doc;
WorldStatic &m_worldStatic;
ox::Vector<Mod> m_mods;
public:
ModifyTilesCommand(
WorldStaticLoader &loader,
WorldDoc &doc,
WorldStatic &worldStatic,
ox::Vector<Mod> mods);
ox::Error redo() noexcept override;
ox::Error undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
private:
void swap() noexcept;
ox::Error swap() noexcept;
};
}

View File

@ -48,11 +48,6 @@ OX_MODEL_BEGIN(WorldTileDragDrop)
OX_MODEL_END()
static WorldDoc makeValid(WorldDoc doc) noexcept {
std::ignore = repair(doc);
return doc;
}
[[nodiscard]]
static ox::Vec2 fbPos(ox::Vec2 fbPos) noexcept {
auto const winPos = ImGui::GetWindowPos();
@ -79,11 +74,9 @@ constexpr ox::Point fbPtToTileAddr(
WorldEditorImGui::WorldEditorImGui(studio::StudioContext &sctx, ox::StringParam path):
Editor(std::move(path)),
m_sctx(sctx),
m_doc(makeValid(*readObj<WorldDoc>(keelCtx(m_sctx), itemPath()).unwrapThrow())),
m_objCache(buildObjCache(keelCtx(m_sctx), m_doc).unwrapThrow()),
m_worldStatic(loadWorldStatic(keelCtx(m_sctx), m_doc)),
m_view(m_sctx, m_worldStatic) {
m_sctx{sctx},
m_doc{*keel::readObj<WorldDoc>(keelCtx(m_sctx), itemPath()).unwrapThrow()} {
OX_THROW_ERROR(m_loader.loadWorldStatic());
OX_THROW_ERROR(loadObjectSets());
m_objSetPicker.filePicked.connect(this, &WorldEditorImGui::addObjSet);
m_sctx.project->fileUpdated.connect(this, &WorldEditorImGui::handleDepUpdate);
@ -148,8 +141,8 @@ void WorldEditorImGui::cut() noexcept {
});
});
std::ignore = pushCommand<ModifyTilesCommand>(
m_loader,
m_doc,
m_worldStatic,
std::move(mods));
}
}
@ -172,8 +165,8 @@ void WorldEditorImGui::paste() {
});
}
std::ignore = pushCommand<ModifyTilesCommand>(
m_loader,
m_doc,
m_worldStatic,
std::move(mods));
}
}
@ -331,7 +324,7 @@ void WorldEditorImGui::drawWorldView() noexcept {
}
}
void WorldEditorImGui::handleMouseSelection(float fbPaneScale) noexcept {
void WorldEditorImGui::handleMouseSelection(float const fbPaneScale) noexcept {
auto const&io = ImGui::GetIO();
if (io.MouseDown[0]) {
auto const fbPos = world::fbPos(ox::Vec2{io.MousePos});
@ -350,7 +343,7 @@ void WorldEditorImGui::handleMouseSelection(float fbPaneScale) noexcept {
}
}
ox::Error WorldEditorImGui::handleDrop(float fbPaneScale) noexcept {
ox::Error WorldEditorImGui::handleDrop(float const fbPaneScale) noexcept {
OX_REQUIRE(objId, ig::getDragDropPayload<WorldTileDragDrop>("WorldTile"));
auto const&io = ImGui::GetIO();
auto const fbPos = world::fbPos(ox::Vec2{io.MousePos});
@ -382,15 +375,15 @@ ox::Error WorldEditorImGui::handleDrop(float fbPaneScale) noexcept {
};
}
std::ignore = pushCommand<ModifyTilesCommand>(
m_loader,
m_doc,
m_worldStatic,
std::move(mods));
}
OX_RETURN_ERROR(loadWorldStatic(keelCtx(m_sctx), m_doc).moveTo(m_worldStatic));
OX_RETURN_ERROR(m_loader.loadWorldStatic());
return {};
}
ox::Error WorldEditorImGui::addObjSet(ox::StringView path) noexcept {
ox::Error WorldEditorImGui::addObjSet(ox::StringViewCR path) noexcept {
OX_REQUIRE(uuid, getUuid(keelCtx(m_sctx), path));
std::ignore = pushCommand<AddObjectSet>(m_doc, uuid);
return {};
@ -400,7 +393,7 @@ void WorldEditorImGui::rmObjSet() noexcept {
std::ignore = pushCommand<RmObjectSet>(m_doc, m_palMgr.selectedIdx);
}
ox::Error WorldEditorImGui::handleDepUpdate(ox::StringView, ox::UUID const&uuid) noexcept {
ox::Error WorldEditorImGui::handleDepUpdate(ox::StringViewCR, ox::UUID const&uuid) noexcept {
auto &kctx = keelCtx(m_sctx);
auto const objSetMatches = [&uuid, &kctx](ObjectSetEntry const&set) {
auto const [setUuid, err] = getUuid(kctx, set.path);
@ -410,10 +403,10 @@ ox::Error WorldEditorImGui::handleDepUpdate(ox::StringView, ox::UUID const&uuid)
return set.key == uuid;
};
auto const depUpdated = ox::any_of(m_doc.objSets.begin(), m_doc.objSets.end(), objSetMatches)
|| ox::any_of(m_dependencies.pairs().begin(), m_dependencies.pairs().end(), depMatches);
|| ox::any_of(m_dependencies.pairs().begin(), m_dependencies.pairs().end(), depMatches);
if (depUpdated) {
OX_RETURN_ERROR(buildObjCache(kctx, m_doc).moveTo(m_objCache));
OX_RETURN_ERROR(loadWorldStatic(keelCtx(m_sctx), m_doc).moveTo(m_worldStatic));
m_loader.reset();
OX_RETURN_ERROR(m_loader.loadWorldStatic());
OX_RETURN_ERROR(loadObjectSets());
}
return {};
@ -435,10 +428,11 @@ ox::Error WorldEditorImGui::loadObjectSets() noexcept {
}
ox::Error WorldEditorImGui::undoStackChanged(studio::UndoCommand const*cmd) {
OX_RETURN_ERROR(m_view.setupWorld());
if (dynamic_cast<EditWorldSizeCommand const*>(cmd)) {
OX_RETURN_ERROR(loadWorldStatic(keelCtx(m_sctx), m_doc).moveTo(m_worldStatic));
m_loader.reset();
OX_RETURN_ERROR(m_loader.loadWorldStatic());
}
OX_RETURN_ERROR(m_view.setupWorld());
return {};
}

View File

@ -9,7 +9,6 @@
#include <studio/studio.hpp>
#include <jasper/world/consts.hpp>
#include <jasper/world/objectcache.hpp>
#include "worldeditorview.hpp"
@ -24,7 +23,6 @@ class WorldEditorImGui: public studio::Editor {
studio::StudioContext &m_sctx;
studio::ig::FilePicker m_objSetPicker{m_sctx, "Choose Object Set", FileExt_jwob};
WorldDoc m_doc;
ObjectCache m_objCache;
struct ObjSetRef {
ox::String name;
uint64_t id{};
@ -38,7 +36,8 @@ class WorldEditorImGui: public studio::Editor {
ox::Vector<ObjSetRef> m_objSets;
ox::SmallMap<ox::UUID, bool> m_dependencies;
WorldStatic m_worldStatic;
WorldEditorView m_view;
WorldStaticLoader m_loader{keelCtx(m_sctx), m_worldStatic, m_doc};
WorldEditorView m_view{m_sctx, m_worldStatic};
struct {
size_t selectedIdx{};
} m_palMgr;
@ -83,12 +82,12 @@ class WorldEditorImGui: public studio::Editor {
ox::Error handleDrop(float fbPaneScale) noexcept;
ox::Error addObjSet(ox::StringView path) noexcept;
ox::Error addObjSet(ox::StringViewCR path) noexcept;
void rmObjSet() noexcept;
// handles the updating of an object set in case it is one used by this world
ox::Error handleDepUpdate(ox::StringView path, ox::UUID const&) noexcept;
ox::Error handleDepUpdate(ox::StringViewCR path, ox::UUID const&) noexcept;
ox::Error loadObjectSets() noexcept;

View File

@ -1,20 +0,0 @@
/*
* Copyright 2023 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <keel/keel.hpp>
#include "worldeditor.hpp"
namespace jasper::world {
WorldEditor::WorldEditor(studio::StudioContext &ctx, ox::StringView path):
m_ctx(ctx),
m_world(*readObj<WorldStatic>(keelCtx(m_ctx.tctx), path).unwrapThrow()) {
}
ox::Error WorldEditor::saveItem() noexcept {
return m_ctx.project->writeObj(m_itemPath, m_world);
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2023 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <turbine/context.hpp>
#include <studio/context.hpp>
#include <jasper/world/world.hpp>
namespace jasper::world {
class WorldEditor {
private:
studio::StudioContext &m_ctx;
ox::String m_itemPath;
WorldStatic m_world;
public:
WorldEditor(studio::StudioContext &ctx, ox::StringView path);
[[nodiscard]]
WorldStatic const&world() const noexcept {
return m_world;
}
protected:
ox::Error saveItem() noexcept;
};
}

View File

@ -83,10 +83,10 @@ void WorldEditorView::setPixelBufferObject(
auto const selection = 1.f * static_cast<float>(selected);
// don't worry, these memcpys gets optimized to something much more ideal
std::array const vertices{
x, y, selection, // bottom left
x, y, selection, // bottom left
x + xmod, y, selection, // bottom right
x + xmod, y + ymod, selection, // top right
x, y + ymod, selection, // top left
x, y + ymod, selection, // top left
};
memcpy(vbo, vertices.data(), sizeof(vertices));
ox::Array<GLuint, 6> const elms{

View File

@ -15,6 +15,9 @@ World::World(ncore::Context &nctx, WorldStatic const&worldStatic) noexcept:
m_worldStatic(worldStatic) {}
ox::Error World::setupDisplay() noexcept {
if (m_worldStatic.objTileRefSets.empty()) {
return ox::Error{1, "WorldStatic must have at least one ref set"};
}
auto &kctx = keelCtx(m_nctx);
if (m_tilesheets.empty()) {
for (auto const&tsAddr : m_worldStatic.tilesheets) {
@ -23,11 +26,10 @@ ox::Error World::setupDisplay() noexcept {
}
}
if (m_worldStatic.palettes.empty()) {
return ox::Error(1, "World has no palettes");
return ox::Error{1, "World has no palettes"};
}
for (auto i = 0u; auto const&palAddr : m_worldStatic.palettes) {
OX_REQUIRE(pal, readObj<ncore::CompactPalette>(keelCtx(m_nctx), palAddr));
OX_RETURN_ERROR(ncore::loadBgPalette(m_nctx, i, *pal));
OX_RETURN_ERROR(ncore::loadBgPalette(m_nctx, i, palAddr));
++i;
}
for (auto const&rs : m_worldStatic.objTileRefSets) {

View File

@ -11,40 +11,106 @@
namespace jasper::world {
static void addToVec(ox::Vector<ox::FileAddress> &vec, ox::FileAddress const&val) {
inline void ensureInVec(ox::Vector<ox::FileAddress> &vec, ox::FileAddress const&val) {
if (!vec.contains(val)) {
vec.emplace_back(val);
}
}
void loadTile(
TileStatic &dst,
TileDoc const&src) noexcept {
WorldStaticLoader::WorldStaticLoader(keel::Context &kctx, WorldStatic &worldStatic, WorldDoc const&doc):
m_kctx{kctx},
m_worldStatic{worldStatic},
m_doc{doc} {}
ox::Error WorldStaticLoader::loadWorldStatic() noexcept {
reset();
auto const tilesPerLayer =
static_cast<size_t>(m_doc.columns) * static_cast<size_t>(m_doc.rows);
for (auto const&setRef : m_doc.objSets) {
auto const [set, err] =
keel::readObj<WorldObjectSet>(m_kctx, setRef.path);
if (!err) {
m_objSets[setRef.id] = *set;
ensureInVec(m_worldStatic.tilesheets, set->tilesheet);
for (auto const&pal : set->palettes) {
ensureInVec(m_worldStatic.palettes, pal);
}
}
}
m_worldStatic.columns = static_cast<int16_t>(m_doc.columns),
m_worldStatic.rows = static_cast<int16_t>(m_doc.rows),
m_worldStatic.map = {
{.tiles = ox::Vector<TileStatic>{tilesPerLayer},},
{.tiles = ox::Vector<TileStatic>{tilesPerLayer},},
{.tiles = ox::Vector<TileStatic>{tilesPerLayer},},
};
// tiles
for (auto lyr = 0u; lyr < layers(m_worldStatic); ++lyr) {
for (auto x = 0u; x < static_cast<size_t>(m_worldStatic.columns); ++x) {
for (auto y = 0u; y < static_cast<size_t>(m_worldStatic.rows); ++y) {
auto &dst = tile(m_worldStatic, lyr, x, y);
auto &src = tile(m_doc, lyr, x, y);;
loadTile(dst, src);
}
}
}
m_loaded = true;
return {};
}
ox::Error WorldStaticLoader::updateTile(
uint32_t const layer,
uint32_t const col,
uint32_t const row,
TileDoc const&oldSrc) noexcept {
auto &dst = tile(m_worldStatic, layer, col, row);
auto &newSrc = tile(m_doc, layer, col, row);
if (oldSrc.obj.unique) {
deleteRefSet(dst.objIdxRefSet, oldSrc.obj);
}
auto const current = m_cache.at(oldSrc.obj);
if (current.ok()) {
--current.value->refCnt;
if (current.value->refCnt == 0) {
auto const rs = dst.objIdxRefSet;
dst.objIdxRefSet = 0;
deleteRefSet(rs, oldSrc.obj);
}
}
loadTile(dst, newSrc);
return {};
}
void WorldStaticLoader::loadTile(TileStatic &dst, TileDoc const&src) noexcept {
dst.tileType = src.type;
setTopEdge(dst.layerAttachments, src.topLayerAttachment);
setBottomEdge(dst.layerAttachments, src.bottomLayerAttachment);
setLeftEdge(dst.layerAttachments, src.leftLayerAttachment);
setRightEdge(dst.layerAttachments, src.rightLayerAttachment);
dst.objIdxRefSet = setupTileResrc(src.obj).or_value(0);;
}
void WorldStaticLoader::reset() {
m_worldStatic = {};
m_cache.clear();
m_objSets.clear();
m_cbbIt = 4;
m_loaded = false;
}
/**
* Returns the index in rsrcs for the given docObjRef.
*/
static ox::Result<uint8_t> setupTileResrc(
ox::SmallMap<DocObjRef, uint8_t> &cache,
keel::Context &kctx,
ox::Vector<ox::FileAddress> const&tilesheets,
ox::SmallMap<uint64_t, WorldObjectSet> const&objSets,
ox::Vector<ObjTileRefSet> &rsrcs,
uint16_t &cbbIt,
DocObjRef const&docObjRef) noexcept {
ox::Result<uint8_t> WorldStaticLoader::setupTileResrc(DocObjRef const&docObjRef) noexcept {
if (!docObjRef.unique) {
auto const [out, err] = cache.at(docObjRef);
auto const [out, err] = m_cache.at(docObjRef);
if (!err) {
return *out;
++out->refCnt;
return out->value;
}
}
OX_REQUIRE(objSet, objSets.at(docObjRef.worldObjectSetId));
OX_REQUIRE(objSet, m_objSets.at(docObjRef.worldObjectSetId));
auto const obj = ox::find_if(
objSet->objects.begin(), objSet->objects.end(),
[&docObjRef](WorldObject const&o) {
@ -53,9 +119,9 @@ static ox::Result<uint8_t> setupTileResrc(
if (obj == objSet->objects.end()) {
return ox::Error{obj == objSet->objects.end(), "could not find WorldObject in WorldObjectSet"};
}
OX_REQUIRE(ts, keel::readObj<ncore::TileSheet>(kctx, objSet->tilesheet));
OX_REQUIRE(ts, keel::readObj<ncore::TileSheet>(m_kctx, objSet->tilesheet));
OX_REQUIRE(tsIdx, ox::findIdx(
tilesheets.begin(), tilesheets.end(), objSet->tilesheet).to<uint8_t>());
m_worldStatic.tilesheets.begin(), m_worldStatic.tilesheets.end(), objSet->tilesheet).to<uint8_t>());
auto const subsheetOffset = ncore::getTileIdx(*ts, obj->subsheetId);
if (!subsheetOffset) {
return ox::Error{1, "invalid subsheet idx"};
@ -64,77 +130,46 @@ static ox::Result<uint8_t> setupTileResrc(
if (!subsheet) {
return ox::Error{1, "could not find subsheet"};
}
auto const out = static_cast<uint8_t>(rsrcs.size());
auto const&refSet = rsrcs.emplace_back(ObjTileRefSet{
auto const out = static_cast<uint8_t>(m_worldStatic.objTileRefSets.size());
auto const&refSet = m_worldStatic.objTileRefSets.emplace_back(ObjTileRefSet{
.palBank = obj->palBank,
.tilesheetIdx = static_cast<uint16_t>(*subsheetOffset),
.cbbIdx = cbbIt,
.cbbIdx = m_cbbIt,
.tilesheetId = tsIdx,
.tileCnt = static_cast<uint8_t>(subsheet->size()),
});
cbbIt += refSet.tileCnt;
m_cbbIt += refSet.tileCnt;
if (!docObjRef.unique) {
cache[docObjRef] = out;
m_cache[docObjRef] = {.value = out, .refCnt = 1};
}
return out;
}
ox::Result<WorldStatic> loadWorldStatic(
keel::Context &kctx,
WorldDoc const&doc) noexcept {
auto const tilesPerLayer =
static_cast<size_t>(doc.columns) * static_cast<size_t>(doc.rows);
ox::Vector<ox::FileAddress> tilesheets;
ox::Vector<ox::FileAddress> palettes;
ox::SmallMap<uint64_t, WorldObjectSet> objSets;
for (auto const&setRef : doc.objSets) {
auto const [set, err] =
keel::readObj<WorldObjectSet>(kctx, setRef.path);
if (!err) {
objSets[setRef.id] = *set;
addToVec(tilesheets, set->tilesheet);
for (auto const&pal : set->palettes) {
addToVec(palettes, pal);
}
}
}
ox::Result<WorldStatic> result = WorldStatic{
.objTileRefSets = {},
.tilesheets = tilesheets,
.palettes = palettes,
.columns = static_cast<int16_t>(doc.columns),
.rows = static_cast<int16_t>(doc.rows),
.map = {
{.tiles = ox::Vector<TileStatic>{tilesPerLayer},},
{.tiles = ox::Vector<TileStatic>{tilesPerLayer},},
{.tiles = ox::Vector<TileStatic>{tilesPerLayer},},
},
};
auto &out = result.value;
ox::SmallMap<DocObjRef, uint8_t> refSetCache;
uint16_t cbbIt = 4;
// tiles
for (auto lyr = 0u; lyr < layers(out); ++lyr) {
for (auto x = 0u; x < static_cast<size_t>(out.columns); ++x) {
for (auto y = 0u; y < static_cast<size_t>(out.rows); ++y) {
auto &dst = tile(out, lyr, x, y);
auto &src = tile(doc, lyr, x, y);;
loadTile(dst, src);
auto const refSetIdx = setupTileResrc(
refSetCache,
kctx,
out.tilesheets,
objSets,
out.objTileRefSets,
cbbIt,
src.obj);
if (refSetIdx.ok()) {
dst.objIdxRefSet = refSetIdx.value;
void WorldStaticLoader::deleteRefSet(uint8_t const refIdx, DocObjRef const&oldObjRef) noexcept {
for (auto lyr = 0u; lyr < layers(m_worldStatic); ++lyr) {
for (auto x = 0u; x < static_cast<size_t>(m_worldStatic.columns); ++x) {
for (auto y = 0u; y < static_cast<size_t>(m_worldStatic.rows); ++y) {
auto &dst = tile(m_worldStatic, lyr, x, y);
auto oldVal = dst.objIdxRefSet;
if (dst.objIdxRefSet > refIdx) {
--dst.objIdxRefSet;
}
oxAssert(dst.objIdxRefSet < m_worldStatic.objTileRefSets.size() - 1, "Invalid objIdxRefSet");
}
}
}
return result;
for (auto &p : m_cache.pairs()) {
if (p.value.value > refIdx) {
--p.value.value;
}
}
auto const&removedRefSet = m_worldStatic.objTileRefSets[refIdx];
for (size_t i = refIdx + 1; i < m_worldStatic.objTileRefSets.size(); ++i) {
auto &refSet = m_worldStatic.objTileRefSets[i];
refSet.cbbIdx -= removedRefSet.frames;
}
std::ignore = m_worldStatic.objTileRefSets.erase(refIdx);
m_cache.erase(oldObjRef);
}
}