Changes between Initial Version and Version 1 of Handle_manager


Ignore:
Timestamp:
Feb 23, 2008, 4:18:58 AM (16 years ago)
Author:
trac
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Handle_manager

    v1 v1  
     1
     2introduction
     3------------
     4
     5a resource is an instance of a specific type of game data (e.g. texture),
     6described by a control block (example fields: format, pointer to tex data).
     7
     8this module allocates storage for the control blocks, which are accessed
     9via handle. it also provides support for transparently reloading resources
     10from disk (allows in-game editing of data), and caches resource data.
     11finally, it frees all resources at exit, preventing leaks.
     12
     13
     14handles
     15-------
     16
     17handles are an indirection layer between client code and resources
     18(represented by their control blocks, which contains/points to its data).
     19they allow an important check not possible with a direct pointer:
     20guaranteeing the handle references a given resource /instance/.
     21
     22problem: code C1 allocates a resource, and receives a pointer p to its
     23control block. C1 passes p on to C2, and later frees it.
     24now other code allocates a resource, and happens to reuse the free slot
     25pointed to by p (also possible if simply allocating from the heap).
     26when C2 accesses p, the pointer is valid, but we cannot tell that
     27it is referring to a resource that had already been freed. big trouble.
     28
     29solution: each allocation receives a unique tag (a global counter that
     30is large enough to never overflow). Handles include this tag, as well
     31as a reference (array index) to the control block, which isn't directly
     32accessible. when dereferencing the handle, we check if the handle's tag
     33matches the copy stored in the control block. this protects against stale
     34handle reuse, double-free, and accidentally referencing other resources.
     35
     36type: each handle has an associated type. these must be checked to prevent
     37using textures as sounds, for example. with the manual vtbl scheme,
     38this type is actually a pointer to the resource object's vtbl, and is
     39set up via H_TYPE_DEFINE. this means that types are private to the module
     40that declared the handle; knowledge of the type ensures the caller
     41actually declared, and owns the resource.
     42
     43
     44guide to defining and using resources
     45-------------------------------------
     46
     471) choose a name for the resource, used to represent all resources
     48of this type. we will call ours Res1; all occurences of it below
     49must be replaced with the actual name (exact spelling).
     50why? the vtbl builder defines its functions as e.g. Res1_reload;
     51your actual definition must match.
     52
     532) declare its control block:
     54{{{
     55   struct Res1
     56   {
     57       void* data1;     // data loaded from file
     58       uint flags;      // set when resource is created
     59   };
     60}}}
     61
     623) build its vtbl:
     63{{{
     64   H_TYPE_DEFINE(Res1);
     65}}}
     66
     67this defines the symbol H_Res1, which is used whenever the handle
     68manager needs its type. it is only accessible to this module
     69(file scope). note that it is actually a pointer to the vtbl.
     70this must come before uses of H_Res1, and after the CB definition;
     71there are no restrictions WRT functions, because the macro
     72forward-declares what it needs.
     73
     744) implement all 'virtual' functions from the resource interface.
     75note that inheritance isn't really possible with this approach -
     76all functions must be defined, even if not needed.
     77
     78--
     79
     80init:
     81one-time init of the control block. called from h_alloc.
     82precondition: control block is initialized to 0.
     83
     84{{{
     85   static void Type_init(Res1* r, va_list args)
     86   {
     87       r->flags = va_arg(args, int);
     88   }
     89}}}
     90
     91if the caller of h_alloc passed additional args, they are available
     92in args. if init references more args than were passed, big trouble.
     93however, this is a bug in your code, and cannot be triggered
     94maliciously. only your code knows the resource type, and it is the
     95only call site of h_alloc.
     96there is no provision for indicating failure. if one-time init fails
     97(rare, but one example might be failure to allocate memory that is
     98for the lifetime of the resource, instead of in reload), it will
     99have to set the control block state such that reload will fail.
     100
     101--
     102
     103reload:
     104does all initialization of the resource that requires its source file.
     105called after init; also after dtor every time the file is reloaded.
     106
     107{{{
     108   static int Type_reload(Res1* r, const char* filename, Handle);
     109   {
     110       // somehow load stuff from filename, and store it in r->data1.
     111       return 0;
     112   }
     113}}}
     114
     115reload must abort if the control block data indicates the resource
     116has already been loaded! example: if texture's reload is called first,
     117it loads itself from file (triggering file.reload); afterwards,
     118file.reload will be called again. we can't avoid this, because the
     119handle manager doesn't know anything about dependencies
     120(here, texture -> file).
     121return value: 0 if successful (includes 'already loaded'),
     122negative error code otherwise. if this fails, the resource is freed
     123(=> dtor is called!).
     124
     125note that any subsequent changes to the resource state must be
     126stored in the control block and 'replayed' when reloading.
     127example: when uploading a texture, store the upload parameters
     128(filter, internal format); when reloading, upload again accordingly.
     129
     130--
     131
     132dtor:
     133frees all data allocated by init and reload. called after h_free,
     134or at exit. control block will be zeroed afterwards.
     135
     136{{{
     137   static void Type_dtor (Res1* r);
     138   {
     139       // free memory r->data1
     140   }
     141}}}
     142
     143again no provision for reporting errors - there's no one to act on it
     144if called at exit. you can assert or log the error, though.
     145
     1465) provide your layer on top of the handle manager:
     147{{{
     148   Handle res1_load(const char* filename, int my_flags)
     149   {
     150       return h_alloc(H_Res1, filename, 0, my_flags);   // my_flags is passed to init
     151   }
     152}}}
     153
     154{{{
     155   int res1_free(Handle& h)
     156   {
     157       return h_free(h, H_Res1);
     158       // zeroes h afterwards
     159   }
     160}}}
     161
     162(this layer allows a res_load interface on top of all the loaders,
     163and is necessary because your module is the only one that knows H_Res1).
     164
     1656) done. the resource will be freed at exit (if not done already).
     166
     167here's how to access the control block, given a handle:
     168a)
     169{{{
     170   Handle h;
     171   H_DEREF(h, Res1, r);
     172}}}
     173
     174creates a variable r of type Res1*, which points to the control block
     175of the resource referenced by h. returns "invalid handle"
     176(a negative error code) on failure.
     177b)
     178{{{
     179   Handle h;
     180   Res1* r = h_user_data(h, H_Res1);
     181      if(!r)
     182          ; // bail
     183}}}
     184
     185useful if H_DEREF's error return (of type signed integer) isn't
     186acceptable. otherwise, prefer a) - this is pretty clunky, and
     187we could switch H_DEREF to throwing an exception on error.