Line data Source code
1 : /* Copyright (C) 2019 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 : #include "precompiled.h"
28 : #include "h_mgr.h"
29 :
30 : #include <unordered_map>
31 :
32 : #include <limits.h> // CHAR_BIT
33 : #include <string.h>
34 : #include <stdlib.h>
35 : #include <new> // std::bad_alloc
36 :
37 : #include "lib/fnv_hash.h"
38 : #include "lib/allocators/overrun_protector.h"
39 : #include "lib/allocators/pool.h"
40 : #include "lib/module_init.h"
41 :
42 : #include <mutex>
43 :
44 : namespace ERR {
45 : static const Status H_IDX_INVALID = -120000; // totally invalid
46 : static const Status H_IDX_UNUSED = -120001; // beyond current cap
47 : static const Status H_TAG_MISMATCH = -120003;
48 : static const Status H_TYPE_MISMATCH = -120004;
49 : static const Status H_ALREADY_FREED = -120005;
50 : }
51 : static const StatusDefinition hStatusDefinitions[] = {
52 : { ERR::H_IDX_INVALID, L"Handle index completely out of bounds" },
53 : { ERR::H_IDX_UNUSED, L"Handle index exceeds high-water mark" },
54 : { ERR::H_TAG_MISMATCH, L"Handle tag mismatch (stale reference?)" },
55 : { ERR::H_TYPE_MISMATCH, L"Handle type mismatch" },
56 : { ERR::H_ALREADY_FREED, L"Handle already freed" }
57 : };
58 : STATUS_ADD_DEFINITIONS(hStatusDefinitions);
59 :
60 :
61 :
62 : // rationale
63 : //
64 : // why fixed size control blocks, instead of just allocating dynamically?
65 : // it is expected that resources be created and freed often. this way is
66 : // much nicer to the memory manager. defining control blocks larger than
67 : // the allotted space is caught by h_alloc (made possible by the vtbl builder
68 : // storing control block size). it is also efficient to have all CBs in an
69 : // more or less contiguous array (see below).
70 : //
71 : // why a manager, instead of a simple pool allocator?
72 : // we need a central list of resources for freeing at exit, checking if a
73 : // resource has already been loaded (for caching), and when reloading.
74 : // may as well keep them in an array, rather than add a list and index.
75 :
76 :
77 :
78 : //
79 : // handle
80 : //
81 :
82 : // 0 = invalid handle value
83 : // < 0 is an error code (we assume < 0 <==> MSB is set -
84 : // true for 1s and 2s complement and sign-magnitude systems)
85 :
86 : // fields:
87 : // (shift value = # bits between LSB and field LSB.
88 : // may be larger than the field type - only shift Handle vars!)
89 :
90 : // - index (0-based) of control block in our array.
91 : // (field width determines maximum currently open handles)
92 : #define IDX_BITS 16
93 : static const u64 IDX_MASK = (1l << IDX_BITS) - 1;
94 :
95 : // - tag (1-based) ensures the handle references a certain resource instance.
96 : // (field width determines maximum unambiguous resource allocs)
97 : using Tag = i64;
98 : #define TAG_BITS 48
99 :
100 : // make sure both fields fit within a Handle variable
101 : cassert(IDX_BITS + TAG_BITS <= sizeof(Handle)*CHAR_BIT);
102 :
103 :
104 : // return the handle's index field (always non-negative).
105 : // no error checking!
106 : static inline size_t h_idx(const Handle h)
107 : {
108 0 : return (size_t)(h & IDX_MASK) - 1;
109 : }
110 :
111 : // build a handle from index and tag.
112 : // can't fail.
113 0 : static inline Handle handle(size_t idx, u64 tag)
114 : {
115 0 : const size_t idxPlusOne = idx+1;
116 0 : ENSURE(idxPlusOne <= IDX_MASK);
117 0 : ENSURE((tag & IDX_MASK) == 0);
118 0 : Handle h = tag | idxPlusOne;
119 0 : ENSURE(h > 0);
120 0 : return h;
121 : }
122 :
123 :
124 : //
125 : // internal per-resource-instance data
126 : //
127 :
128 : // chosen so that all current resource structs are covered.
129 : static const size_t HDATA_USER_SIZE = 104;
130 :
131 :
132 : struct HDATA
133 : {
134 : // we only need the tag, because it is trivial to compute
135 : // &HDATA from idx and vice versa. storing the entire handle
136 : // avoids needing to extract the tag field.
137 : Handle h; // NB: will be overwritten by pool_free
138 :
139 : uintptr_t key;
140 :
141 : intptr_t refs;
142 :
143 : // smaller bit fields combined into 1
144 : // .. if set, do not actually release the resource (i.e. call dtor)
145 : // when the handle is h_free-d, regardless of the refcount.
146 : // set by h_alloc; reset on exit and by housekeeping.
147 : u32 keep_open : 1;
148 : // .. HACK: prevent adding to h_find lookup index if flags & RES_UNIQUE
149 : // (because those handles might have several instances open,
150 : // which the index can't currently handle)
151 : u32 unique : 1;
152 : u32 disallow_reload : 1;
153 :
154 : H_Type type;
155 :
156 : // for statistics
157 : size_t num_derefs;
158 :
159 : // storing PIVFS here is not a good idea since this often isn't
160 : // `freed' due to caching (and there is no dtor), so
161 : // the VFS reference count would never reach zero.
162 : VfsPath pathname;
163 :
164 : u8 user[HDATA_USER_SIZE];
165 : };
166 :
167 :
168 : // max data array entries. compared to last_in_use => signed.
169 : static const ssize_t hdata_cap = (1ul << IDX_BITS)/4;
170 :
171 : // pool of fixed-size elements allows O(1) alloc and free;
172 : // there is a simple mapping between HDATA address and index.
173 : static Pool hpool;
174 :
175 :
176 : // error checking strategy:
177 : // all handles passed in go through h_data(Handle, Type)
178 :
179 :
180 : // get a (possibly new) array entry.
181 : //
182 : // fails if idx is out of bounds.
183 0 : static Status h_data_from_idx(ssize_t idx, HDATA*& hd)
184 : {
185 : // don't check if idx is beyond the current high-water mark, because
186 : // we might be allocating a new entry. subsequent tag checks protect
187 : // against using unallocated entries.
188 0 : if(size_t(idx) >= size_t(hdata_cap)) // also detects negative idx
189 0 : WARN_RETURN(ERR::H_IDX_INVALID);
190 :
191 0 : hd = (HDATA*)(hpool.da.base + idx*hpool.el_size);
192 0 : hd->num_derefs++;
193 0 : return INFO::OK;
194 : }
195 :
196 0 : static ssize_t h_idx_from_data(HDATA* hd)
197 : {
198 0 : if(!pool_contains(&hpool, hd))
199 0 : WARN_RETURN(ERR::INVALID_POINTER);
200 0 : return (uintptr_t(hd) - uintptr_t(hpool.da.base))/hpool.el_size;
201 : }
202 :
203 :
204 : // get HDATA for the given handle.
205 : // only uses (and checks) the index field.
206 : // used by h_force_close (which must work regardless of tag).
207 0 : static inline Status h_data_no_tag(const Handle h, HDATA*& hd)
208 : {
209 0 : ssize_t idx = (ssize_t)h_idx(h);
210 0 : RETURN_STATUS_IF_ERR(h_data_from_idx(idx, hd));
211 : // need to verify it's in range - h_data_from_idx can only verify that
212 : // it's < maximum allowable index.
213 0 : if(uintptr_t(hd) > uintptr_t(hpool.da.base)+hpool.da.pos)
214 0 : WARN_RETURN(ERR::H_IDX_UNUSED);
215 : return INFO::OK;
216 : }
217 :
218 :
219 : static bool ignoreDoubleFree = false;
220 :
221 : // get HDATA for the given handle.
222 : // also verifies the tag field.
223 : // used by functions callable for any handle type, e.g. h_filename.
224 0 : static inline Status h_data_tag(Handle h, HDATA*& hd)
225 : {
226 0 : RETURN_STATUS_IF_ERR(h_data_no_tag(h, hd));
227 :
228 0 : if(hd->key == 0) // HDATA was wiped out and hd->h overwritten by pool_free
229 : {
230 0 : if(ignoreDoubleFree)
231 : return ERR::H_ALREADY_FREED; // NOWARN (see ignoreDoubleFree)
232 : else
233 0 : WARN_RETURN(ERR::H_ALREADY_FREED);
234 : }
235 :
236 0 : if(h != hd->h)
237 0 : WARN_RETURN(ERR::H_TAG_MISMATCH);
238 :
239 : return INFO::OK;
240 : }
241 :
242 :
243 : // get HDATA for the given handle.
244 : // also verifies the type.
245 : // used by most functions accessing handle data.
246 0 : static Status h_data_tag_type(const Handle h, const H_Type type, HDATA*& hd)
247 : {
248 0 : RETURN_STATUS_IF_ERR(h_data_tag(h, hd));
249 :
250 : // h_alloc makes sure type isn't 0, so no need to check that here.
251 0 : if(hd->type != type)
252 : {
253 0 : debug_printf("h_mgr: expected type %s, got %s\n", utf8_from_wstring(hd->type->name).c_str(), utf8_from_wstring(type->name).c_str());
254 0 : WARN_RETURN(ERR::H_TYPE_MISMATCH);
255 : }
256 :
257 : return INFO::OK;
258 : }
259 :
260 :
261 : //-----------------------------------------------------------------------------
262 : // lookup data structure
263 : //-----------------------------------------------------------------------------
264 :
265 : // speed up h_find (called every h_alloc)
266 : // multimap, because we want to add handles of differing type but same key
267 : // (e.g. a VFile and Tex object for the same underlying filename hash key)
268 : //
269 : // store index because it's smaller and Handle can easily be reconstructed
270 : //
271 : //
272 : // note: there may be several RES_UNIQUE handles of the same type and key
273 : // (e.g. sound files - several instances of a sound definition file).
274 : // that wasn't foreseen here, so we'll just refrain from adding to the index.
275 : // that means they won't be found via h_find - no biggie.
276 :
277 : using Key2Idx = std::unordered_multimap<uintptr_t, ssize_t>;
278 : using It = Key2Idx::iterator;
279 : static OverrunProtector<Key2Idx> key2idx_wrapper;
280 :
281 : enum KeyRemoveFlag { KEY_NOREMOVE, KEY_REMOVE };
282 :
283 0 : static Handle key_find(uintptr_t key, H_Type type, KeyRemoveFlag remove_option = KEY_NOREMOVE)
284 : {
285 0 : Key2Idx* key2idx = key2idx_wrapper.get();
286 0 : if(!key2idx)
287 0 : WARN_RETURN(ERR::NO_MEM);
288 :
289 : // initial return value: "not found at all, or it's of the
290 : // wrong type". the latter happens when called by h_alloc to
291 : // check if e.g. a Tex object already exists; at that time,
292 : // only the corresponding VFile exists.
293 0 : Handle ret = -1;
294 :
295 0 : std::pair<It, It> range = key2idx->equal_range(key);
296 0 : for(It it = range.first; it != range.second; ++it)
297 : {
298 0 : ssize_t idx = it->second;
299 0 : HDATA* hd;
300 0 : if(h_data_from_idx(idx, hd) != INFO::OK)
301 0 : continue;
302 0 : if(hd->type != type || hd->key != key)
303 : continue;
304 :
305 : // found a match
306 0 : if(remove_option == KEY_REMOVE)
307 0 : key2idx->erase(it);
308 0 : ret = hd->h;
309 0 : break;
310 : }
311 :
312 : key2idx_wrapper.lock();
313 : return ret;
314 : }
315 :
316 :
317 0 : static void key_add(uintptr_t key, Handle h)
318 : {
319 0 : Key2Idx* key2idx = key2idx_wrapper.get();
320 0 : if(!key2idx)
321 : return;
322 :
323 0 : const ssize_t idx = h_idx(h);
324 : // note: MSDN documentation of stdext::hash_multimap is incorrect;
325 : // there is no overload of insert() that returns pair<iterator, bool>.
326 0 : (void)key2idx->insert(std::make_pair(key, idx));
327 :
328 0 : key2idx_wrapper.lock();
329 : }
330 :
331 :
332 0 : static void key_remove(uintptr_t key, H_Type type)
333 : {
334 0 : Handle ret = key_find(key, type, KEY_REMOVE);
335 0 : ENSURE(ret > 0);
336 0 : }
337 :
338 :
339 : //----------------------------------------------------------------------------
340 : // h_alloc
341 : //----------------------------------------------------------------------------
342 :
343 0 : static void warn_if_invalid(HDATA* hd)
344 : {
345 : #ifndef NDEBUG
346 : H_VTbl* vtbl = hd->type;
347 :
348 : // validate HDATA
349 : // currently nothing to do; <type> is checked by h_alloc and
350 : // the others have no invariants we could check.
351 :
352 : // have the resource validate its user_data
353 : Status err = vtbl->validate(hd->user);
354 : ENSURE(err == INFO::OK);
355 :
356 : // make sure empty space in control block isn't touched
357 : // .. but only if we're not storing a filename there
358 : const u8* start = hd->user + vtbl->user_size;
359 : const u8* end = hd->user + HDATA_USER_SIZE;
360 : for(const u8* p = start; p < end; p++)
361 : ENSURE(*p == 0); // else: handle user data was overrun!
362 : #else
363 0 : UNUSED2(hd);
364 : #endif
365 0 : }
366 :
367 :
368 0 : static Status type_validate(H_Type type)
369 : {
370 0 : if(!type)
371 0 : WARN_RETURN(ERR::INVALID_PARAM);
372 0 : if(type->user_size > HDATA_USER_SIZE)
373 0 : WARN_RETURN(ERR::LIMIT);
374 0 : if(type->name == 0)
375 0 : WARN_RETURN(ERR::INVALID_PARAM);
376 :
377 : return INFO::OK;
378 : }
379 :
380 :
381 : static Tag gen_tag()
382 : {
383 0 : static Tag tag;
384 0 : tag += (1ull << IDX_BITS);
385 : // it's not easy to detect overflow, because compilers
386 : // are allowed to assume it'll never happen. however,
387 : // pow(2, 64-IDX_BITS) is "enough" anyway.
388 0 : return tag;
389 : }
390 :
391 :
392 0 : static Handle reuse_existing_handle(uintptr_t key, H_Type type, size_t flags)
393 : {
394 0 : if(flags & RES_NO_CACHE)
395 : return 0;
396 :
397 : // object of specified key and type doesn't exist yet
398 0 : Handle h = h_find(type, key);
399 0 : if(h <= 0)
400 : return 0;
401 :
402 0 : HDATA* hd;
403 0 : RETURN_STATUS_IF_ERR(h_data_tag_type(h, type, hd)); // h_find means this won't fail
404 :
405 0 : hd->refs += 1;
406 :
407 : // we are reactivating a closed but cached handle.
408 : // need to generate a new tag so that copies of the
409 : // previous handle can no longer access the resource.
410 : // (we don't need to reset the tag in h_free, because
411 : // use before this fails due to refs > 0 check in h_user_data).
412 0 : if(hd->refs == 1)
413 : {
414 0 : const Tag tag = gen_tag();
415 0 : h = handle(h_idx(h), tag); // can't fail
416 0 : hd->h = h;
417 : }
418 :
419 : return h;
420 : }
421 :
422 :
423 0 : static Status call_init_and_reload(Handle h, H_Type type, HDATA* hd, const PIVFS& vfs, const VfsPath& pathname, va_list* init_args)
424 : {
425 0 : Status err = INFO::OK;
426 0 : H_VTbl* vtbl = type; // exact same thing but for clarity
427 :
428 : // init
429 0 : if(vtbl->init)
430 0 : vtbl->init(hd->user, *init_args);
431 :
432 : // reload
433 0 : if(vtbl->reload)
434 : {
435 : // catch exception to simplify reload funcs - let them use new()
436 0 : try
437 : {
438 0 : err = vtbl->reload(hd->user, vfs, pathname, h);
439 : if(err == INFO::OK)
440 : warn_if_invalid(hd);
441 : }
442 0 : catch(std::bad_alloc&)
443 : {
444 0 : err = ERR::NO_MEM;
445 : }
446 : }
447 :
448 0 : return err;
449 : }
450 :
451 :
452 0 : static Handle alloc_new_handle(H_Type type, const PIVFS& vfs, const VfsPath& pathname, uintptr_t key, size_t flags, va_list* init_args)
453 : {
454 0 : HDATA* hd = (HDATA*)pool_alloc(&hpool, 0);
455 0 : if(!hd)
456 0 : WARN_RETURN(ERR::NO_MEM);
457 0 : new(&hd->pathname) VfsPath;
458 :
459 0 : ssize_t idx = h_idx_from_data(hd);
460 0 : RETURN_STATUS_IF_ERR(idx);
461 :
462 : // (don't want to do this before the add-reference exit,
463 : // so as not to waste tags for often allocated handles.)
464 0 : const Tag tag = gen_tag();
465 0 : Handle h = handle(idx, tag); // can't fail.
466 :
467 0 : hd->h = h;
468 0 : hd->key = key;
469 0 : hd->type = type;
470 0 : hd->refs = 1;
471 0 : if(!(flags & RES_NO_CACHE))
472 0 : hd->keep_open = 1;
473 0 : if(flags & RES_DISALLOW_RELOAD)
474 0 : hd->disallow_reload = 1;
475 0 : hd->unique = (flags & RES_UNIQUE) != 0;
476 0 : hd->pathname = pathname;
477 :
478 0 : if(key && !hd->unique)
479 0 : key_add(key, h);
480 :
481 0 : Status err = call_init_and_reload(h, type, hd, vfs, pathname, init_args);
482 0 : if(err < 0)
483 0 : goto fail;
484 :
485 0 : return h;
486 :
487 0 : fail:
488 : // reload failed; free the handle
489 0 : hd->keep_open = 0; // disallow caching (since contents are invalid)
490 0 : (void)h_free(h, type); // (h_free already does WARN_IF_ERR)
491 :
492 : // note: since some uses will always fail (e.g. loading sounds if
493 : // g_Quickstart), do not complain here.
494 : return (Handle)err;
495 : }
496 :
497 :
498 : static std::recursive_mutex h_mutex;
499 :
500 : // any further params are passed to type's init routine
501 0 : Handle h_alloc(H_Type type, const PIVFS& vfs, const VfsPath& pathname, size_t flags, ...)
502 : {
503 0 : std::lock_guard<std::recursive_mutex> lock(h_mutex);
504 :
505 0 : RETURN_STATUS_IF_ERR(type_validate(type));
506 :
507 0 : const uintptr_t key = fnv_hash(pathname.string().c_str(), pathname.string().length()*sizeof(pathname.string()[0]));
508 :
509 : // see if we can reuse an existing handle
510 0 : Handle h = reuse_existing_handle(key, type, flags);
511 0 : RETURN_STATUS_IF_ERR(h);
512 : // .. successfully reused the handle; refcount increased
513 0 : if(h > 0)
514 : return h;
515 : // .. need to allocate a new one:
516 0 : va_list args;
517 0 : va_start(args, flags);
518 0 : h = alloc_new_handle(type, vfs, pathname, key, flags, &args);
519 0 : va_end(args);
520 0 : return h; // alloc_new_handle already does WARN_RETURN_STATUS_IF_ERR
521 : }
522 :
523 :
524 : //-----------------------------------------------------------------------------
525 :
526 0 : static void h_free_hd(HDATA* hd)
527 : {
528 0 : if(hd->refs > 0)
529 0 : hd->refs--;
530 :
531 : // still references open or caching requests it stays - do not release.
532 0 : if(hd->refs > 0 || hd->keep_open)
533 : return;
534 :
535 : // actually release the resource (call dtor, free control block).
536 :
537 : // h_alloc makes sure type != 0; if we get here, it still is
538 0 : H_VTbl* vtbl = hd->type;
539 :
540 : // call its destructor
541 : // note: H_TYPE_DEFINE currently always defines a dtor, but play it safe
542 0 : if(vtbl->dtor)
543 0 : vtbl->dtor(hd->user);
544 :
545 0 : if(hd->key && !hd->unique)
546 0 : key_remove(hd->key, hd->type);
547 :
548 : #ifndef NDEBUG
549 : // to_string is slow for some handles, so avoid calling it if unnecessary
550 : if(debug_filter_allows("H_MGR|"))
551 : {
552 : wchar_t buf[H_STRING_LEN];
553 : if(vtbl->to_string(hd->user, buf) < 0)
554 : wcscpy_s(buf, ARRAY_SIZE(buf), L"(error)");
555 : debug_printf("H_MGR| free %s %s accesses=%lu %s\n", utf8_from_wstring(hd->type->name).c_str(), hd->pathname.string8().c_str(), (unsigned long)hd->num_derefs, utf8_from_wstring(buf).c_str());
556 : }
557 : #endif
558 :
559 0 : hd->pathname.~VfsPath(); // FIXME: ugly hack, but necessary to reclaim memory
560 0 : memset(hd, 0, sizeof(*hd));
561 0 : pool_free(&hpool, hd);
562 : }
563 :
564 :
565 12 : Status h_free(Handle& h, H_Type type)
566 : {
567 36 : std::lock_guard<std::recursive_mutex> lock(h_mutex);
568 :
569 : // 0-initialized or an error code; don't complain because this
570 : // happens often and is harmless.
571 12 : if(h <= 0)
572 : return INFO::OK;
573 :
574 : // wipe out the handle to prevent reuse but keep a copy for below.
575 0 : const Handle h_copy = h;
576 0 : h = 0;
577 :
578 0 : HDATA* hd;
579 0 : RETURN_STATUS_IF_ERR(h_data_tag_type(h_copy, type, hd));
580 :
581 0 : h_free_hd(hd);
582 : return INFO::OK;
583 : }
584 :
585 :
586 : //----------------------------------------------------------------------------
587 : // remaining API
588 :
589 0 : void* h_user_data(const Handle h, const H_Type type)
590 : {
591 0 : HDATA* hd;
592 0 : if(h_data_tag_type(h, type, hd) != INFO::OK)
593 : return 0;
594 :
595 0 : if(!hd->refs)
596 : {
597 : // note: resetting the tag is not enough (user might pass in its value)
598 0 : DEBUG_WARN_ERR(ERR::LOGIC); // no references to resource (it's cached, but someone is accessing it directly)
599 0 : return 0;
600 : }
601 :
602 0 : warn_if_invalid(hd);
603 0 : return hd->user;
604 : }
605 :
606 :
607 0 : VfsPath h_filename(const Handle h)
608 : {
609 : // don't require type check: should be usable for any handle,
610 : // even if the caller doesn't know its type.
611 0 : HDATA* hd;
612 0 : if(h_data_tag(h, hd) != INFO::OK)
613 0 : return VfsPath();
614 0 : return hd->pathname;
615 : }
616 :
617 :
618 : // TODO: what if iterating through all handles is too slow?
619 0 : Status h_reload(const PIVFS& vfs, const VfsPath& pathname)
620 : {
621 0 : std::lock_guard<std::recursive_mutex> lock(h_mutex);
622 :
623 0 : const u32 key = fnv_hash(pathname.string().c_str(), pathname.string().length()*sizeof(pathname.string()[0]));
624 :
625 : // destroy (note: not free!) all handles backed by this file.
626 : // do this before reloading any of them, because we don't specify reload
627 : // order (the parent resource may be reloaded first, and load the child,
628 : // whose original data would leak).
629 0 : for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size))
630 : {
631 0 : if(hd->key == 0 || hd->key != key || hd->disallow_reload)
632 : continue;
633 0 : hd->type->dtor(hd->user);
634 : }
635 :
636 : Status ret = INFO::OK;
637 :
638 : // now reload all affected handles
639 : size_t i = 0;
640 0 : for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size), i++)
641 : {
642 0 : if(hd->key == 0 || hd->key != key || hd->disallow_reload)
643 : continue;
644 :
645 0 : Status err = hd->type->reload(hd->user, vfs, hd->pathname, hd->h);
646 : // don't stop if an error is encountered - try to reload them all.
647 0 : if(err < 0)
648 : {
649 0 : h_free(hd->h, hd->type);
650 0 : if(ret == 0) // don't overwrite first error
651 0 : ret = err;
652 : }
653 : else
654 : warn_if_invalid(hd);
655 : }
656 :
657 0 : return ret;
658 : }
659 :
660 :
661 0 : Handle h_find(H_Type type, uintptr_t key)
662 : {
663 0 : std::lock_guard<std::recursive_mutex> lock(h_mutex);
664 0 : return key_find(key, type);
665 : }
666 :
667 :
668 :
669 : // force the resource to be freed immediately, even if cached.
670 : // tag is not checked - this allows the first Handle returned
671 : // (whose tag will change after being 'freed', but remaining in memory)
672 : // to later close the object.
673 : // this is used when reinitializing the sound engine -
674 : // at that point, all (cached) OpenAL resources must be freed.
675 0 : Status h_force_free(Handle h, H_Type type)
676 : {
677 0 : std::lock_guard<std::recursive_mutex> lock(h_mutex);
678 :
679 : // require valid index; ignore tag; type checked below.
680 0 : HDATA* hd;
681 0 : RETURN_STATUS_IF_ERR(h_data_no_tag(h, hd));
682 0 : if(hd->type != type)
683 0 : WARN_RETURN(ERR::H_TYPE_MISMATCH);
684 0 : hd->keep_open = 0;
685 0 : hd->refs = 0;
686 0 : h_free_hd(hd);
687 : return INFO::OK;
688 : }
689 :
690 :
691 : // increment Handle <h>'s reference count.
692 : // only meant to be used for objects that free a Handle in their dtor,
693 : // so that they are copy-equivalent and can be stored in a STL container.
694 : // do not use this to implement refcounting on top of the Handle scheme,
695 : // e.g. loading a Handle once and then passing it around. instead, have each
696 : // user load the resource; refcounting is done under the hood.
697 0 : void h_add_ref(Handle h)
698 : {
699 0 : HDATA* hd;
700 0 : if(h_data_tag(h, hd) != INFO::OK)
701 0 : return;
702 :
703 0 : ENSURE(hd->refs); // if there are no refs, how did the caller manage to keep a Handle?!
704 0 : hd->refs++;
705 : }
706 :
707 :
708 : // retrieve the internal reference count or a negative error code.
709 : // background: since h_alloc has no way of indicating whether it
710 : // allocated a new handle or reused an existing one, counting references
711 : // within resource control blocks is impossible. since that is sometimes
712 : // necessary (always wrapping objects in Handles is excessive), we
713 : // provide access to the internal reference count.
714 0 : intptr_t h_get_refcnt(Handle h)
715 : {
716 0 : HDATA* hd;
717 0 : RETURN_STATUS_IF_ERR(h_data_tag(h, hd));
718 :
719 0 : ENSURE(hd->refs); // if there are no refs, how did the caller manage to keep a Handle?!
720 0 : return hd->refs;
721 : }
722 :
723 :
724 : static ModuleInitState initState;
725 :
726 2 : static Status Init()
727 : {
728 2 : RETURN_STATUS_IF_ERR(pool_create(&hpool, hdata_cap*sizeof(HDATA), sizeof(HDATA)));
729 : return INFO::OK;
730 : }
731 :
732 2 : static void Shutdown()
733 : {
734 2 : debug_printf("H_MGR| shutdown. any handle frees after this are leaks!\n");
735 : // objects that store handles to other objects are destroyed before their
736 : // children, so the subsequent forced destruction of the child here will
737 : // raise a double-free warning unless we ignore it. (#860, #915, #920)
738 2 : ignoreDoubleFree = true;
739 :
740 4 : std::lock_guard<std::recursive_mutex> lock(h_mutex);
741 :
742 : // forcibly close all open handles
743 2 : for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size))
744 : {
745 : // it's already been freed; don't free again so that this
746 : // doesn't look like an error.
747 0 : if(hd->key == 0)
748 : continue;
749 :
750 : // disable caching; we need to release the resource now.
751 0 : hd->keep_open = 0;
752 0 : hd->refs = 0;
753 :
754 0 : h_free_hd(hd);
755 : }
756 :
757 2 : pool_destroy(&hpool);
758 2 : }
759 :
760 0 : void h_mgr_free_type(const H_Type type)
761 : {
762 0 : ignoreDoubleFree = true;
763 :
764 0 : std::lock_guard<std::recursive_mutex> lock(h_mutex);
765 :
766 : // forcibly close all open handles of the specified type
767 0 : for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size))
768 : {
769 : // free if not previously freed and only free the proper type
770 0 : if (hd->key == 0 || hd->type != type)
771 : continue;
772 :
773 : // disable caching; we need to release the resource now.
774 0 : hd->keep_open = 0;
775 0 : hd->refs = 0;
776 :
777 0 : h_free_hd(hd);
778 : }
779 0 : }
780 :
781 2 : void h_mgr_init()
782 : {
783 2 : ModuleInit(&initState, Init);
784 2 : }
785 :
786 2 : void h_mgr_shutdown()
787 : {
788 2 : ModuleShutdown(&initState, Shutdown);
789 2 : }
|