2004-03-03 16:16:20 +01:00
|
|
|
// handle manager
|
2004-03-03 00:56:51 +01:00
|
|
|
//
|
|
|
|
// Copyright (c) 2003 Jan Wassenberg
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// Contact info:
|
|
|
|
// Jan.Wassenberg@stud.uni-karlsruhe.de
|
|
|
|
// http://www.stud.uni-karlsruhe.de/~urkt/
|
|
|
|
|
2004-03-03 16:16:20 +01:00
|
|
|
#ifndef H_MGR_H__
|
|
|
|
#define H_MGR_H__
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-10-06 16:00:43 +02:00
|
|
|
// do not include from public header files!
|
|
|
|
// handle.h declares type Handle, and avoids making
|
|
|
|
// everything dependent on this (rather often updated) header.
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
#include <stdarg.h> // type init routines get va_list of args
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
#include "lib.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-10-06 16:00:43 +02:00
|
|
|
#ifndef HANDLE_DEFINED
|
|
|
|
#include "handle.h"
|
|
|
|
#endif
|
2004-06-26 00:19:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// handle type (for 'type safety' - can't use a texture handle as a sound)
|
2004-06-01 19:34:12 +02:00
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
//
|
|
|
|
// rationale: we could use the destructor passed to h_alloc to identify
|
|
|
|
// the handle, but it's good to have a list of all types, and we avoid having
|
|
|
|
// to create empty destructors for handle types that wouldn't need them.
|
|
|
|
// finally, we save memory - this fits in a few bits, vs. needing a pointer.
|
|
|
|
|
|
|
|
// registering extension for each module is bad - some may use many
|
|
|
|
// (e.g. texture - many formats).
|
|
|
|
// handle manager shouldn't know about handle types
|
|
|
|
|
|
|
|
/*
|
|
|
|
enum H_Type
|
|
|
|
{
|
|
|
|
H_Mem = 1,
|
|
|
|
H_ZArchive = 2,
|
|
|
|
H_ZFile = 3,
|
|
|
|
H_VFile = 4,
|
|
|
|
H_VRead = 5,
|
|
|
|
|
|
|
|
H_Tex = 6,
|
|
|
|
H_Font = 7,
|
|
|
|
H_Sound = 8,
|
|
|
|
|
|
|
|
NUM_HANDLE_TYPES
|
|
|
|
};
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
///xxx advantage of manual vtbl:
|
|
|
|
no boilerplate init, h_alloc calls ctor directly, make sure it fits in the memory slot
|
|
|
|
vtbl contains sizeof resource data, and name!
|
|
|
|
but- has to handle variable params, a bit ugly
|
|
|
|
*/
|
|
|
|
|
|
|
|
// 'manual vtbl' type id
|
|
|
|
// handles have a type, to prevent using e.g. texture handles as a sound.
|
|
|
|
//
|
|
|
|
// alternatives:
|
|
|
|
// - enum of all handle types (smaller, have to pass all methods to h_alloc)
|
|
|
|
// - class (difficult to compare type, handle manager needs to know of all users)
|
|
|
|
//
|
|
|
|
// checked in h_alloc:
|
2004-03-03 16:16:20 +01:00
|
|
|
// - user_size must fit in what the handle manager provides
|
2004-03-03 00:56:51 +01:00
|
|
|
// - name must not be 0
|
|
|
|
//
|
|
|
|
// init: user data is initially zeroed
|
2004-03-03 16:16:20 +01:00
|
|
|
// dtor: user data is zeroed afterwards
|
2004-03-03 00:56:51 +01:00
|
|
|
// reload: if this resource type is opened by another resource's reload,
|
|
|
|
// our reload routine MUST check if already opened! This is relevant when
|
2004-03-03 16:16:20 +01:00
|
|
|
// a file is reloaded: if e.g. a sound object opens a file, the handle
|
2004-03-03 00:56:51 +01:00
|
|
|
// manager calls the reload routines for the 2 handles in unspecified order.
|
|
|
|
// ensuring the order would require a tag field that can't overflow -
|
|
|
|
// not really guaranteed with 32-bit handles. it'd also be more work
|
|
|
|
// to sort the handles by creation time, or account for several layers of
|
|
|
|
// dependencies.
|
|
|
|
struct H_VTbl
|
|
|
|
{
|
|
|
|
void(*init)(void* user, va_list);
|
2004-06-26 00:19:19 +02:00
|
|
|
int(*reload)(void* user, const char* fn, Handle);
|
2004-03-03 00:56:51 +01:00
|
|
|
void(*dtor)(void* user);
|
|
|
|
size_t user_size;
|
|
|
|
const char* name;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef H_VTbl* H_Type;
|
|
|
|
|
2004-06-02 22:41:05 +02:00
|
|
|
#define H_TYPE_DEFINE(type)\
|
2004-03-03 16:16:20 +01:00
|
|
|
/* forward decls */\
|
2004-06-02 22:41:05 +02:00
|
|
|
static void type##_init(type*, va_list);\
|
2004-06-26 00:19:19 +02:00
|
|
|
static int type##_reload(type*, const char*, Handle);\
|
2004-06-02 22:41:05 +02:00
|
|
|
static void type##_dtor(type*);\
|
|
|
|
static H_VTbl V_##type =\
|
2004-03-03 16:16:20 +01:00
|
|
|
{\
|
2004-06-02 22:41:05 +02:00
|
|
|
(void(*)(void*, va_list))type##_init,\
|
2004-06-26 00:19:19 +02:00
|
|
|
(int(*)(void*, const char*, Handle))type##_reload,\
|
2004-06-02 22:41:05 +02:00
|
|
|
(void(*)(void*))type##_dtor,\
|
|
|
|
sizeof(type), /* control block size */\
|
|
|
|
#type /* name */\
|
2004-03-03 16:16:20 +01:00
|
|
|
};\
|
2004-06-11 04:14:18 +02:00
|
|
|
static H_Type H_##type = &V_##type
|
2004-03-03 16:16:20 +01:00
|
|
|
|
|
|
|
// note: we cast to void* pointers so the functions can be declared to
|
|
|
|
// take the control block pointers, instead of requiring a cast in each.
|
|
|
|
// the forward decls ensure the function signatures are correct.
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-02 22:41:05 +02:00
|
|
|
// convenience macro for h_user_data:
|
|
|
|
// casts its return value to the control block type.
|
|
|
|
// use if H_DEREF's returning a negative error code isn't acceptable.
|
|
|
|
#define H_USER_DATA(h, type) (type*)h_user_data(h, H_##type)
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-02 22:41:05 +02:00
|
|
|
// even more convenient wrapper for h_user_data:
|
|
|
|
// declares a pointer (<var>), assigns it H_USER_DATA, and has
|
|
|
|
// the user's function return a negative error code on failure.
|
2004-03-03 00:56:51 +01:00
|
|
|
#define H_DEREF(h, type, var)\
|
2004-06-02 22:41:05 +02:00
|
|
|
/* don't use STMT - var decl must be visible to "caller" */\
|
|
|
|
type* const var = H_USER_DATA(h, type);\
|
2004-03-03 00:56:51 +01:00
|
|
|
if(!var)\
|
|
|
|
return ERR_INVALID_HANDLE;
|
|
|
|
|
|
|
|
|
|
|
|
// all functions check the passed tag (part of the handle) and type against
|
|
|
|
// the internal values. if they differ, an error is returned.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// resource scope
|
|
|
|
// used together with flags (e.g. in mem), so no separate type
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
RES_TEMP = 1,
|
|
|
|
RES_LEVEL = 2,
|
2004-08-05 14:45:56 +02:00
|
|
|
RES_STATIC = 4
|
2004-03-03 00:56:51 +01:00
|
|
|
};
|
|
|
|
|
2004-08-05 14:45:56 +02:00
|
|
|
#define RES_SCOPE_MASK 7
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
// h_alloc flags
|
|
|
|
enum
|
|
|
|
{
|
2004-08-05 14:45:56 +02:00
|
|
|
// alias for RES_TEMP scope. the handle will not be kept open.
|
|
|
|
RES_NO_CACHE = 1,
|
|
|
|
|
2004-10-05 15:10:49 +02:00
|
|
|
// not cached, and will never reuse a previous instance
|
|
|
|
RES_UNIQUE = 1|16,
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// the resource isn't backed by a file. the fn parameter is treated as the search key (uintptr_t)
|
|
|
|
// currently only used by mem manager
|
2004-08-05 14:45:56 +02:00
|
|
|
RES_KEY = 8
|
2004-03-03 00:56:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// allocate a new handle.
|
|
|
|
// if key is 0, or a (key, type) handle doesn't exist,
|
|
|
|
// the first free entry is used.
|
|
|
|
// otherwise, a handle to the existing object is returned,
|
|
|
|
// and HDATA.size != 0.
|
|
|
|
//// user_size is checked to make sure the user data fits in the handle data space.
|
|
|
|
// dtor is associated with type and called when the object is freed.
|
|
|
|
// handle data is initialized to 0; optionally, a pointer to it is returned.
|
|
|
|
extern Handle h_alloc(H_Type type, const char* fn, uint flags = 0, ...);
|
|
|
|
extern int h_free(Handle& h, H_Type type);
|
|
|
|
|
2004-05-29 14:00:53 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// find and return a handle by key (typically filename hash)
|
2004-10-15 15:19:37 +02:00
|
|
|
// currently O(log n).
|
|
|
|
//
|
|
|
|
// HACK: currently can't find RES_UNIQUE handles, because there
|
|
|
|
// may be multiple instances of them, breaking the lookup data structure.
|
2004-03-03 00:56:51 +01:00
|
|
|
extern Handle h_find(H_Type type, uintptr_t key);
|
|
|
|
|
2004-06-02 22:41:05 +02:00
|
|
|
// returns a void* pointer to the control block of the resource <h>,
|
|
|
|
// or 0 on error (i.e. h is invalid or of the wrong type).
|
|
|
|
// prefer using H_DEREF or H_USER_DATA.
|
2004-03-03 00:56:51 +01:00
|
|
|
extern void* h_user_data(Handle h, H_Type type);
|
|
|
|
|
|
|
|
extern const char* h_filename(Handle h);
|
|
|
|
|
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
extern int h_reload(const char* fn);
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
extern int res_cur_scope;
|
|
|
|
|
2004-10-20 02:58:55 +02:00
|
|
|
// disable caching and allow the handle to actually be freed when refcount
|
|
|
|
// is at 0. used when cached resources must be closed before exit,
|
|
|
|
// e.g. sounds when reinitializing OpenAL (due to changed settings).
|
|
|
|
extern int h_allow_free(Handle h, H_Type type);
|
2004-10-15 15:19:37 +02:00
|
|
|
|
|
|
|
extern void h_mgr_shutdown();
|
|
|
|
|
|
|
|
|
2004-03-03 16:16:20 +01:00
|
|
|
#endif // #ifndef H_MGR_H__
|
2004-03-07 15:17:23 +01:00
|
|
|
|
|
|
|
|
2004-03-07 15:24:47 +01:00
|
|
|
/*
|
2004-03-07 15:17:23 +01:00
|
|
|
|
2004-03-07 15:24:47 +01:00
|
|
|
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
|
|
|
|
{
|
2004-03-07 16:00:51 +01:00
|
|
|
void* data1; // data loaded from file
|
2004-05-08 03:11:51 +02:00
|
|
|
uint flags; // set when resource is created
|
2004-03-07 15:24:47 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
3) build its vtbl:
|
2004-06-11 04:14:18 +02:00
|
|
|
H_TYPE_DEFINE(Res1);
|
2004-03-07 15:24:47 +01:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2004-06-26 00:19:19 +02:00
|
|
|
static int Type_reload(Res1* r, const char* filename, Handle);
|
2004-03-07 15:24:47 +01:00
|
|
|
{
|
|
|
|
// 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!).
|
|
|
|
|
2004-05-13 19:23:07 +02:00
|
|
|
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.
|
|
|
|
|
2004-03-07 15:24:47 +01:00
|
|
|
--
|
|
|
|
|
|
|
|
dtor:
|
|
|
|
frees all data allocated by init and reload. called after h_free,
|
2004-05-13 19:23:07 +02:00
|
|
|
or at exit. control block will be zeroed afterwards.
|
2004-03-07 15:24:47 +01:00
|
|
|
|
|
|
|
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);
|
2004-03-07 16:00:51 +01:00
|
|
|
// zeroes h afterwards
|
2004-03-07 15:24:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
(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).
|
2004-03-07 15:17:23 +01:00
|
|
|
|
2004-03-07 16:00:51 +01:00
|
|
|
here's how to access the control block, given a handle:
|
|
|
|
Handle h;
|
|
|
|
a) 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) 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.
|
|
|
|
|
2004-03-07 15:24:47 +01:00
|
|
|
*/
|
2004-03-07 16:00:51 +01:00
|
|
|
|