forked from 0ad/0ad
Removes unused and redundant h_mgr after 0e599a3176
and dd91a5e0ef
.
This was SVN commit r26369.
This commit is contained in:
parent
dd91a5e0ef
commit
d0115185b9
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2021 Wildfire Games.
|
||||
/* Copyright (C) 2022 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -21,7 +21,6 @@
|
||||
#include "MapIO.h"
|
||||
|
||||
#include "graphics/LightEnv.h"
|
||||
#include "lib/res/handle.h"
|
||||
#include "ps/CStr.h"
|
||||
#include "ps/FileIo.h"
|
||||
#include "scriptinterface/ScriptTypes.h"
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2020 Wildfire Games.
|
||||
/* Copyright (C) 2022 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -22,11 +22,8 @@
|
||||
#ifndef INCLUDED_MINIPATCH
|
||||
#define INCLUDED_MINIPATCH
|
||||
|
||||
#include "lib/res/handle.h"
|
||||
|
||||
class CTerrainTextureEntry;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CMiniPatch: definition of a single terrain tile
|
||||
class CMiniPatch
|
||||
{
|
||||
@ -43,5 +40,4 @@ public:
|
||||
int GetPriority() { return Priority; }
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
#endif // INCLUDED_MINIPATCH
|
||||
|
@ -19,7 +19,6 @@
|
||||
#define INCLUDED_TERRAINTEXTUREMANAGER
|
||||
|
||||
#include "lib/file/vfs/vfs_path.h"
|
||||
#include "lib/res/handle.h"
|
||||
#include "ps/CStr.h"
|
||||
#include "ps/Singleton.h"
|
||||
#include "renderer/backend/gl/DeviceCommandContext.h"
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2021 Wildfire Games.
|
||||
/* Copyright (C) 2022 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -20,7 +20,6 @@
|
||||
#include "graphics/TextureManager.h"
|
||||
#include "lib/external_libraries/libsdl.h"
|
||||
#include "lib/file/vfs/vfs.h"
|
||||
#include "lib/res/h_mgr.h"
|
||||
#include "lib/tex/tex.h"
|
||||
#include "lib/ogl.h"
|
||||
#include "ps/XML/Xeromyces.h"
|
||||
@ -39,8 +38,6 @@ public:
|
||||
TS_ASSERT_OK(m_VFS->Mount(L"", DataDir() / "mods" / "_test.tex" / "", VFS_MOUNT_MUST_EXIST));
|
||||
TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY));
|
||||
|
||||
h_mgr_init();
|
||||
|
||||
CXeromyces::Startup();
|
||||
}
|
||||
|
||||
@ -48,8 +45,6 @@ public:
|
||||
{
|
||||
CXeromyces::Terminate();
|
||||
|
||||
h_mgr_shutdown();
|
||||
|
||||
m_VFS.reset();
|
||||
DeleteDirectory(DataDir()/"_testcache");
|
||||
}
|
||||
|
@ -1,789 +0,0 @@
|
||||
/* Copyright (C) 2019 Wildfire Games.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* handle manager for resources.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
#include "h_mgr.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <limits.h> // CHAR_BIT
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <new> // std::bad_alloc
|
||||
|
||||
#include "lib/fnv_hash.h"
|
||||
#include "lib/allocators/overrun_protector.h"
|
||||
#include "lib/allocators/pool.h"
|
||||
#include "lib/module_init.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace ERR {
|
||||
static const Status H_IDX_INVALID = -120000; // totally invalid
|
||||
static const Status H_IDX_UNUSED = -120001; // beyond current cap
|
||||
static const Status H_TAG_MISMATCH = -120003;
|
||||
static const Status H_TYPE_MISMATCH = -120004;
|
||||
static const Status H_ALREADY_FREED = -120005;
|
||||
}
|
||||
static const StatusDefinition hStatusDefinitions[] = {
|
||||
{ ERR::H_IDX_INVALID, L"Handle index completely out of bounds" },
|
||||
{ ERR::H_IDX_UNUSED, L"Handle index exceeds high-water mark" },
|
||||
{ ERR::H_TAG_MISMATCH, L"Handle tag mismatch (stale reference?)" },
|
||||
{ ERR::H_TYPE_MISMATCH, L"Handle type mismatch" },
|
||||
{ ERR::H_ALREADY_FREED, L"Handle already freed" }
|
||||
};
|
||||
STATUS_ADD_DEFINITIONS(hStatusDefinitions);
|
||||
|
||||
|
||||
|
||||
// rationale
|
||||
//
|
||||
// why fixed size control blocks, instead of just allocating dynamically?
|
||||
// it is expected that resources be created and freed often. this way is
|
||||
// much nicer to the memory manager. defining control blocks larger than
|
||||
// the allotted space is caught by h_alloc (made possible by the vtbl builder
|
||||
// storing control block size). it is also efficient to have all CBs in an
|
||||
// more or less contiguous array (see below).
|
||||
//
|
||||
// why a manager, instead of a simple pool allocator?
|
||||
// we need a central list of resources for freeing at exit, checking if a
|
||||
// resource has already been loaded (for caching), and when reloading.
|
||||
// may as well keep them in an array, rather than add a list and index.
|
||||
|
||||
|
||||
|
||||
//
|
||||
// handle
|
||||
//
|
||||
|
||||
// 0 = invalid handle value
|
||||
// < 0 is an error code (we assume < 0 <==> MSB is set -
|
||||
// true for 1s and 2s complement and sign-magnitude systems)
|
||||
|
||||
// fields:
|
||||
// (shift value = # bits between LSB and field LSB.
|
||||
// may be larger than the field type - only shift Handle vars!)
|
||||
|
||||
// - index (0-based) of control block in our array.
|
||||
// (field width determines maximum currently open handles)
|
||||
#define IDX_BITS 16
|
||||
static const u64 IDX_MASK = (1l << IDX_BITS) - 1;
|
||||
|
||||
// - tag (1-based) ensures the handle references a certain resource instance.
|
||||
// (field width determines maximum unambiguous resource allocs)
|
||||
using Tag = i64;
|
||||
#define TAG_BITS 48
|
||||
|
||||
// make sure both fields fit within a Handle variable
|
||||
cassert(IDX_BITS + TAG_BITS <= sizeof(Handle)*CHAR_BIT);
|
||||
|
||||
|
||||
// return the handle's index field (always non-negative).
|
||||
// no error checking!
|
||||
static inline size_t h_idx(const Handle h)
|
||||
{
|
||||
return (size_t)(h & IDX_MASK) - 1;
|
||||
}
|
||||
|
||||
// build a handle from index and tag.
|
||||
// can't fail.
|
||||
static inline Handle handle(size_t idx, u64 tag)
|
||||
{
|
||||
const size_t idxPlusOne = idx+1;
|
||||
ENSURE(idxPlusOne <= IDX_MASK);
|
||||
ENSURE((tag & IDX_MASK) == 0);
|
||||
Handle h = tag | idxPlusOne;
|
||||
ENSURE(h > 0);
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// internal per-resource-instance data
|
||||
//
|
||||
|
||||
// chosen so that all current resource structs are covered.
|
||||
static const size_t HDATA_USER_SIZE = 104;
|
||||
|
||||
|
||||
struct HDATA
|
||||
{
|
||||
// we only need the tag, because it is trivial to compute
|
||||
// &HDATA from idx and vice versa. storing the entire handle
|
||||
// avoids needing to extract the tag field.
|
||||
Handle h; // NB: will be overwritten by pool_free
|
||||
|
||||
uintptr_t key;
|
||||
|
||||
intptr_t refs;
|
||||
|
||||
// smaller bit fields combined into 1
|
||||
// .. if set, do not actually release the resource (i.e. call dtor)
|
||||
// when the handle is h_free-d, regardless of the refcount.
|
||||
// set by h_alloc; reset on exit and by housekeeping.
|
||||
u32 keep_open : 1;
|
||||
// .. HACK: prevent adding to h_find lookup index if flags & RES_UNIQUE
|
||||
// (because those handles might have several instances open,
|
||||
// which the index can't currently handle)
|
||||
u32 unique : 1;
|
||||
u32 disallow_reload : 1;
|
||||
|
||||
H_Type type;
|
||||
|
||||
// for statistics
|
||||
size_t num_derefs;
|
||||
|
||||
// storing PIVFS here is not a good idea since this often isn't
|
||||
// `freed' due to caching (and there is no dtor), so
|
||||
// the VFS reference count would never reach zero.
|
||||
VfsPath pathname;
|
||||
|
||||
u8 user[HDATA_USER_SIZE];
|
||||
};
|
||||
|
||||
|
||||
// max data array entries. compared to last_in_use => signed.
|
||||
static const ssize_t hdata_cap = (1ul << IDX_BITS)/4;
|
||||
|
||||
// pool of fixed-size elements allows O(1) alloc and free;
|
||||
// there is a simple mapping between HDATA address and index.
|
||||
static Pool hpool;
|
||||
|
||||
|
||||
// error checking strategy:
|
||||
// all handles passed in go through h_data(Handle, Type)
|
||||
|
||||
|
||||
// get a (possibly new) array entry.
|
||||
//
|
||||
// fails if idx is out of bounds.
|
||||
static Status h_data_from_idx(ssize_t idx, HDATA*& hd)
|
||||
{
|
||||
// don't check if idx is beyond the current high-water mark, because
|
||||
// we might be allocating a new entry. subsequent tag checks protect
|
||||
// against using unallocated entries.
|
||||
if(size_t(idx) >= size_t(hdata_cap)) // also detects negative idx
|
||||
WARN_RETURN(ERR::H_IDX_INVALID);
|
||||
|
||||
hd = (HDATA*)(hpool.da.base + idx*hpool.el_size);
|
||||
hd->num_derefs++;
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
static ssize_t h_idx_from_data(HDATA* hd)
|
||||
{
|
||||
if(!pool_contains(&hpool, hd))
|
||||
WARN_RETURN(ERR::INVALID_POINTER);
|
||||
return (uintptr_t(hd) - uintptr_t(hpool.da.base))/hpool.el_size;
|
||||
}
|
||||
|
||||
|
||||
// get HDATA for the given handle.
|
||||
// only uses (and checks) the index field.
|
||||
// used by h_force_close (which must work regardless of tag).
|
||||
static inline Status h_data_no_tag(const Handle h, HDATA*& hd)
|
||||
{
|
||||
ssize_t idx = (ssize_t)h_idx(h);
|
||||
RETURN_STATUS_IF_ERR(h_data_from_idx(idx, hd));
|
||||
// need to verify it's in range - h_data_from_idx can only verify that
|
||||
// it's < maximum allowable index.
|
||||
if(uintptr_t(hd) > uintptr_t(hpool.da.base)+hpool.da.pos)
|
||||
WARN_RETURN(ERR::H_IDX_UNUSED);
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
|
||||
static bool ignoreDoubleFree = false;
|
||||
|
||||
// get HDATA for the given handle.
|
||||
// also verifies the tag field.
|
||||
// used by functions callable for any handle type, e.g. h_filename.
|
||||
static inline Status h_data_tag(Handle h, HDATA*& hd)
|
||||
{
|
||||
RETURN_STATUS_IF_ERR(h_data_no_tag(h, hd));
|
||||
|
||||
if(hd->key == 0) // HDATA was wiped out and hd->h overwritten by pool_free
|
||||
{
|
||||
if(ignoreDoubleFree)
|
||||
return ERR::H_ALREADY_FREED; // NOWARN (see ignoreDoubleFree)
|
||||
else
|
||||
WARN_RETURN(ERR::H_ALREADY_FREED);
|
||||
}
|
||||
|
||||
if(h != hd->h)
|
||||
WARN_RETURN(ERR::H_TAG_MISMATCH);
|
||||
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
|
||||
// get HDATA for the given handle.
|
||||
// also verifies the type.
|
||||
// used by most functions accessing handle data.
|
||||
static Status h_data_tag_type(const Handle h, const H_Type type, HDATA*& hd)
|
||||
{
|
||||
RETURN_STATUS_IF_ERR(h_data_tag(h, hd));
|
||||
|
||||
// h_alloc makes sure type isn't 0, so no need to check that here.
|
||||
if(hd->type != type)
|
||||
{
|
||||
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());
|
||||
WARN_RETURN(ERR::H_TYPE_MISMATCH);
|
||||
}
|
||||
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// lookup data structure
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// speed up h_find (called every h_alloc)
|
||||
// multimap, because we want to add handles of differing type but same key
|
||||
// (e.g. a VFile and Tex object for the same underlying filename hash key)
|
||||
//
|
||||
// store index because it's smaller and Handle can easily be reconstructed
|
||||
//
|
||||
//
|
||||
// note: there may be several RES_UNIQUE handles of the same type and key
|
||||
// (e.g. sound files - several instances of a sound definition file).
|
||||
// that wasn't foreseen here, so we'll just refrain from adding to the index.
|
||||
// that means they won't be found via h_find - no biggie.
|
||||
|
||||
using Key2Idx = std::unordered_multimap<uintptr_t, ssize_t>;
|
||||
using It = Key2Idx::iterator;
|
||||
static OverrunProtector<Key2Idx> key2idx_wrapper;
|
||||
|
||||
enum KeyRemoveFlag { KEY_NOREMOVE, KEY_REMOVE };
|
||||
|
||||
static Handle key_find(uintptr_t key, H_Type type, KeyRemoveFlag remove_option = KEY_NOREMOVE)
|
||||
{
|
||||
Key2Idx* key2idx = key2idx_wrapper.get();
|
||||
if(!key2idx)
|
||||
WARN_RETURN(ERR::NO_MEM);
|
||||
|
||||
// initial return value: "not found at all, or it's of the
|
||||
// wrong type". the latter happens when called by h_alloc to
|
||||
// check if e.g. a Tex object already exists; at that time,
|
||||
// only the corresponding VFile exists.
|
||||
Handle ret = -1;
|
||||
|
||||
std::pair<It, It> range = key2idx->equal_range(key);
|
||||
for(It it = range.first; it != range.second; ++it)
|
||||
{
|
||||
ssize_t idx = it->second;
|
||||
HDATA* hd;
|
||||
if(h_data_from_idx(idx, hd) != INFO::OK)
|
||||
continue;
|
||||
if(hd->type != type || hd->key != key)
|
||||
continue;
|
||||
|
||||
// found a match
|
||||
if(remove_option == KEY_REMOVE)
|
||||
key2idx->erase(it);
|
||||
ret = hd->h;
|
||||
break;
|
||||
}
|
||||
|
||||
key2idx_wrapper.lock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void key_add(uintptr_t key, Handle h)
|
||||
{
|
||||
Key2Idx* key2idx = key2idx_wrapper.get();
|
||||
if(!key2idx)
|
||||
return;
|
||||
|
||||
const ssize_t idx = h_idx(h);
|
||||
// note: MSDN documentation of stdext::hash_multimap is incorrect;
|
||||
// there is no overload of insert() that returns pair<iterator, bool>.
|
||||
(void)key2idx->insert(std::make_pair(key, idx));
|
||||
|
||||
key2idx_wrapper.lock();
|
||||
}
|
||||
|
||||
|
||||
static void key_remove(uintptr_t key, H_Type type)
|
||||
{
|
||||
Handle ret = key_find(key, type, KEY_REMOVE);
|
||||
ENSURE(ret > 0);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// h_alloc
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
static void warn_if_invalid(HDATA* hd)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
H_VTbl* vtbl = hd->type;
|
||||
|
||||
// validate HDATA
|
||||
// currently nothing to do; <type> is checked by h_alloc and
|
||||
// the others have no invariants we could check.
|
||||
|
||||
// have the resource validate its user_data
|
||||
Status err = vtbl->validate(hd->user);
|
||||
ENSURE(err == INFO::OK);
|
||||
|
||||
// make sure empty space in control block isn't touched
|
||||
// .. but only if we're not storing a filename there
|
||||
const u8* start = hd->user + vtbl->user_size;
|
||||
const u8* end = hd->user + HDATA_USER_SIZE;
|
||||
for(const u8* p = start; p < end; p++)
|
||||
ENSURE(*p == 0); // else: handle user data was overrun!
|
||||
#else
|
||||
UNUSED2(hd);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static Status type_validate(H_Type type)
|
||||
{
|
||||
if(!type)
|
||||
WARN_RETURN(ERR::INVALID_PARAM);
|
||||
if(type->user_size > HDATA_USER_SIZE)
|
||||
WARN_RETURN(ERR::LIMIT);
|
||||
if(type->name == 0)
|
||||
WARN_RETURN(ERR::INVALID_PARAM);
|
||||
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
|
||||
static Tag gen_tag()
|
||||
{
|
||||
static Tag tag;
|
||||
tag += (1ull << IDX_BITS);
|
||||
// it's not easy to detect overflow, because compilers
|
||||
// are allowed to assume it'll never happen. however,
|
||||
// pow(2, 64-IDX_BITS) is "enough" anyway.
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
||||
static Handle reuse_existing_handle(uintptr_t key, H_Type type, size_t flags)
|
||||
{
|
||||
if(flags & RES_NO_CACHE)
|
||||
return 0;
|
||||
|
||||
// object of specified key and type doesn't exist yet
|
||||
Handle h = h_find(type, key);
|
||||
if(h <= 0)
|
||||
return 0;
|
||||
|
||||
HDATA* hd;
|
||||
RETURN_STATUS_IF_ERR(h_data_tag_type(h, type, hd)); // h_find means this won't fail
|
||||
|
||||
hd->refs += 1;
|
||||
|
||||
// we are reactivating a closed but cached handle.
|
||||
// need to generate a new tag so that copies of the
|
||||
// previous handle can no longer access the resource.
|
||||
// (we don't need to reset the tag in h_free, because
|
||||
// use before this fails due to refs > 0 check in h_user_data).
|
||||
if(hd->refs == 1)
|
||||
{
|
||||
const Tag tag = gen_tag();
|
||||
h = handle(h_idx(h), tag); // can't fail
|
||||
hd->h = h;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
static Status call_init_and_reload(Handle h, H_Type type, HDATA* hd, const PIVFS& vfs, const VfsPath& pathname, va_list* init_args)
|
||||
{
|
||||
Status err = INFO::OK;
|
||||
H_VTbl* vtbl = type; // exact same thing but for clarity
|
||||
|
||||
// init
|
||||
if(vtbl->init)
|
||||
vtbl->init(hd->user, *init_args);
|
||||
|
||||
// reload
|
||||
if(vtbl->reload)
|
||||
{
|
||||
// catch exception to simplify reload funcs - let them use new()
|
||||
try
|
||||
{
|
||||
err = vtbl->reload(hd->user, vfs, pathname, h);
|
||||
if(err == INFO::OK)
|
||||
warn_if_invalid(hd);
|
||||
}
|
||||
catch(std::bad_alloc&)
|
||||
{
|
||||
err = ERR::NO_MEM;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static Handle alloc_new_handle(H_Type type, const PIVFS& vfs, const VfsPath& pathname, uintptr_t key, size_t flags, va_list* init_args)
|
||||
{
|
||||
HDATA* hd = (HDATA*)pool_alloc(&hpool, 0);
|
||||
if(!hd)
|
||||
WARN_RETURN(ERR::NO_MEM);
|
||||
new(&hd->pathname) VfsPath;
|
||||
|
||||
ssize_t idx = h_idx_from_data(hd);
|
||||
RETURN_STATUS_IF_ERR(idx);
|
||||
|
||||
// (don't want to do this before the add-reference exit,
|
||||
// so as not to waste tags for often allocated handles.)
|
||||
const Tag tag = gen_tag();
|
||||
Handle h = handle(idx, tag); // can't fail.
|
||||
|
||||
hd->h = h;
|
||||
hd->key = key;
|
||||
hd->type = type;
|
||||
hd->refs = 1;
|
||||
if(!(flags & RES_NO_CACHE))
|
||||
hd->keep_open = 1;
|
||||
if(flags & RES_DISALLOW_RELOAD)
|
||||
hd->disallow_reload = 1;
|
||||
hd->unique = (flags & RES_UNIQUE) != 0;
|
||||
hd->pathname = pathname;
|
||||
|
||||
if(key && !hd->unique)
|
||||
key_add(key, h);
|
||||
|
||||
Status err = call_init_and_reload(h, type, hd, vfs, pathname, init_args);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
return h;
|
||||
|
||||
fail:
|
||||
// reload failed; free the handle
|
||||
hd->keep_open = 0; // disallow caching (since contents are invalid)
|
||||
(void)h_free(h, type); // (h_free already does WARN_IF_ERR)
|
||||
|
||||
// note: since some uses will always fail (e.g. loading sounds if
|
||||
// g_Quickstart), do not complain here.
|
||||
return (Handle)err;
|
||||
}
|
||||
|
||||
|
||||
static std::recursive_mutex h_mutex;
|
||||
|
||||
// any further params are passed to type's init routine
|
||||
Handle h_alloc(H_Type type, const PIVFS& vfs, const VfsPath& pathname, size_t flags, ...)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(h_mutex);
|
||||
|
||||
RETURN_STATUS_IF_ERR(type_validate(type));
|
||||
|
||||
const uintptr_t key = fnv_hash(pathname.string().c_str(), pathname.string().length()*sizeof(pathname.string()[0]));
|
||||
|
||||
// see if we can reuse an existing handle
|
||||
Handle h = reuse_existing_handle(key, type, flags);
|
||||
RETURN_STATUS_IF_ERR(h);
|
||||
// .. successfully reused the handle; refcount increased
|
||||
if(h > 0)
|
||||
return h;
|
||||
// .. need to allocate a new one:
|
||||
va_list args;
|
||||
va_start(args, flags);
|
||||
h = alloc_new_handle(type, vfs, pathname, key, flags, &args);
|
||||
va_end(args);
|
||||
return h; // alloc_new_handle already does WARN_RETURN_STATUS_IF_ERR
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static void h_free_hd(HDATA* hd)
|
||||
{
|
||||
if(hd->refs > 0)
|
||||
hd->refs--;
|
||||
|
||||
// still references open or caching requests it stays - do not release.
|
||||
if(hd->refs > 0 || hd->keep_open)
|
||||
return;
|
||||
|
||||
// actually release the resource (call dtor, free control block).
|
||||
|
||||
// h_alloc makes sure type != 0; if we get here, it still is
|
||||
H_VTbl* vtbl = hd->type;
|
||||
|
||||
// call its destructor
|
||||
// note: H_TYPE_DEFINE currently always defines a dtor, but play it safe
|
||||
if(vtbl->dtor)
|
||||
vtbl->dtor(hd->user);
|
||||
|
||||
if(hd->key && !hd->unique)
|
||||
key_remove(hd->key, hd->type);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// to_string is slow for some handles, so avoid calling it if unnecessary
|
||||
if(debug_filter_allows("H_MGR|"))
|
||||
{
|
||||
wchar_t buf[H_STRING_LEN];
|
||||
if(vtbl->to_string(hd->user, buf) < 0)
|
||||
wcscpy_s(buf, ARRAY_SIZE(buf), L"(error)");
|
||||
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());
|
||||
}
|
||||
#endif
|
||||
|
||||
hd->pathname.~VfsPath(); // FIXME: ugly hack, but necessary to reclaim memory
|
||||
memset(hd, 0, sizeof(*hd));
|
||||
pool_free(&hpool, hd);
|
||||
}
|
||||
|
||||
|
||||
Status h_free(Handle& h, H_Type type)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(h_mutex);
|
||||
|
||||
// 0-initialized or an error code; don't complain because this
|
||||
// happens often and is harmless.
|
||||
if(h <= 0)
|
||||
return INFO::OK;
|
||||
|
||||
// wipe out the handle to prevent reuse but keep a copy for below.
|
||||
const Handle h_copy = h;
|
||||
h = 0;
|
||||
|
||||
HDATA* hd;
|
||||
RETURN_STATUS_IF_ERR(h_data_tag_type(h_copy, type, hd));
|
||||
|
||||
h_free_hd(hd);
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// remaining API
|
||||
|
||||
void* h_user_data(const Handle h, const H_Type type)
|
||||
{
|
||||
HDATA* hd;
|
||||
if(h_data_tag_type(h, type, hd) != INFO::OK)
|
||||
return 0;
|
||||
|
||||
if(!hd->refs)
|
||||
{
|
||||
// note: resetting the tag is not enough (user might pass in its value)
|
||||
DEBUG_WARN_ERR(ERR::LOGIC); // no references to resource (it's cached, but someone is accessing it directly)
|
||||
return 0;
|
||||
}
|
||||
|
||||
warn_if_invalid(hd);
|
||||
return hd->user;
|
||||
}
|
||||
|
||||
|
||||
VfsPath h_filename(const Handle h)
|
||||
{
|
||||
// don't require type check: should be usable for any handle,
|
||||
// even if the caller doesn't know its type.
|
||||
HDATA* hd;
|
||||
if(h_data_tag(h, hd) != INFO::OK)
|
||||
return VfsPath();
|
||||
return hd->pathname;
|
||||
}
|
||||
|
||||
|
||||
// TODO: what if iterating through all handles is too slow?
|
||||
Status h_reload(const PIVFS& vfs, const VfsPath& pathname)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(h_mutex);
|
||||
|
||||
const u32 key = fnv_hash(pathname.string().c_str(), pathname.string().length()*sizeof(pathname.string()[0]));
|
||||
|
||||
// destroy (note: not free!) all handles backed by this file.
|
||||
// do this before reloading any of them, because we don't specify reload
|
||||
// order (the parent resource may be reloaded first, and load the child,
|
||||
// whose original data would leak).
|
||||
for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size))
|
||||
{
|
||||
if(hd->key == 0 || hd->key != key || hd->disallow_reload)
|
||||
continue;
|
||||
hd->type->dtor(hd->user);
|
||||
}
|
||||
|
||||
Status ret = INFO::OK;
|
||||
|
||||
// now reload all affected handles
|
||||
size_t i = 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++)
|
||||
{
|
||||
if(hd->key == 0 || hd->key != key || hd->disallow_reload)
|
||||
continue;
|
||||
|
||||
Status err = hd->type->reload(hd->user, vfs, hd->pathname, hd->h);
|
||||
// don't stop if an error is encountered - try to reload them all.
|
||||
if(err < 0)
|
||||
{
|
||||
h_free(hd->h, hd->type);
|
||||
if(ret == 0) // don't overwrite first error
|
||||
ret = err;
|
||||
}
|
||||
else
|
||||
warn_if_invalid(hd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Handle h_find(H_Type type, uintptr_t key)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(h_mutex);
|
||||
return key_find(key, type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// force the resource to be freed immediately, even if cached.
|
||||
// tag is not checked - this allows the first Handle returned
|
||||
// (whose tag will change after being 'freed', but remaining in memory)
|
||||
// to later close the object.
|
||||
// this is used when reinitializing the sound engine -
|
||||
// at that point, all (cached) OpenAL resources must be freed.
|
||||
Status h_force_free(Handle h, H_Type type)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(h_mutex);
|
||||
|
||||
// require valid index; ignore tag; type checked below.
|
||||
HDATA* hd;
|
||||
RETURN_STATUS_IF_ERR(h_data_no_tag(h, hd));
|
||||
if(hd->type != type)
|
||||
WARN_RETURN(ERR::H_TYPE_MISMATCH);
|
||||
hd->keep_open = 0;
|
||||
hd->refs = 0;
|
||||
h_free_hd(hd);
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
|
||||
// increment Handle <h>'s reference count.
|
||||
// only meant to be used for objects that free a Handle in their dtor,
|
||||
// so that they are copy-equivalent and can be stored in a STL container.
|
||||
// do not use this to implement refcounting on top of the Handle scheme,
|
||||
// e.g. loading a Handle once and then passing it around. instead, have each
|
||||
// user load the resource; refcounting is done under the hood.
|
||||
void h_add_ref(Handle h)
|
||||
{
|
||||
HDATA* hd;
|
||||
if(h_data_tag(h, hd) != INFO::OK)
|
||||
return;
|
||||
|
||||
ENSURE(hd->refs); // if there are no refs, how did the caller manage to keep a Handle?!
|
||||
hd->refs++;
|
||||
}
|
||||
|
||||
|
||||
// retrieve the internal reference count or a negative error code.
|
||||
// background: since h_alloc has no way of indicating whether it
|
||||
// allocated a new handle or reused an existing one, counting references
|
||||
// within resource control blocks is impossible. since that is sometimes
|
||||
// necessary (always wrapping objects in Handles is excessive), we
|
||||
// provide access to the internal reference count.
|
||||
intptr_t h_get_refcnt(Handle h)
|
||||
{
|
||||
HDATA* hd;
|
||||
RETURN_STATUS_IF_ERR(h_data_tag(h, hd));
|
||||
|
||||
ENSURE(hd->refs); // if there are no refs, how did the caller manage to keep a Handle?!
|
||||
return hd->refs;
|
||||
}
|
||||
|
||||
|
||||
static ModuleInitState initState;
|
||||
|
||||
static Status Init()
|
||||
{
|
||||
RETURN_STATUS_IF_ERR(pool_create(&hpool, hdata_cap*sizeof(HDATA), sizeof(HDATA)));
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
static void Shutdown()
|
||||
{
|
||||
debug_printf("H_MGR| shutdown. any handle frees after this are leaks!\n");
|
||||
// objects that store handles to other objects are destroyed before their
|
||||
// children, so the subsequent forced destruction of the child here will
|
||||
// raise a double-free warning unless we ignore it. (#860, #915, #920)
|
||||
ignoreDoubleFree = true;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(h_mutex);
|
||||
|
||||
// forcibly close all open handles
|
||||
for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size))
|
||||
{
|
||||
// it's already been freed; don't free again so that this
|
||||
// doesn't look like an error.
|
||||
if(hd->key == 0)
|
||||
continue;
|
||||
|
||||
// disable caching; we need to release the resource now.
|
||||
hd->keep_open = 0;
|
||||
hd->refs = 0;
|
||||
|
||||
h_free_hd(hd);
|
||||
}
|
||||
|
||||
pool_destroy(&hpool);
|
||||
}
|
||||
|
||||
void h_mgr_free_type(const H_Type type)
|
||||
{
|
||||
ignoreDoubleFree = true;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(h_mutex);
|
||||
|
||||
// forcibly close all open handles of the specified type
|
||||
for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size))
|
||||
{
|
||||
// free if not previously freed and only free the proper type
|
||||
if (hd->key == 0 || hd->type != type)
|
||||
continue;
|
||||
|
||||
// disable caching; we need to release the resource now.
|
||||
hd->keep_open = 0;
|
||||
hd->refs = 0;
|
||||
|
||||
h_free_hd(hd);
|
||||
}
|
||||
}
|
||||
|
||||
void h_mgr_init()
|
||||
{
|
||||
ModuleInit(&initState, Init);
|
||||
}
|
||||
|
||||
void h_mgr_shutdown()
|
||||
{
|
||||
ModuleShutdown(&initState, Shutdown);
|
||||
}
|
@ -1,432 +0,0 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* handle manager for resources.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
[KEEP IN SYNC WITH WIKI]
|
||||
|
||||
introduction
|
||||
------------
|
||||
|
||||
a resource is an instance of a specific type of game data (e.g. texture),
|
||||
described by a control block (example fields: format, pointer to tex data).
|
||||
|
||||
this module allocates storage for the control blocks, which are accessed
|
||||
via handle. it also provides support for transparently reloading resources
|
||||
from disk (allows in-game editing of data), and caches resource data.
|
||||
finally, it frees all resources at exit, preventing leaks.
|
||||
|
||||
|
||||
handles
|
||||
-------
|
||||
|
||||
handles are an indirection layer between client code and resources
|
||||
(represented by their control blocks, which contains/points to its data).
|
||||
they allow an important check not possible with a direct pointer:
|
||||
guaranteeing the handle references a given resource /instance/.
|
||||
|
||||
problem: code C1 allocates a resource, and receives a pointer p to its
|
||||
control block. C1 passes p on to C2, and later frees it.
|
||||
now other code allocates a resource, and happens to reuse the free slot
|
||||
pointed to by p (also possible if simply allocating from the heap).
|
||||
when C2 accesses p, the pointer is valid, but we cannot tell that
|
||||
it is referring to a resource that had already been freed. big trouble.
|
||||
|
||||
solution: each allocation receives a unique tag (a global counter that
|
||||
is large enough to never overflow). Handles include this tag, as well
|
||||
as a reference (array index) to the control block, which isn't directly
|
||||
accessible. when dereferencing the handle, we check if the handle's tag
|
||||
matches the copy stored in the control block. this protects against stale
|
||||
handle reuse, double-free, and accidentally referencing other resources.
|
||||
|
||||
type: each handle has an associated type. these must be checked to prevent
|
||||
using textures as sounds, for example. with the manual vtbl scheme,
|
||||
this type is actually a pointer to the resource object's vtbl, and is
|
||||
set up via H_TYPE_DEFINE. this means that types are private to the module
|
||||
that declared the handle; knowledge of the type ensures the caller
|
||||
actually declared, and owns the resource.
|
||||
|
||||
|
||||
guide to defining and using resources
|
||||
-------------------------------------
|
||||
|
||||
1) choose a name for the resource, used to represent all resources
|
||||
of this type. we will call ours "Res1"; all below occurrences of this
|
||||
must be replaced with the actual name (exact spelling).
|
||||
why? the vtbl builder defines its functions as e.g. Res1_reload;
|
||||
your actual definition must match.
|
||||
|
||||
2) declare its control block:
|
||||
struct Res1
|
||||
{
|
||||
void* data; // data loaded from file
|
||||
size_t flags; // set when resource is created
|
||||
};
|
||||
|
||||
Note that all control blocks are stored in fixed-size slots
|
||||
(HDATA_USER_SIZE bytes), so squeezing the size of your data doesn't
|
||||
help unless yours is the largest.
|
||||
|
||||
3) build its vtbl:
|
||||
H_TYPE_DEFINE(Res1);
|
||||
|
||||
this defines the symbol H_Res1, which is used whenever the handle
|
||||
manager needs its type. it is only accessible to this module
|
||||
(file scope). note that it is actually a pointer to the vtbl.
|
||||
this must come before uses of H_Res1, and after the CB definition;
|
||||
there are no restrictions WRT functions, because the macro
|
||||
forward-declares what it needs.
|
||||
|
||||
4) implement all 'virtual' functions from the resource interface.
|
||||
note that inheritance isn't really possible with this approach -
|
||||
all functions must be defined, even if not needed.
|
||||
|
||||
--
|
||||
|
||||
init:
|
||||
one-time init of the control block. called from h_alloc.
|
||||
precondition: control block is initialized to 0.
|
||||
|
||||
static void Type_init(Res1* r, va_list args)
|
||||
{
|
||||
r->flags = va_arg(args, int);
|
||||
}
|
||||
|
||||
if the caller of h_alloc passed additional args, they are available
|
||||
in args. if init references more args than were passed, big trouble.
|
||||
however, this is a bug in your code, and cannot be triggered
|
||||
maliciously. only your code knows the resource type, and it is the
|
||||
only call site of h_alloc.
|
||||
there is no provision for indicating failure. if one-time init fails
|
||||
(rare, but one example might be failure to allocate memory that is
|
||||
for the lifetime of the resource, instead of in reload), it will
|
||||
have to set the control block state such that reload will fail.
|
||||
|
||||
--
|
||||
|
||||
reload:
|
||||
does all initialization of the resource that requires its source file.
|
||||
called after init; also after dtor every time the file is reloaded.
|
||||
|
||||
static Status Type_reload(Res1* r, const VfsPath& pathname, Handle);
|
||||
{
|
||||
// already loaded; done
|
||||
if(r->data)
|
||||
return 0;
|
||||
|
||||
r->data = malloc(100);
|
||||
if(!r->data)
|
||||
WARN_RETURN(ERR::NO_MEM);
|
||||
// (read contents of <pathname> into r->data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
reload must abort if the control block data indicates the resource
|
||||
has already been loaded! example: if texture's reload is called first,
|
||||
it loads itself from file (triggering file.reload); afterwards,
|
||||
file.reload will be called again. we can't avoid this, because the
|
||||
handle manager doesn't know anything about dependencies
|
||||
(here, texture -> file).
|
||||
return value: 0 if successful (includes 'already loaded'),
|
||||
negative error code otherwise. if this fails, the resource is freed
|
||||
(=> dtor is called!).
|
||||
|
||||
note that any subsequent changes to the resource state must be
|
||||
stored in the control block and 'replayed' when reloading.
|
||||
example: when uploading a texture, store the upload parameters
|
||||
(filter, internal format); when reloading, upload again accordingly.
|
||||
|
||||
--
|
||||
|
||||
dtor:
|
||||
frees all data allocated by init and reload. called after reload has
|
||||
indicated failure, before reloading a resource, after h_free,
|
||||
or at exit (if the resource is still extant).
|
||||
except when reloading, the control block will be zeroed afterwards.
|
||||
|
||||
static void Type_dtor(Res1* r);
|
||||
{
|
||||
free(r->data);
|
||||
}
|
||||
|
||||
again no provision for reporting errors - there's no one to act on it
|
||||
if called at exit. you can ENSURE or log the error, though.
|
||||
|
||||
be careful to correctly handle the different cases in which this routine
|
||||
can be called! some flags should persist across reloads (e.g. choices made
|
||||
during resource init time that must remain valid), while everything else
|
||||
*should be zeroed manually* (to behave correctly when reloading).
|
||||
be advised that this interface may change; a "prepare for reload" method
|
||||
or "compact/free extraneous resources" may be added.
|
||||
|
||||
--
|
||||
|
||||
validate:
|
||||
makes sure the resource control block is in a valid state. returns 0 if
|
||||
all is well, or a negative error code.
|
||||
called automatically when the Handle is dereferenced or freed.
|
||||
|
||||
static Status Type_validate(const Res1* r);
|
||||
{
|
||||
const int permissible_flags = 0x01;
|
||||
if(debug_IsPointerBogus(r->data))
|
||||
WARN_RETURN(ERR::_1);
|
||||
if(r->flags & ~permissible_flags)
|
||||
WARN_RETURN(ERR::_2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
5) provide your layer on top of the handle manager:
|
||||
Handle res1_load(const VfsPath& pathname, int my_flags)
|
||||
{
|
||||
// passes my_flags to init
|
||||
return h_alloc(H_Res1, pathname, 0, my_flags);
|
||||
}
|
||||
|
||||
Status res1_free(Handle& h)
|
||||
{
|
||||
// control block is automatically zeroed after this.
|
||||
return h_free(h, H_Res1);
|
||||
}
|
||||
|
||||
(this layer allows a res_load interface on top of all the loaders,
|
||||
and is necessary because your module is the only one that knows H_Res1).
|
||||
|
||||
6) done. the resource will be freed at exit (if not done already).
|
||||
|
||||
here's how to access the control block, given a <Handle h>:
|
||||
a)
|
||||
H_DEREF(h, Res1, r);
|
||||
|
||||
creates a variable r of type Res1*, which points to the control block
|
||||
of the resource referenced by h. returns "invalid handle"
|
||||
(a negative error code) on failure.
|
||||
b)
|
||||
Res1* r = h_user_data(h, H_Res1);
|
||||
if(!r)
|
||||
; // bail
|
||||
|
||||
useful if H_DEREF's error return (of type signed integer) isn't
|
||||
acceptable. otherwise, prefer a) - this is pretty clunky, and
|
||||
we could switch H_DEREF to throwing an exception on error.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_H_MGR
|
||||
#define INCLUDED_H_MGR
|
||||
|
||||
// do not include from public header files!
|
||||
// handle.h declares type Handle, and avoids making
|
||||
// everything dependent on this (rather often updated) header.
|
||||
|
||||
|
||||
#include <stdarg.h> // type init routines get va_list of args
|
||||
|
||||
#ifndef INCLUDED_HANDLE
|
||||
#include "handle.h"
|
||||
#endif
|
||||
|
||||
#include "lib/file/vfs/vfs.h"
|
||||
|
||||
extern void h_mgr_init();
|
||||
extern void h_mgr_shutdown();
|
||||
|
||||
|
||||
// handle type (for 'type safety' - can't use a texture handle as a sound)
|
||||
|
||||
// registering extension for each module is bad - some may use many
|
||||
// (e.g. texture - many formats).
|
||||
// handle manager shouldn't know about handle types
|
||||
|
||||
|
||||
/*
|
||||
///xxx advantage of manual vtbl:
|
||||
no boilerplate init, h_alloc calls ctor directly, make sure it fits in the memory slot
|
||||
vtbl contains sizeof resource data, and name!
|
||||
but- has to handle variable params, a bit ugly
|
||||
*/
|
||||
|
||||
// 'manual vtbl' type id
|
||||
// handles have a type, to prevent using e.g. texture handles as a sound.
|
||||
//
|
||||
// alternatives:
|
||||
// - enum of all handle types (smaller, have to pass all methods to h_alloc)
|
||||
// - class (difficult to compare type, handle manager needs to know of all users)
|
||||
//
|
||||
// checked in h_alloc:
|
||||
// - user_size must fit in what the handle manager provides
|
||||
// - name must not be 0
|
||||
//
|
||||
// init: user data is initially zeroed
|
||||
// dtor: user data is zeroed afterwards
|
||||
// reload: if this resource type is opened by another resource's reload,
|
||||
// our reload routine MUST check if already opened! This is relevant when
|
||||
// a file is reloaded: if e.g. a sound object opens a file, the handle
|
||||
// manager calls the reload routines for the 2 handles in unspecified order.
|
||||
// ensuring the order would require a tag field that can't overflow -
|
||||
// not really guaranteed with 32-bit handles. it'd also be more work
|
||||
// to sort the handles by creation time, or account for several layers of
|
||||
// dependencies.
|
||||
struct H_VTbl
|
||||
{
|
||||
void (*init)(void* user, va_list);
|
||||
Status (*reload)(void* user, const PIVFS& vfs, const VfsPath& pathname, Handle);
|
||||
void (*dtor)(void* user);
|
||||
Status (*validate)(const void* user);
|
||||
Status (*to_string)(const void* user, wchar_t* buf);
|
||||
size_t user_size;
|
||||
const wchar_t* name;
|
||||
};
|
||||
|
||||
typedef H_VTbl* H_Type;
|
||||
|
||||
#define H_TYPE_DEFINE(type)\
|
||||
/* forward decls */\
|
||||
static void type##_init(type*, va_list);\
|
||||
static Status type##_reload(type*, const PIVFS&, const VfsPath&, Handle);\
|
||||
static void type##_dtor(type*);\
|
||||
static Status type##_validate(const type*);\
|
||||
static Status type##_to_string(const type*, wchar_t* buf);\
|
||||
static H_VTbl V_##type =\
|
||||
{\
|
||||
(void (*)(void*, va_list))type##_init,\
|
||||
(Status (*)(void*, const PIVFS&, const VfsPath&, Handle))type##_reload,\
|
||||
(void (*)(void*))type##_dtor,\
|
||||
(Status (*)(const void*))type##_validate,\
|
||||
(Status (*)(const void*, wchar_t*))type##_to_string,\
|
||||
sizeof(type), /* control block size */\
|
||||
WIDEN(#type) /* name */\
|
||||
};\
|
||||
static H_Type H_##type = &V_##type
|
||||
|
||||
// note: we cast to void* pointers so the functions can be declared to
|
||||
// take the control block pointers, instead of requiring a cast in each.
|
||||
// the forward decls ensure the function signatures are correct.
|
||||
|
||||
|
||||
// convenience macro for h_user_data:
|
||||
// casts its return value to the control block type.
|
||||
// use if H_DEREF's returning a negative error code isn't acceptable.
|
||||
#define H_USER_DATA(h, type) (type*)h_user_data(h, H_##type)
|
||||
|
||||
// even more convenient wrapper for h_user_data:
|
||||
// declares a pointer (<var>), assigns it H_USER_DATA, and has
|
||||
// the user's function return a negative error code on failure.
|
||||
//
|
||||
// note: don't use STMT - var decl must be visible to "caller"
|
||||
#define H_DEREF(h, type, var)\
|
||||
/* h already indicates an error - return immediately to pass back*/\
|
||||
/* that specific error, rather than only ERR::INVALID_HANDLE*/\
|
||||
if(h < 0)\
|
||||
WARN_RETURN((Status)h);\
|
||||
type* const var = H_USER_DATA(h, type);\
|
||||
if(!var)\
|
||||
WARN_RETURN(ERR::INVALID_HANDLE);
|
||||
|
||||
|
||||
// all functions check the passed tag (part of the handle) and type against
|
||||
// the internal values. if they differ, an error is returned.
|
||||
|
||||
|
||||
|
||||
|
||||
// h_alloc flags
|
||||
enum
|
||||
{
|
||||
// alias for RES_TEMP scope. the handle will not be kept open.
|
||||
RES_NO_CACHE = 0x01,
|
||||
|
||||
// not cached, and will never reuse a previous instance
|
||||
RES_UNIQUE = RES_NO_CACHE|0x10,
|
||||
|
||||
// object is requesting it never be reloaded (e.g. because it's not
|
||||
// backed by a file)
|
||||
RES_DISALLOW_RELOAD = 0x20
|
||||
};
|
||||
|
||||
const size_t H_STRING_LEN = 256;
|
||||
|
||||
|
||||
|
||||
// allocate a new handle.
|
||||
// if key is 0, or a (key, type) handle doesn't exist,
|
||||
// some free entry is used.
|
||||
// otherwise, a handle to the existing object is returned,
|
||||
// and HDATA.size != 0.
|
||||
//// user_size is checked to make sure the user data fits in the handle data space.
|
||||
// dtor is associated with type and called when the object is freed.
|
||||
// handle data is initialized to 0; optionally, a pointer to it is returned.
|
||||
extern Handle h_alloc(H_Type type, const PIVFS& vfs, const VfsPath& pathname, size_t flags = 0, ...);
|
||||
extern Status h_free(Handle& h, H_Type type);
|
||||
|
||||
|
||||
// Forcibly frees all handles of a specified type.
|
||||
void h_mgr_free_type(const H_Type type);
|
||||
|
||||
|
||||
// find and return a handle by key (typically filename hash)
|
||||
// currently O(log n).
|
||||
//
|
||||
// HACK: currently can't find RES_UNIQUE handles, because there
|
||||
// may be multiple instances of them, breaking the lookup data structure.
|
||||
extern Handle h_find(H_Type type, uintptr_t key);
|
||||
|
||||
// returns a void* pointer to the control block of the resource <h>,
|
||||
// or 0 on error (i.e. h is invalid or of the wrong type).
|
||||
// prefer using H_DEREF or H_USER_DATA.
|
||||
extern void* h_user_data(Handle h, H_Type type);
|
||||
|
||||
extern VfsPath h_filename(Handle h);
|
||||
|
||||
|
||||
extern Status h_reload(const PIVFS& vfs, const VfsPath& pathname);
|
||||
|
||||
// force the resource to be freed immediately, even if cached.
|
||||
// tag is not checked - this allows the first Handle returned
|
||||
// (whose tag will change after being 'freed', but remaining in memory)
|
||||
// to later close the object.
|
||||
// this is used when reinitializing the sound engine -
|
||||
// at that point, all (cached) OpenAL resources must be freed.
|
||||
extern Status h_force_free(Handle h, H_Type type);
|
||||
|
||||
// increment Handle <h>'s reference count.
|
||||
// only meant to be used for objects that free a Handle in their dtor,
|
||||
// so that they are copy-equivalent and can be stored in a STL container.
|
||||
// do not use this to implement refcounting on top of the Handle scheme,
|
||||
// e.g. loading a Handle once and then passing it around. instead, have each
|
||||
// user load the resource; refcounting is done under the hood.
|
||||
extern void h_add_ref(Handle h);
|
||||
|
||||
// retrieve the internal reference count or a negative error code.
|
||||
// background: since h_alloc has no way of indicating whether it
|
||||
// allocated a new handle or reused an existing one, counting references
|
||||
// within resource control blocks is impossible. since that is sometimes
|
||||
// necessary (always wrapping objects in Handles is excessive), we
|
||||
// provide access to the internal reference count.
|
||||
extern intptr_t h_get_refcnt(Handle h);
|
||||
|
||||
#endif // #ifndef INCLUDED_H_MGR
|
@ -1,43 +0,0 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* forward declaration of Handle (reduces dependencies)
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_HANDLE
|
||||
#define INCLUDED_HANDLE
|
||||
|
||||
/**
|
||||
* `handle' representing a reference to a resource (sound, texture, etc.)
|
||||
*
|
||||
* 0 is the (silently ignored) invalid handle value; < 0 is an error code.
|
||||
*
|
||||
* this is 64 bits because we want tags to remain unique. (tags are a
|
||||
* counter that disambiguate several subsequent uses of the same
|
||||
* resource array slot). 32-bit handles aren't enough because the index
|
||||
* field requires at least 12 bits, thus leaving only about 512K possible
|
||||
* tag values.
|
||||
**/
|
||||
typedef i64 Handle;
|
||||
|
||||
#endif // #ifndef INCLUDED_HANDLE
|
@ -104,7 +104,6 @@ library and IO layer. Read and write are zero-copy.
|
||||
#ifndef INCLUDED_TEX
|
||||
#define INCLUDED_TEX
|
||||
|
||||
#include "lib/res/handle.h"
|
||||
#include "lib/os_path.h"
|
||||
#include "lib/file/vfs/vfs_path.h"
|
||||
#include "lib/allocators/dynarray.h"
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2020 Wildfire Games.
|
||||
/* Copyright (C) 2022 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -16,16 +16,15 @@
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "Filesystem.h"
|
||||
|
||||
#include "lib/sysdep/dir_watch.h"
|
||||
#include "lib/utf8.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/CStr.h"
|
||||
#include "ps/Profile.h"
|
||||
|
||||
#include "lib/res/h_mgr.h" // h_reload
|
||||
#include "lib/sysdep/dir_watch.h"
|
||||
#include "lib/utf8.h"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
PIVFS g_VFS;
|
||||
@ -93,8 +92,6 @@ Status ReloadChangedFiles()
|
||||
|
||||
for (size_t j = 0; j < g_ReloadFuncs.size(); ++j)
|
||||
g_ReloadFuncs[j].first(g_ReloadFuncs[j].second, pathname);
|
||||
|
||||
RETURN_STATUS_IF_ERR(h_reload(g_VFS, pathname));
|
||||
}
|
||||
}
|
||||
return INFO::OK;
|
||||
|
@ -30,8 +30,6 @@
|
||||
#include "lib/external_libraries/libsdl.h"
|
||||
#include "lib/file/common/file_stats.h"
|
||||
#include "lib/input.h"
|
||||
#include "lib/ogl.h"
|
||||
#include "lib/res/h_mgr.h"
|
||||
#include "lib/timer.h"
|
||||
#include "lobby/IXmppClient.h"
|
||||
#include "network/NetServer.h"
|
||||
@ -422,10 +420,6 @@ from_config:
|
||||
|
||||
g_VFS.reset();
|
||||
|
||||
// this forcibly frees all open handles (thus preventing real leaks),
|
||||
// and makes further access to h_mgr impossible.
|
||||
h_mgr_shutdown();
|
||||
|
||||
file_stats_dump();
|
||||
|
||||
TIMER_END(L"resource modules");
|
||||
@ -551,8 +545,6 @@ bool AutostartVisualReplay(const std::string& replayFile);
|
||||
|
||||
bool Init(const CmdLineArgs& args, int flags)
|
||||
{
|
||||
h_mgr_init();
|
||||
|
||||
// Do this as soon as possible, because it chdirs
|
||||
// and will mess up the error reporting if anything
|
||||
// crashes before the working directory is set.
|
||||
|
@ -22,7 +22,6 @@
|
||||
#include "graphics/TerrainTextureManager.h"
|
||||
#include "lib/timer.h"
|
||||
#include "lib/file/file_system.h"
|
||||
#include "lib/res/h_mgr.h"
|
||||
#include "lib/tex/tex.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Game.h"
|
||||
@ -248,9 +247,6 @@ void CReplayPlayer::Replay(const bool serializationtest, const int rejointesttur
|
||||
if (ooslog)
|
||||
g_Game->GetSimulation2()->EnableOOSLog();
|
||||
|
||||
// Initialise h_mgr so it doesn't crash when emitting sounds
|
||||
h_mgr_init();
|
||||
|
||||
ScriptRequest rq(g_Game->GetSimulation2()->GetScriptInterface());
|
||||
JS::RootedValue attribs(rq.cx);
|
||||
ENSURE(Script::ParseJSON(rq, attribsStr, &attribs));
|
||||
|
Loading…
Reference in New Issue
Block a user