dox now in /**/ comments
This was SVN commit r178.
This commit is contained in:
parent
757aca19ef
commit
dc4cc78e7b
@ -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).
|
||||
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user