introduction


a resource is an instance of a specific type of game data (e.g. texture), described by a control block (example fields: format, pointer to tex data).

this module allocates storage for the control blocks, which are accessed via handle. it also provides support for transparently reloading resources from disk (allows in-game editing of data), and caches resource data. finally, it frees all resources at exit, preventing leaks.

handles


handles are an indirection layer between client code and resources (represented by their control blocks, which contains/points to its data). they allow an important check not possible with a direct pointer: guaranteeing the handle references a given resource /instance/.

problem: code C1 allocates a resource, and receives a pointer p to its control block. C1 passes p on to C2, and later frees it. now other code allocates a resource, and happens to reuse the free slot pointed to by p (also possible if simply allocating from the heap). when C2 accesses p, the pointer is valid, but we cannot tell that it is referring to a resource that had already been freed. big trouble.

solution: each allocation receives a unique tag (a global counter that is large enough to never overflow). Handles include this tag, as well as a reference (array index) to the control block, which isn't directly accessible. when dereferencing the handle, we check if the handle's tag matches the copy stored in the control block. this protects against stale handle reuse, double-free, and accidentally referencing other resources.

type: each handle has an associated type. these must be checked to prevent using textures as sounds, for example. with the manual vtbl scheme, this type is actually a pointer to the resource object's vtbl, and is set up via H_TYPE_DEFINE. this means that types are private to the module that declared the handle; knowledge of the type ensures the caller actually declared, and owns the resource.

guide to defining and using resources


1) choose a name for the resource, used to represent all resources of this type. we will call ours Res1; all occurences of it below must be replaced with the actual name (exact spelling). why? the vtbl builder defines its functions as e.g. Res1_reload; your actual definition must match.

2) declare its control block:

   struct Res1
   {
       void* data1;	// data loaded from file
       uint flags;	// set when resource is created
   };

3) build its vtbl:

   H_TYPE_DEFINE(Res1);

this defines the symbol H_Res1, which is used whenever the handle manager needs its type. it is only accessible to this module (file scope). note that it is actually a pointer to the vtbl. this must come before uses of H_Res1, and after the CB definition; there are no restrictions WRT functions, because the macro forward-declares what it needs.

4) implement all 'virtual' functions from the resource interface. note that inheritance isn't really possible with this approach - all functions must be defined, even if not needed.

--

init: one-time init of the control block. called from h_alloc. precondition: control block is initialized to 0.

   static void Type_init(Res1* r, va_list args)
   {
       r->flags = va_arg(args, int);
   }

if the caller of h_alloc passed additional args, they are available in args. if init references more args than were passed, big trouble. however, this is a bug in your code, and cannot be triggered maliciously. only your code knows the resource type, and it is the only call site of h_alloc. there is no provision for indicating failure. if one-time init fails (rare, but one example might be failure to allocate memory that is for the lifetime of the resource, instead of in reload), it will have to set the control block state such that reload will fail.

--

reload: does all initialization of the resource that requires its source file. called after init; also after dtor every time the file is reloaded.

   static int Type_reload(Res1* r, const char* filename, Handle);
   {
       // somehow load stuff from filename, and store it in r->data1.
       return 0;
   }

reload must abort if the control block data indicates the resource has already been loaded! example: if texture's reload is called first, it loads itself from file (triggering file.reload); afterwards, file.reload will be called again. we can't avoid this, because the handle manager doesn't know anything about dependencies (here, texture -> file). return value: 0 if successful (includes 'already loaded'), negative error code otherwise. if this fails, the resource is freed (=> dtor is called!).

note that any subsequent changes to the resource state must be stored in the control block and 'replayed' when reloading. example: when uploading a texture, store the upload parameters (filter, internal format); when reloading, upload again accordingly.

--

dtor: frees all data allocated by init and reload. called after h_free, or at exit. control block will be zeroed afterwards.

   static void Type_dtor (Res1* r);
   {
       // free memory r->data1
   }

again no provision for reporting errors - there's no one to act on it if called at exit. you can assert or log the error, though.

5) provide your layer on top of the handle manager:

   Handle res1_load(const char* filename, int my_flags)
   {
       return h_alloc(H_Res1, filename, 0, my_flags);	// my_flags is passed to init
   }
   int res1_free(Handle& h)
   {
       return h_free(h, H_Res1);
       // zeroes h afterwards
   }

(this layer allows a res_load interface on top of all the loaders, and is necessary because your module is the only one that knows H_Res1).

6) done. the resource will be freed at exit (if not done already).

here's how to access the control block, given a handle: a)

   Handle h;
   H_DEREF(h, Res1, r);

creates a variable r of type Res1*, which points to the control block of the resource referenced by h. returns "invalid handle" (a negative error code) on failure. b)

   Handle h;
   Res1* r = h_user_data(h, H_Res1);
      if(!r)
          ; // bail

useful if H_DEREF's error return (of type signed integer) isn't acceptable. otherwise, prefer a) - this is pretty clunky, and we could switch H_DEREF to throwing an exception on error.

Last modified 16 years ago Last modified on Feb 23, 2008, 4:18:58 AM
Note: See TracWiki for help on using the wiki.