diff --git a/source/lib/res/h_mgr.h b/source/lib/res/h_mgr.h index 22843fc850..92a249478a 100755 --- a/source/lib/res/h_mgr.h +++ b/source/lib/res/h_mgr.h @@ -195,154 +195,153 @@ extern int res_cur_scope; #endif // #ifndef H_MGR_H__ +/* + +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. -// 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. +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 -// int 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); -// { -// // 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!). -// -// -- -// -// dtor: -// frees all data allocated by init and reload. called after h_free, -// or at exit. control block is 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, /* additional param passed to init -> */ my_flags); -// } -// -// int res1_free(Handle& h) -// { -// return h_free(h, H_Res1); -// } -// -// the h parameter is zeroed by h_free. -// -// (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). +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 + int 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); + { + // 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!). + + -- + + dtor: + frees all data allocated by init and reload. called after h_free, + or at exit. control block is 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); + } + + the h parameter is zeroed by h_free. + + (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). + +*/