Skip to content

Instantly share code, notes, and snippets.

@jmakitalo
Last active December 23, 2015 00:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmakitalo/93102107bb44d66f83d5 to your computer and use it in GitHub Desktop.
Save jmakitalo/93102107bb44d66f83d5 to your computer and use it in GitHub Desktop.
Resource manager
#ifndef MANAGER_HH
#define MANAGER_HH
#include <cstdint.h>
#include <vector>
#include <queue>
#include <map>
// CVar and CVarGroup classes.
#include "var.hh"
enum EResourceStatus{
resourceVoid,
resourceReady,
resourceLoading
};
enum EResourcePriority{
resourceImmediately,
resourceGradually
};
template<typename T>
class CResourceManager;
// Game objects retrieve handles to resources at startup
// and use them to retrieve the actual data during gameplay.
// The data may or may not be what is wanted at any given time due
// to delayed loading or missing resources.
template<typename T>
class CHandle
{
friend class CResourceManager<T>;
private:
// Index to storage in manager.
size_t index;
// Owner of resource.
CResourceManager<T> *pMgr;
// Only manager can create handles to ensure handle validity.
CHandle(uint32_t _index, CResourceManager<T> *_pMgr) : index(_index), pMgr(_pMgr)
{
// Creating a handle means there's one more user.
pMgr->increment(index);
}
public:
static const size_t indexDummy = 0;
static const size_t indexInvalid = std::numeric_limits<std::size_t>::max();
~CHandle()
{
// Destroying a handle means there's one less user.
pMgr->decrement(index);
}
// Get resource data via handle.
// This is called from hot rendering code.
const T *getData() const
{
pMgr->getData(this);
}
void discard()
{
pMgr->decrement(index);
index = indexInvalid;
}
// Index may be used by renderer to sort render objects by resource.
size_t getIndex() const
{
return index;
}
};
// This is derived by all resource types.
template<typename T>
class CResourceBase
{
friend class CResourceManager<T>;
private:
EResourceStatus status
uint32_t useCount;
// Holds resource parameters of generic type.
// Includes the name of the resource as the variable group name.
// This interface allows easy XML import/export and in-game editing.
CVarGroup varGroup;
// Owner of the resource.
CResourceManager<T> *pMgr;
// Only manager can create resources.
CResourceBase(CResourceManager<T> *_pMgr) : status(resourceVoid), pMgr(_pMgr), useCount(0) {}
public:
virtual ~CResourceBase();
EResourceStatus getStatus() const
{
return status;
}
};
// This is derived by managers of different types of resources.
// Type T must be derived from CResourceBase.
template<typename T>
class CResourceManager
{
friend CHandle<T>;
protected:
// Store resources contigusouly in memory.
// T should have solid copy constructors?
std::vector<T> resources;
// Map hash value to resource index.
// Useful for quick lookup of resources.
std::map<size_t, size_t> indices;
// Indices to resources that are being loaded by streaming.
std::queue<size_t> updateList;
void increment(size_t index)
{
if(index!=CHandle<T>::indexInvalid)
resources[index].useCount++;
}
void decrement(size_t index)
{
if(index!=CHandle<T>::indexInvalid)
resources[index].useCount--;
}
public:
// Generate hash value from resource name.
static size_t getHash(const std::string &name);
// Load headers from XML file.
// tag identifies XML elements specific to this type of resource.
// This can be implemented in base class due to the use of the CVarGroup class.
bool fromXML(const std::string &filename, const std::string &tag);
// Write headers to XML (if some resource properties were changed in-game).
bool toXML(const std::string &filename) const;
// Get handle to resource. Can require the resource to be loaded at once.
// This is usually called to set up handles for entities at start up.
CHandle<T> get(const std::string &name, EResourcePriority priority=resourceGradually)
{
auto it = indices.find(getHash(name));
// If resource not found, return the dummy resource at index 0.
if(it==indices.end){
//cerr << "Could not get resource " << name << "!\n";
return CHandle<T>(0, this);
}
EResourceStatus status = resources[it->second]->getStatus();
// Getter requires the resource to be loaded immediately.
if(status==resourceVoid && priority==priorityImmediately){
if(!load(it->second))
return CHandle<T>(0, this);
}
return CHandle<T>(it->second, this);
}
// Return resource data (or dummy data if resource is not yet available).
// This is usually called via handle during gameplay.
const T *getData(const CHandle<T> &handle) const
{
size_t index = handle->getIndex();
if(index==CHandle<T>::indexInvalid)
return &resources[CHandle<T>::indexDummy];
const T *res = &resources[index];
EResourceStatus status = res->getStatus();
// If not loaded, push to queue.
if(status==resourceVoid){
updateList.push_back(it->second);
}
// If resource is not yet loaded, return dummy data.
if(status!=resourceReady)
return &resources[CHandle<T>::indexDummy];
return res;
}
// Load resource.
// Usually this is called by the manager from get() or something else.
virtual bool load(size_t index) = 0;
// Free resource.
virtual void unload(size_t index) = 0;
// If loading data during gameplay, load next piece.
// By default calls the direct loader.
virtual bool update(size_t index)
{
return load(index);
}
// Update all resources as needed.
// Called by engine each frame.
void update()
{
update(updateList.front());
}
};
// *********************************************
// Example instances:
class CMaterialResource;
typedef CResourceHandle<CMaterialResource> HMaterial;
class CMaterialResource : public CResourceBase<CMaterialResource>
{
private:
CVar<string> textureFilename;
CVar<float> shininess;
CVar<vector3f> diffuseColor;
public:
};
class CMaterialManager : public CResourceManager<CMaterialResource>
{
public:
bool load(size_t index) override
{
// At this point material is just a bunch of CVar objects.
// It will depend on texture and shader resources in practice.
return true;
}
void unload(size_t index) override
{
}
};
class CMeshResource;
typedef CResourceHandle<CMeshResource> HMesh;
class CMeshResource : public CResourceBase<CMeshResource>
{
private:
CVar<string> filename;
// Mesh needs a material.
// In practice the mesh will have many groups each with its own material.
HMaterial material;
// There should also be vertex array object handle.
public:
// Initialize material handle with dummy as handles can only be created by
// managers, but we don't know the material name at this point.
CMeshResource(CMeshManager *_pMgr) : CResourceBase<CMeshResource>(_pMgr),
material(_pMgr->pMaterialMgr->get("dummy")) {}
const CMaterialResource *getMaterial() const
{
return material.getData();
}
};
class CMeshManager : public CResourceManager<CMeshResource>
{
private:
// Mesh manager has to be able to load material resources.
CMaterialManager *pMaterialMgr;
public:
// Use smart pointers to prevent passing invalid pointer?
CMeshManager(CMaterialManager *_pMaterialMgr) : pMaterialMgr(_pMaterialMgr) {}
bool load(size_t index) override
{
CMeshResource &res = resources[index];
// Some class to load mesh data from file.
CMesh mesh;
if(!mesh.load(res.filename))
return false;
// Get handle to material via material manager.
// Initially created dummy material handle is destroyed.
res.material = pMaterialMgr->get(mesh.getMaterialName());
return true;
}
void unload(size_t index) override
{
CMeshResource &res = resources[index];
// Discard material: it will then point to dummy data
// and counter of original resource is decremented.
res.material.discard();
}
}
class CWorld
{
CResourceManager<CMaterial> materialMgr;
CResourceManager<CMesh> meshMgr;
CWorld() : meshMgr(&materialMgr) {}
// Here handles are setup for objects.
void init()
{
// There will be a lot of these because each manager is separate object.
materialMgr.fromXML("resources.xml", "material");
meshMgr.fromXML("resources.xml", "mesh");
// Give some object a mesh handle.
someObject.HMesh = meshMgr.get("house");
}
// Here objects are rendered and resource data is accessed via handles.
void render()
{
// Will always be valid: dummy or the real deal.
const CMeshResource *pMesh = someObject.HMesh.getData();
// Will always be valid: dummy or the real deal.
const CMaterialResource *pMat = pMesh->getMaterial();
renderMesh(pMesh, pMat, someObjectTransform);
}
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment