332 lines
7.3 KiB
C++
332 lines
7.3 KiB
C++
/*
|
|
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#ifndef OX_BARE_METAL
|
|
#include <functional>
|
|
#endif
|
|
|
|
#include <ox/event/signal.hpp>
|
|
#include <ox/fs/fs.hpp>
|
|
#include <ox/model/typenamecatcher.hpp>
|
|
#include <ox/std/hashmap.hpp>
|
|
#include <ox/std/utility.hpp>
|
|
|
|
namespace keel {
|
|
|
|
class AssetManager;
|
|
|
|
template<typename T>
|
|
class AssetRef;
|
|
|
|
#ifndef OX_BARE_METAL
|
|
template<typename T>
|
|
class AssetContainer {
|
|
|
|
friend AssetManager;
|
|
friend AssetRef<T>;
|
|
|
|
protected:
|
|
ox::Signal<ox::Error()> updated;
|
|
|
|
private:
|
|
T m_obj;
|
|
mutable int m_references = 0;
|
|
|
|
public:
|
|
template<class... Args>
|
|
explicit constexpr AssetContainer(Args&&... args): m_obj(ox::forward<Args>(args)...) {
|
|
}
|
|
|
|
AssetContainer(AssetContainer const&) = delete;
|
|
AssetContainer(AssetContainer&&) = delete;
|
|
AssetContainer &operator=(AssetContainer const&) = delete;
|
|
AssetContainer &operator=(AssetContainer&&) = delete;
|
|
|
|
[[nodiscard]]
|
|
constexpr T *get() noexcept {
|
|
return &m_obj;
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr T const*get() const noexcept {
|
|
return &m_obj;
|
|
}
|
|
|
|
constexpr void set(T &&val) {
|
|
m_obj = std::move(val);
|
|
}
|
|
|
|
constexpr void set(T const&val) {
|
|
m_obj = val;
|
|
}
|
|
|
|
protected:
|
|
constexpr void incRefs() const noexcept {
|
|
++m_references;
|
|
}
|
|
|
|
constexpr void decRefs() const noexcept {
|
|
--m_references;
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr int references() const noexcept {
|
|
return m_references;
|
|
}
|
|
|
|
};
|
|
|
|
template<typename T>
|
|
class AssetRef: public ox::SignalHandler {
|
|
private:
|
|
AssetContainer<T> const*m_ctr = nullptr;
|
|
|
|
public:
|
|
ox::Signal<ox::Error()> updated;
|
|
|
|
explicit constexpr AssetRef(AssetContainer<T> const*c = nullptr) noexcept;
|
|
|
|
constexpr AssetRef(AssetRef const&h) noexcept;
|
|
|
|
constexpr AssetRef(AssetRef &&h) noexcept;
|
|
|
|
~AssetRef() noexcept override {
|
|
if (m_ctr) {
|
|
m_ctr->decRefs();
|
|
}
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr T const*get() const noexcept {
|
|
if (m_ctr) {
|
|
return m_ctr->get();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
constexpr T const&operator*() const noexcept {
|
|
return *m_ctr->get();
|
|
}
|
|
|
|
constexpr T const*operator->() const noexcept {
|
|
return m_ctr->get();
|
|
}
|
|
|
|
AssetRef &operator=(AssetRef const&h) noexcept {
|
|
if (this == &h) {
|
|
return *this;
|
|
}
|
|
if (m_ctr) {
|
|
m_ctr->decRefs();
|
|
std::ignore = m_ctr->updated.disconnectObject(this);
|
|
}
|
|
m_ctr = h.m_ctr;
|
|
if (m_ctr) {
|
|
m_ctr->updated.connect(&updated, &ox::Signal<ox::Error()>::emitCheckError);
|
|
m_ctr->incRefs();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
AssetRef &operator=(AssetRef &&h) noexcept {
|
|
if (this == &h) {
|
|
return *this;
|
|
}
|
|
if (m_ctr) {
|
|
m_ctr->decRefs();
|
|
std::ignore = m_ctr->updated.disconnectObject(this);
|
|
}
|
|
m_ctr = h.m_ctr;
|
|
if (m_ctr) {
|
|
std::ignore = m_ctr->updated.disconnectObject(&h);
|
|
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
|
|
}
|
|
h.m_ctr = nullptr;
|
|
return *this;
|
|
}
|
|
|
|
explicit constexpr operator bool() const noexcept {
|
|
return m_ctr;
|
|
}
|
|
|
|
private:
|
|
constexpr ox::Error emitUpdated() const noexcept {
|
|
updated.emit();
|
|
return ox::Error(0);
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
constexpr AssetRef<T>::AssetRef(AssetContainer<T> const*c) noexcept: m_ctr(c) {
|
|
if (m_ctr) {
|
|
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
constexpr AssetRef<T>::AssetRef(AssetRef const&h) noexcept: m_ctr(h.m_ctr) {
|
|
if (m_ctr) {
|
|
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
|
|
m_ctr->incRefs();
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
constexpr AssetRef<T>::AssetRef(AssetRef &&h) noexcept: m_ctr(h.m_ctr) {
|
|
if (m_ctr) {
|
|
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
|
|
}
|
|
h.m_ctr = nullptr;
|
|
}
|
|
|
|
class Context;
|
|
|
|
class AssetManager {
|
|
private:
|
|
class AssetTypeManagerBase: public ox::SignalHandler {
|
|
public:
|
|
~AssetTypeManagerBase() override = default;
|
|
|
|
virtual void gc() noexcept = 0;
|
|
};
|
|
|
|
template<typename T>
|
|
class AssetTypeManager: public AssetTypeManagerBase {
|
|
public:
|
|
using Loader = std::function<ox::Result<T>(ox::StringViewCR assetId)>;
|
|
private:
|
|
Loader m_loader{};
|
|
ox::HashMap<ox::String, ox::UPtr<AssetContainer<T>>> m_cache;
|
|
|
|
public:
|
|
AssetTypeManager(Loader &&loader) noexcept: m_loader(std::move(loader)) {}
|
|
|
|
ox::Result<AssetRef<T>> getAsset(ox::StringViewCR assetId) const noexcept {
|
|
OX_REQUIRE(out, m_cache.at(assetId));
|
|
if (!out || !*out) {
|
|
return ox::Error(1, "asset is null");
|
|
}
|
|
return AssetRef<T>(out->get());
|
|
}
|
|
|
|
ox::Result<AssetRef<T>> loadAsset(ox::StringViewCR assetId) noexcept {
|
|
auto &p = m_cache[assetId];
|
|
OX_REQUIRE_M(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::StringViewCR assetId) noexcept {
|
|
auto &p = m_cache[assetId];
|
|
OX_REQUIRE_M(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 {
|
|
for (auto const&ack : m_cache.keys()) {
|
|
auto &ac = m_cache[ack];
|
|
if (!ac->references()) {
|
|
m_cache.erase(ack);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
ox::HashMap<ox::String, ox::UPtr<AssetTypeManagerBase>> m_assetTypeManagers;
|
|
ox::HashMap<ox::String, ox::Signal<ox::Error(ox::StringViewCR assetId)>> m_fileUpdated;
|
|
|
|
template<typename T>
|
|
ox::Result<AssetTypeManager<T>*> getTypeManager() noexcept {
|
|
constexpr auto &typeId = ox::ModelTypeId_v<T>;
|
|
static_assert(typeId != "", "Types must have TypeName to use AssetManager");
|
|
auto &am = m_assetTypeManagers[typeId];
|
|
auto const out = dynamic_cast<AssetTypeManager<T>*>(am.get());
|
|
if (!out) {
|
|
return ox::Error(1, "no AssetTypeManager for type");
|
|
}
|
|
return out;
|
|
}
|
|
|
|
public:
|
|
template<typename T>
|
|
void initTypeManager(auto const&makeLoader, Context &ctx) noexcept {
|
|
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::StringViewCR assetId) noexcept {
|
|
OX_REQUIRE(m, getTypeManager<T>());
|
|
return m->getAsset(assetId);
|
|
}
|
|
|
|
ox::Error reloadAsset(ox::StringViewCR assetId) noexcept {
|
|
m_fileUpdated[assetId].emit(assetId);
|
|
return {};
|
|
}
|
|
|
|
template<typename T>
|
|
ox::Result<AssetRef<T>> loadAsset(ox::StringViewCR assetId) noexcept {
|
|
OX_REQUIRE(m, getTypeManager<T>());
|
|
OX_REQUIRE(out, m->loadAsset(assetId));
|
|
m_fileUpdated[assetId].connect(m, &AssetTypeManager<T>::reloadAsset);
|
|
return out;
|
|
}
|
|
|
|
void gc() noexcept {
|
|
for (auto const&amk : m_assetTypeManagers.keys()) {
|
|
auto &am = m_assetTypeManagers[amk];
|
|
am->gc();
|
|
}
|
|
}
|
|
};
|
|
#else
|
|
template<typename T>
|
|
class AssetRef {
|
|
private:
|
|
T const* m_obj = nullptr;
|
|
|
|
public:
|
|
constexpr AssetRef() noexcept = default;
|
|
|
|
explicit constexpr AssetRef(T const*obj) noexcept: m_obj(obj) {
|
|
}
|
|
|
|
constexpr T const*get() const noexcept {
|
|
return m_obj;
|
|
}
|
|
|
|
constexpr T const&operator*() const & noexcept {
|
|
return *m_obj;
|
|
}
|
|
|
|
constexpr T const*operator->() const noexcept {
|
|
return m_obj;
|
|
}
|
|
|
|
explicit constexpr operator bool() const noexcept {
|
|
return m_obj;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
}
|