8000 [WIP] Asset/resource system rework by adriengivry · Pull Request #501 · Overload-Technologies/Overload · GitHub
[go: up one dir, main page]

Skip to content

[WIP] Asset/resource system rework #501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions Sources/Overload/OvCore/include/OvCore/Assets/Asset.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#pragma once

#include <any>
#include <cstdint>
#include <optional>

namespace OvCore::Assets
{
using GUID = uint32_t;

GUID GenerateGUID()
{
static GUID id = 0;
return ++id;
}

using AssetID = GUID;

/**
* Can be seen as a reference to something on the disk (File).
*/
class File
{
public:
File(const std::string_view p_path) : m_path(p_path) {}
bool Exists() const { return std::filesystem::exists(m_path); }
void MoveTo(const std::string_view p_newPath) { m_path = p_newPath; }
void Delete() { std::filesystem::remove(m_path); }
void SaveToDisk() { _Write(_Serialize()); }
void LoadFromDisk() { _Deserialize(_Read()); }
const std::string& GetPath() const { return m_path; }

protected:
std::string _Serialize() { return std::string{}; }
void _Deserialize(const std::string& p_data) { /*...*/ }

private:
void _Write(const std::string& p_content) {}; // Write to disk
std::string _Read() {}; // Read from disk

protected:
std::string m_path;
};

/**
* An asset is a file that can be loaded and used in the engine, and is identified by a GUID.
*/
class AAssetBase : public File
{
public:
// Make sure only the asset registry can create assets
AAssetBase(const std::string_view p_path) :
m_id(GenerateGUID()),
File(p_path)
{
}

void Reload()
{
Unload();
Load();
}

// Synchronous loading
virtual void Load() = 0;
virtual void Unload() = 0;
virtual bool IsLoaded() const = 0;

private:
AssetID m_id;
std::string m_path;
};

class Mock
{
public:
Mock(int x, int y) {}
};

// Is this even needed?
class MockFactory
{
public:
Mock&& Create(int x, int y)
{
return Mock(x, y);
}

Mock&& CreateFromPath(std::string p_path)
{
// Load file, parse, etc...
return Mock(0, 0);
}
};

template<class UnderlyingType>
class Asset : public AAssetBase
{
public:
Asset(const std::string_view p_path) : AAssetBase(p_path) {}

// Template specialized for each asset type
void Load() override;

void Unload() override
{
m_underlying.reset();
}

bool IsLoaded() const override
{
return m_underlying.has_value();
}

private:
std::optional<UnderlyingType> m_underlying; // Ideally this stays hidden, no getter.
};

// Template specialization for Mock
void Asset<Mock>::Load()
{
auto factory = MockFactory{};
m_underlying = std::make_optional<Mock>(factory.Create(1, 2));
}
}

#include <iostream>
#include <OvCore/Global/ServiceLocator.h>

namespace
{
// Example use case
int main()
{
using namespace OvCore::Assets;
using namespace OvCore::Global;

// Somewhere at init
MockFactory factory;
ServiceLocator::Provide(factory);

// This should be done in the asset registry
{
const auto asset = Asset<Mock>{ "path/to/asset.ovmock" };
}

// The asset registry should only return a reference to the asset
const AssetRef<Mock> mockAssetRef; // Let's assume this is returned by the asset registry

// Check if the asset reference is valid (not expired)
if (mockAssetRef.IsValid()) // same as `if (mockAssetRef)` with operator bool. This only checks that the reference is valid, not that the asset is loaded.
{
// -> to access the underlying MockAsset&
Asset<Mock>& asset = mockAssetRef.Get(); // Shouldn't fail since IsValid() returned true

// If the asset is not loaded, this will load it.
// A fallback strategy could be added for asynchronous loading.
// e.g. while the asset is loading, use a placeholder asset.
Mock& mock = asset.Get();

// TODO: Consider using some sort of monad to handle the double indirection.
}

// Should print "Is loaded: false"
std::cout << std::format("Is loaded: {}", asset.IsLoaded()) << std::endl;

if (auto& mockInstance = mockAssetRef->GetUnderlying())
{

}

// Constructor a mock instance
auto mockInstance = OvCore::Global::ServiceLocator::Get<MockFactory>().Create(1, 2);


return 0;
}
}
46 changes: 46 additions & 0 deletions Sources/Overload/OvCore/include/OvCore/Assets/AssetRef.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#include <concepts>
#include <optional>
#include <OvCore/Assets/Asset.h>
#include <OvCore/Global/ServiceLocator.h>

namespace OvCore::Assets
{
template<typename T>
concept AssetType = std::is_base_of<Asset, T>::value;

/**
* A simple asset reference. Doesn't need the underlying asset to be loaded.
*/
struct GenericAssetRef
{
AssetID id;
};

/**
* Provide utility functions to manage the underlying asset.
*/
template<AssetType T>
class AssetRef : public GenericAssetRef
{
public:
AssetID id;

/**
* Returns the underlying asset.
*/
std::optional<T> Get()
{
auto& assetRegistry = OvCore::Global::ServiceLocator::Get<AssetRegistry>();
return assetRegistry.GetAsset(id);
return static_cast<T&>(m_assets[id]);
}

bool IsExpired() const
{
auto& assetRegistry = OvCore::Global::ServiceLocator::Get<AssetRegistry>();
return assetRegistry.
}
};
}
48 changes: 48 additions & 0 deletions Sources/Overload/OvCore/include/OvCore/Assets/AssetRegistry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once

#include <filesystem>
#include <unordered_map>

#include <OvCore/Assets/Asset.h>
#include <OvCore/Assets/AssetRef.h>

namespace OvCore::Assets
{
class AssetRegistry
{
public:
/**
* Discover assets in the given path and register them in the asset registry.
* @note This is quite expensive, and should probably be called only once at the beginning of the application.
* @param p_path
* @param p_recursive
*/
uint32_t DiscoverAssets(const std::filesystem::path& p_path, bool p_recursive = true);

/**
* Register a single asset in the asset registry.
* @param p_path
*/
GenericAssetRef RegisterAssetAtPath(const std::filesystem::path& p_path);

/**
* Returns a reference to the asset with the given ID.
* @param p_id
*/
Asset& GetAsset(uint32_t p_id)
{
auto it = m_assets.find(p_id);
if (it != m_assets.end())
{
return it->second;
}
else
{
throw std::runtime_error("Asset not found");
}
}

private:
std::unordered_map<uint32_t, Asset> m_assets;
};
}
0