26
\$\begingroup\$

What is the best approach to designing an AssestManager that will hold references to graphics, sounds, etc. of a game?

Should these assets be stored in a key/value Map pair? I.e. I ask for "background" asset and the Map returns the associated bitmap? Is there an even better way?

Specifically I'm writing an Android/Java game, but answers can be generic.

\$\endgroup\$

5 Answers 5

18
\$\begingroup\$

It depends on the scope of your game. An asset manager is absolutely essential for larger titles, less so for smaller games.

For larger titles you have to manage problems such as the following:

  • Shared assets - is that brick texture being used by multiple models?
  • Asset lifetime - is that asset you loaded 15 minutes ago no longer needed? Reference counting your assets to make sure you know when something is finished with etc
  • In DirectX 9 if certain asset types are loaded and your graphics device gets 'lost' (this happens if you press Ctrl+Alt+Del amongst other things) - your game will need to recreate them
  • Loading assets in advance of needing them - you couldn't build big open world games without this
  • Bulk loading assets - We often pack lots of assets into a single file to improve loading times - seeking around the disc is very time consuming

For smaller titles these things are less of an issue, frameworks like XNA have asset managers within them - there is very little point in re-inventing it.

If you find yourself needing an asset manager, there is no one-size-fits-all solution really, but I've found that a hash map with the key as a hash* of the filename (lowered and separators all 'fixed') works well for the projects I've worked on.

It is usually not advisable to hardcode filenames in your app, it is usually better to have another data format (such as xml) depict filenames to 'IDs'.

  • As an amusing side note, you normally get one hash collision per project.
\$\endgroup\$
2
  • \$\begingroup\$ Just because you need to manage assets does not necessitate AssetManagers, a capitalized important noun that probably has too many methods, poor performance, and muddy memory semantics. For a comparison, think about what happens if you have a lot of project management (usually good), and then when you have a lot of project managers (usually bad). \$\endgroup\$
    – user744
    Commented Jul 27, 2010 at 20:20
  • 2
    \$\begingroup\$ @Joe Wreschnig -- how would you address the five requirements mentioned by icStatic without using an asset manager? \$\endgroup\$
    – antinome
    Commented May 13, 2013 at 17:24
9
\$\begingroup\$

(Trying to avoid the "don't use an asset manager"-discussion here, since I consider it offtopic.)

A key/value map is a very usable approach.

We have one ResourceManager implementation where Factories for different Resource types can register.

The "getResource" method uses templates to find the correct Factory for the wanted resourcetype and returns a specific ResourceHandle (again using the template to return a SpecificResourceHandle).

The resources are refcounted by the ResourceManager (inside the ResourceHandle) and released when they are not needed anymore.

The first addon we wrote was the "reload(XYZ)" method, which allows us to change resources from outside the running engine without changing any code or reloading the game. (This is essential when artists work on consoles ;) )

Most of the time we have only on instance of the ResourceManager, but sometimes we create a new instance just for a level or a map. This way we can just call "shutdown" on the levelResourceManager and ensure nothing is leaking.

(brief) example

// very abbreviated!
// this code would never survive our coding guidelines ;)

ResourceManager* pRm = new ResourceManager;
pRm->initialize( );
pRm->registerFactory( new TextureFactory );
// [...]
TextureHandle tex = pRm->getResource<Texture>( "test.otx" ); // in real code we use some macro magic here to use CRCs for filenames
tex->storeToHardware( 0 ); // channel 0

pRm->releaseResource( pRm );

// [...]
pRm->shutdown(); // will log any leaked resource
\$\endgroup\$
6
\$\begingroup\$

Dedicated Manager classes are almost never the right engineering tool. If you only need the asset once (like a background or map) you should only request it once, and let it die normally when you're done with it. If you need to cache a particular kind of object, you should use a factory that first checks a cache and otherwise loads something, puts it in the cache, and then returns it - and that factory can just be a static function accessing a static variable, not a type of its own.

Steve Yegge (among many, many others) has written a good story about how useless manager classes, by way of the singleton pattern, end up being. http://sites.google.com/site/steveyegge2/singleton-considered-stupid

