Managing resources

Started by
3 comments, last by Tocs1001 10 years, 9 months ago

I have a resource manager that loads and returns resources. Currently I just return shared_ptrs for the resources I create, but I'm thinking about going over to using something like simple handles, whereby you get an ID you use to access the resource. The reasons are more explicit ownership and also less overhead rather than using smart_ptrs.

Are resource handles the most efficient way to access resources, should I stick with shared_ptrs or are there other alternatives?

The reason I am asking is that I was reading this article http://www.gamedev.net/page/resources/_/technical/game-programming/a-simple-fast-resource-manager-using-c-and-stl-r2503 which made me wonder if I should implement something like that instead.

Thanks

Advertisement

I'm not a big fan of shared_ptr for resources, because it places you at the mercy of the client developer. As long as he holds onto that shared_ptr, you can't dispose of the resource. If you switch to handles, then you have the option of disposing of inactive resources in low-memory situations, and re-loading them lazily next time they are accessed.

This may not be a big concern for PC development, where memory is plentiful. However, on mobile, not only is memory in short supply, but the OS will also kill your application summarily if it feels you are hogging memory...

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]


Are resource handles the most efficient way to access resources, should I stick with shared_ptrs or are there other alternatives?

there are always lot of alternatives, probably too many alternatives.

one thing to decide is the trade off between efficiency and traditional "good coding practices". although, a good coder can usually achieve most of both at once.

i use the mantra of "let the code fit the task", and the KISS principle. so i use static global resource data structures and ID numbers (handles). no pointers, no mallocs, no frees, none of that stuff. hence no memory leaks etc. a data structure may be an array of objects (such as textures), but they all get created at once at program start, and all get released at once at program shutdown.

another good strategy is divvying things up based on data type, such as skinned mesh, vs memory image of static model, vs streaming terrain chunks. do a search on recent posts by "L. Spiro" here on gamedev for more details. I believe her(?) posts also address the pointers question.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

I often use ID numbers as well, and sometimes opaque tagged-reference types, however a difference I think is that most of my memory is heap allocated (though typically not with "malloc()", as it sort of has a problem of "generally not being very good on most targets"...).

I usually like to keep the internals of things hidden behind APIs though.

one drawback that is (sometimes) an issue with using raw integers for a handle is that they can allow unsafe conversions between different types, leading to the IMO sometimes ironic solution of using a struct to wrap a single integer (or sometimes a pointer):

typedef struct fooMyHandle_s { int v; } fooMyHandle;

basically for the reason that this helps prevent accidental unsafe conversions and also can generally be optimized away by the compiler.

this may not always be an appropriate solution though, and in my case is generally used for things like tagged references (where, for example, the low-order bits will encode the specific sub-type, and the high-order bits may hold the value or identify the referenced object).

generally, unlike with traditional pointer-based practices, in these cases I generally prefer against transfer of ownership.

for example, with pointers it is often implied that ownership of the pointed-to object is passed along with the pointer, pretty much with the "last party to use it" responsible for its release (via free or delete or whatever else).

with handles or opaque references, it is more common for the ownership to be retained by the API which created the resource, with it more often serving as a means of referring to the object rather than as a means of ownership.

granted, this is not always the case though, for example, in the tagged references used by my dynamic type-system, the references do generally transfer ownership over the referred-to object (well, and for on-heap types, it is possible to convert back and forth between these references and the use of explicit pointers), and I do typically use explicit freeing (my project uses a GC but it isn't very good, so avoiding leaks is preferable).

another difference is that a tagged reference (unlike a pointer) need not refer to a concrete memory address, and as such gives an API the ability to relocate or change the form of the underlying memory objects.

an example in a resource manager would be unloading the resource, but still keeping around a "stub" which if-used will trigger the resource to be pulled back in (when non-resident, its storage may simply be the name of the resource, or whatever else, which is then replaced with the actual resource when loaded, and then swapped back for its name when it is later unloaded).

granted, what is most appropriate for a given API may depend a fair bit on what is most appropriate for the API, and doing otherwise may lead to pain or other issues later on.

for example, an opaque handle is a painful solution to cases where really you just need a class or struct pointer.

but, OTOH, such a pointer (or giving access to its contents) may not be necessary or appropriate, say, if the API retains strict control over the object (which may not necessarily have context, or existence, outside the API).

sometimes (especially with tagged-reference systems), the actual "object" may not actually exist in memory (it is simply a literal value itself contained within the reference), ...

for example, in my dynamic type-system, I eventually ended up using 64-bit references even on 32-bit targets, mostly because of a subtle issue:

there were lots of boxed values (int/long/float/double/...), which were proving expensive (and my 32-bit pointer-based system could only give 28 bits to the floating-point type, ...). it turned out to eventually be cheaper simply to switch a lot of stuff over to 64-bit references, and use twiddly such that most of the double and long-long value range could be encoded without needing to box these values. (the savings due to avoiding as much boxing mostly outweighed the costs due to the larger references...).

of course, a lot of parts of the engine still use the older (pointer-based) system, which still results in a lot of boxing...

granted, wherever possible it is preferable to use a statically-typed values, but there are cases where mandating everything to be statically typed itself turns out to be rather limiting, so this isn't always the best solution either.

(it isn't generally as ugly/hackish as it all might sound, generally because most of the mechanics are kept hidden behind an API).

or such...

I ended up writing my own smart pointer for resources.
I'm only mildly happy with it. There might be errors in my reference counting shenanigans but I haven't noticed a memory issue yet, though I haven't put it through the wringer yet. And there's probably some C++ sloppyness in there but hopefully the idea gets across.
It functions like a shared_ptr<> because it reference counts. But it lets you handle the storage of the asset. So if you wanted to unload assets until they're used again you could do that on the back end without uses of Asset<> ever knowing. Since its a template, it duck types a LoadFromFile () static function on the type you pass to handle loading. Eventually I want to add in things like loading on another thread and using a dummy asset until its complete. And automatic reloading of modified files. It can all be done behind the scenes.
I have classes like "Texture2D", "Mesh", "Shader" and these all provide a LoadFromFile (). I simply call Asset<Texture2D>::Load("Somefile.png");
I have a special function if I have to use a generated asset.(Such as a procedurally generated texture.) I can either give it a name and enter it into the registry of assets, or I can "wrap" it which creates an Asset<> who's job is not to delete the resource its referencing. Instead its some external object's job to delete it. This is the part I'm not entirely comfortable with, but it unifies the two methods of resource containment. (File Loaded and Generated).
I can't pass extra arguments into LoadFromFile (Though this could be solved with a variardic template). There is global state here, but I suppose it wouldn't be hard to have some sort of "AssetCache<T>" instead of using global state. Also I'm using a std::map<> because I was lazy which can be slightly slow depending on how picky you are, however I don't call Asset<T>::Load() every frame. However if I have some sort of rapidly created and destroyed object that initialized itself by acquiring an asset I could hammer my std::map. Like I said I'm only mildly happy with it but I thought I'd post it in case it provided some sort of idea, also if Its a horrible idea I would love to know too.

This topic is closed to new replies.

Advertisement