LCOV - code coverage report
Current view: top level - source/lib/res - h_mgr.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 18 234 7.7 %
Date: 2021-09-24 14:46:47 Functions: 5 29 17.2 %

          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 : }

Generated by: LCOV version 1.13