Last active
December 23, 2015 00:44
-
-
Save jmakitalo/93102107bb44d66f83d5 to your computer and use it in GitHub Desktop.
Resource manager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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