all resource implementations:
- add automatically called validate function - make sure dtors correctly handle partial reload failures all file code: IO routines renamed to io_* from *_io h_mgr: add suballocator for filenames; some reorganization+cleanup; better example code and added some notes to docs snd: reorg to move resource methods next to struct This was SVN commit r2907.
This commit is contained in:
parent
c498468fd0
commit
e9864faa97
@ -95,13 +95,14 @@
|
||||
struct VDir
|
||||
{
|
||||
TreeDirIterator it;
|
||||
uint it_valid : 1; // <it> will be closed iff == 1
|
||||
|
||||
// safety check
|
||||
#ifndef NDEBUG
|
||||
const char* filter;
|
||||
// has filter been assigned? this flag is necessary because there are no
|
||||
// "invalid" filter values we can use.
|
||||
bool filter_latched;
|
||||
uint filter_latched : 1;
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -113,7 +114,13 @@ static void VDir_init(VDir* UNUSED(vd), va_list UNUSED(args))
|
||||
|
||||
static void VDir_dtor(VDir* vd)
|
||||
{
|
||||
tree_dir_close(&vd->it);
|
||||
// note: TreeDirIterator has no way of checking if it's valid;
|
||||
// we must therefore only free it if reload() succeeded.
|
||||
if(vd->it_valid)
|
||||
{
|
||||
tree_dir_close(&vd->it);
|
||||
vd->it_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int VDir_reload(VDir* vd, const char* path, Handle UNUSED(hvd))
|
||||
@ -121,9 +128,20 @@ static int VDir_reload(VDir* vd, const char* path, Handle UNUSED(hvd))
|
||||
// add required trailing slash if not already present to make
|
||||
// caller's life easier.
|
||||
char V_path_slash[PATH_MAX];
|
||||
CHECK_ERR(vfs_path_append(V_path_slash, path, ""));
|
||||
RETURN_ERR(vfs_path_append(V_path_slash, path, ""));
|
||||
|
||||
CHECK_ERR(tree_dir_open(V_path_slash, &vd->it));
|
||||
RETURN_ERR(tree_dir_open(V_path_slash, &vd->it));
|
||||
vd->it_valid = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int VDir_validate(const VDir* vd)
|
||||
{
|
||||
// note: <it> is opaque and cannot be validated.
|
||||
#ifndef NDEBUG
|
||||
if(vd->filter && !isprint(vd->filter[0]))
|
||||
return -2;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -175,7 +193,7 @@ int vfs_dir_next_ent(const Handle hd, DirEnt* ent, const char* filter)
|
||||
if(!vd->filter_latched)
|
||||
{
|
||||
vd->filter = filter;
|
||||
vd->filter_latched = true;
|
||||
vd->filter_latched = 1;
|
||||
}
|
||||
if(vd->filter != filter)
|
||||
debug_warn("vfs_dir_next_ent: filter has changed for this directory. are you scanning it twice?");
|
||||
@ -335,7 +353,7 @@ struct VFile
|
||||
Handle hm;
|
||||
|
||||
// current file pointer. this is necessary because file.cpp's interface
|
||||
// requires passing an offset for every VIo; see file_start_io.
|
||||
// requires passing an offset for every VIo; see file_io_issue.
|
||||
off_t ofs;
|
||||
|
||||
XFile xf;
|
||||
@ -347,24 +365,21 @@ struct VFile
|
||||
|
||||
H_TYPE_DEFINE(VFile);
|
||||
|
||||
|
||||
|
||||
static void VFile_init(VFile* vf, va_list args)
|
||||
{
|
||||
uint flags = va_arg(args, int);
|
||||
x_set_flags(&vf->xf, flags); // can't fail
|
||||
}
|
||||
|
||||
|
||||
static void VFile_dtor(VFile* vf)
|
||||
{
|
||||
// note: checking if reload() succeeded is unnecessary because
|
||||
// x_close and mem_free_h safely handle 0-initialized data.
|
||||
WARN_ERR(x_close(&vf->xf));
|
||||
|
||||
mem_free_h(vf->hm);
|
||||
(void)mem_free_h(vf->hm);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int VFile_reload(VFile* vf, const char* V_path, Handle)
|
||||
{
|
||||
const uint flags = x_flags(&vf->xf);
|
||||
@ -391,6 +406,13 @@ static int VFile_reload(VFile* vf, const char* V_path, Handle)
|
||||
return x_open(m, V_exact_path, flags, tf, &vf->xf);
|
||||
}
|
||||
|
||||
static int VFile_validate(const VFile* vf)
|
||||
{
|
||||
// <ofs> doesn't have any invariant we can check.
|
||||
RETURN_ERR(x_validate(&vf->xf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// return the size of an already opened file, or a negative error code.
|
||||
ssize_t vfs_size(Handle hf)
|
||||
@ -467,7 +489,7 @@ debug_printf("vfs_io size=%d\n", size);
|
||||
// we allocate
|
||||
else
|
||||
{
|
||||
buf = mem_alloc(round_up(size, 4096), 4096);
|
||||
buf = mem_alloc(round_up(size, 4096), FILE_BLOCK_SIZE);
|
||||
if(!buf)
|
||||
return ERR_NO_MEM;
|
||||
*p = buf;
|
||||
@ -546,7 +568,7 @@ debug_printf("vfs_load v_fn=%s\n", v_fn);
|
||||
debug_warn("vfs_load: invalid MEM attached to vfile (0 pointer)");
|
||||
// happens if someone frees the pointer. not an error!
|
||||
}
|
||||
|
||||
/*
|
||||
// allocate memory. does expose implementation details of File
|
||||
// (padding), but it greatly simplifies returning the Handle
|
||||
// (if we allow File to alloc, have to make sure the Handle references
|
||||
@ -560,17 +582,19 @@ debug_printf("vfs_load v_fn=%s\n", v_fn);
|
||||
goto ret;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
{
|
||||
ssize_t nread = vfs_timed_io(hf, size, &p);
|
||||
// failed
|
||||
if(nread < 0)
|
||||
{
|
||||
mem_free_h(hm);
|
||||
mem_free(p);
|
||||
hm = nread; // error code
|
||||
}
|
||||
else
|
||||
{
|
||||
hm = mem_wrap(p, size, 0, 0, 0, 0, 0);
|
||||
|
||||
if(flags & FILE_CACHE)
|
||||
vf->hm = hm;
|
||||
}
|
||||
@ -613,6 +637,8 @@ ssize_t vfs_store(const char* v_fn, void* p, const size_t size, uint flags /* de
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// we don't support forcibly closing files => don't need to keep track of
|
||||
// all IOs pending for each file. too much work, little benefit.
|
||||
struct VIo
|
||||
{
|
||||
Handle hf;
|
||||
@ -624,46 +650,51 @@ struct VIo
|
||||
|
||||
H_TYPE_DEFINE(VIo);
|
||||
|
||||
|
||||
// don't support forcibly closing files => don't need to keep track of
|
||||
// all IOs pending for each file. too much work, little benefit.
|
||||
|
||||
|
||||
static void VIo_init(VIo* xio, va_list args)
|
||||
static void VIo_init(VIo* vio, va_list args)
|
||||
{
|
||||
xio->hf = va_arg(args, Handle);
|
||||
xio->size = va_arg(args, size_t);
|
||||
xio->buf = va_arg(args, void*);
|
||||
vio->hf = va_arg(args, Handle);
|
||||
vio->size = va_arg(args, size_t);
|
||||
vio->buf = va_arg(args, void*);
|
||||
}
|
||||
|
||||
|
||||
static void VIo_dtor(VIo* xio)
|
||||
static void VIo_dtor(VIo* vio)
|
||||
{
|
||||
WARN_ERR(x_io_close(&xio->xio));
|
||||
// note: checking if reload() succeeded is unnecessary because
|
||||
// x_io_discard safely handles 0-initialized data.
|
||||
WARN_ERR(x_io_discard(&vio->xio));
|
||||
}
|
||||
|
||||
|
||||
// we don't support transparent read resume after file invalidation.
|
||||
// if the file has changed, we'd risk returning inconsistent data.
|
||||
// doesn't look possible without controlling the AIO implementation:
|
||||
// when we cancel, we can't prevent the app from calling
|
||||
// aio_result, which would terminate the read.
|
||||
static int VIo_reload(VIo* xio, const char* UNUSED(fn), Handle UNUSED(h))
|
||||
static int VIo_reload(VIo* vio, const char* UNUSED(fn), Handle UNUSED(h))
|
||||
{
|
||||
size_t size = xio->size;
|
||||
void* buf = xio->buf;
|
||||
size_t size = vio->size;
|
||||
void* buf = vio->buf;
|
||||
|
||||
H_DEREF(xio->hf, VFile, vf);
|
||||
H_DEREF(vio->hf, VFile, vf);
|
||||
off_t ofs = vf->ofs;
|
||||
vf->ofs += (off_t)size;
|
||||
|
||||
return x_io_start(&vf->xf, ofs, size, buf, &xio->xio);
|
||||
return x_io_issue(&vf->xf, ofs, size, buf, &vio->xio);
|
||||
}
|
||||
|
||||
static int VIo_validate(const VIo* vio)
|
||||
{
|
||||
if(vio->hf < 0)
|
||||
return -200;
|
||||
// <size> doesn't have any invariant we can check.
|
||||
if(debug_is_pointer_bogus(vio->buf))
|
||||
return -201;
|
||||
return x_io_validate(&vio->xio);
|
||||
}
|
||||
|
||||
|
||||
// begin transferring <size> bytes, starting at <ofs>. get result
|
||||
// with vfs_wait_io; when no longer needed, free via vfs_discard_io.
|
||||
Handle vfs_start_io(Handle hf, size_t size, void* buf)
|
||||
// with vfs_io_wait; when no longer needed, free via vfs_io_discard.
|
||||
Handle vfs_io_issue(Handle hf, size_t size, void* buf)
|
||||
{
|
||||
const char* fn = 0;
|
||||
int flags = 0;
|
||||
@ -671,8 +702,8 @@ Handle vfs_start_io(Handle hf, size_t size, void* buf)
|
||||
}
|
||||
|
||||
|
||||
// finished with transfer <hio> - free its buffer (returned by vfs_wait_io)
|
||||
int vfs_discard_io(Handle& hio)
|
||||
// finished with transfer <hio> - free its buffer (returned by vfs_io_wait)
|
||||
int vfs_io_discard(Handle& hio)
|
||||
{
|
||||
return h_free(hio, H_VIo);
|
||||
}
|
||||
@ -680,19 +711,19 @@ int vfs_discard_io(Handle& hio)
|
||||
|
||||
// indicates if the VIo referenced by <xio> has completed.
|
||||
// return value: 0 if pending, 1 if complete, < 0 on error.
|
||||
int vfs_io_complete(Handle hio)
|
||||
int vfs_io_has_completed(Handle hio)
|
||||
{
|
||||
H_DEREF(hio, VIo, xio);
|
||||
return x_io_complete(&xio->xio);
|
||||
H_DEREF(hio, VIo, vio);
|
||||
return x_io_has_completed(&vio->xio);
|
||||
}
|
||||
|
||||
|
||||
// wait until the transfer <hio> completes, and return its buffer.
|
||||
// output parameters are zeroed on error.
|
||||
int vfs_wait_io(Handle hio, void*& p, size_t& size)
|
||||
int vfs_io_wait(Handle hio, void*& p, size_t& size)
|
||||
{
|
||||
H_DEREF(hio, VIo, xio);
|
||||
return x_io_wait(&xio->xio, p, size);
|
||||
H_DEREF(hio, VIo, vio);
|
||||
return x_io_wait(&vio->xio, p, size);
|
||||
}
|
||||
|
||||
|
||||
|
@ -332,19 +332,19 @@ extern int vfs_close(Handle& h);
|
||||
// low-level file routines - no caching or alignment.
|
||||
|
||||
// begin transferring <size> bytes, starting at <ofs>. get result
|
||||
// with vfs_wait_read; when no longer needed, free via vfs_discard_io.
|
||||
extern Handle vfs_start_io(Handle hf, size_t size, void* buf);
|
||||
// with vfs_wait_read; when no longer needed, free via vfs_io_discard.
|
||||
extern Handle vfs_io_issue(Handle hf, size_t size, void* buf);
|
||||
|
||||
// indicates if the given IO has completed.
|
||||
// return value: 0 if pending, 1 if complete, < 0 on error.
|
||||
extern int vfs_io_complete(Handle hio);
|
||||
extern int vfs_io_has_completed(Handle hio);
|
||||
|
||||
// wait until the transfer <hio> completes, and return its buffer.
|
||||
// output parameters are zeroed on error.
|
||||
extern int vfs_wait_io(Handle hio, void*& p, size_t& size);
|
||||
extern int vfs_io_wait(Handle hio, void*& p, size_t& size);
|
||||
|
||||
// finished with transfer <hio> - free its buffer (returned by vfs_wait_read).
|
||||
extern int vfs_discard_io(Handle& hio);
|
||||
extern int vfs_io_discard(Handle& hio);
|
||||
|
||||
|
||||
//
|
||||
|
@ -946,6 +946,33 @@ int x_close(XFile* xf)
|
||||
}
|
||||
|
||||
|
||||
int x_validate(const XFile* xf)
|
||||
{
|
||||
switch(xf->type)
|
||||
{
|
||||
case MT_NONE:
|
||||
if(xf->tf != 0)
|
||||
return -100;
|
||||
return 0; // ok, nothing else to check
|
||||
|
||||
case MT_FILE:
|
||||
if(xf->tf == 0)
|
||||
return -101;
|
||||
return file_validate(&xf->u.f);
|
||||
|
||||
case MT_ARCHIVE:
|
||||
// (archive files can't be written to, so this should be 0)
|
||||
if(xf->tf != 0)
|
||||
return -102;
|
||||
return zip_validate(&xf->u.zf);
|
||||
|
||||
default:
|
||||
return -103; // invalid type
|
||||
}
|
||||
UNREACHABLE;
|
||||
}
|
||||
|
||||
|
||||
bool x_is_open(const XFile* xf)
|
||||
{
|
||||
return (xf->type != MT_NONE);
|
||||
@ -1028,32 +1055,32 @@ int x_unmap(XFile* xf)
|
||||
}
|
||||
|
||||
|
||||
int x_io_start(XFile* xf, off_t ofs, size_t size, void* buf, XIo* xio)
|
||||
int x_io_issue(XFile* xf, off_t ofs, size_t size, void* buf, XIo* xio)
|
||||
{
|
||||
xio->type = xf->type;
|
||||
switch(xio->type)
|
||||
{
|
||||
case MT_ARCHIVE:
|
||||
return zip_start_io(&xf->u.zf, ofs, size, buf, &xio->u.zio);
|
||||
return zip_io_issue(&xf->u.zf, ofs, size, buf, &xio->u.zio);
|
||||
case MT_FILE:
|
||||
return file_start_io(&xf->u.f, ofs, size, buf, &xio->u.fio);
|
||||
return file_io_issue(&xf->u.f, ofs, size, buf, &xio->u.fio);
|
||||
default:
|
||||
debug_warn("vfs_start_io: invalid file type");
|
||||
debug_warn("vfs_io_issue: invalid file type");
|
||||
return ERR_CORRUPTED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int x_io_complete(XIo* xio)
|
||||
int x_io_has_completed(XIo* xio)
|
||||
{
|
||||
switch(xio->type)
|
||||
{
|
||||
case MT_ARCHIVE:
|
||||
return zip_io_complete(&xio->u.zio);
|
||||
return zip_io_has_completed(&xio->u.zio);
|
||||
case MT_FILE:
|
||||
return file_io_complete(&xio->u.fio);
|
||||
return file_io_has_completed(&xio->u.fio);
|
||||
default:
|
||||
debug_warn("vfs_io_complete: invalid type");
|
||||
debug_warn("vfs_io_has_completed: invalid type");
|
||||
return ERR_CORRUPTED;
|
||||
}
|
||||
}
|
||||
@ -1064,26 +1091,41 @@ int x_io_wait(XIo* xio, void*& p, size_t& size)
|
||||
switch(xio->type)
|
||||
{
|
||||
case MT_ARCHIVE:
|
||||
return zip_wait_io(&xio->u.zio, p, size);
|
||||
return zip_io_wait(&xio->u.zio, p, size);
|
||||
case MT_FILE:
|
||||
return file_wait_io(&xio->u.fio, p, size);
|
||||
return file_io_wait(&xio->u.fio, p, size);
|
||||
default:
|
||||
debug_warn("vfs_wait_io: invalid type");
|
||||
debug_warn("vfs_io_wait: invalid type");
|
||||
return ERR_CORRUPTED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int x_io_close(XIo* xio)
|
||||
int x_io_discard(XIo* xio)
|
||||
{
|
||||
switch(xio->type)
|
||||
{
|
||||
case MT_ARCHIVE:
|
||||
return zip_discard_io(&xio->u.zio);
|
||||
return zip_io_discard(&xio->u.zio);
|
||||
case MT_FILE:
|
||||
return file_discard_io(&xio->u.fio);
|
||||
return file_io_discard(&xio->u.fio);
|
||||
default:
|
||||
debug_warn("VIo_dtor: invalid type");
|
||||
return ERR_CORRUPTED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int x_io_validate(const XIo* xio)
|
||||
{
|
||||
switch(xio->type)
|
||||
{
|
||||
case MT_ARCHIVE:
|
||||
return zip_io_validate(&xio->u.zio);
|
||||
case MT_FILE:
|
||||
return file_io_validate(&xio->u.fio);
|
||||
default:
|
||||
return -103; // invalid type
|
||||
}
|
||||
UNREACHABLE;
|
||||
}
|
@ -62,6 +62,8 @@ extern int x_realpath(const Mount* m, const char* V_exact_path, char* P_real_pat
|
||||
extern int x_open(const Mount* m, const char* V_exact_path, int flags, TFile* tf, XFile* xf);
|
||||
extern int x_close(XFile* xf);
|
||||
|
||||
extern int x_validate(const XFile* xf);
|
||||
|
||||
extern bool x_is_open(const XFile* xf);
|
||||
extern off_t x_size(const XFile* xf);
|
||||
extern uint x_flags(const XFile* xf);
|
||||
@ -72,11 +74,11 @@ extern int x_io(XFile* xf, off_t ofs, size_t size, void* buf, FileIOCB cb, uintp
|
||||
extern int x_map(XFile* xf, void*& p, size_t& size);
|
||||
extern int x_unmap(XFile* xf);
|
||||
|
||||
extern int x_io_start(XFile* xf, off_t ofs, size_t size, void* buf, XIo* xio);
|
||||
extern int x_io_complete(XIo* xio);
|
||||
extern int x_io_issue(XFile* xf, off_t ofs, size_t size, void* buf, XIo* xio);
|
||||
extern int x_io_has_completed(XIo* xio);
|
||||
extern int x_io_wait(XIo* xio, void*& p, size_t& size);
|
||||
extern int x_io_close(XIo* xio);
|
||||
|
||||
extern int x_io_discard(XIo* xio);
|
||||
extern int x_io_validate(const XIo* xio);
|
||||
|
||||
|
||||
|
||||
|
@ -96,26 +96,26 @@ struct ZLoc
|
||||
|
||||
|
||||
// Zip file data structures and signatures
|
||||
static const char cdfh_id[] = "PK\1\2";
|
||||
static const char lfh_id[] = "PK\3\4";
|
||||
static const char ecdr_id[] = "PK\5\6";
|
||||
static const char cdfh_id[] = {'P','K','\1','\2'};
|
||||
static const char lfh_id[] = {'P','K','\3','\4'};
|
||||
static const char ecdr_id[] = {'P','K','\5','\6'};
|
||||
// lengths include the id field!
|
||||
const size_t CDFH_SIZE = 46;
|
||||
const size_t LFH_SIZE = 30;
|
||||
const size_t ECDR_SIZE = 22;
|
||||
|
||||
|
||||
// return -1 if file is obviously not a valid Zip archive,
|
||||
// otherwise 0. used as early-out test in lookup_init (see call site).
|
||||
static inline int z_validate(const u8* file, size_t size)
|
||||
// return false if file is obviously not a valid Zip archive,
|
||||
// otherwise true. used as early-out test in lookup_init (see call site).
|
||||
static inline bool z_is_header(const u8* file, size_t size)
|
||||
{
|
||||
// make sure it's big enough to check the header and for
|
||||
// z_find_ecdr to succeed (if smaller, it's definitely bogus).
|
||||
if(size < ECDR_SIZE)
|
||||
return ERR_CORRUPTED;
|
||||
return false;
|
||||
|
||||
// check "header" (first LFH) signature
|
||||
return (*(u32*)file == *(u32*)&lfh_id)? 0 : -1;
|
||||
return *(u32*)file == *(u32*)&lfh_id;
|
||||
}
|
||||
|
||||
|
||||
@ -156,7 +156,7 @@ static const u8* z_find_id(const u8* file, size_t size, const u8* start, const c
|
||||
|
||||
|
||||
// find "End of Central Dir Record" in file.
|
||||
// z_validate has made sure size >= ECDR_SIZE.
|
||||
// z_is_header has made sure size >= ECDR_SIZE.
|
||||
// return -1 on failure (output param invalid), otherwise 0.
|
||||
static int z_find_ecdr(const u8* file, size_t size, const u8*& ecdr_)
|
||||
{
|
||||
@ -483,8 +483,8 @@ static int lookup_init(LookupInfo* li, const u8* file, const size_t size)
|
||||
// check if it's even a Zip file.
|
||||
// the VFS blindly opens files when mounting; it needs to open
|
||||
// all archives, but doesn't know their extension (e.g. ".pk3").
|
||||
err = z_validate(file, size);
|
||||
RETURN_ERR(err);
|
||||
if(!z_is_header(file, size))
|
||||
return ERR_UNKNOWN_FORMAT;
|
||||
|
||||
li->next_file = 0;
|
||||
li->idx = new LookupIdx;
|
||||
@ -501,9 +501,23 @@ static int lookup_init(LookupInfo* li, const u8* file, const size_t size)
|
||||
}
|
||||
|
||||
|
||||
static int lookup_validate(const LookupInfo* li)
|
||||
{
|
||||
if(debug_is_pointer_bogus(li->ents) || debug_is_pointer_bogus(li->fn_hashes))
|
||||
return -2;
|
||||
if(li->num_files > li->num_entries || li->next_file > li->num_entries)
|
||||
return -3;
|
||||
if(li->num_entries < 0 || li->num_files < 0 || li->next_file < 0)
|
||||
return -4;
|
||||
if(debug_is_pointer_bogus(li->idx))
|
||||
return -5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// free lookup data structure.
|
||||
// (no use-after-free checking - that's handled by the VFS)
|
||||
static int lookup_free(LookupInfo* li)
|
||||
static void lookup_free(LookupInfo* li)
|
||||
{
|
||||
// free memory allocated for filenames
|
||||
for(i32 i = 0; i < li->num_files; i++)
|
||||
@ -517,7 +531,7 @@ static int lookup_free(LookupInfo* li)
|
||||
delete li->idx;
|
||||
|
||||
// frees both ents and fn_hashes! (they share an allocation)
|
||||
return mem_free(li->ents);
|
||||
(void)mem_free(li->ents);
|
||||
}
|
||||
|
||||
|
||||
@ -593,76 +607,66 @@ struct ZArchive
|
||||
|
||||
LookupInfo li;
|
||||
|
||||
// problem:
|
||||
// if ZArchive_reload aborts due to file_open failing, ZArchive_dtor
|
||||
// is called by h_alloc, and file_close complains the File is
|
||||
// invalid (wasn't open). this happens e.g. if vfs_mount blindly
|
||||
// tries to open a directory as an archive.
|
||||
// workaround:
|
||||
// only free the above if ZArchive_reload succeeds, i.e. is_open.
|
||||
// note:
|
||||
// if lookup_init fails after file_open opened the file,
|
||||
// we wouldn't file_close in the dtor,
|
||||
// but it's taken care of by ZArchive_reload.
|
||||
bool is_open;
|
||||
// note: we need to keep track of what resources reload() allocated,
|
||||
// so the dtor can free everything correctly.
|
||||
uint is_open : 1;
|
||||
uint is_mapped : 1;
|
||||
uint is_loaded : 1;
|
||||
};
|
||||
|
||||
H_TYPE_DEFINE(ZArchive);
|
||||
|
||||
|
||||
static void ZArchive_init(ZArchive*, va_list)
|
||||
{}
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
static void ZArchive_dtor(ZArchive* za)
|
||||
{
|
||||
if(za->is_loaded)
|
||||
{
|
||||
lookup_free(&za->li);
|
||||
za->is_loaded = 0;
|
||||
}
|
||||
if(za->is_mapped)
|
||||
{
|
||||
(void)file_unmap(&za->f);
|
||||
za->is_mapped = 0;
|
||||
}
|
||||
if(za->is_open)
|
||||
{
|
||||
file_close(&za->f);
|
||||
lookup_free(&za->li);
|
||||
|
||||
za->is_open = false;
|
||||
(void)file_close(&za->f);
|
||||
za->is_open = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int ZArchive_reload(ZArchive* za, const char* fn, Handle)
|
||||
{
|
||||
int err;
|
||||
// (note: don't warn on failure - this happens when
|
||||
// vfs_mount blindly zip_archive_opens a dir)
|
||||
RETURN_ERR(file_open(fn, FILE_CACHE_BLOCK, &za->f));
|
||||
za->is_open = 1;
|
||||
|
||||
// open
|
||||
err = file_open(fn, FILE_CACHE_BLOCK, &za->f);
|
||||
if(err < 0)
|
||||
// don't complain - this happens when vfs_mount blindly
|
||||
// zip_archive_opens a dir.
|
||||
return err;
|
||||
void* file; size_t size;
|
||||
RETURN_ERR(file_map(&za->f, file, size));
|
||||
za->is_mapped = 1;
|
||||
|
||||
// map
|
||||
void* file;
|
||||
size_t size;
|
||||
err = file_map(&za->f, file, size);
|
||||
if(err < 0)
|
||||
goto fail_close;
|
||||
RETURN_ERR(lookup_init(&za->li, (u8*)file, size));
|
||||
za->is_loaded = 1;
|
||||
|
||||
err = lookup_init(&za->li, (u8*)file, size);
|
||||
if(err < 0)
|
||||
goto fail_unmap_close;
|
||||
// we map the file only for convenience when loading;
|
||||
// extraction is via aio (faster, better mem use).
|
||||
(void)file_unmap(&za->f);
|
||||
za->is_mapped = 0;
|
||||
|
||||
file_unmap(&za->f);
|
||||
// we map the file only for convenience when loading;
|
||||
// extraction is via aio (faster, better mem use).
|
||||
|
||||
za->is_open = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
fail_unmap_close:
|
||||
file_unmap(&za->f);
|
||||
fail_close:
|
||||
file_close(&za->f);
|
||||
|
||||
// don't complain here either; this happens when vfs_mount's
|
||||
// zip_archive_opens an invalid file that's in a mount point dir.
|
||||
return err;
|
||||
static int ZArchive_validate(const ZArchive* za)
|
||||
{
|
||||
RETURN_ERR(file_validate(&za->f));
|
||||
RETURN_ERR(lookup_validate(&za->li));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -872,48 +876,6 @@ int inf_free_ctx(uintptr_t _ctx)
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// marker for ZFile struct, to make sure it's valid
|
||||
#if CONFIG_PARANOIA
|
||||
static const u32 ZFILE_MAGIC = FOURCC('Z','F','I','L');
|
||||
#endif
|
||||
|
||||
|
||||
// return 0 <==> ZFile seems valid
|
||||
static int zfile_validate(uint line, ZFile* zf)
|
||||
{
|
||||
const char* msg = "";
|
||||
int err = -1;
|
||||
|
||||
if(!zf)
|
||||
{
|
||||
msg = "ZFile* parameter = 0";
|
||||
err = ERR_INVALID_PARAM;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
// else if(!h_user_data(zf->ha, H_ZArchive))
|
||||
// msg = "invalid archive handle";
|
||||
// disabled: happens at shutdown because handles are freed out-of order;
|
||||
// archive is freed before its files, making its Handle invalid
|
||||
#endif
|
||||
else if(!zf->ucsize)
|
||||
msg = "ucsize = 0";
|
||||
else if(!zf->inf_ctx)
|
||||
msg = "read context invalid";
|
||||
// everything is OK
|
||||
else
|
||||
return 0;
|
||||
|
||||
// failed somewhere - err is the error code,
|
||||
// or -1 if not set specifically above.
|
||||
debug_printf("zfile_validate at line %d failed: %s\n", line, msg);
|
||||
debug_warn("zfile_validate failed");
|
||||
return err;
|
||||
}
|
||||
|
||||
#define CHECK_ZFILE(f) RETURN_ERR(zfile_validate(__LINE__, f))
|
||||
|
||||
|
||||
// convenience function, allows implementation change in ZFile.
|
||||
// note that size == ucsize isn't foolproof, and adding a flag to
|
||||
// ofs or size is ugly and error-prone.
|
||||
@ -967,8 +929,6 @@ int zip_open(const Handle ha, const char* fn, int flags, ZFile* zf)
|
||||
zf->ha = ha;
|
||||
zf->inf_ctx = inf_init_ctx(zfile_compressed(zf));
|
||||
zf->is_mapped = 0;
|
||||
CHECK_ZFILE(zf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -976,13 +936,26 @@ int zip_open(const Handle ha, const char* fn, int flags, ZFile* zf)
|
||||
// close file.
|
||||
int zip_close(ZFile* zf)
|
||||
{
|
||||
CHECK_ZFILE(zf);
|
||||
|
||||
// remaining ZFile fields don't need to be freed/cleared
|
||||
return inf_free_ctx(zf->inf_ctx);
|
||||
}
|
||||
|
||||
|
||||
int zip_validate(const ZFile* zf)
|
||||
{
|
||||
if(!zf)
|
||||
return ERR_INVALID_PARAM;
|
||||
// note: don't check zf->ha - it may be freed at shutdown before
|
||||
// its files. TODO: revisit once dependency support is added.
|
||||
if(!zf->ucsize)
|
||||
return -2;
|
||||
else if(!zf->inf_ctx)
|
||||
return -3;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// sync and async I/O
|
||||
@ -1001,8 +974,8 @@ int zip_close(ZFile* zf)
|
||||
static const size_t CHUNK_SIZE = 16*KiB;
|
||||
|
||||
// begin transferring <size> bytes, starting at <ofs>. get result
|
||||
// with zip_wait_io; when no longer needed, free via zip_discard_io.
|
||||
int zip_start_io(ZFile* zf, off_t user_ofs, size_t max_output_size, void* user_buf, ZipIo* io)
|
||||
// with zip_io_wait; when no longer needed, free via zip_io_discard.
|
||||
int zip_io_issue(ZFile* zf, off_t user_ofs, size_t max_output_size, void* user_buf, ZipIo* io)
|
||||
{
|
||||
// not needed, since ZFile tells us the last read offset in the file.
|
||||
UNUSED2(user_ofs);
|
||||
@ -1010,7 +983,6 @@ int zip_start_io(ZFile* zf, off_t user_ofs, size_t max_output_size, void* user_b
|
||||
// zero output param in case we fail below.
|
||||
memset(io, 0, sizeof(ZipIo));
|
||||
|
||||
CHECK_ZFILE(zf);
|
||||
H_DEREF(zf->ha, ZArchive, za);
|
||||
|
||||
// transfer params that differ if compressed
|
||||
@ -1054,7 +1026,7 @@ int zip_start_io(ZFile* zf, off_t user_ofs, size_t max_output_size, void* user_b
|
||||
|
||||
zf->last_read_ofs += (off_t)size;
|
||||
|
||||
CHECK_ERR(file_start_io(&za->f, ofs, size, buf, &io->io));
|
||||
CHECK_ERR(file_io_issue(&za->f, ofs, size, buf, &io->io));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1062,17 +1034,17 @@ int zip_start_io(ZFile* zf, off_t user_ofs, size_t max_output_size, void* user_b
|
||||
|
||||
// indicates if the IO referenced by <io> has completed.
|
||||
// return value: 0 if pending, 1 if complete, < 0 on error.
|
||||
int zip_io_complete(ZipIo* io)
|
||||
int zip_io_has_completed(ZipIo* io)
|
||||
{
|
||||
if(io->already_inflated)
|
||||
return 1;
|
||||
return file_io_complete(&io->io);
|
||||
return file_io_has_completed(&io->io);
|
||||
}
|
||||
|
||||
|
||||
// wait until the transfer <io> completes, and return its buffer.
|
||||
// output parameters are zeroed on error.
|
||||
int zip_wait_io(ZipIo* io, void*& buf, size_t& size)
|
||||
int zip_io_wait(ZipIo* io, void*& buf, size_t& size)
|
||||
{
|
||||
buf = io->user_buf;
|
||||
size = io->max_output_size;
|
||||
@ -1081,7 +1053,7 @@ int zip_wait_io(ZipIo* io, void*& buf, size_t& size)
|
||||
|
||||
void* raw_buf;
|
||||
size_t raw_size;
|
||||
CHECK_ERR(file_wait_io(&io->io, raw_buf, raw_size));
|
||||
CHECK_ERR(file_io_wait(&io->io, raw_buf, raw_size));
|
||||
|
||||
if(io->inf_ctx)
|
||||
{
|
||||
@ -1102,15 +1074,25 @@ int zip_wait_io(ZipIo* io, void*& buf, size_t& size)
|
||||
}
|
||||
|
||||
|
||||
// finished with transfer <io> - free its buffer (returned by zip_wait_io)
|
||||
int zip_discard_io(ZipIo* io)
|
||||
// finished with transfer <io> - free its buffer (returned by zip_io_wait)
|
||||
int zip_io_discard(ZipIo* io)
|
||||
{
|
||||
if(io->already_inflated)
|
||||
return 0;
|
||||
return file_discard_io(&io->io);
|
||||
return file_io_discard(&io->io);
|
||||
}
|
||||
|
||||
|
||||
int zip_io_validate(const ZipIo* io)
|
||||
{
|
||||
if(debug_is_pointer_bogus(io->user_buf))
|
||||
return -2;
|
||||
if(*(u8*)&io->already_inflated > 1)
|
||||
return -3;
|
||||
// <inf_ctx> and <max_output_size> have no invariants we could check.
|
||||
RETURN_ERR(file_io_validate(&io->io));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -1163,8 +1145,6 @@ static ssize_t read_cb(uintptr_t ctx, void* buf, size_t size)
|
||||
// return bytes read, or a negative error code.
|
||||
ssize_t zip_read(ZFile* zf, off_t ofs, size_t size, void* p, FileIOCB cb, uintptr_t ctx)
|
||||
{
|
||||
CHECK_ZFILE(zf);
|
||||
|
||||
//const bool compressed = zfile_compressed(zf);
|
||||
|
||||
H_DEREF(zf->ha, ZArchive, za);
|
||||
@ -1240,8 +1220,6 @@ int zip_map(ZFile* zf, void*& p, size_t& size)
|
||||
p = 0;
|
||||
size = 0;
|
||||
|
||||
CHECK_ZFILE(zf);
|
||||
|
||||
// mapping compressed files doesn't make sense because the
|
||||
// compression algorithm is unspecified - disallow it.
|
||||
if(zfile_compressed(zf))
|
||||
@ -1273,8 +1251,6 @@ int zip_map(ZFile* zf, void*& p, size_t& size)
|
||||
// may be removed when no longer needed.
|
||||
int zip_unmap(ZFile* zf)
|
||||
{
|
||||
CHECK_ZFILE(zf);
|
||||
|
||||
// make sure archive mapping refcount remains balanced:
|
||||
// don't allow multiple|"false" unmaps.
|
||||
if(!zf->is_mapped)
|
||||
|
@ -78,6 +78,8 @@ extern int zip_open(Handle ha, const char* fn, int flags, ZFile* zf);
|
||||
// close file.
|
||||
extern int zip_close(ZFile* zf);
|
||||
|
||||
extern int zip_validate(const ZFile* zf);
|
||||
|
||||
|
||||
//
|
||||
// asynchronous read
|
||||
@ -96,19 +98,21 @@ struct ZipIo
|
||||
};
|
||||
|
||||
// begin transferring <size> bytes, starting at <ofs>. get result
|
||||
// with zip_wait_io; when no longer needed, free via zip_discard_io.
|
||||
extern int zip_start_io(ZFile* zf, off_t ofs, size_t size, void* buf, ZipIo* io);
|
||||
// with zip_io_wait; when no longer needed, free via zip_io_discard.
|
||||
extern int zip_io_issue(ZFile* zf, off_t ofs, size_t size, void* buf, ZipIo* io);
|
||||
|
||||
// indicates if the IO referenced by <io> has completed.
|
||||
// return value: 0 if pending, 1 if complete, < 0 on error.
|
||||
extern int zip_io_complete(ZipIo* io);
|
||||
extern int zip_io_has_completed(ZipIo* io);
|
||||
|
||||
// wait until the transfer <io> completes, and return its buffer.
|
||||
// output parameters are zeroed on error.
|
||||
extern int zip_wait_io(ZipIo* io, void*& p, size_t& size);
|
||||
extern int zip_io_wait(ZipIo* io, void*& p, size_t& size);
|
||||
|
||||
// finished with transfer <io> - free its buffer (returned by zip_wait_io)
|
||||
extern int zip_discard_io(ZipIo* io);
|
||||
// finished with transfer <io> - free its buffer (returned by zip_io_wait)
|
||||
extern int zip_io_discard(ZipIo* io);
|
||||
|
||||
extern int zip_io_validate(const ZipIo* io);
|
||||
|
||||
|
||||
//
|
||||
|
@ -21,6 +21,7 @@
|
||||
class GLCursor
|
||||
{
|
||||
Handle ht;
|
||||
|
||||
uint w, h;
|
||||
uint hotspotx, hotspoty;
|
||||
|
||||
@ -41,10 +42,12 @@ public:
|
||||
|
||||
void destroy()
|
||||
{
|
||||
// note: we're stored in a resource => ht is initially 0 =>
|
||||
// this is safe, no need for an is_valid flag
|
||||
(void)ogl_tex_free(ht);
|
||||
}
|
||||
|
||||
void draw(uint x, uint y)
|
||||
void draw(uint x, uint y) const
|
||||
{
|
||||
(void)ogl_tex_bind(ht);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
@ -63,6 +66,16 @@ public:
|
||||
glTexCoord2i(0, 1); glVertex2i( x-hotspotx, y+hotspoty-h );
|
||||
glEnd();
|
||||
}
|
||||
|
||||
int validate() const
|
||||
{
|
||||
const uint A = 128; // no cursor is expected to get this big
|
||||
if(w > A || h > A || hotspotx > A || hotspoty > A)
|
||||
return -2;
|
||||
if(ht < 0)
|
||||
return -3;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -82,6 +95,8 @@ static void Cursor_init(Cursor* UNUSED(c), va_list UNUSED(args))
|
||||
|
||||
static void Cursor_dtor(Cursor* c)
|
||||
{
|
||||
// (note: these are safe, no need for an is_valid flag)
|
||||
|
||||
if(c->sys_cursor)
|
||||
WARN_ERR(sys_cursor_free(c->sys_cursor));
|
||||
else
|
||||
@ -120,6 +135,17 @@ static int Cursor_reload(Cursor* c, const char* name, Handle)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Cursor_validate(const Cursor* c)
|
||||
{
|
||||
// note: system cursors have no state to speak of, so we don't need to
|
||||
// validate them.
|
||||
|
||||
if(!c->sys_cursor)
|
||||
RETURN_ERR(c->gl_cursor.validate());
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// note: these standard resource interface functions are not exposed to the
|
||||
// caller. all we need here is storage for the sys_cursor / GLCursor and
|
||||
// a name -> data lookup mechanism, both provided by h_mgr.
|
||||
|
@ -72,10 +72,10 @@ static void Ogl_Shader_init(Ogl_Shader* shdr, va_list args)
|
||||
// have absolutely no effect on a program object that contains these shaders
|
||||
// when the program object is already linked.
|
||||
// So, how can we inform the "parent object" (i.e. the program object) of our change?
|
||||
static int Ogl_Shader_reload(Ogl_Shader* shdr, const char* filename, Handle h)
|
||||
static int Ogl_Shader_reload(Ogl_Shader* shdr, const char* filename, Handle UNUSED(h))
|
||||
{
|
||||
int err = -666;
|
||||
|
||||
|
||||
if (shdr->id)
|
||||
return 0;
|
||||
|
||||
@ -162,6 +162,11 @@ static void Ogl_Shader_dtor(Ogl_Shader* shdr)
|
||||
}
|
||||
}
|
||||
|
||||
static int Ogl_Shader_validate(const Ogl_Shader* shdr)
|
||||
{
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@ -212,7 +217,7 @@ H_TYPE_DEFINE(Ogl_Program);
|
||||
|
||||
// One-time initialization, called once by h_alloc, which is
|
||||
// in turn called by ogl_program_load
|
||||
static void Ogl_Program_init(Ogl_Program* p, va_list args)
|
||||
static void Ogl_Program_init(Ogl_Program* UNUSED(p), va_list UNUSED(args))
|
||||
{
|
||||
}
|
||||
|
||||
@ -374,6 +379,12 @@ static void Ogl_Program_dtor(Ogl_Program* p)
|
||||
}
|
||||
}
|
||||
|
||||
static int Ogl_Program_validate(const Ogl_Program* p)
|
||||
{
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
@ -43,23 +43,31 @@ struct UniFont
|
||||
|
||||
H_TYPE_DEFINE(UniFont);
|
||||
|
||||
static void UniFont_init(UniFont* f, va_list UNUSED(args))
|
||||
static void UniFont_init(UniFont* UNUSED(f), va_list UNUSED(args))
|
||||
{
|
||||
f->glyphs_id = new glyphmap_id;
|
||||
f->glyphs_size = new glyphmap_size;
|
||||
}
|
||||
|
||||
static void UniFont_dtor(UniFont* f)
|
||||
{
|
||||
ogl_tex_free(f->ht);
|
||||
glDeleteLists(f->ListBase, (GLsizei)f->glyphs_id->size());
|
||||
delete f->glyphs_id;
|
||||
delete f->glyphs_size;
|
||||
}
|
||||
// these are all safe, no is_valid flags needed
|
||||
(void)ogl_tex_free(f->ht);
|
||||
|
||||
glDeleteLists(f->ListBase, (GLsizei)f->glyphs_id->size());
|
||||
f->ListBase = 0;
|
||||
|
||||
SAFE_DELETE(f->glyphs_id);
|
||||
SAFE_DELETE(f->glyphs_size);
|
||||
}
|
||||
|
||||
static int UniFont_reload(UniFont* f, const char* fn, Handle UNUSED(h))
|
||||
{
|
||||
// already loaded
|
||||
if(f->ht > 0)
|
||||
return 0;
|
||||
|
||||
f->glyphs_id = new glyphmap_id;
|
||||
f->glyphs_size = new glyphmap_size;
|
||||
|
||||
// fn is the base filename, e.g. "console"
|
||||
// The font definition file is "fonts/"+fn+".fnt" and the texture is "fonts/"+fn+".tga"
|
||||
std::string FilenameBase = "fonts/"; FilenameBase += fn;
|
||||
@ -79,7 +87,7 @@ static int UniFont_reload(UniFont* f, const char* fn, Handle UNUSED(h))
|
||||
if (Version != 100) // Make sure this is from a recent version of the font builder
|
||||
{
|
||||
debug_warn("Invalid .fnt version number");
|
||||
return -1;
|
||||
return ERR_UNKNOWN_FORMAT;
|
||||
}
|
||||
|
||||
int TextureWidth, TextureHeight;
|
||||
@ -165,6 +173,21 @@ static int UniFont_reload(UniFont* f, const char* fn, Handle UNUSED(h))
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int UniFont_validate(const UniFont* f)
|
||||
{
|
||||
if(f->ht < 0)
|
||||
return -2;
|
||||
if(debug_is_pointer_bogus(f->glyphs_id) || debug_is_pointer_bogus(f->glyphs_size))
|
||||
return -3;
|
||||
// <LineSpacing> and <Height> are read directly from font file.
|
||||
// negative values don't make sense, but that's all we can check.
|
||||
if(f->LineSpacing < 0 || f->Height < 0)
|
||||
return -4;
|
||||
if(f->ListBase == 0 || f->ListBase > 1000000) // suspicious
|
||||
return -5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Handle unifont_load(const char* fn, int scope)
|
||||
{
|
||||
|
@ -20,13 +20,14 @@
|
||||
|
||||
#include "lib.h"
|
||||
#include "h_mgr.h"
|
||||
|
||||
#include "dyn_array.h"
|
||||
|
||||
#include <limits.h> // CHAR_BIT
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <new> // std::bad_alloc
|
||||
|
||||
static const uint MAX_EXTANT_HANDLES = 10000;
|
||||
|
||||
// rationale
|
||||
//
|
||||
@ -142,6 +143,9 @@ struct HDATA
|
||||
u32 disallow_reload : 1;
|
||||
|
||||
H_Type type;
|
||||
|
||||
// for statistics
|
||||
uint num_derefs;
|
||||
|
||||
const char* fn;
|
||||
|
||||
@ -198,7 +202,9 @@ static HDATA* h_data_from_idx(const i32 idx)
|
||||
|
||||
// note: VC7.1 optimizes the divides to shift and mask.
|
||||
|
||||
return &page[idx % hdata_per_page];
|
||||
HDATA* hd = &page[idx % hdata_per_page];
|
||||
hd->num_derefs++;
|
||||
return hd;
|
||||
}
|
||||
|
||||
|
||||
@ -251,11 +257,7 @@ static HDATA* h_data_tag_type(const Handle h, const H_Type type)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// idx and hd are undefined if we fail.
|
||||
// called by h_alloc only.
|
||||
@ -322,6 +324,8 @@ static int free_idx(i32 idx)
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// 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)
|
||||
@ -364,121 +368,123 @@ static Handle find_key(uintptr_t key, H_Type type, bool remove = false)
|
||||
}
|
||||
|
||||
|
||||
static void add_key(uintptr_t key, Handle h)
|
||||
static void key_add(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)
|
||||
static void key_remove(uintptr_t key, H_Type type)
|
||||
{
|
||||
Handle ret = find_key(key, type, true);
|
||||
debug_assert(ret > 0);
|
||||
}
|
||||
|
||||
|
||||
// currently cannot fail.
|
||||
static int h_free_idx(i32 idx, HDATA* hd)
|
||||
//
|
||||
// path string suballocator (they're stored in HDATA)
|
||||
//
|
||||
|
||||
static Pool fn_pool;
|
||||
|
||||
|
||||
// if fn is longer than this, it has to be allocated from the heap.
|
||||
// choose this to balance internal fragmentation and accessing the heap.
|
||||
static const size_t FN_POOL_EL_SIZE = 64;
|
||||
|
||||
static void fn_init()
|
||||
{
|
||||
// debug_printf("free %s %s\n", type->name, hd->fn);
|
||||
|
||||
// only decrement if refcount not already 0.
|
||||
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 0;
|
||||
|
||||
// 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)
|
||||
remove_key(hd->key, hd->type);
|
||||
|
||||
free((void*)hd->fn);
|
||||
|
||||
memset(hd, 0, sizeof(HDATA));
|
||||
|
||||
free_idx(idx);
|
||||
|
||||
return 0;
|
||||
// (if this fails, so will subsequent fn_stores - no need to complain here)
|
||||
(void)pool_create(&fn_pool, MAX_EXTANT_HANDLES*FN_POOL_EL_SIZE, FN_POOL_EL_SIZE);
|
||||
}
|
||||
|
||||
|
||||
int h_free(Handle& h, H_Type type)
|
||||
static void fn_store(HDATA* hd, const char* fn)
|
||||
{
|
||||
i32 idx = h_idx(h);
|
||||
HDATA* hd = h_data_tag_type(h, type);
|
||||
ONCE(fn_init());
|
||||
|
||||
// wipe out the handle to prevent reuse but keep a copy for below.
|
||||
const Handle h_copy = h;
|
||||
h = 0;
|
||||
const size_t len = strlen(fn);
|
||||
debug_printf("FN_STORE %d %s\n", len, fn);
|
||||
|
||||
// h was invalid
|
||||
if(!hd)
|
||||
hd->fn = 0;
|
||||
// this is disabled until it's determined that strings are actually likely
|
||||
// to fit there (unlikely)
|
||||
if(hd->type->user_size + len < HDATA_USER_SIZE)
|
||||
hd->fn = (const char*)hd->user + hd->type->user_size;
|
||||
else if(len+1 <= FN_POOL_EL_SIZE)
|
||||
hd->fn = (const char*)pool_alloc(&fn_pool);
|
||||
|
||||
// in case none of the above applied and were successful:
|
||||
// fall back to heap alloc.
|
||||
if(!hd->fn)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
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)
|
||||
hd->fn = (const char*)malloc(len+1);
|
||||
// still failed - bail (avoid strcpy to 0)
|
||||
if(!hd->fn)
|
||||
{
|
||||
debug_warn("h_mgr_shutdown: h_data_from_idx failed - why?!");
|
||||
continue;
|
||||
debug_warn("not enough memory to store hd->fn!");
|
||||
return;
|
||||
}
|
||||
|
||||
// it's already been freed; don't free again so that this
|
||||
// doesn't look like an error.
|
||||
if(!hd->tag)
|
||||
continue;
|
||||
|
||||
// if(hd->refs != 0)
|
||||
// debug_printf("leaked %s from %s\n", hd->type->name, hd->fn);
|
||||
|
||||
// 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++)
|
||||
strcpy((char*)hd->fn, fn);
|
||||
}
|
||||
|
||||
// TODO: store this in a flag - faster.
|
||||
static bool fn_is_in_HDATA(HDATA* hd)
|
||||
{
|
||||
u8* p = (u8*)hd->fn; // needed for type-correct comparison
|
||||
return (hd->user+hd->type->user_size <= p && p <= hd->user+HDATA_USER_SIZE);
|
||||
}
|
||||
|
||||
static void fn_free(HDATA* hd)
|
||||
{
|
||||
void* el = (void*)hd->fn;
|
||||
if(fn_is_in_HDATA(hd))
|
||||
{
|
||||
free(pages[j]);
|
||||
pages[j] = 0;
|
||||
}
|
||||
else if(pool_contains(&fn_pool, el))
|
||||
pool_free(&fn_pool, el);
|
||||
else
|
||||
free(el);
|
||||
}
|
||||
|
||||
static void fn_shutdown()
|
||||
{
|
||||
pool_destroy(&fn_pool);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// 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
|
||||
int err = vtbl->validate(hd->user);
|
||||
debug_assert(err == 0);
|
||||
|
||||
// make sure empty space in control block isn't touched
|
||||
// .. but only if we're not storing a filename there
|
||||
if(!fn_is_in_HDATA(hd))
|
||||
{
|
||||
const u8* start = hd->user + vtbl->user_size;
|
||||
const u8* end = hd->user + HDATA_USER_SIZE;
|
||||
for(const u8* p = start; p < end; p++)
|
||||
if(*p != 0)
|
||||
debug_warn("handle user data was overrun!");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static int type_validate(H_Type type)
|
||||
{
|
||||
@ -572,6 +578,8 @@ static int call_init_and_reload(Handle h, H_Type type, HDATA* hd, const char* fn
|
||||
try
|
||||
{
|
||||
err = vtbl->reload(hd->user, fn, h);
|
||||
if(err == 0)
|
||||
warn_if_invalid(hd);
|
||||
}
|
||||
catch(std::bad_alloc)
|
||||
{
|
||||
@ -608,10 +616,10 @@ static Handle alloc_new_handle(H_Type type, const char* fn, uintptr_t key,
|
||||
// .. 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);
|
||||
fn_store(hd, fn);
|
||||
|
||||
if(key && !hd->unique)
|
||||
add_key(key, h);
|
||||
key_add(key, h);
|
||||
|
||||
int err = call_init_and_reload(h, type, hd, fn, init_args);
|
||||
if(err < 0)
|
||||
@ -671,6 +679,80 @@ Handle h_alloc(H_Type type, const char* fn, uint flags, ...)
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// currently cannot fail.
|
||||
static int h_free_idx(i32 idx, HDATA* hd)
|
||||
{
|
||||
// debug_printf("free %s %s\n", type->name, hd->fn);
|
||||
|
||||
// only decrement if refcount not already 0.
|
||||
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 0;
|
||||
|
||||
// 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);
|
||||
|
||||
const char* fn = "(0)";
|
||||
if(hd->fn)
|
||||
{
|
||||
const char* slash = strrchr(hd->fn, '/');
|
||||
fn = slash? slash+1 : hd->fn;
|
||||
}
|
||||
debug_printf("H_FREE %s %s accesses=%d\n", hd->type->name, fn, hd->num_derefs);
|
||||
|
||||
fn_free(hd);
|
||||
|
||||
memset(hd, 0, sizeof(HDATA));
|
||||
|
||||
free_idx(idx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int h_free(Handle& h, H_Type type)
|
||||
{
|
||||
i32 idx = h_idx(h);
|
||||
HDATA* hd = h_data_tag_type(h, type);
|
||||
|
||||
// wipe out the handle to prevent reuse but keep a copy for below.
|
||||
const Handle h_copy = h;
|
||||
h = 0;
|
||||
|
||||
// h was invalid
|
||||
if(!hd)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
return h_free_idx(idx, hd);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// remaining API
|
||||
|
||||
void* h_user_data(const Handle h, const H_Type type)
|
||||
{
|
||||
HDATA* hd = h_data_tag_type(h, type);
|
||||
@ -684,6 +766,7 @@ void* h_user_data(const Handle h, const H_Type type)
|
||||
return 0;
|
||||
}
|
||||
|
||||
warn_if_invalid(hd);
|
||||
return hd->user;
|
||||
}
|
||||
|
||||
@ -744,6 +827,8 @@ int h_reload(const char* fn)
|
||||
if(ret == 0) // don't overwrite first error
|
||||
ret = err;
|
||||
}
|
||||
else
|
||||
warn_if_invalid(hd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -819,3 +904,43 @@ int h_get_refcnt(Handle h)
|
||||
|
||||
return hd->refs;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
if(hd->refs != 0)
|
||||
debug_printf("leaked %s from %s\n", hd->type->name, hd->fn);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
fn_shutdown();
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ 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 occurences of it below
|
||||
of this type. we will call ours "Res1"; all below occurences 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.
|
||||
@ -74,10 +74,16 @@ your actual definition must match.
|
||||
2) declare its control block:
|
||||
struct Res1
|
||||
{
|
||||
void* data1; // data loaded from file
|
||||
uint flags; // set when resource is created
|
||||
void* data; // data loaded from file
|
||||
uint 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
|
||||
necessarily help unless yours is the largest. However, if the filename
|
||||
passed to h_alloc fits within the remaining space, it is stored there
|
||||
(thus saving time+memory). Therefore, do not be extravagant with space.
|
||||
|
||||
3) build its vtbl:
|
||||
H_TYPE_DEFINE(Res1);
|
||||
|
||||
@ -100,7 +106,7 @@ precondition: control block is initialized to 0.
|
||||
|
||||
static void Type_init(Res1* r, va_list args)
|
||||
{
|
||||
r->flags = va_arg(args, int);
|
||||
r->flags = va_arg(args, int);
|
||||
}
|
||||
|
||||
if the caller of h_alloc passed additional args, they are available
|
||||
@ -121,8 +127,15 @@ called after init; also after dtor every time the file is reloaded.
|
||||
|
||||
static int Type_reload(Res1* r, const char* filename, Handle);
|
||||
{
|
||||
// somehow load stuff from filename, and store it in r->data1.
|
||||
return 0;
|
||||
// already loaded; done
|
||||
if(r->data)
|
||||
return 0;
|
||||
|
||||
r->data = malloc(100);
|
||||
if(!r->data)
|
||||
return ERR_NO_MEM;
|
||||
// (read contents of <filename> into r->data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
reload must abort if the control block data indicates the resource
|
||||
@ -143,27 +156,55 @@ example: when uploading a texture, store the upload parameters
|
||||
--
|
||||
|
||||
dtor:
|
||||
frees all data allocated by init and reload. called after h_free,
|
||||
or at exit. control block will be zeroed afterwards.
|
||||
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);
|
||||
static void Type_dtor(Res1* r);
|
||||
{
|
||||
// free memory r->data1
|
||||
free(r->data);
|
||||
}
|
||||
|
||||
again no provision for reporting errors - there's no one to act on it
|
||||
if called at exit. you can debug_assert 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 distinct negative value (error code, if appropriate).
|
||||
called automatically when the Handle is dereferenced or freed.
|
||||
|
||||
static int Type_validate(const Res1* r);
|
||||
{
|
||||
const int permissible_flags = 0x01;
|
||||
if(debug_is_pointer_bogus(r->data))
|
||||
return -2;
|
||||
if(r->flags & ~permissible_flags)
|
||||
return -3;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
5) provide your layer on top of the handle manager:
|
||||
Handle res1_load(const char* filename, int my_flags)
|
||||
{
|
||||
return h_alloc(H_Res1, filename, 0, my_flags); // my_flags is passed to init
|
||||
// passes my_flags to init
|
||||
return h_alloc(H_Res1, filename, 0, my_flags);
|
||||
}
|
||||
|
||||
int res1_free(Handle& h)
|
||||
{
|
||||
return h_free(h, H_Res1);
|
||||
// zeroes h afterwards
|
||||
// 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,
|
||||
@ -171,16 +212,17 @@ 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:
|
||||
Handle h;
|
||||
a) H_DEREF(h, Res1, r);
|
||||
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
|
||||
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
|
||||
@ -269,6 +311,7 @@ struct H_VTbl
|
||||
void(*init)(void* user, va_list);
|
||||
int(*reload)(void* user, const char* fn, Handle);
|
||||
void(*dtor)(void* user);
|
||||
int(*validate)(const void* user);
|
||||
size_t user_size;
|
||||
const char* name;
|
||||
};
|
||||
@ -280,11 +323,13 @@ typedef H_VTbl* H_Type;
|
||||
static void type##_init(type*, va_list);\
|
||||
static int type##_reload(type*, const char*, Handle);\
|
||||
static void type##_dtor(type*);\
|
||||
static int type##_validate(const type*);\
|
||||
static H_VTbl V_##type =\
|
||||
{\
|
||||
(void(*)(void*, va_list))type##_init,\
|
||||
(int(*)(void*, const char*, Handle))type##_reload,\
|
||||
(void(*)(void*))type##_dtor,\
|
||||
(int(*)(const void*))type##_validate,\
|
||||
sizeof(type), /* control block size */\
|
||||
#type /* name */\
|
||||
};\
|
||||
|
@ -133,21 +133,29 @@ static void set_alloc(void* raw_p, Handle hm)
|
||||
|
||||
static void Mem_init(Mem* m, va_list args)
|
||||
{
|
||||
// HACK: we pass along raw_p from h_alloc for use in Mem_reload
|
||||
// (that means add/remove from mapping code is only here)
|
||||
m->raw_p = va_arg(args, void*);
|
||||
// these are passed to h_alloc instead of assigning in mem_wrap after a
|
||||
// H_DEREF so that Mem_validate won't complain about invalid (0) values.
|
||||
//
|
||||
// additional bonus: by setting raw_p before reload, that and the
|
||||
// dtor will be the only call site of set/remove_alloc.
|
||||
m->p = va_arg(args, void*);
|
||||
m->size = va_arg(args, size_t);
|
||||
m->raw_p = va_arg(args, void*);
|
||||
m->raw_size = va_arg(args, size_t);
|
||||
m->dtor = va_arg(args, MEM_DTOR);
|
||||
m->ctx = va_arg(args, uintptr_t);
|
||||
}
|
||||
|
||||
|
||||
static void Mem_dtor(Mem* m)
|
||||
{
|
||||
// (reload can't fail)
|
||||
|
||||
remove_alloc(m->raw_p);
|
||||
|
||||
if(m->dtor)
|
||||
m->dtor(m->raw_p, m->raw_size, m->ctx);
|
||||
}
|
||||
|
||||
|
||||
// can't alloc here, because h_alloc needs the key when called
|
||||
// (key == pointer we allocate)
|
||||
static int Mem_reload(Mem* m, const char* UNUSED(fn), Handle hm)
|
||||
@ -156,6 +164,20 @@ static int Mem_reload(Mem* m, const char* UNUSED(fn), Handle hm)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Mem_validate(const Mem* m)
|
||||
{
|
||||
if(debug_is_pointer_bogus(m->p))
|
||||
return -2;
|
||||
if(!m->size)
|
||||
return -3;
|
||||
if(m->raw_p && m->raw_p > m->p)
|
||||
return -4;
|
||||
if(m->raw_size && m->raw_size < m->size)
|
||||
return -5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// "*": aligned memory returned by allocator.
|
||||
@ -275,16 +297,8 @@ Handle mem_wrap(void* p, size_t size, uint flags, void* raw_p, size_t raw_size,
|
||||
if(!raw_size)
|
||||
raw_size = size;
|
||||
|
||||
hm = h_alloc(H_Mem, (const char*)p, flags|RES_KEY|RES_NO_CACHE, raw_p);
|
||||
RETURN_ERR(hm);
|
||||
|
||||
H_DEREF(hm, Mem, m);
|
||||
m->p = p;
|
||||
m->size = size;
|
||||
m->raw_p = raw_p;
|
||||
m->raw_size = raw_size;
|
||||
m->dtor = dtor;
|
||||
m->ctx = ctx;
|
||||
hm = h_alloc(H_Mem, (const char*)p, flags|RES_KEY|RES_NO_CACHE,
|
||||
p, size, raw_p, raw_size, dtor, ctx);
|
||||
return hm;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ extern void mem_shutdown(void);
|
||||
|
||||
extern Handle mem_wrap(void* p, size_t size, uint flags, void* raw_p, size_t raw_size, MEM_DTOR dtor, uintptr_t ctx);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -703,7 +703,7 @@ static int stream_issue(Stream* s)
|
||||
if(!buf)
|
||||
return ERR_NO_MEM;
|
||||
|
||||
Handle h = vfs_start_io(s->hf, STREAM_BUF_SIZE, buf);
|
||||
Handle h = vfs_io_issue(s->hf, STREAM_BUF_SIZE, buf);
|
||||
CHECK_ERR(h);
|
||||
s->ios[s->active_ios++] = h;
|
||||
return 0;
|
||||
@ -720,14 +720,14 @@ static int stream_buf_get(Stream* s, void*& data, size_t& size)
|
||||
Handle hio = s->ios[0];
|
||||
|
||||
// has it finished? if not, bail.
|
||||
int is_complete = vfs_io_complete(hio);
|
||||
int is_complete = vfs_io_has_completed(hio);
|
||||
CHECK_ERR(is_complete);
|
||||
if(is_complete == 0)
|
||||
return ERR_AGAIN;
|
||||
|
||||
// get its buffer.
|
||||
CHECK_ERR(vfs_wait_io(hio, data, size));
|
||||
// no delay, since vfs_io_complete == 1
|
||||
CHECK_ERR(vfs_io_wait(hio, data, size));
|
||||
// no delay, since vfs_io_has_completed == 1
|
||||
|
||||
s->last_buf = data;
|
||||
// (next stream_buf_discard will free this buffer)
|
||||
@ -743,7 +743,7 @@ static int stream_buf_discard(Stream* s)
|
||||
{
|
||||
Handle hio = s->ios[0];
|
||||
|
||||
int ret = vfs_discard_io(hio);
|
||||
int ret = vfs_io_discard(hio);
|
||||
|
||||
// we implement the required 'circular queue' as a stack;
|
||||
// have to shift all items after this one down.
|
||||
@ -777,7 +777,7 @@ static int stream_open(Stream* s, const char* fn)
|
||||
|
||||
for(int i = 0; i < MAX_IOS; i++)
|
||||
CHECK_ERR(stream_issue(s));
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -816,6 +816,15 @@ static int stream_close(Stream* s)
|
||||
}
|
||||
|
||||
|
||||
static int stream_validate(const Stream* s)
|
||||
{
|
||||
if(s->active_ios > MAX_IOS)
|
||||
return -2;
|
||||
// <last_buf> has no invariant we could check.
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// sound data provider: holds audio data (clip or stream) and returns
|
||||
@ -849,7 +858,8 @@ struct SndData
|
||||
// clip
|
||||
ALuint al_buf;
|
||||
|
||||
bool is_stream;
|
||||
uint is_stream : 1;
|
||||
uint is_valid : 1;
|
||||
|
||||
#ifdef OGG_HACK
|
||||
// pointer to Ogg instance
|
||||
@ -859,63 +869,6 @@ void* o;
|
||||
|
||||
H_TYPE_DEFINE(SndData);
|
||||
|
||||
|
||||
//
|
||||
// SndData instance list: ensures all allocated since last al_shutdown
|
||||
// are freed when desired (they are cached => extra work needed).
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// rationale: all SndData objects (actually, their OpenAL buffers) must be
|
||||
// freed during al_shutdown, to prevent leaks. we can't rely on list*
|
||||
// to free all VSrc, and thereby their associated SndData objects -
|
||||
// completed sounds are no longer in the list.
|
||||
//
|
||||
// nor can we use the h_mgr_shutdown automatic leaked resource cleanup:
|
||||
// we need to be able to al_shutdown at runtime
|
||||
// (when resetting OpenAL, after e.g. device change).
|
||||
//
|
||||
// h_mgr support is required to forcibly close SndData objects,
|
||||
// since they are cached (kept open). it requires that this come after
|
||||
// H_TYPE_DEFINE(SndData), since h_force_free needs the handle type.
|
||||
//
|
||||
// we never need to delete single entries: hsd_list_free_all
|
||||
// (called by al_shutdown) frees each entry and clears the entire list.
|
||||
|
||||
typedef std::vector<Handle> Handles;
|
||||
static Handles hsd_list;
|
||||
|
||||
// called from SndData_reload; will later be removed via hsd_list_free_all.
|
||||
static void hsd_list_add(Handle hsd)
|
||||
{
|
||||
hsd_list.push_back(hsd);
|
||||
}
|
||||
|
||||
|
||||
// called by al_shutdown (at exit, or when reinitializing OpenAL).
|
||||
static void hsd_list_free_all()
|
||||
{
|
||||
for(Handles::iterator it = hsd_list.begin(); it != hsd_list.end(); ++it)
|
||||
{
|
||||
Handle& hsd = *it;
|
||||
|
||||
h_force_free(hsd, H_SndData);
|
||||
// ignore errors - if hsd was a stream, and its associated source
|
||||
// was active when al_shutdown was called, it will already have been
|
||||
// freed (list_free_all would free the source; it then releases
|
||||
// its SndData reference, which closes the instance because it's
|
||||
// RES_UNIQUE).
|
||||
}
|
||||
|
||||
// leave its memory intact, so we don't have to reallocate it later
|
||||
// if we are now reinitializing OpenAL (not exiting).
|
||||
hsd_list.resize(0);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
static void SndData_init(SndData* sd, va_list args)
|
||||
{
|
||||
// olsner: pass as int instead of bool for GCC compat.
|
||||
@ -924,15 +877,22 @@ static void SndData_init(SndData* sd, va_list args)
|
||||
|
||||
static void SndData_dtor(SndData* sd)
|
||||
{
|
||||
// in case reload failed and the following haven't been initialized yet
|
||||
// (freeing them would fail in that case, so bail)
|
||||
if(!sd->is_valid)
|
||||
return;
|
||||
|
||||
if(sd->is_stream)
|
||||
stream_close(&sd->s);
|
||||
else
|
||||
al_buf_free(sd->al_buf);
|
||||
|
||||
#ifdef OGG_HACK
|
||||
if(sd->o) ogg_release(sd->o);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void hsd_list_add(Handle hsd);
|
||||
|
||||
static int SndData_reload(SndData* sd, const char* fn, Handle hsd)
|
||||
{
|
||||
@ -1038,6 +998,21 @@ else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int SndData_validate(const SndData* sd)
|
||||
{
|
||||
if(sd->al_fmt == 0)
|
||||
return -100;
|
||||
if((uint)sd->al_freq > 100000) // suspicious
|
||||
return -101;
|
||||
if(sd->al_buf == 0)
|
||||
return -102;
|
||||
|
||||
if(sd->is_stream)
|
||||
RETURN_ERR(stream_validate(&sd->s));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// open and return a handle to a sound file's data.
|
||||
static Handle snd_data_load(const char* fn, bool is_stream)
|
||||
@ -1057,6 +1032,61 @@ static int snd_data_free(Handle& hsd)
|
||||
return h_free(hsd, H_SndData);
|
||||
}
|
||||
|
||||
//
|
||||
// SndData instance list: ensures all allocated since last al_shutdown
|
||||
// are freed when desired (they are cached => extra work needed).
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// rationale: all SndData objects (actually, their OpenAL buffers) must be
|
||||
// freed during al_shutdown, to prevent leaks. we can't rely on list*
|
||||
// to free all VSrc, and thereby their associated SndData objects -
|
||||
// completed sounds are no longer in the list.
|
||||
//
|
||||
// nor can we use the h_mgr_shutdown automatic leaked resource cleanup:
|
||||
// we need to be able to al_shutdown at runtime
|
||||
// (when resetting OpenAL, after e.g. device change).
|
||||
//
|
||||
// h_mgr support is required to forcibly close SndData objects,
|
||||
// since they are cached (kept open). it requires that this come after
|
||||
// H_TYPE_DEFINE(SndData), since h_force_free needs the handle type.
|
||||
//
|
||||
// we never need to delete single entries: hsd_list_free_all
|
||||
// (called by al_shutdown) frees each entry and clears the entire list.
|
||||
|
||||
typedef std::vector<Handle> Handles;
|
||||
static Handles hsd_list;
|
||||
|
||||
// called from SndData_reload; will later be removed via hsd_list_free_all.
|
||||
static void hsd_list_add(Handle hsd)
|
||||
{
|
||||
hsd_list.push_back(hsd);
|
||||
}
|
||||
|
||||
|
||||
// called by al_shutdown (at exit, or when reinitializing OpenAL).
|
||||
static void hsd_list_free_all()
|
||||
{
|
||||
for(Handles::iterator it = hsd_list.begin(); it != hsd_list.end(); ++it)
|
||||
{
|
||||
Handle& hsd = *it;
|
||||
|
||||
h_force_free(hsd, H_SndData);
|
||||
// ignore errors - if hsd was a stream, and its associated source
|
||||
// was active when al_shutdown was called, it will already have been
|
||||
// freed (list_free_all would free the source; it then releases
|
||||
// its SndData reference, which closes the instance because it's
|
||||
// RES_UNIQUE).
|
||||
}
|
||||
|
||||
// leave its memory intact, so we don't have to reallocate it later
|
||||
// if we are now reinitializing OpenAL (not exiting).
|
||||
hsd_list.resize(0);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// (need to convert ERR_EOF and ERR_AGAIN to legitimate return values -
|
||||
// for the caller, those aren't errors.)
|
||||
@ -1154,7 +1184,9 @@ enum VSrcFlags
|
||||
// this VSrc was added via list_add and needs to be removed with
|
||||
// list_remove in VSrc_dtor.
|
||||
// not set if load fails somehow (avoids list_remove "not found" error).
|
||||
VS_IN_LIST = 4
|
||||
VS_IN_LIST = 4,
|
||||
|
||||
VS_ALL_FLAGS = VS_EOF|VS_IS_STREAM|VS_IN_LIST
|
||||
};
|
||||
|
||||
struct VSrc
|
||||
@ -1168,7 +1200,7 @@ struct VSrc
|
||||
Handle hsd;
|
||||
|
||||
// controls vsrc_update behavior
|
||||
uint flags;
|
||||
uint flags; // VSrcFlags
|
||||
|
||||
// AL source properties (set via snd_set*)
|
||||
ALfloat pos[3];
|
||||
@ -1184,6 +1216,135 @@ struct VSrc
|
||||
|
||||
H_TYPE_DEFINE(VSrc);
|
||||
|
||||
static void VSrc_init(VSrc* vs, va_list args)
|
||||
{
|
||||
vs->flags = va_arg(args, uint);
|
||||
}
|
||||
|
||||
static void list_remove(VSrc* vs);
|
||||
static int vsrc_reclaim(VSrc* vs);
|
||||
|
||||
static void VSrc_dtor(VSrc* vs)
|
||||
{
|
||||
// only remove if added (not the case if load failed)
|
||||
if(vs->flags & VS_IN_LIST)
|
||||
{
|
||||
list_remove(vs);
|
||||
vs->flags &= ~VS_IN_LIST;
|
||||
}
|
||||
|
||||
// these are safe, even if reload (partially) failed:
|
||||
vsrc_reclaim(vs);
|
||||
(void)snd_data_free(vs->hsd);
|
||||
}
|
||||
|
||||
static int VSrc_reload(VSrc* vs, const char* fn, Handle hvs)
|
||||
{
|
||||
// cannot wait till play(), need to init here:
|
||||
// must load OpenAL so that snd_data_load can check for OGG extension.
|
||||
int err = snd_init();
|
||||
// .. don't complain if sound is disabled; fail silently.
|
||||
if(err == ERR_AGAIN)
|
||||
return err;
|
||||
// .. catch genuine errors during init.
|
||||
CHECK_ERR(err);
|
||||
|
||||
//
|
||||
// if extension is .txt, fn is a definition file containing the
|
||||
// sound file name and its gain; otherwise, read directly from fn
|
||||
// and assume default gain (1.0).
|
||||
//
|
||||
|
||||
const char* snd_fn; // actual sound file name
|
||||
std::string snd_fn_s;
|
||||
// extracted from stringstream;
|
||||
// declare here so that it doesn't go out of scope below.
|
||||
|
||||
const char* ext = strchr(fn, '.');
|
||||
if(ext && !stricmp(ext, ".txt"))
|
||||
{
|
||||
void* def_file;
|
||||
size_t def_size;
|
||||
RETURN_ERR(vfs_load(fn, def_file, def_size));
|
||||
std::istringstream def(std::string((char*)def_file, (int)def_size));
|
||||
mem_free(def_file);
|
||||
|
||||
float gain_percent;
|
||||
def >> snd_fn_s;
|
||||
def >> gain_percent;
|
||||
|
||||
snd_fn = snd_fn_s.c_str();
|
||||
vs->gain = gain_percent / 100.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
snd_fn = fn;
|
||||
vs->gain = 1.0f;
|
||||
}
|
||||
|
||||
// note: vs->gain can legitimately be > 1.0 - don't clamp.
|
||||
|
||||
vs->pitch = 1.0f;
|
||||
|
||||
vs->hvs = hvs;
|
||||
// needed so we can snd_free ourselves when done playing.
|
||||
|
||||
bool is_stream = (vs->flags & VS_IS_STREAM) != 0;
|
||||
vs->hsd = snd_data_load(snd_fn, is_stream);
|
||||
RETURN_ERR(vs->hsd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int VSrc_validate(const VSrc* vs)
|
||||
{
|
||||
if(vs->al_src == 0)
|
||||
return -2;
|
||||
if(vs->flags & ~VS_ALL_FLAGS)
|
||||
return -3;
|
||||
// no limitations on <pos>
|
||||
if(!(0.0f <= vs->gain && vs->gain <= 1.0f))
|
||||
return -4;
|
||||
if(!(0.0f < vs->pitch && vs->pitch <= 1.0f))
|
||||
return -5;
|
||||
if(*(u8*)&vs->loop > 1 || *(u8*)&vs->relative > 1)
|
||||
return -6;
|
||||
// <static_pri> and <cur_pri> have no invariant we could check.
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// open and return a handle to a sound instance.
|
||||
//
|
||||
// if <snd_fn> is a text file (extension ".txt"), it is assumed
|
||||
// to be a definition file containing the sound file name and
|
||||
// its gain (0.0 .. 1.0).
|
||||
// otherwise, <snd_fn> is taken to be the sound file name and
|
||||
// gain is set to the default of 1.0 (no attenuation).
|
||||
//
|
||||
// is_stream (default false) forces the sound to be opened as a stream:
|
||||
// opening is faster, it won't be kept in memory, but only one instance
|
||||
// can be open at a time.
|
||||
//
|
||||
// note: RES_UNIQUE forces each instance to get a new resource
|
||||
// (which is of course what we want).
|
||||
Handle snd_open(const char* snd_fn, bool is_stream)
|
||||
{
|
||||
uint flags = 0;
|
||||
if(is_stream)
|
||||
flags |= VS_IS_STREAM;
|
||||
return h_alloc(H_VSrc, snd_fn, RES_UNIQUE, flags);
|
||||
}
|
||||
|
||||
|
||||
// close the sound <hvs> and set hvs to 0. if it was playing,
|
||||
// it will be stopped. sounds are closed automatically when done
|
||||
// playing; this is provided for completeness only.
|
||||
int snd_free(Handle& hvs)
|
||||
{
|
||||
return h_free(hvs, H_VSrc);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// list of active sounds. used by voice management component,
|
||||
@ -1430,118 +1591,6 @@ static int vsrc_reclaim(VSrc* vs)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
static void VSrc_init(VSrc* vs, va_list args)
|
||||
{
|
||||
vs->flags = va_arg(args, uint);
|
||||
}
|
||||
|
||||
|
||||
static void VSrc_dtor(VSrc* vs)
|
||||
{
|
||||
// only remove if added (not the case if load failed)
|
||||
if(vs->flags & VS_IN_LIST)
|
||||
{
|
||||
list_remove(vs);
|
||||
vs->flags &= ~VS_IN_LIST;
|
||||
}
|
||||
|
||||
vsrc_reclaim(vs);
|
||||
snd_data_free(vs->hsd);
|
||||
}
|
||||
|
||||
|
||||
static int VSrc_reload(VSrc* vs, const char* fn, Handle hvs)
|
||||
{
|
||||
// cannot wait till play(), need to init here:
|
||||
// must load OpenAL so that snd_data_load can check for OGG extension.
|
||||
int err = snd_init();
|
||||
// .. don't complain if sound is disabled; fail silently.
|
||||
if(err == ERR_AGAIN)
|
||||
return err;
|
||||
// .. catch genuine errors during init.
|
||||
CHECK_ERR(err);
|
||||
|
||||
//
|
||||
// if extension is .txt, fn is a definition file containing the
|
||||
// sound file name and its gain; otherwise, read directly from fn
|
||||
// and assume default gain (1.0).
|
||||
//
|
||||
|
||||
const char* snd_fn; // actual sound file name
|
||||
std::string snd_fn_s;
|
||||
// extracted from stringstream;
|
||||
// declare here so that it doesn't go out of scope below.
|
||||
|
||||
const char* ext = strchr(fn, '.');
|
||||
if(ext && !stricmp(ext, ".txt"))
|
||||
{
|
||||
void* def_file;
|
||||
size_t def_size;
|
||||
RETURN_ERR(vfs_load(fn, def_file, def_size));
|
||||
std::istringstream def(std::string((char*)def_file, (int)def_size));
|
||||
mem_free(def_file);
|
||||
|
||||
float gain_percent;
|
||||
def >> snd_fn_s;
|
||||
def >> gain_percent;
|
||||
|
||||
snd_fn = snd_fn_s.c_str();
|
||||
vs->gain = gain_percent / 100.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
snd_fn = fn;
|
||||
vs->gain = 1.0f;
|
||||
}
|
||||
|
||||
// note: vs->gain can legitimately be > 1.0 - don't clamp.
|
||||
|
||||
vs->pitch = 1.0f;
|
||||
|
||||
vs->hvs = hvs;
|
||||
// needed so we can snd_free ourselves when done playing.
|
||||
|
||||
bool is_stream = (vs->flags & VS_IS_STREAM) != 0;
|
||||
vs->hsd = snd_data_load(snd_fn, is_stream);
|
||||
CHECK_ERR(vs->hsd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// open and return a handle to a sound instance.
|
||||
//
|
||||
// if <snd_fn> is a text file (extension ".txt"), it is assumed
|
||||
// to be a definition file containing the sound file name and
|
||||
// its gain (0.0 .. 1.0).
|
||||
// otherwise, <snd_fn> is taken to be the sound file name and
|
||||
// gain is set to the default of 1.0 (no attenuation).
|
||||
//
|
||||
// is_stream (default false) forces the sound to be opened as a stream:
|
||||
// opening is faster, it won't be kept in memory, but only one instance
|
||||
// can be open at a time.
|
||||
//
|
||||
// note: RES_UNIQUE forces each instance to get a new resource
|
||||
// (which is of course what we want).
|
||||
Handle snd_open(const char* snd_fn, bool is_stream)
|
||||
{
|
||||
uint flags = 0;
|
||||
if(is_stream)
|
||||
flags |= VS_IS_STREAM;
|
||||
return h_alloc(H_VSrc, snd_fn, RES_UNIQUE, flags);
|
||||
}
|
||||
|
||||
|
||||
// close the sound <hvs> and set hvs to 0. if it was playing,
|
||||
// it will be stopped. sounds are closed automatically when done
|
||||
// playing; this is provided for completeness only.
|
||||
int snd_free(Handle& hvs)
|
||||
{
|
||||
return h_free(hvs, H_VSrc);
|
||||
}
|
||||
|
||||
|
||||
// request the sound <hs> be played. once done playing, the sound is
|
||||
// automatically closed (allows fire-and-forget play code).
|
||||
// if no hardware voice is available, this sound may not be played at all,
|
||||
|
Loading…
Reference in New Issue
Block a user