You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
9.3 KiB
C
316 lines
9.3 KiB
C
/* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2021 Stefano Trettel
|
|
*
|
|
* Software repository: MoonCCD, https://github.com/stetre/moonccd
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "tree.h"
|
|
#include "udata.h"
|
|
#include "lua.h"
|
|
#include "lauxlib.h"
|
|
#include "lualib.h"
|
|
|
|
struct moonccd_udata_s
|
|
{
|
|
RB_ENTRY(moonccd_udata_s)
|
|
entry;
|
|
uint64_t id; /* object id (search key) */
|
|
/* references on the Lua registry */
|
|
int ref; /* the correspoding userdata */
|
|
void *mem; /* userdata memory area allocated and released by Lua */
|
|
const char *mt;
|
|
};
|
|
|
|
#define UNEXPECTED_ERROR "unexpected error (%s, %d)", __FILE__, __LINE__
|
|
|
|
static int cmp(udata_t *udata1, udata_t *udata2) /* the compare function */
|
|
{
|
|
return (udata1->id < udata2->id ? -1 : udata1->id > udata2->id);
|
|
}
|
|
|
|
static RB_HEAD(udatatree_s, udata_s) Head = RB_INITIALIZER(&Head);
|
|
|
|
RB_PROTOTYPE_STATIC(udatatree_s, udata_s, entry, cmp)
|
|
RB_GENERATE_STATIC(udatatree_s, udata_s, entry, cmp)
|
|
|
|
static udata_t *udata_remove(udata_t *udata)
|
|
{
|
|
return RB_REMOVE(udatatree_s, &Head, udata);
|
|
}
|
|
static udata_t *udata_insert(udata_t *udata)
|
|
{
|
|
return RB_INSERT(udatatree_s, &Head, udata);
|
|
}
|
|
static udata_t *udata_search(uint64_t id)
|
|
{
|
|
udata_t tmp;
|
|
tmp.id = id;
|
|
return RB_FIND(udatatree_s, &Head, &tmp);
|
|
}
|
|
static udata_t *udata_first(uint64_t id)
|
|
{
|
|
udata_t tmp;
|
|
tmp.id = id;
|
|
return RB_NFIND(udatatree_s, &Head, &tmp);
|
|
}
|
|
#if 0
|
|
static udata_t *udata_next(udata_t *udata)
|
|
{ return RB_NEXT(udatatree_s, &Head, udata); }
|
|
static udata_t *udata_prev(udata_t *udata)
|
|
{ return RB_PREV(udatatree_s, &Head, udata); }
|
|
static udata_t *udata_min(void)
|
|
{ return RB_MIN(udatatree_s, &Head); }
|
|
static udata_t *udata_max(void)
|
|
{ return RB_MAX(udatatree_s, &Head); }
|
|
static udata_t *udata_root(void)
|
|
{ return RB_ROOT(&Head); }
|
|
#endif
|
|
|
|
void *udata_new(lua_State *L, size_t size, uint64_t id_, const char *mt)
|
|
/* Creates a new Lua userdata, optionally sets its metatable to mt (if != NULL),
|
|
* associates the userdata with the passed id and pushes the userdata on the stack.
|
|
*
|
|
* The userdata can be subsquently pushed on the Lua stack with udata_push(L, id).
|
|
* (This is useful to bind C/C++ objects to Lua userdata).
|
|
*
|
|
* If id=0, the pointer to the memory area allocated by Lua is used as identifier
|
|
* (this function returnes it).
|
|
*/
|
|
{
|
|
udata_t *udata;
|
|
if ((udata = (udata_t *)Malloc(L, sizeof(udata_t))) == NULL)
|
|
{
|
|
luaL_error(L, "cannot allocate memory");
|
|
return NULL;
|
|
}
|
|
memset(udata, 0, sizeof(udata_t));
|
|
udata->mem = lua_newuserdata(L, size);
|
|
if (!udata->mem)
|
|
{
|
|
Free(L, udata);
|
|
luaL_error(L, "lua_newuserdata error");
|
|
return NULL;
|
|
}
|
|
udata->id = id_ != 0 ? id_ : (uint64_t)(uintptr_t)(udata->mem);
|
|
if (udata_search(udata->id))
|
|
{
|
|
Free(L, udata);
|
|
luaL_error(L, "duplicated object %I", id_);
|
|
return NULL;
|
|
}
|
|
/* create a reference for later push's */
|
|
lua_pushvalue(L, -1); /* the newly created userdata */
|
|
udata->ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
udata_insert(udata);
|
|
if (mt)
|
|
{
|
|
udata->mt = mt;
|
|
luaL_getmetatable(L, mt);
|
|
lua_setmetatable(L, -2);
|
|
}
|
|
return udata->mem;
|
|
}
|
|
|
|
void *udata_mem(uint64_t id)
|
|
{
|
|
udata_t *udata = udata_search(id);
|
|
return udata ? udata->mem : NULL;
|
|
}
|
|
|
|
int udata_unref(lua_State *L, uint64_t id)
|
|
/* unreference udata so that it will be garbage collected */
|
|
{
|
|
// printf("unref object %lu\n", id);
|
|
udata_t *udata = udata_search(id);
|
|
if (!udata)
|
|
return luaL_error(L, "invalid object identifier %p", id);
|
|
if (udata->ref != LUA_NOREF)
|
|
{
|
|
luaL_unref(L, LUA_REGISTRYINDEX, udata->ref);
|
|
udata->ref = LUA_NOREF;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int udata_free(lua_State *L, uint64_t id)
|
|
/* this should be called in the __gc metamethod
|
|
*/
|
|
{
|
|
udata_t *udata = udata_search(id);
|
|
// printf("free object %lu\n", id);
|
|
if (!udata)
|
|
return luaL_error(L, "invalid object identifier %p", id);
|
|
/* release all references */
|
|
if (udata->ref != LUA_NOREF)
|
|
luaL_unref(L, LUA_REGISTRYINDEX, udata->ref);
|
|
udata_remove(udata);
|
|
Free(L, udata);
|
|
/* mem is released by Lua at garbage collection */
|
|
return 0;
|
|
}
|
|
|
|
int udata_push(lua_State *L, uint64_t id)
|
|
{
|
|
udata_t *udata = udata_search(id);
|
|
if (!udata)
|
|
return luaL_error(L, "invalid object identifier %p", id);
|
|
if (udata->ref == LUA_NOREF)
|
|
return luaL_error(L, "unreferenced object");
|
|
if (lua_rawgeti(L, LUA_REGISTRYINDEX, udata->ref) != LUA_TUSERDATA)
|
|
return luaL_error(L, UNEXPECTED_ERROR);
|
|
return 1; /* one value pushed */
|
|
}
|
|
|
|
void udata_free_all(lua_State *L)
|
|
/* free all without unreferencing (for atexit()) */
|
|
{
|
|
udata_t *udata;
|
|
while ((udata = udata_first(0)))
|
|
{
|
|
udata_remove(udata);
|
|
Free(L, udata);
|
|
}
|
|
}
|
|
|
|
int udata_scan(lua_State *L, const char *mt,
|
|
void *info, int (*func)(lua_State *L, const void *mem, const char *mt, const void *info))
|
|
/* scans the udata database, and calls the func callback for every 'mt' object found
|
|
* (the object may be deleted in the callback).
|
|
* func must return 0 to continue the scan, !=0 to interrupt it.
|
|
* returns 1 if interrupted, 0 otherwise
|
|
*/
|
|
{
|
|
int stop = 0;
|
|
uint64_t id = 0;
|
|
udata_t *udata;
|
|
while ((udata = udata_first(id)))
|
|
{
|
|
id = udata->id + 1;
|
|
if (mt == udata->mt)
|
|
{
|
|
stop = func(L, (const void *)(udata->mem), mt, info);
|
|
if (stop)
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int is_subclass(lua_State *L, int arg, int mt_index)
|
|
{
|
|
int ok;
|
|
if (luaL_getmetafield(L, arg, "__index") == LUA_TNIL)
|
|
return 0;
|
|
if (lua_rawequal(L, mt_index, lua_gettop(L)))
|
|
{
|
|
lua_pop(L, 1);
|
|
return 1;
|
|
}
|
|
ok = is_subclass(L, lua_gettop(L), mt_index);
|
|
lua_pop(L, 1);
|
|
return ok;
|
|
}
|
|
|
|
void *udata_test(lua_State *L, int arg, const char *mt)
|
|
/* same as luaL_testudata(), but succeeds also if the userdata has not
|
|
* mt as metatable but as ancestor
|
|
*/
|
|
{
|
|
void *mem;
|
|
int ok;
|
|
if ((mem = luaL_testudata(L, arg, mt)))
|
|
return mem;
|
|
|
|
if ((mem = lua_touserdata(L, arg)) == NULL)
|
|
return NULL;
|
|
|
|
if (luaL_getmetatable(L, mt) != LUA_TTABLE)
|
|
{
|
|
luaL_error(L, "cannot find metatable '%s'", mt);
|
|
return NULL;
|
|
}
|
|
ok = is_subclass(L, arg, lua_gettop(L));
|
|
lua_pop(L, 1);
|
|
return ok ? mem : NULL;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------*
|
|
| newmetatable for 'class' userdata |
|
|
*------------------------------------------------------------------------------*/
|
|
|
|
int udata_define(lua_State *L, const char *mt, const luaL_Reg *methods, const luaL_Reg *metamethods)
|
|
{
|
|
/* create the metatable */
|
|
if (!luaL_newmetatable(L, mt))
|
|
return luaL_error(L, "cannot create metatable '%s'", mt);
|
|
lua_pushvalue(L, -1);
|
|
lua_setfield(L, -2, "__index");
|
|
if (metamethods)
|
|
/* add metamethods */
|
|
luaL_setfuncs(L, metamethods, 0);
|
|
if (methods)
|
|
{
|
|
/* add methods */
|
|
luaL_getsubtable(L, -1, "__index");
|
|
luaL_setfuncs(L, methods, 0);
|
|
lua_pop(L, 1);
|
|
}
|
|
lua_pop(L, 1);
|
|
return 0;
|
|
}
|
|
|
|
int udata_inherit(lua_State *L, const char *sub, const char *super)
|
|
/* Sets metatable(sub).__index = metatable(super)
|
|
*
|
|
* This way, if one accesses a field/method of a 'sub' object which is not defined in
|
|
* the 'sub' metatable, Lua searches for it in the 'super' metatable (i.e., the 'sub'
|
|
* type inherits from 'super').
|
|
*/
|
|
{
|
|
if (luaL_getmetatable(L, sub) != LUA_TTABLE)
|
|
return luaL_error(L, "cannot find metatable '%s'", sub);
|
|
luaL_getsubtable(L, -1, "__index");
|
|
if (luaL_getmetatable(L, super) != LUA_TTABLE)
|
|
return luaL_error(L, "cannot find metatable '%s'", super);
|
|
lua_setmetatable(L, -2);
|
|
lua_pop(L, 2);
|
|
return 0;
|
|
}
|
|
|
|
int udata_addmethods(lua_State *L, const char *mt, const luaL_Reg *methods)
|
|
/* Adds methods to the metatable mt */
|
|
{
|
|
if (luaL_getmetatable(L, mt) != LUA_TTABLE)
|
|
return luaL_error(L, "cannot find metatable '%s'", mt);
|
|
if (methods)
|
|
{
|
|
/* add methods */
|
|
luaL_getsubtable(L, -1, "__index");
|
|
luaL_setfuncs(L, methods, 0);
|
|
lua_pop(L, 1);
|
|
}
|
|
lua_pop(L, 1);
|
|
return 0;
|
|
}
|