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 364 RES_UNIQUE = RES_NO_CACHE|0x10, 365 366 // object is requesting it never be reloaded (e.g. because it's not 367 // backed by a file) 368 RES_DISALLOW_RELOAD = 0x20 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 433