[keel,studio] Fix hotloading for files that get loaded as multiple types
All checks were successful
Build / build (push) Successful in 2m33s

This commit is contained in:
Gary Talent 2024-05-27 19:26:58 -05:00
parent 2bb7c51425
commit 27b38ed250
4 changed files with 133 additions and 37 deletions

View File

@ -4,6 +4,10 @@
#pragma once #pragma once
#ifndef OX_BARE_METAL
#include <functional>
#endif
#include <ox/event/signal.hpp> #include <ox/event/signal.hpp>
#include <ox/fs/fs.hpp> #include <ox/fs/fs.hpp>
#include <ox/model/typenamecatcher.hpp> #include <ox/model/typenamecatcher.hpp>
@ -178,9 +182,11 @@ constexpr AssetRef<T>::AssetRef(AssetRef &&h) noexcept: m_ctr(h.m_ctr) {
h.m_ctr = nullptr; h.m_ctr = nullptr;
} }
class Context;
class AssetManager { class AssetManager {
private: private:
class AssetTypeManagerBase { class AssetTypeManagerBase: public ox::SignalHandler {
public: public:
virtual ~AssetTypeManagerBase() = default; virtual ~AssetTypeManagerBase() = default;
@ -189,17 +195,24 @@ class AssetManager {
template<typename T> template<typename T>
class AssetTypeManager: public AssetTypeManagerBase { class AssetTypeManager: public AssetTypeManagerBase {
public:
using Loader = std::function<ox::Result<T>(ox::StringView assetId)>;
private: private:
Loader m_loader{};
ox::HashMap<ox::String, ox::UPtr<AssetContainer<T>>> m_cache; ox::HashMap<ox::String, ox::UPtr<AssetContainer<T>>> m_cache;
public: public:
ox::Result<AssetRef<T>> getAsset(ox::StringView const&assetId) const noexcept { AssetTypeManager(Loader loader) noexcept: m_loader(loader) {}
auto out = m_cache.at(assetId);
oxReturnError(out); ox::Result<AssetRef<T>> getAsset(ox::StringView const assetId) const noexcept {
return AssetRef<T>(out.value->get()); oxRequire(out, m_cache.at(assetId));
if (!out || !*out) {
return OxError(1, "asset is null");
}
return AssetRef<T>(out->get());
} }
ox::Result<AssetRef<T>> setAsset(ox::StringView const&assetId, T const&obj) noexcept { ox::Result<AssetRef<T>> setAsset(ox::StringView const assetId, T const&obj) noexcept {
auto &p = m_cache[assetId]; auto &p = m_cache[assetId];
if (!p) { if (!p) {
p = ox::make_unique<AssetContainer<T>>(obj); p = ox::make_unique<AssetContainer<T>>(obj);
@ -210,7 +223,7 @@ class AssetManager {
return AssetRef<T>(p.get()); return AssetRef<T>(p.get());
} }
ox::Result<AssetRef<T>> setAsset(ox::StringView const&assetId, T &&obj) noexcept { ox::Result<AssetRef<T>> setAsset(ox::StringView const assetId, T &&obj) noexcept {
auto &p = m_cache[assetId]; auto &p = m_cache[assetId];
if (!p) { if (!p) {
p = ox::make_unique<AssetContainer<T>>(obj); p = ox::make_unique<AssetContainer<T>>(obj);
@ -221,6 +234,30 @@ class AssetManager {
return AssetRef<T>(p.get()); return AssetRef<T>(p.get());
} }
ox::Result<AssetRef<T>> loadAsset(ox::StringView const assetId) noexcept {
auto &p = m_cache[assetId];
oxRequireM(obj, m_loader(assetId));
if (!p) {
p = ox::make_unique<AssetContainer<T>>(std::move(obj));
} else {
p->set(std::move(obj));
p->updated.emit();
}
return AssetRef<T>(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<AssetContainer<T>>(std::move(obj));
} else {
p->set(std::move(obj));
p->updated.emit();
}
return {};
}
void gc() noexcept final { void gc() noexcept final {
for (auto const&ack : m_cache.keys()) { for (auto const&ack : m_cache.keys()) {
auto &ac = m_cache[ack]; auto &ac = m_cache[ack];
@ -232,29 +269,58 @@ class AssetManager {
}; };
ox::HashMap<ox::String, ox::UPtr<AssetTypeManagerBase>> m_assetTypeManagers; ox::HashMap<ox::String, ox::UPtr<AssetTypeManagerBase>> m_assetTypeManagers;
struct FileTracker {
ox::Signal<ox::Error(ox::StringView assetId)> updated;
};
ox::HashMap<ox::String, FileTracker> m_fileTrackers;
template<typename T> template<typename T>
AssetTypeManager<T> *getTypeManager() noexcept { ox::Result<AssetTypeManager<T>*> getTypeManager() noexcept {
constexpr auto typeName = ox::requireModelTypeName<T>(); constexpr auto &typeId = ox::ModelTypeId_v<T>;
static_assert(ox::StringView(typeName) != "", "Types must have TypeName to use AssetManager"); static_assert(typeId != "", "Types must have TypeName to use AssetManager");
auto &am = m_assetTypeManagers[typeName]; auto &am = m_assetTypeManagers[typeId];
if (!am) { auto const out = dynamic_cast<AssetTypeManager<T>*>(am.get());
am = ox::make_unique<AssetTypeManager<T>>(); if (!out) {
return OxError(1, "no AssetTypeManager for type");
} }
return dynamic_cast<AssetTypeManager<T>*>(am.get()); return out;
} }
public: public:
template<typename T> template<typename T>
ox::Result<AssetRef<T>> getAsset(ox::CRStringView assetId) noexcept { void initTypeManager(auto const&makeLoader, Context &ctx) noexcept {
auto m = getTypeManager<T>(); constexpr auto &typeId = ox::ModelTypeId_v<T>;
static_assert(typeId != "", "Types must have TypeName to use AssetManager");
auto &am = m_assetTypeManagers[typeId];
if (!am) {
am = ox::make_unique<AssetTypeManager<T>>(makeLoader(ctx));
}
}
template<typename T>
ox::Result<AssetRef<T>> getAsset(ox::StringView assetId) noexcept {
oxRequire(m, getTypeManager<T>());
return m->getAsset(assetId); return m->getAsset(assetId);
} }
template<typename T> template<typename T>
ox::Result<AssetRef<T>> setAsset(ox::CRStringView assetId, T const&obj) noexcept { ox::Result<AssetRef<T>> setAsset(ox::StringView assetId, T const&obj) noexcept {
auto m = getTypeManager<T>(); oxRequire(m, getTypeManager<T>());
return m->setAsset(assetId, obj); oxReturnError(m->setAsset(assetId, obj));
return {};
}
ox::Error reloadAsset(ox::StringView assetId) noexcept {
m_fileTrackers[assetId].updated.emit(assetId);
return {};
}
template<typename T>
ox::Result<AssetRef<T>> loadAsset(ox::StringView assetId) noexcept {
oxRequire(m, getTypeManager<T>());
oxRequire(out, m->loadAsset(assetId));
m_fileTrackers[assetId].updated.connect(m, &AssetTypeManager<T>::reloadAsset);
return out;
} }
void gc() noexcept { void gc() noexcept {

View File

@ -61,13 +61,14 @@ ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept;
#ifndef OX_BARE_METAL #ifndef OX_BARE_METAL
namespace detail {
template<typename T> template<typename T>
ox::Result<keel::AssetRef<T>> readObjFile( constexpr auto makeLoader(Context &ctx) {
keel::Context &ctx, return [&ctx](ox::StringView assetId) -> ox::Result<T> {
ox::StringView assetId, ox::StringView path;
bool forceLoad) noexcept { oxRequire(p, ctx.uuidToPath.at(assetId));
constexpr auto readConvert = [](Context &ctx, const ox::Buffer &buff) path = *p;
-> ox::Result<T> { oxRequire(buff, ctx.rom->read(path));
auto [obj, err] = readAsset<T>(buff); auto [obj, err] = readAsset<T>(buff);
if (err) { if (err) {
if (err != ox::Error_ClawTypeVersionMismatch && err != ox::Error_ClawTypeMismatch) { if (err != ox::Error_ClawTypeVersionMismatch && err != ox::Error_ClawTypeMismatch) {
@ -77,14 +78,19 @@ ox::Result<keel::AssetRef<T>> readObjFile(
} }
return std::move(obj); return std::move(obj);
}; };
ox::StringView path; };
}
template<typename T>
ox::Result<keel::AssetRef<T>> readObjFile(
keel::Context &ctx,
ox::StringView assetId,
bool forceLoad) noexcept {
ox::UUIDStr uuidStr; ox::UUIDStr uuidStr;
if (beginsWith(assetId, "uuid://")) { if (beginsWith(assetId, "uuid://")) {
assetId = substr(assetId, 7); assetId = substr(assetId, 7);
oxRequire(p, ctx.uuidToPath.at(assetId)); oxRequire(p, keel::uuidToPath(ctx, assetId));
path = *p;
} else { } else {
path = assetId;
auto const [uuid, uuidErr] = getUuid(ctx, assetId); auto const [uuid, uuidErr] = getUuid(ctx, assetId);
if (!uuidErr) { if (!uuidErr) {
uuidStr = uuid.toString(); uuidStr = uuid.toString();
@ -92,16 +98,14 @@ ox::Result<keel::AssetRef<T>> readObjFile(
} }
} }
if (forceLoad) { if (forceLoad) {
oxRequire(buff, ctx.rom->read(path)); ctx.assetManager.initTypeManager<T>(detail::makeLoader<T>, ctx);
oxRequire(obj, readConvert(ctx, buff)); oxRequire(cached, ctx.assetManager.loadAsset<T>(assetId));
oxRequire(cached, ctx.assetManager.setAsset(assetId, obj));
return cached; return cached;
} else { } else {
auto [cached, err] = ctx.assetManager.getAsset<T>(assetId); auto [cached, err] = ctx.assetManager.getAsset<T>(assetId);
if (err) { if (err) {
oxRequire(buff, ctx.rom->read(path)); ctx.assetManager.initTypeManager<T>(detail::makeLoader<T>, ctx);
oxRequire(obj, readConvert(ctx, buff)); oxReturnError(ctx.assetManager.loadAsset<T>(assetId).moveTo(cached));
oxReturnError(ctx.assetManager.setAsset(assetId, obj).moveTo(cached));
} }
return cached; return cached;
} }
@ -122,8 +126,14 @@ ox::Result<keel::AssetRef<T>> readObjNoCache(
} }
#endif #endif
ox::Error reloadAsset(keel::Context &ctx, ox::StringView assetId) noexcept;
template<typename T> template<typename T>
ox::Result<AssetRef<T>> setAsset(keel::Context &ctx, ox::StringView assetId, T const&asset) noexcept { ox::Result<AssetRef<T>> setAsset(
keel::Context &ctx,
ox::StringView assetId,
T const&asset) noexcept {
#ifndef OX_BARE_METAL #ifndef OX_BARE_METAL
if (assetId.len() == 0) { if (assetId.len() == 0) {
return OxError(1, "Invalid asset ID"); return OxError(1, "Invalid asset ID");
@ -134,6 +144,7 @@ ox::Result<AssetRef<T>> setAsset(keel::Context &ctx, ox::StringView assetId, T c
uuidStr = id->toString(); uuidStr = id->toString();
assetId = uuidStr; assetId = uuidStr;
} }
ctx.assetManager.initTypeManager<T>(detail::makeLoader<T>, ctx);
return ctx.assetManager.setAsset(assetId, asset); return ctx.assetManager.setAsset(assetId, asset);
#else #else
return OxError(1, "Not supported on this platform"); return OxError(1, "Not supported on this platform");

View File

@ -166,6 +166,21 @@ ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept {
return {}; 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 #else
@ -219,6 +234,10 @@ ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const
return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset; return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset;
} }
ox::Error reloadAsset(keel::Context&, ox::StringView) noexcept {
return OxError(1, "reloadAsset is unsupported on this platform");
}
} }
#endif #endif

View File

@ -144,7 +144,7 @@ ox::Error Project::writeObj(ox::CRStringView path, T const&obj, ox::ClawFormat f
if (!descExists) { if (!descExists) {
oxReturnError(writeTypeStore()); oxReturnError(writeTypeStore());
} }
oxReturnError(keel::setAsset(m_ctx, path, obj)); oxReturnError(keel::reloadAsset(m_ctx, path));
oxRequire(uuid, pathToUuid(m_ctx, path)); oxRequire(uuid, pathToUuid(m_ctx, path));
fileUpdated.emit(path, uuid); fileUpdated.emit(path, uuid);
return {}; return {};