[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<> template<>
struct std::hash<jasper::world::DocObjRef> { struct ox::hash<jasper::world::DocObjRef> {
[[nodiscard]] [[nodiscard]]
constexpr size_t operator()(jasper::world::DocObjRef const&v) const noexcept { constexpr size_t operator()(jasper::world::DocObjRef const&v) const noexcept {
return static_cast<uint32_t>(v.worldObjectSetId) return static_cast<uint32_t>(v.worldObjectSetId)

View File

@ -10,8 +10,6 @@
#include <ox/std/types.hpp> #include <ox/std/types.hpp>
#include <ox/std/vector.hpp> #include <ox/std/vector.hpp>
#include <jasper/world/objectcache.hpp>
#include "worlddoc.hpp" #include "worlddoc.hpp"
namespace jasper::world { 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]] [[nodiscard]]
constexpr size_t layers(WorldStatic const&ws) noexcept { constexpr size_t layers(WorldStatic const&ws) noexcept {
return ws.map.size(); return ws.map.size();
@ -164,8 +158,46 @@ auto &tile(
} }
[[nodiscard]] [[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; 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( add_library(
JasperWorld JasperWorld
objectcache.cpp
world.cpp world.cpp
worlddoc.cpp worlddoc.cpp
worldobject.cpp worldobject.cpp

View File

@ -5,8 +5,6 @@
#include <nostalgia/core/gfx.hpp> #include <nostalgia/core/gfx.hpp>
#include <keel/media.hpp> #include <keel/media.hpp>
#include <jasper/world/objectcache.hpp>
#include "typeconv.hpp" #include "typeconv.hpp"
namespace jasper::world { namespace jasper::world {
@ -15,7 +13,7 @@ ox::Error WorldDocToWorldStaticConverter::convert(
keel::Context &kctx, keel::Context &kctx,
WorldDoc &src, WorldDoc &src,
WorldStatic &dst) const noexcept { 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 { namespace jasper::world {
ModifyTilesCommand::ModifyTilesCommand( ModifyTilesCommand::ModifyTilesCommand(
WorldStaticLoader &loader,
WorldDoc &doc, WorldDoc &doc,
WorldStatic &worldStatic,
ox::Vector<Mod> mods): ox::Vector<Mod> mods):
m_doc(doc), m_loader{loader},
m_worldStatic(worldStatic), m_doc{doc},
m_mods(std::move(mods)) { m_mods{std::move(mods)} {
auto const unchanged = [this](Mod const&mod) { auto const unchanged = [this](Mod const&mod) {
auto const&docTile = tile(m_doc, mod.layer, mod.tileAddr.x, mod.tileAddr.y); 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; return docTile.obj.worldObjectId == mod.objId && docTile.obj.worldObjectSetId == mod.setId;
@ -26,27 +26,30 @@ ModifyTilesCommand::ModifyTilesCommand(
} }
ox::Error ModifyTilesCommand::redo() noexcept { ox::Error ModifyTilesCommand::redo() noexcept {
swap(); return swap();
return {};
} }
ox::Error ModifyTilesCommand::undo() noexcept { ox::Error ModifyTilesCommand::undo() noexcept {
swap(); return swap();
return {};
} }
int ModifyTilesCommand::commandId() const noexcept { int ModifyTilesCommand::commandId() const noexcept {
return static_cast<int>(WorldEditorCommand::ModifyTiles); return static_cast<int>(WorldEditorCommand::ModifyTiles);
} }
void ModifyTilesCommand::swap() noexcept { ox::Error ModifyTilesCommand::swap() noexcept {
for (auto &mod : m_mods) { for (auto &mod : m_mods) {
auto &docTile = tile(m_doc, mod.layer, mod.tileAddr.x, mod.tileAddr.y); 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.worldObjectId, mod.objId);
std::swap(docTile.obj.worldObjectSetId, mod.setId); 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{}; uint64_t setId{};
}; };
private: private:
WorldStaticLoader &m_loader;
WorldDoc &m_doc; WorldDoc &m_doc;
WorldStatic &m_worldStatic;
ox::Vector<Mod> m_mods; ox::Vector<Mod> m_mods;
public: public:
ModifyTilesCommand( ModifyTilesCommand(
WorldStaticLoader &loader,
WorldDoc &doc, WorldDoc &doc,
WorldStatic &worldStatic,
ox::Vector<Mod> mods); ox::Vector<Mod> mods);
ox::Error redo() noexcept override; ox::Error redo() noexcept override;
ox::Error undo() noexcept override; ox::Error undo() noexcept override;
[[nodiscard]] [[nodiscard]]
int commandId() const noexcept override; int commandId() const noexcept override;
private: private:
void swap() noexcept; ox::Error swap() noexcept;
}; };
} }

View File

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

View File

@ -9,7 +9,6 @@
#include <studio/studio.hpp> #include <studio/studio.hpp>
#include <jasper/world/consts.hpp> #include <jasper/world/consts.hpp>
#include <jasper/world/objectcache.hpp>
#include "worldeditorview.hpp" #include "worldeditorview.hpp"
@ -24,7 +23,6 @@ class WorldEditorImGui: public studio::Editor {
studio::StudioContext &m_sctx; studio::StudioContext &m_sctx;
studio::ig::FilePicker m_objSetPicker{m_sctx, "Choose Object Set", FileExt_jwob}; studio::ig::FilePicker m_objSetPicker{m_sctx, "Choose Object Set", FileExt_jwob};
WorldDoc m_doc; WorldDoc m_doc;
ObjectCache m_objCache;
struct ObjSetRef { struct ObjSetRef {
ox::String name; ox::String name;
uint64_t id{}; uint64_t id{};
@ -38,7 +36,8 @@ class WorldEditorImGui: public studio::Editor {
ox::Vector<ObjSetRef> m_objSets; ox::Vector<ObjSetRef> m_objSets;
ox::SmallMap<ox::UUID, bool> m_dependencies; ox::SmallMap<ox::UUID, bool> m_dependencies;
WorldStatic m_worldStatic; WorldStatic m_worldStatic;
WorldEditorView m_view; WorldStaticLoader m_loader{keelCtx(m_sctx), m_worldStatic, m_doc};
WorldEditorView m_view{m_sctx, m_worldStatic};
struct { struct {
size_t selectedIdx{}; size_t selectedIdx{};
} m_palMgr; } m_palMgr;
@ -83,12 +82,12 @@ class WorldEditorImGui: public studio::Editor {
ox::Error handleDrop(float fbPaneScale) noexcept; ox::Error handleDrop(float fbPaneScale) noexcept;
ox::Error addObjSet(ox::StringView path) noexcept; ox::Error addObjSet(ox::StringViewCR path) noexcept;
void rmObjSet() noexcept; void rmObjSet() noexcept;
// handles the updating of an object set in case it is one used by this world // 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; 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); auto const selection = 1.f * static_cast<float>(selected);
// don't worry, these memcpys gets optimized to something much more ideal // don't worry, these memcpys gets optimized to something much more ideal
std::array const vertices{ std::array const vertices{
x, y, selection, // bottom left x, y, selection, // bottom left
x + xmod, y, selection, // bottom right x + xmod, y, selection, // bottom right
x + xmod, y + ymod, selection, // top 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)); memcpy(vbo, vertices.data(), sizeof(vertices));
ox::Array<GLuint, 6> const elms{ ox::Array<GLuint, 6> const elms{

View File

@ -15,6 +15,9 @@ World::World(ncore::Context &nctx, WorldStatic const&worldStatic) noexcept:
m_worldStatic(worldStatic) {} m_worldStatic(worldStatic) {}
ox::Error World::setupDisplay() noexcept { 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); auto &kctx = keelCtx(m_nctx);
if (m_tilesheets.empty()) { if (m_tilesheets.empty()) {
for (auto const&tsAddr : m_worldStatic.tilesheets) { for (auto const&tsAddr : m_worldStatic.tilesheets) {
@ -23,11 +26,10 @@ ox::Error World::setupDisplay() noexcept {
} }
} }
if (m_worldStatic.palettes.empty()) { 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) { 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, palAddr));
OX_RETURN_ERROR(ncore::loadBgPalette(m_nctx, i, *pal));
++i; ++i;
} }
for (auto const&rs : m_worldStatic.objTileRefSets) { for (auto const&rs : m_worldStatic.objTileRefSets) {

View File

@ -11,40 +11,106 @@
namespace jasper::world { 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)) { if (!vec.contains(val)) {
vec.emplace_back(val); vec.emplace_back(val);
} }
} }
void loadTile(
TileStatic &dst, WorldStaticLoader::WorldStaticLoader(keel::Context &kctx, WorldStatic &worldStatic, WorldDoc const&doc):
TileDoc const&src) noexcept { 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; dst.tileType = src.type;
setTopEdge(dst.layerAttachments, src.topLayerAttachment); setTopEdge(dst.layerAttachments, src.topLayerAttachment);
setBottomEdge(dst.layerAttachments, src.bottomLayerAttachment); setBottomEdge(dst.layerAttachments, src.bottomLayerAttachment);
setLeftEdge(dst.layerAttachments, src.leftLayerAttachment); setLeftEdge(dst.layerAttachments, src.leftLayerAttachment);
setRightEdge(dst.layerAttachments, src.rightLayerAttachment); 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. * Returns the index in rsrcs for the given docObjRef.
*/ */
static ox::Result<uint8_t> setupTileResrc( ox::Result<uint8_t> WorldStaticLoader::setupTileResrc(DocObjRef const&docObjRef) noexcept {
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 {
if (!docObjRef.unique) { if (!docObjRef.unique) {
auto const [out, err] = cache.at(docObjRef); auto const [out, err] = m_cache.at(docObjRef);
if (!err) { 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( auto const obj = ox::find_if(
objSet->objects.begin(), objSet->objects.end(), objSet->objects.begin(), objSet->objects.end(),
[&docObjRef](WorldObject const&o) { [&docObjRef](WorldObject const&o) {
@ -53,9 +119,9 @@ static ox::Result<uint8_t> setupTileResrc(
if (obj == objSet->objects.end()) { if (obj == objSet->objects.end()) {
return ox::Error{obj == objSet->objects.end(), "could not find WorldObject in WorldObjectSet"}; 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( 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); auto const subsheetOffset = ncore::getTileIdx(*ts, obj->subsheetId);
if (!subsheetOffset) { if (!subsheetOffset) {
return ox::Error{1, "invalid subsheet idx"}; return ox::Error{1, "invalid subsheet idx"};
@ -64,77 +130,46 @@ static ox::Result<uint8_t> setupTileResrc(
if (!subsheet) { if (!subsheet) {
return ox::Error{1, "could not find subsheet"}; return ox::Error{1, "could not find subsheet"};
} }
auto const out = static_cast<uint8_t>(rsrcs.size()); auto const out = static_cast<uint8_t>(m_worldStatic.objTileRefSets.size());
auto const&refSet = rsrcs.emplace_back(ObjTileRefSet{ auto const&refSet = m_worldStatic.objTileRefSets.emplace_back(ObjTileRefSet{
.palBank = obj->palBank, .palBank = obj->palBank,
.tilesheetIdx = static_cast<uint16_t>(*subsheetOffset), .tilesheetIdx = static_cast<uint16_t>(*subsheetOffset),
.cbbIdx = cbbIt, .cbbIdx = m_cbbIt,
.tilesheetId = tsIdx, .tilesheetId = tsIdx,
.tileCnt = static_cast<uint8_t>(subsheet->size()), .tileCnt = static_cast<uint8_t>(subsheet->size()),
}); });
cbbIt += refSet.tileCnt; m_cbbIt += refSet.tileCnt;
if (!docObjRef.unique) { if (!docObjRef.unique) {
cache[docObjRef] = out; m_cache[docObjRef] = {.value = out, .refCnt = 1};
} }
return out; return out;
} }
ox::Result<WorldStatic> loadWorldStatic( void WorldStaticLoader::deleteRefSet(uint8_t const refIdx, DocObjRef const&oldObjRef) noexcept {
keel::Context &kctx, for (auto lyr = 0u; lyr < layers(m_worldStatic); ++lyr) {
WorldDoc const&doc) noexcept { for (auto x = 0u; x < static_cast<size_t>(m_worldStatic.columns); ++x) {
auto const tilesPerLayer = for (auto y = 0u; y < static_cast<size_t>(m_worldStatic.rows); ++y) {
static_cast<size_t>(doc.columns) * static_cast<size_t>(doc.rows); auto &dst = tile(m_worldStatic, lyr, x, y);
ox::Vector<ox::FileAddress> tilesheets; auto oldVal = dst.objIdxRefSet;
ox::Vector<ox::FileAddress> palettes; if (dst.objIdxRefSet > refIdx) {
ox::SmallMap<uint64_t, WorldObjectSet> objSets; --dst.objIdxRefSet;
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;
} }
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);
} }
} }