1
0
forked from 0ad/0ad

dox now in /**/ comments

This was SVN commit r178.
This commit is contained in:
janwas 2004-03-07 14:24:47 +00:00
parent 757aca19ef
commit dc4cc78e7b

View File

@ -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).
*/