HomeServer/lualib-src/lua-sharedata.c

795 lines
16 KiB
C
Raw Permalink Normal View History

2024-11-20 15:41:09 +08:00
#define LUA_LIB
#include <lua.h>
#include <lauxlib.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "atomic.h"
#define KEYTYPE_INTEGER 0
#define KEYTYPE_STRING 1
#define VALUETYPE_NIL 0
#define VALUETYPE_REAL 1
#define VALUETYPE_STRING 2
#define VALUETYPE_BOOLEAN 3
#define VALUETYPE_TABLE 4
#define VALUETYPE_INTEGER 5
struct table;
union value {
lua_Number n;
lua_Integer d;
struct table * tbl;
int string;
int boolean;
};
struct node {
union value v;
int key; // integer key or index of string table
int next; // next slot index
uint32_t keyhash;
uint8_t keytype; // key type must be integer or string
uint8_t valuetype; // value type can be number/string/boolean/table
uint8_t nocolliding; // 0 means colliding slot
};
struct state {
int dirty;
int ref;
struct table * root;
};
struct table {
int sizearray;
int sizehash;
uint8_t *arraytype;
union value * array;
struct node * hash;
lua_State * L;
};
struct context {
lua_State * L;
struct table * tbl;
int string_index;
};
struct ctrl {
struct table * root;
struct table * update;
};
static int
countsize(lua_State *L, int sizearray) {
int n = 0;
lua_pushnil(L);
while (lua_next(L, 1) != 0) {
int type = lua_type(L, -2);
++n;
if (type == LUA_TNUMBER) {
if (!lua_isinteger(L, -2)) {
luaL_error(L, "Invalid key %f", lua_tonumber(L, -2));
}
lua_Integer nkey = lua_tointeger(L, -2);
if (nkey > 0 && nkey <= sizearray) {
--n;
}
} else if (type != LUA_TSTRING && type != LUA_TTABLE) {
luaL_error(L, "Invalid key type %s", lua_typename(L, type));
}
lua_pop(L, 1);
}
return n;
}
static uint32_t
calchash(const char * str, size_t l) {
uint32_t h = (uint32_t)l;
size_t l1;
size_t step = (l >> 5) + 1;
for (l1 = l; l1 >= step; l1 -= step) {
h = h ^ ((h<<5) + (h>>2) + (uint8_t)(str[l1 - 1]));
}
return h;
}
static int
stringindex(struct context *ctx, const char * str, size_t sz) {
lua_State *L = ctx->L;
lua_pushlstring(L, str, sz);
lua_pushvalue(L, -1);
lua_rawget(L, 1);
int index;
// stringmap(1) str index
if (lua_isnil(L, -1)) {
index = ++ctx->string_index;
lua_pop(L, 1);
lua_pushinteger(L, index);
lua_rawset(L, 1);
} else {
index = lua_tointeger(L, -1);
lua_pop(L, 2);
}
return index;
}
static int convtable(lua_State *L);
static void
setvalue(struct context * ctx, lua_State *L, int index, struct node *n) {
int vt = lua_type(L, index);
switch(vt) {
case LUA_TNIL:
n->valuetype = VALUETYPE_NIL;
break;
case LUA_TNUMBER:
if (lua_isinteger(L, index)) {
n->v.d = lua_tointeger(L, index);
n->valuetype = VALUETYPE_INTEGER;
} else {
n->v.n = lua_tonumber(L, index);
n->valuetype = VALUETYPE_REAL;
}
break;
case LUA_TSTRING: {
size_t sz = 0;
const char * str = lua_tolstring(L, index, &sz);
n->v.string = stringindex(ctx, str, sz);
n->valuetype = VALUETYPE_STRING;
break;
}
case LUA_TBOOLEAN:
n->v.boolean = lua_toboolean(L, index);
n->valuetype = VALUETYPE_BOOLEAN;
break;
case LUA_TTABLE: {
struct table *tbl = ctx->tbl;
ctx->tbl = (struct table *)malloc(sizeof(struct table));
if (ctx->tbl == NULL) {
ctx->tbl = tbl;
luaL_error(L, "memory error");
// never get here
}
memset(ctx->tbl, 0, sizeof(struct table));
int absidx = lua_absindex(L, index);
lua_pushcfunction(L, convtable);
lua_pushvalue(L, absidx);
lua_pushlightuserdata(L, ctx);
lua_call(L, 2, 0);
n->v.tbl = ctx->tbl;
n->valuetype = VALUETYPE_TABLE;
ctx->tbl = tbl;
break;
}
default:
luaL_error(L, "Unsupport value type %s", lua_typename(L, vt));
break;
}
}
static void
setarray(struct context *ctx, lua_State *L, int index, int key) {
struct node n;
setvalue(ctx, L, index, &n);
struct table *tbl = ctx->tbl;
--key; // base 0
tbl->arraytype[key] = n.valuetype;
tbl->array[key] = n.v;
}
static int
ishashkey(struct context * ctx, lua_State *L, int index, int *key, uint32_t *keyhash, int *keytype) {
int sizearray = ctx->tbl->sizearray;
int kt = lua_type(L, index);
if (kt == LUA_TNUMBER) {
*key = lua_tointeger(L, index);
if (*key > 0 && *key <= sizearray) {
return 0;
}
*keyhash = (uint32_t)*key;
*keytype = KEYTYPE_INTEGER;
} else {
size_t sz = 0;
const char * s = lua_tolstring(L, index, &sz);
*keyhash = calchash(s, sz);
*key = stringindex(ctx, s, sz);
*keytype = KEYTYPE_STRING;
}
return 1;
}
static void
fillnocolliding(lua_State *L, struct context *ctx) {
struct table * tbl = ctx->tbl;
lua_pushnil(L);
while (lua_next(L, 1) != 0) {
int key;
int keytype;
uint32_t keyhash;
if (!ishashkey(ctx, L, -2, &key, &keyhash, &keytype)) {
setarray(ctx, L, -1, key);
} else {
struct node * n = &tbl->hash[keyhash % tbl->sizehash];
if (n->valuetype == VALUETYPE_NIL) {
n->key = key;
n->keytype = keytype;
n->keyhash = keyhash;
n->next = -1;
n->nocolliding = 1;
setvalue(ctx, L, -1, n); // set n->v , n->valuetype
}
}
lua_pop(L,1);
}
}
static void
fillcolliding(lua_State *L, struct context *ctx) {
struct table * tbl = ctx->tbl;
int sizehash = tbl->sizehash;
int emptyslot = 0;
int i;
lua_pushnil(L);
while (lua_next(L, 1) != 0) {
int key;
int keytype;
uint32_t keyhash;
if (ishashkey(ctx, L, -2, &key, &keyhash, &keytype)) {
struct node * mainpos = &tbl->hash[keyhash % tbl->sizehash];
if (!(mainpos->keytype == keytype && mainpos->key == key)) {
// the key has not insert
struct node * n = NULL;
for (i=emptyslot;i<sizehash;i++) {
if (tbl->hash[i].valuetype == VALUETYPE_NIL) {
n = &tbl->hash[i];
emptyslot = i + 1;
break;
}
}
assert(n);
n->next = mainpos->next;
mainpos->next = n - tbl->hash;
mainpos->nocolliding = 0;
n->key = key;
n->keytype = keytype;
n->keyhash = keyhash;
n->nocolliding = 0;
setvalue(ctx, L, -1, n); // set n->v , n->valuetype
}
}
lua_pop(L,1);
}
}
// table need convert
// struct context * ctx
static int
convtable(lua_State *L) {
int i;
struct context *ctx = lua_touserdata(L,2);
struct table *tbl = ctx->tbl;
tbl->L = ctx->L;
int sizearray = lua_rawlen(L, 1);
if (sizearray) {
tbl->arraytype = (uint8_t *)malloc(sizearray * sizeof(uint8_t));
if (tbl->arraytype == NULL) {
goto memerror;
}
for (i=0;i<sizearray;i++) {
tbl->arraytype[i] = VALUETYPE_NIL;
}
tbl->array = (union value *)malloc(sizearray * sizeof(union value));
if (tbl->array == NULL) {
goto memerror;
}
tbl->sizearray = sizearray;
}
int sizehash = countsize(L, sizearray);
if (sizehash) {
tbl->hash = (struct node *)malloc(sizehash * sizeof(struct node));
if (tbl->hash == NULL) {
goto memerror;
}
for (i=0;i<sizehash;i++) {
tbl->hash[i].valuetype = VALUETYPE_NIL;
tbl->hash[i].nocolliding = 0;
}
tbl->sizehash = sizehash;
fillnocolliding(L, ctx);
fillcolliding(L, ctx);
} else {
int i;
for (i=1;i<=sizearray;i++) {
lua_rawgeti(L, 1, i);
setarray(ctx, L, -1, i);
lua_pop(L,1);
}
}
return 0;
memerror:
return luaL_error(L, "memory error");
}
static void
delete_tbl(struct table *tbl) {
int i;
for (i=0;i<tbl->sizearray;i++) {
if (tbl->arraytype[i] == VALUETYPE_TABLE) {
delete_tbl(tbl->array[i].tbl);
}
}
for (i=0;i<tbl->sizehash;i++) {
if (tbl->hash[i].valuetype == VALUETYPE_TABLE) {
delete_tbl(tbl->hash[i].v.tbl);
}
}
free(tbl->arraytype);
free(tbl->array);
free(tbl->hash);
free(tbl);
}
static int
pconv(lua_State *L) {
struct context *ctx = lua_touserdata(L,1);
lua_State * pL = lua_touserdata(L, 2);
int ret;
lua_settop(L, 0);
// init L (may throw memory error)
// create a table for string map
lua_newtable(L);
lua_pushcfunction(pL, convtable);
lua_pushvalue(pL,1);
lua_pushlightuserdata(pL, ctx);
ret = lua_pcall(pL, 2, 0, 0);
if (ret != LUA_OK) {
size_t sz = 0;
const char * error = lua_tolstring(pL, -1, &sz);
lua_pushlstring(L, error, sz);
lua_error(L);
// never get here
}
luaL_checkstack(L, ctx->string_index + 3, NULL);
lua_settop(L,1);
return 1;
}
static void
convert_stringmap(struct context *ctx, struct table *tbl) {
lua_State *L = ctx->L;
lua_checkstack(L, ctx->string_index + LUA_MINSTACK);
lua_settop(L, ctx->string_index + 1);
lua_pushvalue(L, 1);
struct state * s = lua_newuserdatauv(L, sizeof(*s), 1);
s->dirty = 0;
s->ref = 0;
s->root = tbl;
lua_replace(L, 1);
lua_replace(L, -2);
lua_pushnil(L);
// ... stringmap nil
while (lua_next(L, -2) != 0) {
int idx = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_pushvalue(L, -1);
lua_replace(L, idx);
}
lua_pop(L, 1);
lua_gc(L, LUA_GCCOLLECT, 0);
}
static int
lnewconf(lua_State *L) {
int ret;
struct context ctx;
struct table * tbl = NULL;
luaL_checktype(L,1,LUA_TTABLE);
ctx.L = luaL_newstate();
ctx.tbl = NULL;
ctx.string_index = 1; // 1 reserved for dirty flag
if (ctx.L == NULL) {
lua_pushliteral(L, "memory error");
goto error;
}
tbl = (struct table *)malloc(sizeof(struct table));
if (tbl == NULL) {
// lua_pushliteral may fail because of memory error, close first.
lua_close(ctx.L);
ctx.L = NULL;
lua_pushliteral(L, "memory error");
goto error;
}
memset(tbl, 0, sizeof(struct table));
ctx.tbl = tbl;
lua_pushcfunction(ctx.L, pconv);
lua_pushlightuserdata(ctx.L , &ctx);
lua_pushlightuserdata(ctx.L , L);
ret = lua_pcall(ctx.L, 2, 1, 0);
if (ret != LUA_OK) {
size_t sz = 0;
const char * error = lua_tolstring(ctx.L, -1, &sz);
lua_pushlstring(L, error, sz);
goto error;
}
convert_stringmap(&ctx, tbl);
lua_pushlightuserdata(L, tbl);
return 1;
error:
if (ctx.L) {
lua_close(ctx.L);
}
if (tbl) {
delete_tbl(tbl);
}
lua_error(L);
return -1;
}
static struct table *
get_table(lua_State *L, int index) {
struct table *tbl = lua_touserdata(L,index);
if (tbl == NULL) {
luaL_error(L, "Need a conf object");
}
return tbl;
}
static int
ldeleteconf(lua_State *L) {
struct table *tbl = get_table(L,1);
lua_close(tbl->L);
delete_tbl(tbl);
return 0;
}
static void
pushvalue(lua_State *L, lua_State *sL, uint8_t vt, union value *v) {
switch(vt) {
case VALUETYPE_REAL:
lua_pushnumber(L, v->n);
break;
case VALUETYPE_INTEGER:
lua_pushinteger(L, v->d);
break;
case VALUETYPE_STRING: {
size_t sz = 0;
const char *str = lua_tolstring(sL, v->string, &sz);
lua_pushlstring(L, str, sz);
break;
}
case VALUETYPE_BOOLEAN:
lua_pushboolean(L, v->boolean);
break;
case VALUETYPE_TABLE:
lua_pushlightuserdata(L, v->tbl);
break;
default:
lua_pushnil(L);
break;
}
}
static struct node *
lookup_key(struct table *tbl, uint32_t keyhash, int key, int keytype, const char *str, size_t sz) {
if (tbl->sizehash == 0)
return NULL;
struct node *n = &tbl->hash[keyhash % tbl->sizehash];
if (keyhash != n->keyhash && n->nocolliding)
return NULL;
for (;;) {
if (keyhash == n->keyhash) {
if (n->keytype == KEYTYPE_INTEGER) {
if (keytype == KEYTYPE_INTEGER && n->key == key) {
return n;
}
} else {
// n->keytype == KEYTYPE_STRING
if (keytype == KEYTYPE_STRING) {
size_t sz2 = 0;
const char * str2 = lua_tolstring(tbl->L, n->key, &sz2);
if (sz == sz2 && memcmp(str,str2,sz) == 0) {
return n;
}
}
}
}
if (n->next < 0) {
return NULL;
}
n = &tbl->hash[n->next];
}
}
static int
lindexconf(lua_State *L) {
struct table *tbl = get_table(L,1);
int kt = lua_type(L,2);
uint32_t keyhash;
int key = 0;
int keytype;
size_t sz = 0;
const char * str = NULL;
if (kt == LUA_TNUMBER) {
if (!lua_isinteger(L, 2)) {
return luaL_error(L, "Invalid key %f", lua_tonumber(L, 2));
}
key = (int)lua_tointeger(L, 2);
if (key > 0 && key <= tbl->sizearray) {
--key;
pushvalue(L, tbl->L, tbl->arraytype[key], &tbl->array[key]);
return 1;
}
keytype = KEYTYPE_INTEGER;
keyhash = (uint32_t)key;
} else {
str = luaL_checklstring(L, 2, &sz);
keyhash = calchash(str, sz);
keytype = KEYTYPE_STRING;
}
struct node *n = lookup_key(tbl, keyhash, key, keytype, str, sz);
if (n) {
pushvalue(L, tbl->L, n->valuetype, &n->v);
return 1;
} else {
return 0;
}
}
static void
pushkey(lua_State *L, lua_State *sL, struct node *n) {
if (n->keytype == KEYTYPE_INTEGER) {
lua_pushinteger(L, n->key);
} else {
size_t sz = 0;
const char * str = lua_tolstring(sL, n->key, &sz);
lua_pushlstring(L, str, sz);
}
}
static int
pushfirsthash(lua_State *L, struct table * tbl) {
if (tbl->sizehash) {
pushkey(L, tbl->L, &tbl->hash[0]);
return 1;
} else {
return 0;
}
}
static int
lnextkey(lua_State *L) {
struct table *tbl = get_table(L,1);
if (lua_isnoneornil(L,2)) {
if (tbl->sizearray > 0) {
int i;
for (i=0;i<tbl->sizearray;i++) {
if (tbl->arraytype[i] != VALUETYPE_NIL) {
lua_pushinteger(L, i+1);
return 1;
}
}
}
return pushfirsthash(L, tbl);
}
int kt = lua_type(L,2);
uint32_t keyhash;
int key = 0;
int keytype;
size_t sz=0;
const char *str = NULL;
int sizearray = tbl->sizearray;
if (kt == LUA_TNUMBER) {
if (!lua_isinteger(L, 2)) {
return 0;
}
key = (int)lua_tointeger(L, 2);
if (key > 0 && key <= sizearray) {
lua_Integer i;
for (i=key;i<sizearray;i++) {
if (tbl->arraytype[i] != VALUETYPE_NIL) {
lua_pushinteger(L, i+1);
return 1;
}
}
return pushfirsthash(L, tbl);
}
keyhash = (uint32_t)key;
keytype = KEYTYPE_INTEGER;
} else {
str = luaL_checklstring(L, 2, &sz);
keyhash = calchash(str, sz);
keytype = KEYTYPE_STRING;
}
struct node *n = lookup_key(tbl, keyhash, key, keytype, str, sz);
if (n) {
++n;
int index = n-tbl->hash;
if (index == tbl->sizehash) {
return 0;
}
pushkey(L, tbl->L, n);
return 1;
} else {
return 0;
}
}
static int
llen(lua_State *L) {
struct table *tbl = get_table(L,1);
lua_pushinteger(L, tbl->sizearray);
return 1;
}
static int
lhashlen(lua_State *L) {
struct table *tbl = get_table(L,1);
lua_pushinteger(L, tbl->sizehash);
return 1;
}
static int
releaseobj(lua_State *L) {
struct ctrl *c = lua_touserdata(L, 1);
struct table *tbl = c->root;
struct state *s = lua_touserdata(tbl->L, 1);
ATOM_DEC(&s->ref);
c->root = NULL;
c->update = NULL;
return 0;
}
static int
lboxconf(lua_State *L) {
struct table * tbl = get_table(L,1);
struct state * s = lua_touserdata(tbl->L, 1);
ATOM_INC(&s->ref);
struct ctrl * c = lua_newuserdatauv(L, sizeof(*c), 1);
c->root = tbl;
c->update = NULL;
if (luaL_newmetatable(L, "confctrl")) {
lua_pushcfunction(L, releaseobj);
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
return 1;
}
static int
lmarkdirty(lua_State *L) {
struct table *tbl = get_table(L,1);
struct state * s = lua_touserdata(tbl->L, 1);
s->dirty = 1;
return 0;
}
static int
lisdirty(lua_State *L) {
struct table *tbl = get_table(L,1);
struct state * s = lua_touserdata(tbl->L, 1);
int d = s->dirty;
lua_pushboolean(L, d);
return 1;
}
static int
lgetref(lua_State *L) {
struct table *tbl = get_table(L,1);
struct state * s = lua_touserdata(tbl->L, 1);
lua_pushinteger(L , s->ref);
return 1;
}
static int
lincref(lua_State *L) {
struct table *tbl = get_table(L,1);
struct state * s = lua_touserdata(tbl->L, 1);
int ref = ATOM_INC(&s->ref);
lua_pushinteger(L , ref);
return 1;
}
static int
ldecref(lua_State *L) {
struct table *tbl = get_table(L,1);
struct state * s = lua_touserdata(tbl->L, 1);
int ref = ATOM_DEC(&s->ref);
lua_pushinteger(L , ref);
return 1;
}
static int
lneedupdate(lua_State *L) {
struct ctrl * c = lua_touserdata(L, 1);
if (c->update) {
lua_pushlightuserdata(L, c->update);
lua_getiuservalue(L, 1, 1);
return 2;
}
return 0;
}
static int
lupdate(lua_State *L) {
luaL_checktype(L, 1, LUA_TUSERDATA);
luaL_checktype(L, 2, LUA_TLIGHTUSERDATA);
luaL_checktype(L, 3, LUA_TTABLE);
struct ctrl * c= lua_touserdata(L, 1);
struct table *n = lua_touserdata(L, 2);
if (c->root == n) {
return luaL_error(L, "You should update a new object");
}
lua_settop(L, 3);
lua_setiuservalue(L, 1, 1);
c->update = n;
return 0;
}
LUAMOD_API int
luaopen_skynet_sharedata_core(lua_State *L) {
luaL_Reg l[] = {
// used by host
{ "new", lnewconf },
{ "delete", ldeleteconf },
{ "markdirty", lmarkdirty },
{ "getref", lgetref },
{ "incref", lincref },
{ "decref", ldecref },
// used by client
{ "box", lboxconf },
{ "index", lindexconf },
{ "nextkey", lnextkey },
{ "len", llen },
{ "hashlen", lhashlen },
{ "isdirty", lisdirty },
{ "needupdate", lneedupdate },
{ "update", lupdate },
{ NULL, NULL },
};
luaL_checkversion(L);
luaL_newlib(L, l);
return 1;
}