Lost in Asset Management

posted in Tocs' Blog
Published July 22, 2013
Advertisement
So I've been contemplating a good way to manage assets used by my game and so far I've been losing the mental battle.

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.
1 likes 3 comments

Comments

popsoftheyear

Why not

Asset<Texture2D> editMe = Asset<Texture2D>::Create("ProceduralTex", 100, 100, ...);

then later

Asset<Texture2D> useMe = Asset<Texture2D>::Load("ProceduralTex");

July 23, 2013 04:54 PM
vomitBasin

Would it be possible to fix the thread safety issues simply by applying a lock around reads/writes to the container to associate filenames to assets?

I've worked with a system that seems similar to yours, and we were able to stream in several textures at the same time using an Asset wrapper as you've done. The resource loader would lock the container that associated the asset filename with the asset object, and grab a new thread to use in loading the texture data. Once loaded, the data was submitted to the GPU, and the Asset was marked with a "DataLoaded" flag to signal that the texture was ready to be bound and used.

This was a successful system because it also allowed us to stream things in on the fly. Your Asset wrapper could do the same thing: Game objects can hold on to a reference to a texture all the time, even if your asset isn't loaded. When the asset is bound for use, and it's not in memory, queue it to stream in and use a placeholder or transparent texture in its place until the "DataLoaded" flag has been updated on the asset.

July 26, 2013 01:58 PM
Tocs1001

achild: I like the syntactic sugar when I move away from VS2010 and have variardic templates I'll definitely put that in.

uint32_t: It would definately be possible to do things like you described to make it thread safe. I just haven't done that yet. I feel it would be trickier to work in thread-saftey with live reload of assets because the "DataLoaded" flag would no longer function.

July 29, 2013 02:22 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement