2004-03-03 16:16:20 +01:00
|
|
|
// handle manager
|
2004-03-03 00:56:51 +01:00
|
|
|
//
|
|
|
|
// Copyright (c) 2003 Jan Wassenberg
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// Contact info:
|
|
|
|
// Jan.Wassenberg@stud.uni-karlsruhe.de
|
|
|
|
// http://www.stud.uni-karlsruhe.de/~urkt/
|
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
#include "precompiled.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
#include "lib.h"
|
2005-08-12 19:06:53 +02:00
|
|
|
#include "h_mgr.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-06-28 06:06:25 +02:00
|
|
|
|
2004-06-04 14:41:53 +02:00
|
|
|
#include <limits.h> // CHAR_BIT
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
2004-08-08 18:40:59 +02:00
|
|
|
#include <new> // std::bad_alloc
|
2004-03-07 15:17:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
// rationale
|
2004-03-03 00:56:51 +01:00
|
|
|
//
|
2004-03-07 15:17:23 +01:00
|
|
|
// 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
|
2004-05-06 19:14:30 +02:00
|
|
|
// the allotted space is caught by h_alloc (made possible by the vtbl builder
|
2004-03-07 15:17:23 +01:00
|
|
|
// storing control block size). it is also efficient to have all CBs in an
|
|
|
|
// more or less contiguous array (see below).
|
2004-03-03 00:56:51 +01:00
|
|
|
//
|
2004-03-07 15:17:23 +01:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-03-03 16:16:20 +01:00
|
|
|
//
|
2004-03-07 15:17:23 +01:00
|
|
|
// handle
|
2004-03-03 16:16:20 +01:00
|
|
|
//
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
// 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:
|
2004-03-03 16:16:20 +01:00
|
|
|
// (shift value = # bits between LSB and field LSB.
|
|
|
|
// may be larger than the field type - only shift Handle vars!)
|
|
|
|
|
|
|
|
// - tag (1-based) ensures the handle references a certain resource instance.
|
|
|
|
// (field width determines maximum unambiguous resource allocs)
|
2004-03-03 00:56:51 +01:00
|
|
|
#define TAG_BITS 32
|
|
|
|
const uint TAG_SHIFT = 0;
|
2004-03-03 16:16:20 +01:00
|
|
|
const u32 TAG_MASK = 0xffffffff; // safer than (1 << 32) - 1
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
// - index (0-based) of control block in our array.
|
2004-03-03 16:16:20 +01:00
|
|
|
// (field width determines maximum currently open handles)
|
2004-03-03 00:56:51 +01:00
|
|
|
#define IDX_BITS 16
|
|
|
|
const uint IDX_SHIFT = 32;
|
2004-05-06 19:14:30 +02:00
|
|
|
const u32 IDX_MASK = (1l << IDX_BITS) - 1;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
// make sure both fields fit within a Handle variable
|
2004-03-03 00:56:51 +01:00
|
|
|
cassert(IDX_BITS + TAG_BITS <= sizeof(Handle)*CHAR_BIT);
|
|
|
|
|
|
|
|
|
|
|
|
// return the handle's index field (always non-negative).
|
|
|
|
// no error checking!
|
2004-05-06 19:14:30 +02:00
|
|
|
static inline u32 h_idx(const Handle h)
|
2005-03-27 19:24:57 +02:00
|
|
|
{
|
|
|
|
return (u32)((h >> IDX_SHIFT) & IDX_MASK) - 1;
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
// return the handle's tag field.
|
|
|
|
// no error checking!
|
|
|
|
static inline u32 h_tag(const Handle h)
|
2005-03-27 19:24:57 +02:00
|
|
|
{
|
|
|
|
return (u32)((h >> TAG_SHIFT) & TAG_MASK);
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-02 15:46:44 +02:00
|
|
|
// build a handle from index and tag.
|
|
|
|
// can't fail.
|
2004-05-13 15:52:48 +02:00
|
|
|
static inline Handle handle(const u32 _idx, const u32 tag)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-05-13 15:52:48 +02:00
|
|
|
const u32 idx = _idx+1;
|
2005-06-28 06:06:25 +02:00
|
|
|
debug_assert(idx <= IDX_MASK && tag <= TAG_MASK && "handle: idx or tag too big");
|
2004-03-03 00:56:51 +01:00
|
|
|
// somewhat clunky, but be careful with the shift:
|
|
|
|
// *_SHIFT may be larger than its field's type.
|
2004-05-13 15:52:48 +02:00
|
|
|
Handle h_idx = idx & IDX_MASK; h_idx <<= IDX_SHIFT;
|
|
|
|
Handle h_tag = tag & TAG_MASK; h_tag <<= TAG_SHIFT;
|
2004-08-02 15:46:44 +02:00
|
|
|
Handle h = h_idx | h_tag;
|
2005-06-28 06:06:25 +02:00
|
|
|
debug_assert(h > 0);
|
2004-08-02 15:46:44 +02:00
|
|
|
return h;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// internal per-resource-instance data
|
|
|
|
//
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// determines maximum number of references to a resource.
|
2004-08-08 18:40:59 +02:00
|
|
|
static const uint REF_BITS = 16;
|
2004-08-05 15:07:51 +02:00
|
|
|
static const u32 REF_MAX = (1ul << REF_BITS)-1;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
static const uint TYPE_BITS = 8;
|
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// chosen so that all current resource structs are covered,
|
|
|
|
// and so sizeof(HDATA) is a power of 2 (for more efficient array access
|
|
|
|
// and array page usage).
|
2004-07-31 13:29:57 +02:00
|
|
|
static const size_t HDATA_USER_SIZE = 44+64;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// 64 bytes
|
2004-05-13 15:52:48 +02:00
|
|
|
// TODO: not anymore, fix later
|
2004-03-03 00:56:51 +01:00
|
|
|
struct HDATA
|
|
|
|
{
|
|
|
|
uintptr_t key;
|
2004-07-31 21:36:46 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
u32 tag : TAG_BITS;
|
2004-07-31 21:36:46 +02:00
|
|
|
|
2004-08-02 15:46:44 +02:00
|
|
|
// smaller bitfields combined into 1
|
2004-03-03 00:56:51 +01:00
|
|
|
u32 refs : REF_BITS;
|
2004-05-06 19:14:30 +02:00
|
|
|
u32 type_idx : TYPE_BITS;
|
2005-09-27 17:35:17 +02:00
|
|
|
// .. 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.
|
2004-08-02 15:46:44 +02:00
|
|
|
u32 keep_open : 1;
|
2005-09-27 17:35:17 +02:00
|
|
|
// .. 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)
|
2004-10-15 15:19:37 +02:00
|
|
|
u32 unique : 1;
|
2005-09-27 17:35:17 +02:00
|
|
|
u32 disallow_reload : 1;
|
2004-07-31 21:36:46 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
H_Type type;
|
2004-11-25 00:47:48 +01:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
const char* fn;
|
2004-07-31 13:29:57 +02:00
|
|
|
|
|
|
|
u8 user[HDATA_USER_SIZE];
|
2004-03-03 00:56:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// max data array entries. compared to last_in_use => signed.
|
|
|
|
static const i32 hdata_cap = 1ul << IDX_BITS;
|
|
|
|
|
|
|
|
// allocate entries as needed so as not to waste memory
|
|
|
|
// (hdata_cap may be large). deque-style array of pages
|
|
|
|
// to balance locality, fragmentation, and waste.
|
|
|
|
static const size_t PAGE_SIZE = 4096;
|
|
|
|
static const uint hdata_per_page = PAGE_SIZE / sizeof(HDATA);
|
|
|
|
static const uint num_pages = hdata_cap / hdata_per_page;
|
|
|
|
static HDATA* pages[num_pages];
|
|
|
|
|
|
|
|
// these must be signed, because there won't always be a valid
|
|
|
|
// first or last element.
|
|
|
|
static i32 first_free = -1; // don't want to scan array every h_alloc
|
|
|
|
static i32 last_in_use = -1; // don't search unused entries
|
|
|
|
|
|
|
|
|
|
|
|
// error checking strategy:
|
|
|
|
// all handles passed in go through h_data(Handle, Type)
|
|
|
|
|
|
|
|
|
2004-10-22 23:58:03 +02:00
|
|
|
// get a (possibly new) array entry; array is non-contiguous.
|
2004-07-31 13:29:57 +02:00
|
|
|
//
|
2004-03-03 00:56:51 +01:00
|
|
|
// fails (returns 0) if idx is out of bounds, or if accessing a new page
|
|
|
|
// for the first time, and there's not enough memory to allocate it.
|
2004-07-31 13:29:57 +02:00
|
|
|
//
|
|
|
|
// also used by h_data, and alloc_idx to find a free entry.
|
2004-10-22 23:58:03 +02:00
|
|
|
static HDATA* h_data_from_idx(const i32 idx)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-10-22 23:58:03 +02:00
|
|
|
// makes things *crawl*!
|
2005-08-09 18:23:19 +02:00
|
|
|
#if CONFIG_PARANOIA
|
2005-06-22 05:23:22 +02:00
|
|
|
debug_heap_check();
|
2004-10-22 23:58:03 +02:00
|
|
|
#endif
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// don't compare against last_in_use - this is called before allocating
|
|
|
|
// new entries, and to check if the next (but possibly not yet valid)
|
|
|
|
// entry is free. tag check protects against using unallocated entries.
|
|
|
|
if(idx < 0 || idx >= hdata_cap)
|
|
|
|
return 0;
|
|
|
|
HDATA*& page = pages[idx / hdata_per_page];
|
|
|
|
if(!page)
|
|
|
|
{
|
2005-01-23 18:54:20 +01:00
|
|
|
page = (HDATA*)calloc(1, PAGE_SIZE);
|
2004-03-03 00:56:51 +01:00
|
|
|
if(!page)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2004-08-18 04:08:15 +02:00
|
|
|
// note: VC7.1 optimizes the divides to shift and mask.
|
2004-08-17 23:06:08 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
return &page[idx % hdata_per_page];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-10-22 23:58:03 +02:00
|
|
|
// 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 HDATA* h_data_no_tag(const Handle h)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-10-22 23:58:03 +02:00
|
|
|
i32 idx = (i32)h_idx(h);
|
|
|
|
// need to verify it's in range - h_data_from_idx can only verify that
|
|
|
|
// it's < maximum allowable index.
|
|
|
|
if(0 > idx || idx > last_in_use)
|
2004-03-03 00:56:51 +01:00
|
|
|
return 0;
|
2004-10-22 23:58:03 +02:00
|
|
|
return h_data_from_idx(idx);
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-10-22 23:58:03 +02:00
|
|
|
|
|
|
|
// 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 HDATA* h_data_tag(const Handle h)
|
|
|
|
{
|
|
|
|
HDATA* hd = h_data_no_tag(h);
|
2004-03-03 00:56:51 +01:00
|
|
|
if(!hd)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// note: tag = 0 marks unused entries => is invalid
|
|
|
|
u32 tag = h_tag(h);
|
|
|
|
if(tag == 0 || tag != hd->tag)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return hd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-10-22 23:58:03 +02:00
|
|
|
// get HDATA for the given handle.
|
|
|
|
// also verifies the type.
|
2004-03-03 00:56:51 +01:00
|
|
|
// used by most functions accessing handle data.
|
2004-10-22 23:58:03 +02:00
|
|
|
static HDATA* h_data_tag_type(const Handle h, const H_Type type)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-10-22 23:58:03 +02:00
|
|
|
HDATA* hd = h_data_tag(h);
|
2004-03-03 00:56:51 +01:00
|
|
|
if(!hd)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// h_alloc makes sure type isn't 0, so no need to check that here.
|
|
|
|
if(hd->type != type)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return hd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2004-03-03 16:16:20 +01:00
|
|
|
// idx and hd are undefined if we fail.
|
|
|
|
// called by h_alloc only.
|
|
|
|
static int alloc_idx(i32& idx, HDATA*& hd)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
|
|
|
// we already know the first free entry
|
|
|
|
if(first_free != -1)
|
|
|
|
{
|
|
|
|
idx = first_free;
|
2004-10-22 23:58:03 +02:00
|
|
|
hd = h_data_from_idx(idx);
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
// need to look for a free entry, or alloc another
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// look for an unused entry
|
|
|
|
for(idx = 0; idx <= last_in_use; idx++)
|
|
|
|
{
|
2004-10-22 23:58:03 +02:00
|
|
|
hd = h_data_from_idx(idx);
|
2005-06-28 06:06:25 +02:00
|
|
|
debug_assert(hd); // can't fail - idx is valid
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
// found one - done
|
|
|
|
if(!hd->tag)
|
|
|
|
goto have_idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add another
|
|
|
|
if(last_in_use >= hdata_cap)
|
|
|
|
{
|
2005-08-09 18:23:19 +02:00
|
|
|
debug_warn("alloc_idx: too many open handles (increase IDX_BITS)");
|
2004-12-01 22:37:01 +01:00
|
|
|
return ERR_LIMIT;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
idx = last_in_use+1; // just incrementing idx would start it at 1
|
2004-10-22 23:58:03 +02:00
|
|
|
hd = h_data_from_idx(idx);
|
2004-03-03 00:56:51 +01:00
|
|
|
if(!hd)
|
|
|
|
return ERR_NO_MEM;
|
|
|
|
// can't fail for any other reason - idx is checked above.
|
|
|
|
{ // VC6 goto fix
|
|
|
|
bool is_unused = !hd->tag;
|
2005-06-28 06:06:25 +02:00
|
|
|
debug_assert(is_unused && "alloc_idx: invalid last_in_use");
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
have_idx:;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if next entry is free
|
2004-10-22 23:58:03 +02:00
|
|
|
HDATA* hd2 = h_data_from_idx(idx+1);
|
2004-03-03 00:56:51 +01:00
|
|
|
if(hd2 && hd2->tag == 0)
|
|
|
|
first_free = idx+1;
|
|
|
|
else
|
|
|
|
first_free = -1;
|
|
|
|
|
|
|
|
if(idx > last_in_use)
|
|
|
|
last_in_use = idx;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int free_idx(i32 idx)
|
|
|
|
{
|
|
|
|
if(first_free == -1 || idx < first_free)
|
|
|
|
first_free = idx;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-08-18 04:08:15 +02:00
|
|
|
// 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)
|
2004-08-19 14:03:15 +02:00
|
|
|
//
|
|
|
|
// store index because it's smaller and Handle can easily be reconstructed
|
2004-10-15 15:19:37 +02:00
|
|
|
//
|
|
|
|
//
|
|
|
|
// 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 forseen here, so we'll just refrain from adding to the index.
|
|
|
|
// that means they won't be found via h_find - no biggie.
|
|
|
|
|
2004-08-18 04:08:15 +02:00
|
|
|
typedef STL_HASH_MULTIMAP<uintptr_t, i32> Key2Idx;
|
|
|
|
typedef Key2Idx::iterator It;
|
|
|
|
static Key2Idx key2idx;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2004-08-19 14:03:15 +02:00
|
|
|
|
|
|
|
static Handle find_key(uintptr_t key, H_Type type, bool remove = false)
|
|
|
|
{
|
2004-09-19 13:23:12 +02:00
|
|
|
std::pair<It, It> range = key2idx.equal_range(key);
|
|
|
|
for(It it = range.first; it != range.second; ++it)
|
2004-08-19 14:03:15 +02:00
|
|
|
{
|
|
|
|
i32 idx = it->second;
|
2004-10-22 23:58:03 +02:00
|
|
|
HDATA* hd = h_data_from_idx(idx);
|
2004-08-19 14:03:15 +02:00
|
|
|
// found match
|
|
|
|
if(hd && hd->type == type && hd->key == key)
|
|
|
|
{
|
|
|
|
if(remove)
|
|
|
|
key2idx.erase(it);
|
|
|
|
return handle(idx, hd->tag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-09-19 13:23:12 +02:00
|
|
|
// 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,
|
2004-08-19 14:03:15 +02:00
|
|
|
// only the corresponding VFile exists.
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void add_key(uintptr_t key, Handle h)
|
|
|
|
{
|
|
|
|
const i32 idx = h_idx(h);
|
|
|
|
key2idx.insert(std::make_pair(key, idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void remove_key(uintptr_t key, H_Type type)
|
|
|
|
{
|
|
|
|
Handle ret = find_key(key, type, true);
|
2005-06-28 06:06:25 +02:00
|
|
|
debug_assert(ret > 0);
|
2004-08-19 14:03:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-10-22 23:58:03 +02:00
|
|
|
// currently cannot fail.
|
|
|
|
static int h_free_idx(i32 idx, HDATA* hd)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2005-05-11 20:56:30 +02:00
|
|
|
// debug_printf("free %s %s\n", type->name, hd->fn);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-02 15:46:44 +02:00
|
|
|
// only decrement if refcount not already 0.
|
2004-03-03 16:16:20 +01:00
|
|
|
if(hd->refs > 0)
|
|
|
|
hd->refs--;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-02 15:46:44 +02:00
|
|
|
// still references open or caching requests it stays - do not release.
|
|
|
|
if(hd->refs > 0 || hd->keep_open)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// actually release the resource (call dtor, free control block).
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2004-10-15 15:19:37 +02:00
|
|
|
if(hd->key && !hd->unique)
|
2004-10-22 23:58:03 +02:00
|
|
|
remove_key(hd->key, hd->type);
|
2004-08-18 04:08:15 +02:00
|
|
|
|
2004-05-27 19:30:06 +02:00
|
|
|
free((void*)hd->fn);
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
memset(hd, 0, sizeof(HDATA));
|
|
|
|
|
|
|
|
free_idx(idx);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-10-22 23:58:03 +02:00
|
|
|
int h_free(Handle& h, H_Type type)
|
|
|
|
{
|
|
|
|
i32 idx = h_idx(h);
|
|
|
|
HDATA* hd = h_data_tag_type(h, type);
|
|
|
|
|
2005-08-09 18:23:19 +02:00
|
|
|
// wipe out the handle to prevent reuse but keep a copy for below.
|
|
|
|
const Handle h_copy = h;
|
2004-10-22 23:58:03 +02:00
|
|
|
h = 0;
|
|
|
|
|
2005-08-09 18:23:19 +02:00
|
|
|
// h was invalid
|
2004-10-22 23:58:03 +02:00
|
|
|
if(!hd)
|
2005-08-09 18:23:19 +02:00
|
|
|
{
|
|
|
|
// 0-initialized or an error code; don't complain because this
|
|
|
|
// happens often and is harmless.
|
|
|
|
if(h_copy <= 0)
|
|
|
|
return 0;
|
|
|
|
// this was a valid handle but was probably freed in the meantime.
|
|
|
|
// complain because this probably indicates a bug somewhere.
|
|
|
|
CHECK_ERR(ERR_INVALID_HANDLE);
|
|
|
|
}
|
|
|
|
|
2004-10-22 23:58:03 +02:00
|
|
|
return h_free_idx(idx, hd);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void h_mgr_shutdown()
|
|
|
|
{
|
|
|
|
// close open handles
|
|
|
|
for(i32 i = 0; i <= last_in_use; i++)
|
|
|
|
{
|
|
|
|
HDATA* hd = h_data_from_idx(i);
|
|
|
|
// can't fail - i is in bounds by definition, and
|
|
|
|
// each HDATA entry has already been allocated.
|
|
|
|
if(!hd)
|
|
|
|
{
|
|
|
|
debug_warn("h_mgr_shutdown: h_data_from_idx failed - why?!");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// it's already been freed; don't free again so that this
|
|
|
|
// doesn't look like an error.
|
|
|
|
if(!hd->tag)
|
|
|
|
continue;
|
|
|
|
|
2004-12-16 02:17:50 +01:00
|
|
|
// if(hd->refs != 0)
|
2005-05-11 20:56:30 +02:00
|
|
|
// debug_printf("leaked %s from %s\n", hd->type->name, hd->fn);
|
2004-12-16 02:17:50 +01:00
|
|
|
|
2004-10-22 23:58:03 +02:00
|
|
|
// disable caching; we need to release the resource now.
|
|
|
|
hd->keep_open = 0;
|
|
|
|
hd->refs = 0;
|
|
|
|
|
|
|
|
h_free_idx(i, hd); // currently cannot fail
|
|
|
|
}
|
|
|
|
|
|
|
|
// free HDATA array
|
|
|
|
for(uint j = 0; j < num_pages; j++)
|
|
|
|
{
|
|
|
|
free(pages[j]);
|
|
|
|
pages[j] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
//----------------------------------------------------------------------------
|
2004-10-22 23:58:03 +02:00
|
|
|
|
2004-08-02 15:46:44 +02:00
|
|
|
static int type_validate(H_Type type)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-08-02 15:46:44 +02:00
|
|
|
int err = ERR_INVALID_PARAM;
|
2004-03-03 16:16:20 +01:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
if(!type)
|
|
|
|
{
|
2004-08-02 15:46:44 +02:00
|
|
|
debug_warn("h_alloc: type is 0");
|
|
|
|
goto fail;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
if(type->user_size > HDATA_USER_SIZE)
|
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
debug_warn("h_alloc: type's user data is too large for HDATA");
|
2004-08-02 15:46:44 +02:00
|
|
|
goto fail;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
if(type->name == 0)
|
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
debug_warn("h_alloc: type's name field is 0");
|
2004-08-02 15:46:44 +02:00
|
|
|
goto fail;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2004-08-02 15:46:44 +02:00
|
|
|
// success
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
static u32 gen_tag()
|
2004-08-02 15:46:44 +02:00
|
|
|
{
|
2005-09-21 18:11:43 +02:00
|
|
|
static u32 tag;
|
|
|
|
if(++tag >= TAG_MASK)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2005-09-21 18:11:43 +02:00
|
|
|
debug_warn("h_mgr: tag overflow - allocations are no longer unique."\
|
|
|
|
"may not notice stale handle reuse. increase TAG_BITS.");
|
|
|
|
tag = 1;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
2005-09-21 18:11:43 +02:00
|
|
|
return tag;
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
static Handle reuse_existing_handle(uintptr_t key, H_Type type, uint flags)
|
|
|
|
{
|
|
|
|
if(flags & RES_NO_CACHE)
|
|
|
|
return 0;
|
2004-08-08 20:04:03 +02:00
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
// object of specified key and type doesn't exist yet
|
|
|
|
Handle h = h_find(type, key);
|
|
|
|
if(h <= 0) // "failure" is meaningless; we only care if found or not
|
|
|
|
return 0;
|
2004-08-18 04:08:15 +02:00
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
HDATA* hd = h_data_tag_type(h, type);
|
|
|
|
if(hd->refs == REF_MAX)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2005-09-21 18:11:43 +02:00
|
|
|
debug_warn("reuse_existing: too many references to a handle - increase REF_BITS");
|
|
|
|
return ERR_LIMIT;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
hd->refs++;
|
2004-08-08 20:04:03 +02:00
|
|
|
|
2004-08-02 15:46:44 +02:00
|
|
|
// we are reactivating a closed but cached handle.
|
2005-09-21 18:11:43 +02:00
|
|
|
// need to generate a new tag so that copies of the
|
|
|
|
// previous handle can no longer access the resource.
|
2004-08-02 15:46:44 +02:00
|
|
|
// (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).
|
2004-08-08 20:04:03 +02:00
|
|
|
if(hd->refs == 1)
|
2004-08-02 15:46:44 +02:00
|
|
|
{
|
2005-09-21 18:11:43 +02:00
|
|
|
const u32 tag = gen_tag();
|
2004-08-02 15:46:44 +02:00
|
|
|
hd->tag = tag;
|
2005-09-21 18:11:43 +02:00
|
|
|
h = handle(h_idx(h), tag); // can't fail
|
2004-08-02 15:46:44 +02:00
|
|
|
}
|
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
return h;
|
|
|
|
}
|
2004-05-13 15:52:48 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
static int call_init_and_reload(Handle h, H_Type type, HDATA* hd, const char* fn, va_list* init_args)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
H_VTbl* vtbl = type; // exact same thing but for clarity
|
2005-08-09 18:23:19 +02:00
|
|
|
|
2004-08-02 15:46:44 +02:00
|
|
|
// init
|
2004-03-03 00:56:51 +01:00
|
|
|
if(vtbl->init)
|
2005-09-21 18:11:43 +02:00
|
|
|
vtbl->init(hd->user, *init_args);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-02 15:46:44 +02:00
|
|
|
// reload
|
2004-03-03 00:56:51 +01:00
|
|
|
if(vtbl->reload)
|
|
|
|
{
|
2004-06-19 16:45:46 +02:00
|
|
|
// catch exception to simplify reload funcs - let them use new()
|
|
|
|
try
|
|
|
|
{
|
2004-06-26 00:19:19 +02:00
|
|
|
err = vtbl->reload(hd->user, fn, h);
|
2004-06-19 16:45:46 +02:00
|
|
|
}
|
|
|
|
catch(std::bad_alloc)
|
|
|
|
{
|
|
|
|
err = ERR_NO_MEM;
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static Handle alloc_new_handle(H_Type type, const char* fn, uintptr_t key,
|
|
|
|
uint flags, va_list* init_args)
|
|
|
|
{
|
|
|
|
i32 idx;
|
|
|
|
HDATA* hd;
|
|
|
|
CHECK_ERR(alloc_idx(idx, hd));
|
|
|
|
|
|
|
|
// (don't want to do this before the add-reference exit,
|
|
|
|
// so as not to waste tags for often allocated handles.)
|
|
|
|
const u32 tag = gen_tag();
|
|
|
|
Handle h = handle(idx, tag); // can't fail.
|
|
|
|
|
|
|
|
hd->tag = tag;
|
|
|
|
hd->key = key;
|
|
|
|
hd->type = type;
|
|
|
|
hd->refs = 1;
|
|
|
|
if(!(flags & RES_NO_CACHE))
|
|
|
|
hd->keep_open = 1;
|
2005-09-27 17:35:17 +02:00
|
|
|
if(flags & RES_DISALLOW_RELOAD)
|
|
|
|
hd->disallow_reload = 1;
|
2005-09-21 18:11:43 +02:00
|
|
|
hd->unique = (flags & RES_UNIQUE) != 0;
|
|
|
|
hd->fn = 0;
|
|
|
|
// .. filename is valid - store in hd
|
|
|
|
// note: if the original fn param was a key, it was reset to 0 above.
|
|
|
|
if(fn)
|
|
|
|
hd->fn = strdup(fn);
|
|
|
|
|
|
|
|
if(key && !hd->unique)
|
|
|
|
add_key(key, h);
|
|
|
|
|
|
|
|
int err = call_init_and_reload(h, type, hd, fn, init_args);
|
|
|
|
if(err < 0)
|
|
|
|
goto fail;
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
return h;
|
2005-08-09 18:23:19 +02:00
|
|
|
|
|
|
|
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_ERR)
|
|
|
|
|
|
|
|
// note: since some uses will always fail (e.g. loading sounds if
|
|
|
|
// g_Quickstart), do not complain here.
|
|
|
|
return (Handle)err;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
// any further params are passed to type's init routine
|
|
|
|
Handle h_alloc(H_Type type, const char* fn, uint flags, ...)
|
|
|
|
{
|
|
|
|
CHECK_ERR(type_validate(type));
|
|
|
|
|
|
|
|
// get key (either hash of filename, or fn param)
|
|
|
|
uintptr_t key = 0;
|
|
|
|
// not backed by file; fn is the key
|
|
|
|
if(flags & RES_KEY)
|
|
|
|
{
|
|
|
|
key = (uintptr_t)fn;
|
|
|
|
fn = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(fn)
|
|
|
|
key = fnv_hash(fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
//debug_printf("alloc %s %s\n", type->name, fn);
|
|
|
|
|
|
|
|
// no key => can never be found. disallow caching
|
|
|
|
if(!key)
|
|
|
|
flags |= RES_NO_CACHE;
|
|
|
|
|
|
|
|
// see if we can reuse an existing handle
|
|
|
|
Handle h = reuse_existing_handle(key, type, flags);
|
|
|
|
// .. error
|
|
|
|
CHECK_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, fn, key, flags, &args);
|
|
|
|
va_end(args);
|
|
|
|
return h; // alloc_new_handle already does CHECK_ERR
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
void* h_user_data(const Handle h, const H_Type type)
|
|
|
|
{
|
2004-10-22 23:58:03 +02:00
|
|
|
HDATA* hd = h_data_tag_type(h, type);
|
2004-07-31 13:29:57 +02:00
|
|
|
if(!hd)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if(!hd->refs)
|
|
|
|
{
|
|
|
|
// note: resetting the tag is not enough (user might pass in its value)
|
|
|
|
debug_warn("h_user_data: no references to resource (it's cached, but someone is accessing it directly)");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hd->user;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2004-03-03 01:37:41 +01:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
const char* h_filename(const Handle h)
|
|
|
|
{
|
2005-09-29 19:33:30 +02:00
|
|
|
// don't require type check: should be useable for any handle,
|
|
|
|
// even if the caller doesn't know its type.
|
2004-10-22 23:58:03 +02:00
|
|
|
HDATA* hd = h_data_tag(h);
|
2005-09-29 19:33:30 +02:00
|
|
|
if(!hd)
|
|
|
|
{
|
|
|
|
debug_warn("h_filename failed");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return hd->fn;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2004-07-31 13:29:57 +02:00
|
|
|
|
2005-09-27 17:35:17 +02:00
|
|
|
// TODO: what if iterating through all handles is too slow?
|
2004-03-03 00:56:51 +01:00
|
|
|
int h_reload(const char* fn)
|
|
|
|
{
|
|
|
|
if(!fn)
|
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
debug_warn("h_reload: fn = 0");
|
2004-03-03 00:56:51 +01:00
|
|
|
return ERR_INVALID_PARAM;
|
|
|
|
}
|
|
|
|
|
2004-03-05 17:23:31 +01:00
|
|
|
const u32 key = fnv_hash(fn);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
// 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).
|
2005-09-27 17:35:17 +02:00
|
|
|
for(i32 i = 0; i <= last_in_use; i++)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-10-22 23:58:03 +02:00
|
|
|
HDATA* hd = h_data_from_idx(i);
|
2005-09-27 17:35:17 +02:00
|
|
|
if(!hd || hd->key != key || hd->disallow_reload)
|
|
|
|
continue;
|
|
|
|
hd->type->dtor(hd->user);
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
// now reload all affected handles
|
2005-09-27 17:35:17 +02:00
|
|
|
for(i32 i = 0; i <= last_in_use; i++)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-10-22 23:58:03 +02:00
|
|
|
HDATA* hd = h_data_from_idx(i);
|
2005-09-27 17:35:17 +02:00
|
|
|
if(!hd || hd->key != key || hd->disallow_reload)
|
2004-03-03 00:56:51 +01:00
|
|
|
continue;
|
|
|
|
|
2004-06-26 00:19:19 +02:00
|
|
|
Handle h = handle(i, hd->tag);
|
|
|
|
|
|
|
|
int err = hd->type->reload(hd->user, hd->fn, h);
|
2004-03-03 00:56:51 +01:00
|
|
|
// don't stop if an error is encountered - try to reload them all.
|
|
|
|
if(err < 0)
|
2004-03-07 15:17:23 +01:00
|
|
|
{
|
|
|
|
h_free(h, hd->type);
|
2005-02-27 15:34:46 +01:00
|
|
|
if(ret == 0) // don't overwrite first error
|
|
|
|
ret = err;
|
2004-03-07 15:17:23 +01:00
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-05-29 14:00:53 +02:00
|
|
|
Handle h_find(H_Type type, uintptr_t key)
|
|
|
|
{
|
2004-08-19 14:03:15 +02:00
|
|
|
return find_key(key, type);
|
2004-05-29 14:00:53 +02:00
|
|
|
}
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-10-22 23:58:03 +02:00
|
|
|
|
|
|
|
// 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.
|
|
|
|
int h_force_free(Handle h, H_Type type)
|
2004-10-20 02:58:55 +02:00
|
|
|
{
|
2004-10-22 23:58:03 +02:00
|
|
|
// require valid index; ignore tag; type checked below.
|
|
|
|
HDATA* hd = h_data_no_tag(h);
|
|
|
|
if(!hd || hd->type != type)
|
2004-10-20 02:58:55 +02:00
|
|
|
return ERR_INVALID_HANDLE;
|
2004-10-22 23:58:03 +02:00
|
|
|
u32 idx = h_idx(h);
|
2004-10-20 02:58:55 +02:00
|
|
|
hd->keep_open = 0;
|
2004-10-22 23:58:03 +02:00
|
|
|
return h_free_idx(idx, hd);
|
2004-10-20 02:58:55 +02:00
|
|
|
}
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
// increment Handle <h>'s reference count.
|
2004-12-16 02:17:50 +01:00
|
|
|
// 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 = h_data_tag(h);
|
|
|
|
if(!hd)
|
|
|
|
{
|
|
|
|
debug_warn("h_add_ref: invalid handle");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
// if there are no refs, how did the caller manage to keep a Handle?!
|
2004-12-16 02:17:50 +01:00
|
|
|
if(!hd->refs)
|
|
|
|
debug_warn("h_add_ref: no refs open - resource is cached");
|
2005-09-21 18:11:43 +02:00
|
|
|
|
2004-12-16 02:17:50 +01:00
|
|
|
hd->refs++;
|
|
|
|
}
|
|
|
|
|
2004-08-18 04:08:15 +02:00
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
// 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.
|
|
|
|
int h_get_refcnt(Handle h)
|
|
|
|
{
|
|
|
|
HDATA* hd = h_data_tag(h);
|
|
|
|
if(!hd)
|
|
|
|
{
|
|
|
|
debug_warn("h_get_refcnt: invalid handle");
|
|
|
|
return ERR_INVALID_PARAM;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there are no refs, how did the caller manage to keep a Handle?!
|
|
|
|
if(!hd->refs)
|
|
|
|
debug_warn("h_get_refcnt: no refs open - resource is cached");
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-09-21 18:11:43 +02:00
|
|
|
return hd->refs;
|
|
|
|
}
|