1
1
forked from 0ad/0ad

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:
janwas 2005-10-12 04:35:01 +00:00
parent c498468fd0
commit e9864faa97
14 changed files with 867 additions and 518 deletions

View File

@ -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);
}

View File

@ -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);
//

View File

@ -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;
}

View File

@ -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);

View File

@ -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)

View File

@ -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);
//

View File

@ -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.

View File

@ -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;
}
//----------------------------------------------------------------------------

View File

@ -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)
{

View File

@ -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();
}

View File

@ -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 */\
};\

View File

@ -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;
}

View File

@ -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

View File

@ -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,