[jasper/world] Add support for incremental updating of WorldStatic
This commit is contained in:
parent
cdd6ac9a4d
commit
2103a03a15
@ -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;
|
||||
|
||||
}
|
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
|
||||
add_library(
|
||||
JasperWorld
|
||||
objectcache.cpp
|
||||
world.cpp
|
||||
worlddoc.cpp
|
||||
worldobject.cpp
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
@ -412,8 +405,8 @@ ox::Error WorldEditorImGui::handleDepUpdate(ox::StringView, ox::UUID const&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);
|
||||
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 {};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
std::ignore = m_worldStatic.objTileRefSets.erase(refIdx);
|
||||
m_cache.erase(oldObjRef);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user