/* * Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. */ #pragma once #ifndef OX_BARE_METAL #include #endif #include #include #include #include #include namespace keel { class AssetManager; template class AssetRef; #ifndef OX_BARE_METAL template class AssetContainer { friend AssetManager; friend AssetRef; protected: ox::Signal updated; private: T m_obj; mutable int m_references = 0; public: template explicit constexpr AssetContainer(Args&&... args): m_obj(ox::forward(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 class AssetRef: public ox::SignalHandler { private: AssetContainer const*m_ctr = nullptr; public: ox::Signal updated; explicit constexpr AssetRef(AssetContainer 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::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 constexpr AssetRef::AssetRef(AssetContainer const*c) noexcept: m_ctr(c) { if (m_ctr) { m_ctr->updated.connect(this, &AssetRef::emitUpdated); } } template constexpr AssetRef::AssetRef(AssetRef const&h) noexcept: m_ctr(h.m_ctr) { if (m_ctr) { m_ctr->updated.connect(this, &AssetRef::emitUpdated); m_ctr->incRefs(); } } template constexpr AssetRef::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 class AssetTypeManager: public AssetTypeManagerBase { public: using Loader = std::function(ox::StringView assetId)>; private: Loader m_loader{}; ox::HashMap>> m_cache; public: 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 ox::Error(1, "asset is null"); } return AssetRef(out->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]; if (!ac->references()) { m_cache.erase(ack); } } } }; ox::HashMap> m_assetTypeManagers; ox::HashMap> m_fileUpdated; template 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 ox::Error(1, "no AssetTypeManager for type"); } return out; } public: template 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); } ox::Error reloadAsset(ox::StringView assetId) noexcept { m_fileUpdated[assetId].emit(assetId); return {}; } template ox::Result> loadAsset(ox::StringView assetId) noexcept { oxRequire(m, getTypeManager()); oxRequire(out, m->loadAsset(assetId)); m_fileUpdated[assetId].connect(m, &AssetTypeManager::reloadAsset); return out; } void gc() noexcept { for (auto const&amk : m_assetTypeManagers.keys()) { auto &am = m_assetTypeManagers[amk]; am->gc(); } } }; #else template 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 }