\$\endgroup\$
8
  • 2
    \$\begingroup\$ Okay, sure. But in cases like Android (or other games) you need to load in a lot of graphics/sounds in to memory before you start the game, not during. How can I use what you're saying (factories) to do this during a load screen? Just hit every object in the factory on the load screen so it caches them? \$\endgroup\$ Commented Jul 27, 2010 at 18:44
  • \$\begingroup\$ I'm unfamiliar with Android details but I have no idea what you mean by "before you start the game". Is it really impossible to load a resource when you need (or when you'll need it 'soon') it rather than when you start the program? I find that extremely unlikely, otherwise e.g. you could never have more textures than fit in Android's meager RAM. \$\endgroup\$
    – user744
    Commented Jul 27, 2010 at 20:17
  • \$\begingroup\$ @Joe take a look at my other question about "loading screens": gamedev.stackexchange.com/questions/1171/… Hitting an empty cache means long times to go to to disk and could result in some FPS performance hits on those first calls. If you already know what you're going to hit ahead of time, might as well hit it during loading to pre-cache it, right? \$\endgroup\$ Commented Jul 27, 2010 at 21:46
  • \$\begingroup\$ Again I can't speak to Android, but usually going to disk is exactly what you can do without taking FPS hits, because the thread going to disk won't use up any CPU at all. You just need to budget doing it far enough in advance that you don't get pop-in. If you're going to pre-cache everything because you know ahead of time what you need, you really don't need an AssetManager, because you don't need to manage assets at all - they're already all at hand. \$\endgroup\$
    – user744
    Commented Jul 27, 2010 at 21:58
  • 1
    \$\begingroup\$ @Joe, isn't a factory also a "Dedicated Manager"? \$\endgroup\$
    – MSN
    Commented Jul 27, 2010 at 23:09
4
\$\begingroup\$

What I like to do for assets is to set up a lump manager. Inspired by the Doom engine, lumps are pieces of data that contain assets, stored in a lump file that declares lumps' names, lengths, type (bitmap, sound, shader, etc.), and content type (file, another lump, inside the lump file itself). On startup, these lumps are entered into a binary tree, but not loaded yet. Each map (which is also a lump) has a list of dependencies, which are simply the names of lumps that the map needs to work. These lumps, unless they've already been loaded, are loaded at the time the map is loaded. Additionally, the map's adjoining maps' lumps are loaded, just not at the same time, but when the engine is idling for some reason. This can make the maps seamless, and there is no loading screen.

My method is perfect for open-world maps, but a level-based game won't benefit from the seamlessness this method gets you. Hope this helps!

\$\endgroup\$
2
\$\begingroup\$

I've always thought that a good asset manager should have several modes of operation. These modes would most likely be separate source modules adhering to a common interface. The two basic modes of operation would be:

  • Production Mode - all assets are local and stripped of all meta data
  • Development Mode - assests are stored in a database (e.g. MySQL, etc) with additional meta data. The database would be a two tier system with a local database caching a shared database. Content creators would be able to edit and update the shared database and updates automatically propegated to developer / QA systems. It should also be possible to create placeholder content. Since everything is in a database, queries can be made on the database and reports generated to analyse the state of the production.

You'd need a tool that can grab all the assests from the shared database and create the production dataset.

In my years as a developer, I've never seen anything like this, although I've only worked for a handful of companies so my view is not really representative.

Update

OK, some negative votes. I'll expand on this design.

Firstly, you don't really need factory classes because if you've got:

TextureHandle tex = pRm->getResource<Texture>( "test.otx" );

you know the type, so just do:

TextureHandle tex = new TextureHandle ("test.otx");

but then, what I was trying to say above is that you wouldn't be using explicit filenames anyway, the texture to load would be specified by the model the texture is used on, so you don't actually need a human readable name, it could be a 32 bit integer value, which is much easier for the CPU to handle. So, in the constructor for TextureHandle you'd have:

if (texture already loaded)
  update texture reference count
else
  asset_stream = new AssetStream (resource_id)
  asset_stream->ReadBytes
  create texture
  set texture ref count to 1

AssetStream uses the resource_id parameter to find the location of the data. The way it did this would be dependant on the environment you're running in:

In Development: the stream looks up the ID in a database (using SQL for example) to get a filename and then opens the file, the file could be cached locally, or pulled from a server if the local file doesn't exist or is out of date.

In Release: the stream looks up the ID in a key/value table to get an offset/size into a large, packed file (like Doom's WAD file).

\$\endgroup\$
2
  • \$\begingroup\$ I voted you down because you suggested shoehorning everything into a SQL table with primary keys rather than using a real VCS. Also I consider using opaque IDs rather than string names premature optimization. I used strings on two large projects for all assets other than translation keys, of which we had hundreds of thousands of very long string keys (and then only to port to consoles). They were usually normalized so we could use pointer compares rather than string compares, but string compares are often dominated by the cost of the memory fetch and not the actual compare anyway. \$\endgroup\$
    – user744
    Commented Jul 29, 2010 at 14:23
  • \$\begingroup\$ @Joe: I only gave SQL as an example and then only in a development environment, you could equally use a VCS. I only suggested SQL database since you can then add extra information to the stored objects and use the SQL functions to query information from the database (more a management gain than anything else). As for opaque IDs as premature optimisation - some might see it that way I guess, but I think it would be easier to start off with that rather than show-horning it in at a later point in the development. I don't think it would affect development much if you used ID or strings. \$\endgroup\$
    – Skizz
    Commented Jul 29, 2010 at 15:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .