Jump to content

  • Log In with Google      Sign In   
  • Create Account





Lost in Asset Management

Posted by Tocs, 22 July 2013 · 651 views

C++ assets for sale
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<Texture2D> tex = Asset<Texture2D>::Load("UglyTexture.png");
And if you load it twice.
Asset<Texture2D> tex1 = Asset<Texture2D>::Load("UglyTexture.png");
Asset<Texture2D> tex2 = Asset<Texture2D>::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<Texture2D>::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 proceduraltex
Asset<Texture2D> tex = Asset<Texture2D>::Manage(proceduraltex,"ProceduralTex");


//Somewhere else
Asset<Texture2D> texagain = Asset<Texture2D>::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<Texture2D> &asset) {}

class Foo
{
    Texture2D footex;
    Foo ()
    {
        SomeAssetFunction(Asset<Texture2D>::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<Texture2D> tex = Asset<Texture2D>::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<Texture2D> tex = Asset<Texture2D>::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<T> 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");

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.

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.

October 2014 »

S M T W T F S
   1234
567891011
12131415161718
19202122 23 2425
262728293031 

Recent Comments

PARTNERS