Pyrogenesis  trunk
h_mgr.h
Go to the documentation of this file.
1 /* Copyright (c) 2010 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 /*
24  * handle manager for resources.
25  */
26 
27 /*
28 
29 [KEEP IN SYNC WITH WIKI]
30 
31 introduction
32 ------------
33 
34 a resource is an instance of a specific type of game data (e.g. texture),
35 described by a control block (example fields: format, pointer to tex data).
36 
37 this module allocates storage for the control blocks, which are accessed
38 via handle. it also provides support for transparently reloading resources
39 from disk (allows in-game editing of data), and caches resource data.
40 finally, it frees all resources at exit, preventing leaks.
41 
42 
43 handles
44 -------
45 
46 handles are an indirection layer between client code and resources
47 (represented by their control blocks, which contains/points to its data).
48 they allow an important check not possible with a direct pointer:
49 guaranteeing the handle references a given resource /instance/.
50 
51 problem: code C1 allocates a resource, and receives a pointer p to its
52 control block. C1 passes p on to C2, and later frees it.
53 now other code allocates a resource, and happens to reuse the free slot
54 pointed to by p (also possible if simply allocating from the heap).
55 when C2 accesses p, the pointer is valid, but we cannot tell that
56 it is referring to a resource that had already been freed. big trouble.
57 
58 solution: each allocation receives a unique tag (a global counter that
59 is large enough to never overflow). Handles include this tag, as well
60 as a reference (array index) to the control block, which isn't directly
61 accessible. when dereferencing the handle, we check if the handle's tag
62 matches the copy stored in the control block. this protects against stale
63 handle reuse, double-free, and accidentally referencing other resources.
64 
65 type: each handle has an associated type. these must be checked to prevent
66 using textures as sounds, for example. with the manual vtbl scheme,
67 this type is actually a pointer to the resource object's vtbl, and is
68 set up via H_TYPE_DEFINE. this means that types are private to the module
69 that declared the handle; knowledge of the type ensures the caller
70 actually declared, and owns the resource.
71 
72 
73 guide to defining and using resources
74 -------------------------------------
75 
76 1) choose a name for the resource, used to represent all resources
77 of this type. we will call ours "Res1"; all below occurrences of this
78 must be replaced with the actual name (exact spelling).
79 why? the vtbl builder defines its functions as e.g. Res1_reload;
80 your actual definition must match.
81 
82 2) declare its control block:
83 struct Res1
84 {
85  void* data; // data loaded from file
86  size_t flags; // set when resource is created
87 };
88 
89 Note that all control blocks are stored in fixed-size slots
90 (HDATA_USER_SIZE bytes), so squeezing the size of your data doesn't
91 help unless yours is the largest.
92 
93 3) build its vtbl:
94 H_TYPE_DEFINE(Res1);
95 
96 this defines the symbol H_Res1, which is used whenever the handle
97 manager needs its type. it is only accessible to this module
98 (file scope). note that it is actually a pointer to the vtbl.
99 this must come before uses of H_Res1, and after the CB definition;
100 there are no restrictions WRT functions, because the macro
101 forward-declares what it needs.
102 
103 4) implement all 'virtual' functions from the resource interface.
104 note that inheritance isn't really possible with this approach -
105 all functions must be defined, even if not needed.
106 
107 --
108 
109 init:
110 one-time init of the control block. called from h_alloc.
111 precondition: control block is initialized to 0.
112 
113 static void Type_init(Res1* r, va_list args)
114 {
115  r->flags = va_arg(args, int);
116 }
117 
118 if the caller of h_alloc passed additional args, they are available
119 in args. if init references more args than were passed, big trouble.
120 however, this is a bug in your code, and cannot be triggered
121 maliciously. only your code knows the resource type, and it is the
122 only call site of h_alloc.
123 there is no provision for indicating failure. if one-time init fails
124 (rare, but one example might be failure to allocate memory that is
125 for the lifetime of the resource, instead of in reload), it will
126 have to set the control block state such that reload will fail.
127 
128 --
129 
130 reload:
131 does all initialization of the resource that requires its source file.
132 called after init; also after dtor every time the file is reloaded.
133 
134 static Status Type_reload(Res1* r, const VfsPath& pathname, Handle);
135 {
136  // already loaded; done
137  if(r->data)
138  return 0;
139 
140  r->data = malloc(100);
141  if(!r->data)
142  WARN_RETURN(ERR::NO_MEM);
143  // (read contents of <pathname> into r->data)
144  return 0;
145 }
146 
147 reload must abort if the control block data indicates the resource
148 has already been loaded! example: if texture's reload is called first,
149 it loads itself from file (triggering file.reload); afterwards,
150 file.reload will be called again. we can't avoid this, because the
151 handle manager doesn't know anything about dependencies
152 (here, texture -> file).
153 return value: 0 if successful (includes 'already loaded'),
154 negative error code otherwise. if this fails, the resource is freed
155 (=> dtor is called!).
156 
157 note that any subsequent changes to the resource state must be
158 stored in the control block and 'replayed' when reloading.
159 example: when uploading a texture, store the upload parameters
160 (filter, internal format); when reloading, upload again accordingly.
161 
162 --
163 
164 dtor:
165 frees all data allocated by init and reload. called after reload has
166 indicated failure, before reloading a resource, after h_free,
167 or at exit (if the resource is still extant).
168 except when reloading, the control block will be zeroed afterwards.
169 
170 static void Type_dtor(Res1* r);
171 {
172  free(r->data);
173 }
174 
175 again no provision for reporting errors - there's no one to act on it
176 if called at exit. you can ENSURE or log the error, though.
177 
178 be careful to correctly handle the different cases in which this routine
179 can be called! some flags should persist across reloads (e.g. choices made
180 during resource init time that must remain valid), while everything else
181 *should be zeroed manually* (to behave correctly when reloading).
182 be advised that this interface may change; a "prepare for reload" method
183 or "compact/free extraneous resources" may be added.
184 
185 --
186 
187 validate:
188 makes sure the resource control block is in a valid state. returns 0 if
189 all is well, or a negative error code.
190 called automatically when the Handle is dereferenced or freed.
191 
192 static Status Type_validate(const Res1* r);
193 {
194  const int permissible_flags = 0x01;
195  if(debug_IsPointerBogus(r->data))
196  WARN_RETURN(ERR::_1);
197  if(r->flags & ~permissible_flags)
198  WARN_RETURN(ERR::_2);
199  return 0;
200 }
201 
202 
203 5) provide your layer on top of the handle manager:
204 Handle res1_load(const VfsPath& pathname, int my_flags)
205 {
206  // passes my_flags to init
207  return h_alloc(H_Res1, pathname, 0, my_flags);
208 }
209 
210 Status res1_free(Handle& h)
211 {
212  // control block is automatically zeroed after this.
213  return h_free(h, H_Res1);
214 }
215 
216 (this layer allows a res_load interface on top of all the loaders,
217 and is necessary because your module is the only one that knows H_Res1).
218 
219 6) done. the resource will be freed at exit (if not done already).
220 
221 here's how to access the control block, given a <Handle h>:
222 a)
223  H_DEREF(h, Res1, r);
224 
225 creates a variable r of type Res1*, which points to the control block
226 of the resource referenced by h. returns "invalid handle"
227 (a negative error code) on failure.
228 b)
229  Res1* r = h_user_data(h, H_Res1);
230  if(!r)
231  ; // bail
232 
233 useful if H_DEREF's error return (of type signed integer) isn't
234 acceptable. otherwise, prefer a) - this is pretty clunky, and
235 we could switch H_DEREF to throwing an exception on error.
236 
237 */
238 
239 #ifndef INCLUDED_H_MGR
240 #define INCLUDED_H_MGR
241 
242 // do not include from public header files!
243 // handle.h declares type Handle, and avoids making
244 // everything dependent on this (rather often updated) header.
245 
246 
247 #include <stdarg.h> // type init routines get va_list of args
248 
249 #ifndef INCLUDED_HANDLE
250 #include "handle.h"
251 #endif
252 
253 #include "lib/file/vfs/vfs.h"
254 
255 extern void h_mgr_init();
256 extern void h_mgr_shutdown();
257 
258 
259 // handle type (for 'type safety' - can't use a texture handle as a sound)
260 
261 // registering extension for each module is bad - some may use many
262 // (e.g. texture - many formats).
263 // handle manager shouldn't know about handle types
264 
265 
266 /*
267 ///xxx advantage of manual vtbl:
268 no boilerplate init, h_alloc calls ctor directly, make sure it fits in the memory slot
269 vtbl contains sizeof resource data, and name!
270 but- has to handle variable params, a bit ugly
271 */
272 
273 // 'manual vtbl' type id
274 // handles have a type, to prevent using e.g. texture handles as a sound.
275 //
276 // alternatives:
277 // - enum of all handle types (smaller, have to pass all methods to h_alloc)
278 // - class (difficult to compare type, handle manager needs to know of all users)
279 //
280 // checked in h_alloc:
281 // - user_size must fit in what the handle manager provides
282 // - name must not be 0
283 //
284 // init: user data is initially zeroed
285 // dtor: user data is zeroed afterwards
286 // reload: if this resource type is opened by another resource's reload,
287 // our reload routine MUST check if already opened! This is relevant when
288 // a file is reloaded: if e.g. a sound object opens a file, the handle
289 // manager calls the reload routines for the 2 handles in unspecified order.
290 // ensuring the order would require a tag field that can't overflow -
291 // not really guaranteed with 32-bit handles. it'd also be more work
292 // to sort the handles by creation time, or account for several layers of
293 // dependencies.
294 struct H_VTbl
295 {
296  void (*init)(void* user, va_list);
297  Status (*reload)(void* user, const PIVFS& vfs, const VfsPath& pathname, Handle);
298  void (*dtor)(void* user);
299  Status (*validate)(const void* user);
300  Status (*to_string)(const void* user, wchar_t* buf);
301  size_t user_size;
302  const wchar_t* name;
303 };
304 
305 typedef H_VTbl* H_Type;
306 
307 #define H_TYPE_DEFINE(type)\
308  /* forward decls */\
309  static void type##_init(type*, va_list);\
310  static Status type##_reload(type*, const PIVFS&, const VfsPath&, Handle);\
311  static void type##_dtor(type*);\
312  static Status type##_validate(const type*);\
313  static Status type##_to_string(const type*, wchar_t* buf);\
314  static H_VTbl V_##type =\
315  {\
316  (void (*)(void*, va_list))type##_init,\
317  (Status (*)(void*, const PIVFS&, const VfsPath&, Handle))type##_reload,\
318  (void (*)(void*))type##_dtor,\
319  (Status (*)(const void*))type##_validate,\
320  (Status (*)(const void*, wchar_t*))type##_to_string,\
321  sizeof(type), /* control block size */\
322  WIDEN(#type) /* name */\
323  };\
324  static H_Type H_##type = &V_##type
325 
326  // note: we cast to void* pointers so the functions can be declared to
327  // take the control block pointers, instead of requiring a cast in each.
328  // the forward decls ensure the function signatures are correct.
329 
330 
331 // convenience macro for h_user_data:
332 // casts its return value to the control block type.
333 // use if H_DEREF's returning a negative error code isn't acceptable.
334 #define H_USER_DATA(h, type) (type*)h_user_data(h, H_##type)
335 
336 // even more convenient wrapper for h_user_data:
337 // declares a pointer (<var>), assigns it H_USER_DATA, and has
338 // the user's function return a negative error code on failure.
339 //
340 // note: don't use STMT - var decl must be visible to "caller"
341 #define H_DEREF(h, type, var)\
342  /* h already indicates an error - return immediately to pass back*/\
343  /* that specific error, rather than only ERR::INVALID_HANDLE*/\
344  if(h < 0)\
345  WARN_RETURN((Status)h);\
346  type* const var = H_USER_DATA(h, type);\
347  if(!var)\
348  WARN_RETURN(ERR::INVALID_HANDLE);
349 
350 
351 // all functions check the passed tag (part of the handle) and type against
352 // the internal values. if they differ, an error is returned.
353 
354 
355 
356 
357 // h_alloc flags
358 enum
359 {
360  // alias for RES_TEMP scope. the handle will not be kept open.
361  RES_NO_CACHE = 0x01,
362 
363  // not cached, and will never reuse a previous instance
365 
366  // object is requesting it never be reloaded (e.g. because it's not
367  // backed by a file)
369 };
370 
371 const size_t H_STRING_LEN = 256;
372 
373 
374 
375 // allocate a new handle.
376 // if key is 0, or a (key, type) handle doesn't exist,
377 // some free entry is used.
378 // otherwise, a handle to the existing object is returned,
379 // and HDATA.size != 0.
380 //// user_size is checked to make sure the user data fits in the handle data space.
381 // dtor is associated with type and called when the object is freed.
382 // handle data is initialized to 0; optionally, a pointer to it is returned.
383 extern Handle h_alloc(H_Type type, const PIVFS& vfs, const VfsPath& pathname, size_t flags = 0, ...);
384 extern Status h_free(Handle& h, H_Type type);
385 
386 
387 // Forcibly frees all handles of a specified type.
388 void h_mgr_free_type(const H_Type type);
389 
390 
391 // find and return a handle by key (typically filename hash)
392 // currently O(log n).
393 //
394 // HACK: currently can't find RES_UNIQUE handles, because there
395 // may be multiple instances of them, breaking the lookup data structure.
396 extern Handle h_find(H_Type type, uintptr_t key);
397 
398 // returns a void* pointer to the control block of the resource <h>,
399 // or 0 on error (i.e. h is invalid or of the wrong type).
400 // prefer using H_DEREF or H_USER_DATA.
401 extern void* h_user_data(Handle h, H_Type type);
402 
403 extern VfsPath h_filename(Handle h);
404 
405 
406 extern Status h_reload(const PIVFS& vfs, const VfsPath& pathname);
407 
408 // force the resource to be freed immediately, even if cached.
409 // tag is not checked - this allows the first Handle returned
410 // (whose tag will change after being 'freed', but remaining in memory)
411 // to later close the object.
412 // this is used when reinitializing the sound engine -
413 // at that point, all (cached) OpenAL resources must be freed.
414 extern Status h_force_free(Handle h, H_Type type);
415 
416 // increment Handle <h>'s reference count.
417 // only meant to be used for objects that free a Handle in their dtor,
418 // so that they are copy-equivalent and can be stored in a STL container.
419 // do not use this to implement refcounting on top of the Handle scheme,
420 // e.g. loading a Handle once and then passing it around. instead, have each
421 // user load the resource; refcounting is done under the hood.
422 extern void h_add_ref(Handle h);
423 
424 // retrieve the internal reference count or a negative error code.
425 // background: since h_alloc has no way of indicating whether it
426 // allocated a new handle or reused an existing one, counting references
427 // within resource control blocks is impossible. since that is sometimes
428 // necessary (always wrapping objects in Handles is excessive), we
429 // provide access to the internal reference count.
430 extern intptr_t h_get_refcnt(Handle h);
431 
432 #endif // #ifndef INCLUDED_H_MGR
Status(* validate)(const void *user)
Definition: h_mgr.h:299
Handle h_alloc(H_Type type, const PIVFS &vfs, const VfsPath &pathname, size_t flags=0,...)
Definition: h_mgr.cpp:519
void h_mgr_shutdown()
Definition: h_mgr.cpp:816
Handle h_find(H_Type type, uintptr_t key)
Definition: h_mgr.cpp:679
Status h_force_free(Handle h, H_Type type)
Definition: h_mgr.cpp:693
shared_ptr< IVFS > PIVFS
Definition: vfs.h:226
void(* dtor)(void *user)
Definition: h_mgr.h:298
void * h_user_data(Handle h, H_Type type)
Definition: h_mgr.cpp:607
Status(* to_string)(const void *user, wchar_t *buf)
Definition: h_mgr.h:300
Status(* reload)(void *user, const PIVFS &vfs, const VfsPath &pathname, Handle)
Definition: h_mgr.h:297
Definition: h_mgr.h:294
size_t user_size
Definition: h_mgr.h:301
H_VTbl * H_Type
Definition: h_mgr.h:305
Definition: path.h:77
pthread_key_t key
Definition: wpthread.cpp:140
Status h_reload(const PIVFS &vfs, const VfsPath &pathname)
Definition: h_mgr.cpp:637
void h_mgr_init()
Definition: h_mgr.cpp:811
Definition: h_mgr.h:361
i64 Status
Error handling system.
Definition: status.h:171
i64 Handle
`handle&#39; representing a reference to a resource (sound, texture, etc.)
Definition: handle.h:41
Definition: h_mgr.h:364
const wchar_t * name
Definition: h_mgr.h:302
intptr_t h_get_refcnt(Handle h)
Definition: h_mgr.cpp:732
void h_mgr_free_type(const H_Type type)
Definition: h_mgr.cpp:790
Definition: h_mgr.h:368
const size_t H_STRING_LEN
Definition: h_mgr.h:371
Definition: vfs_util.cpp:39
VfsPath h_filename(Handle h)
Definition: h_mgr.cpp:625
void(* init)(void *user, va_list)
Definition: h_mgr.h:296
void h_add_ref(Handle h)
Definition: h_mgr.cpp:715
Status h_free(Handle &h, H_Type type)
Definition: h_mgr.cpp:583