is there a better way top refer to assets in a game?

Started by
32 comments, last by Norman Barrows 9 years, 4 months ago

In my system I've been developing I do as Zipster says. There is no reason for the native code to deal with specific resources - it's all routed through script. Since I'm implementing a sort of Scheme dialect, I have hashed keywords out of the box, which work just fine. I limit the use of strings strictly to user interfaces.

My reinterpretation of Zipster's example would be:


(entity gigantopithicus
  (attack-sound :gigantopithicus-attack.wav))

"So there you have it, ladies and gentlemen: the only API I’ve ever used that requires both elevated privileges and a dedicated user thread just to copy a block of structures from the kernel to the user." - Casey Muratori

boreal.aggydaggy.com

Advertisement

I've seen both of those in practice, but ultimately this is rarely a concern on PC games.

Yes, there is a tiny cost of dealing with a text string. That cost is completely dwarfed by the costs of IO.

Back on the small handheld era, with space measured in "megabits" and "kilobits", we used a macro solution similar to what was suggested. Something like:


result = LoadModel( MAKE_RESOURCE_KEY( "real_name", 0x12345) );

The macro would validate the name matched the hash and made an entry in some debugging logs.

But as phantom and others pointed out, that is a fairly rare operation and the result is a pointer. After that every time you deal with it you are working with a pointer instead so there is no concern.

Also, these days on the PC you are much less likely to pre-process all your resources down to integer IDs, and the cost of computing the IDs on the fairly rare times you need them is extremely small for simple hashing algorithms. Even on SSD you're looking at an overhead of milliseconds for the disk read, spending a microsecond on the rarely-executed hash is not going to be a performance concern.

Most of us are not on systems any more where encoding "filename.mus" in the source code causes too much data in the executable. If we really need a number we can hash it and use the result.

This is a solution to a problem we don't normally have any more.

so i take it that data driven is the normal approach, eh? and binding of human readable format to some numeric internal representation is done prior to runtime or perhaps at load time?

but i assume that real time on the fly filename to array index conversion would still be too slow for non-data driven, correct? if i'm drawing 16,000 non-instanced meshes, i don't want to be looking up the array index for the mesh filename of each one. especially if i have hundreds of mesh filenames to search through.

.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php


Do you have evidence to back up that claim? How is passing a string to a sound playing function noticeably slower than passing an integer?

you'd have to convert the string to an array index, then call playwav(i) :

#define maxwavs 200

playwav(filename):

for i=0 i<maxwavs i++

{

if strcmp(wav.filename,filename)==0)

{

playwav(i);

return

}

}

