From 27b38ed250e3cbd3b2539e771b3352dd2d87962b Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Mon, 27 May 2024 19:26:58 -0500 Subject: [PATCH] [keel,studio] Fix hotloading for files that get loaded as multiple types --- .../keel/include/keel/assetmanager.hpp | 104 ++++++++++++++---- src/olympic/keel/include/keel/media.hpp | 45 +++++--- src/olympic/keel/src/media.cpp | 19 ++++ .../studio/modlib/include/studio/project.hpp | 2 +- 4 files changed, 133 insertions(+), 37 deletions(-) diff --git a/src/olympic/keel/include/keel/assetmanager.hpp b/src/olympic/keel/include/keel/assetmanager.hpp index b13a131e..1820fcb0 100644 --- a/src/olympic/keel/include/keel/assetmanager.hpp +++ b/src/olympic/keel/include/keel/assetmanager.hpp @@ -4,6 +4,10 @@ #pragma once +#ifndef OX_BARE_METAL +#include +#endif + #include #include #include @@ -178,9 +182,11 @@ constexpr AssetRef::AssetRef(AssetRef &&h) noexcept: m_ctr(h.m_ctr) { h.m_ctr = nullptr; } +class Context; + class AssetManager { private: - class AssetTypeManagerBase { + class AssetTypeManagerBase: public ox::SignalHandler { public: virtual ~AssetTypeManagerBase() = default; @@ -189,17 +195,24 @@ class AssetManager { template class AssetTypeManager: public AssetTypeManagerBase { + public: + using Loader = std::function(ox::StringView assetId)>; private: + Loader m_loader{}; ox::HashMap>> m_cache; public: - ox::Result> getAsset(ox::StringView const&assetId) const noexcept { - auto out = m_cache.at(assetId); - oxReturnError(out); - return AssetRef(out.value->get()); + AssetTypeManager(Loader loader) noexcept: m_loader(loader) {} + + ox::Result> getAsset(ox::StringView const assetId) const noexcept { + oxRequire(out, m_cache.at(assetId)); + if (!out || !*out) { + return OxError(1, "asset is null"); + } + return AssetRef(out->get()); } - ox::Result> setAsset(ox::StringView const&assetId, T const&obj) noexcept { + ox::Result> setAsset(ox::StringView const assetId, T const&obj) noexcept { auto &p = m_cache[assetId]; if (!p) { p = ox::make_unique>(obj); @@ -210,7 +223,7 @@ class AssetManager { return AssetRef(p.get()); } - ox::Result> setAsset(ox::StringView const&assetId, T &&obj) noexcept { + ox::Result> setAsset(ox::StringView const assetId, T &&obj) noexcept { auto &p = m_cache[assetId]; if (!p) { p = ox::make_unique>(obj); @@ -221,6 +234,30 @@ class AssetManager { return AssetRef(p.get()); } + ox::Result> loadAsset(ox::StringView const assetId) noexcept { + auto &p = m_cache[assetId]; + oxRequireM(obj, m_loader(assetId)); + if (!p) { + p = ox::make_unique>(std::move(obj)); + } else { + p->set(std::move(obj)); + p->updated.emit(); + } + return AssetRef(p.get()); + } + + ox::Error reloadAsset(ox::StringView const assetId) noexcept { + auto &p = m_cache[assetId]; + oxRequireM(obj, m_loader(assetId)); + if (!p) { + p = ox::make_unique>(std::move(obj)); + } else { + p->set(std::move(obj)); + p->updated.emit(); + } + return {}; + } + void gc() noexcept final { for (auto const&ack : m_cache.keys()) { auto &ac = m_cache[ack]; @@ -232,29 +269,58 @@ class AssetManager { }; ox::HashMap> m_assetTypeManagers; + struct FileTracker { + ox::Signal updated; + }; + ox::HashMap m_fileTrackers; template - AssetTypeManager *getTypeManager() noexcept { - constexpr auto typeName = ox::requireModelTypeName(); - static_assert(ox::StringView(typeName) != "", "Types must have TypeName to use AssetManager"); - auto &am = m_assetTypeManagers[typeName]; - if (!am) { - am = ox::make_unique>(); + ox::Result*> getTypeManager() noexcept { + constexpr auto &typeId = ox::ModelTypeId_v; + static_assert(typeId != "", "Types must have TypeName to use AssetManager"); + auto &am = m_assetTypeManagers[typeId]; + auto const out = dynamic_cast*>(am.get()); + if (!out) { + return OxError(1, "no AssetTypeManager for type"); } - return dynamic_cast*>(am.get()); + return out; } public: template - ox::Result> getAsset(ox::CRStringView assetId) noexcept { - auto m = getTypeManager(); + void initTypeManager(auto const&makeLoader, Context &ctx) noexcept { + constexpr auto &typeId = ox::ModelTypeId_v; + static_assert(typeId != "", "Types must have TypeName to use AssetManager"); + auto &am = m_assetTypeManagers[typeId]; + if (!am) { + am = ox::make_unique>(makeLoader(ctx)); + } + } + + template + ox::Result> getAsset(ox::StringView assetId) noexcept { + oxRequire(m, getTypeManager()); return m->getAsset(assetId); } template - ox::Result> setAsset(ox::CRStringView assetId, T const&obj) noexcept { - auto m = getTypeManager(); - return m->setAsset(assetId, obj); + ox::Result> setAsset(ox::StringView assetId, T const&obj) noexcept { + oxRequire(m, getTypeManager()); + oxReturnError(m->setAsset(assetId, obj)); + return {}; + } + + ox::Error reloadAsset(ox::StringView assetId) noexcept { + m_fileTrackers[assetId].updated.emit(assetId); + return {}; + } + + template + ox::Result> loadAsset(ox::StringView assetId) noexcept { + oxRequire(m, getTypeManager()); + oxRequire(out, m->loadAsset(assetId)); + m_fileTrackers[assetId].updated.connect(m, &AssetTypeManager::reloadAsset); + return out; } void gc() noexcept { diff --git a/src/olympic/keel/include/keel/media.hpp b/src/olympic/keel/include/keel/media.hpp index d13f5acd..462933c2 100644 --- a/src/olympic/keel/include/keel/media.hpp +++ b/src/olympic/keel/include/keel/media.hpp @@ -61,13 +61,14 @@ ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept; #ifndef OX_BARE_METAL +namespace detail { template -ox::Result> readObjFile( - keel::Context &ctx, - ox::StringView assetId, - bool forceLoad) noexcept { - constexpr auto readConvert = [](Context &ctx, const ox::Buffer &buff) - -> ox::Result { +constexpr auto makeLoader(Context &ctx) { + return [&ctx](ox::StringView assetId) -> ox::Result { + ox::StringView path; + oxRequire(p, ctx.uuidToPath.at(assetId)); + path = *p; + oxRequire(buff, ctx.rom->read(path)); auto [obj, err] = readAsset(buff); if (err) { if (err != ox::Error_ClawTypeVersionMismatch && err != ox::Error_ClawTypeMismatch) { @@ -77,14 +78,19 @@ ox::Result> readObjFile( } return std::move(obj); }; - ox::StringView path; +}; +} + +template +ox::Result> readObjFile( + keel::Context &ctx, + ox::StringView assetId, + bool forceLoad) noexcept { ox::UUIDStr uuidStr; if (beginsWith(assetId, "uuid://")) { assetId = substr(assetId, 7); - oxRequire(p, ctx.uuidToPath.at(assetId)); - path = *p; + oxRequire(p, keel::uuidToPath(ctx, assetId)); } else { - path = assetId; auto const [uuid, uuidErr] = getUuid(ctx, assetId); if (!uuidErr) { uuidStr = uuid.toString(); @@ -92,16 +98,14 @@ ox::Result> readObjFile( } } if (forceLoad) { - oxRequire(buff, ctx.rom->read(path)); - oxRequire(obj, readConvert(ctx, buff)); - oxRequire(cached, ctx.assetManager.setAsset(assetId, obj)); + ctx.assetManager.initTypeManager(detail::makeLoader, ctx); + oxRequire(cached, ctx.assetManager.loadAsset(assetId)); return cached; } else { auto [cached, err] = ctx.assetManager.getAsset(assetId); if (err) { - oxRequire(buff, ctx.rom->read(path)); - oxRequire(obj, readConvert(ctx, buff)); - oxReturnError(ctx.assetManager.setAsset(assetId, obj).moveTo(cached)); + ctx.assetManager.initTypeManager(detail::makeLoader, ctx); + oxReturnError(ctx.assetManager.loadAsset(assetId).moveTo(cached)); } return cached; } @@ -122,8 +126,14 @@ ox::Result> readObjNoCache( } #endif + +ox::Error reloadAsset(keel::Context &ctx, ox::StringView assetId) noexcept; + template -ox::Result> setAsset(keel::Context &ctx, ox::StringView assetId, T const&asset) noexcept { +ox::Result> setAsset( + keel::Context &ctx, + ox::StringView assetId, + T const&asset) noexcept { #ifndef OX_BARE_METAL if (assetId.len() == 0) { return OxError(1, "Invalid asset ID"); @@ -134,6 +144,7 @@ ox::Result> setAsset(keel::Context &ctx, ox::StringView assetId, T c uuidStr = id->toString(); assetId = uuidStr; } + ctx.assetManager.initTypeManager(detail::makeLoader, ctx); return ctx.assetManager.setAsset(assetId, asset); #else return OxError(1, "Not supported on this platform"); diff --git a/src/olympic/keel/src/media.cpp b/src/olympic/keel/src/media.cpp index 0f98ca1f..ca3c857a 100644 --- a/src/olympic/keel/src/media.cpp +++ b/src/olympic/keel/src/media.cpp @@ -166,6 +166,21 @@ ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept { return {}; } +ox::Error reloadAsset(keel::Context &ctx, ox::StringView assetId) noexcept { + ox::UUIDStr uuidStr; + if (beginsWith(assetId, "uuid://")) { + assetId = substr(assetId, 7); + oxRequire(p, keel::uuidToPath(ctx, assetId)); + } else { + auto const [uuid, uuidErr] = getUuid(ctx, assetId); + if (!uuidErr) { + uuidStr = uuid.toString(); + assetId = uuidStr; + } + } + return ctx.assetManager.reloadAsset(assetId); +} + } #else @@ -219,6 +234,10 @@ ox::Result getPreloadAddr(keel::Context &ctx, ox::FileAddress const return static_cast(p.preloadAddr) + ctx.preloadSectionOffset; } +ox::Error reloadAsset(keel::Context&, ox::StringView) noexcept { + return OxError(1, "reloadAsset is unsupported on this platform"); +} + } #endif diff --git a/src/olympic/studio/modlib/include/studio/project.hpp b/src/olympic/studio/modlib/include/studio/project.hpp index 58fcda3e..223fc099 100644 --- a/src/olympic/studio/modlib/include/studio/project.hpp +++ b/src/olympic/studio/modlib/include/studio/project.hpp @@ -144,7 +144,7 @@ ox::Error Project::writeObj(ox::CRStringView path, T const&obj, ox::ClawFormat f if (!descExists) { oxReturnError(writeTypeStore()); } - oxReturnError(keel::setAsset(m_ctx, path, obj)); + oxReturnError(keel::reloadAsset(m_ctx, path)); oxRequire(uuid, pathToUuid(m_ctx, path)); fileUpdated.emit(path, uuid); return {};