My current idea is a custom smart pointer much like shared_ptr, in addition to reference counting it keeps track of which files have been loaded and simply returns a reference if its already loaded.
This makes loading files worry free as it looks like this.
Asset tex = Asset::Load("UglyTexture.png");
And if you load it twice.
Asset tex1 = Asset::Load("UglyTexture.png");Asset tex2 = Asset::Load("UglyTexture.png");//UglyTexture.png is only loaded once.
It won't waste CPU or GPU memory.
And preloading commonly used things is easy too, it just adds the texture to the table with an extra reference so it never gets unloaded.
Asset::PreLoad("UglyTexture.png");
Things get tricky though if I generate some sort of texture at runtime, it becomes a question of who should own the Texture object.
If an object is dynamically allocated, it's rather simple to say Asset should determine when it's deleted with something like.
Texture2D *proceduraltex= new Texture2D (100,100,...);//Do something to proceduraltexAsset tex = Asset::Manage(proceduraltex,"ProceduralTex");//Somewhere elseAsset texagain = Asset::Load("ProceduralTex");
But than I began to wonder about the case of the Texture2D object belonging to something else, and how i would use it.
void SomeAssetFunction (const Asset &asset) {}class Foo{ Texture2D footex; Foo () { SomeAssetFunction(Asset::Wrap (footex)); }};
But since the "Wrapped" asset doesn't own the texture it could go out of scope and suddenly the Asset<>'s promise of the texture existing is meaningless.
So my thought was to only use Asset<>'s as member variables, it could easily cause a problem of loading a texture every frame if the Asset<> is used as a local variable.
void SomeFunctionCalledEveryFrame (){ Asset tex = Asset::Load("NonPreloadedTex.png"); //use tex}//NonPredloadedTex.png gets destroyed.
And that any function that needed a Texture2D simply took a Texture2D reference.
void SomeFunctionThatTakesATexture (const Texture2D &);Asset tex = Asset::Load("UglyTexture.png");SomeFunctionThatTakesATexture(tex.Get ());
Overall I'm just mildly happy with the system. On one hand its nice to have the separation between the resource object (Texture2D, Mesh, Shader, etc) and the asset handling it seems to reduce cruft and redundancy in each file. On the other hand the disconnect between generated assets and file loaded assets is a tad annoying.
Its also expandable I plan to add live reload of assets behind the scenes. Since Asset<> controls the storage of each asset, its possible to just flop out the asset behind the scenes and anything using the Asset<> will instantly have the new resource to work with.
Downsides include:
- Thread Unsafety: I might be able to work this out if I was super clever but as it stands the storage container for keying filenames to assets is thread unsafe. Also live reload would be tougher to add with thread safety.
- Global state: Currently I'm not bothered by the fact it assumes there's only one OpenGL context and will put all resources there. But I eventually want to be able to use my graphics code in an editor where I might have multiple contexts.
- No special parameters: Sometimes its nice to specify additional info for how a file should be loaded such as filtering for textures. Perhaps some variardic templates and a little SFINAE wizardry could produce usable results. (static_assert when type T of Asset doesn't supply the proper functions. Otherwise cryptic compile errors appear)
This was my first ever journal post, and I just kinda vomited thoughts all over the page. If you have suggestions on how to handle assets leave me a comment.
Why not
Asset<Texture2D> editMe = Asset<Texture2D>::Create("ProceduralTex", 100, 100, ...);
then later
Asset<Texture2D> useMe = Asset<Texture2D>::Load("ProceduralTex");