playwav(i) is simply wav.lpd3dsoundbuffer.play (or whatever the exact xaudio2 call is - i'm too lazy to pull up the exact code at the moment, but you get the idea).

only by use of a #define or enum or data driven do you avoid the string to index lookup.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php


In any case, you shouldn't have code like playwav("some_sound.wav"); though -- more like:
Sound* some_sound = loadwav("some_sound.wav"); //filename processing paid once, pointer obtained
...
playwav(some_sound); // no details of filesystem involved per frame

oh trust me, i don't! <g>

its more like playwav(some_sound_pointer);

but i'd like to be able to use some more human readable format such as playwav(some_sound_name);

preferably without a slow lookup, making everything data driven, or enum'ing everything, but those seem to be the only options.

i guess enums would be no more typing than some sort of lookup table, but more typing than coding up a simple string lookup function.

otoh, simply keeping a side list of filenames and their indexes isn't that big a deal, i may just stick with that for non-data driven stuff. its less typing than enums.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Yes using a string to refer assets is slower than integer. But it will not be "noticeably" slower. Unless you are referring it ten thousand times each frame, and that means your design is probably wrong.

Since you are doing this on a PC, time and space spent on this simply do not matter. I would say you should not waste too much time on this, it would probably give you a 0.01% performance gain with a super fast resource id;

A simple modification you can do is instead of a linear search through a array of strings, use something like map<string,asset> that can do it in log(n).

I strongly disagree with anyone recommending integer values or enumerations. They're ugly has hell and they can seriously damage your "flow." What happens if I want my artist and designers to constantly iterate their work? They'll have to get knee deep in my source code just to add a few lines in strange places.

The handle + string approach is much easier. Simply return a handle (can be as simple as an integer), and you can dynamically register new content easily without having to edit anything.


AssetCache asset_cache;
SoundEngine sound_engine;

// Index in a vector or something showing where the sound is located
unsigned int sound_handle = asset_cache.get_sound("test_sound.ogg");

// With some operator-overloading magic, this can easily work.
//
// Just figure out what the resource type at the index is, and return a pointer/reference to the raw sound resource
sound_engine.play(asset_cache[sound_handle]);


so i take it that data driven is the normal approach, eh?

In every modern engine I've worked with, yeah. The primary motivation is to eliminate the engineer from the content iteration loop, since engineering resources are costly and slow. And in the case of updating asset references, greatly misused.

I strongly disagree with anyone recommending integer values or enumerations. They're ugly has hell and they can seriously damage your "flow." What happens if I want my artist and designers to constantly iterate their work? They'll have to get knee deep in my source code just to add a few lines in strange places.

That's why you shouldn't have them in the source at all in my opinion. Neither strings nor integers/enums.

Artist edits the "resource definition file" or whatever you call it, preferrably with a special editor for easier workflow, but in the simplest case that can happen in a text editor, writing out XML or JSON or any other format, even a custom one if you want.

Artist refers to "kaboom.wav" as "explosion_sound" when referencing it from within "grenade". The toolchain packs the whole stuff together into a binary file. That file can contain the strings and you look up assets by strings (but this requires using the equivalent of a map structure at runtime), or the build system will translate "explosion_sound" to, say 51 and "grenade" to, say, 213. If the artist edits the file, it may happen that the numbers are different, but that doesn't matter since only the build system has to worry that the mapping is consistent (that is, if asset #213 references #51 and due to a change #51 becomes #63, then #213 references #63). The application only uses what the datafile provides, it needs not care about consistency.

While it is true that the overhead of hashing a string or even looking up a string in a map is neglegible compared to disk I/O it is also true that this overhead is completely unnecessary. Hashes or IDs can be calculated at compile-time if you insist on having the names hardcoded (but I recommend against that unless you really only have 5 assets), and are otherwise calculated by the build tool.

Most of us are not on systems any more where encoding "filename.mus" in the source code causes too much data in the executable.

But it's not really about the size of that string (nor the overhead).

Artists do not want to, and should not tamper with source files. And you do not want to, nor should you need to recompile the whole program only because the artist decided to add another sound or another sprite. Making the application run is your responsibility -- keep it there. Putting the "art stuff" together is the artist's responsibility -- keep it there, as well. Don't mix the two, and don't mess with something that's not your responsibility. Changing one component should not require rebuilding the other, nor should it possibly make it fail. Saying that hardcoding assets and having artists edit source files is a guarantee for failure would probably be going too far, but you get me. It's something that can break, and things that can break will eventually break.

if i'm drawing 16,000 non-instanced meshes, i don't want to be looking up the array index for the mesh filename of each one

Good grief, who is modelling all these? Surely you mean 160 -- not 16,000?

if i'm drawing 16,000 non-instanced meshes, i don't want to be looking up the array index for the mesh filename of each one.


Well, no... even if you weren't doing 'data driven' you'd still pre-cache the index on creation because doing it every draw would be dumb dumb dumb - if you are looking up data which can not change every frame on every frame then you are Doing It Wrong regardless of if you are doing the look up via text, id or lasers bounced off the moon.

Also 16,000 draw calls is going to crash your framerate so you've already got problems...

This topic is closed to new replies.

Advertisement