From b582805df069fab5e5f4a8fbd4056d5c80afb62e Mon Sep 17 00:00:00 2001 From: xiaojin Date: Thu, 16 Sep 2021 14:22:43 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=B3chore(=E5=BA=93):=20=E8=B0=83?= =?UTF-8?q?=E6=95=B4submodule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitmodules | 3 - code/framework/3rd/goscon | 1 - code/framework/lualib-src/lua-aoi/lua_aoi.c | 1 + code/framework/lualib-src/lua-ecs/luaecs.c | 1434 +++++++----- code/framework/lualib-src/lua-ecs/luaecs.h | 80 +- code/framework/lualib-src/lua-ecs/test.lua | 2 +- code/framework/lualib-src/lua-ecs/test10.lua | 2 +- code/framework/lualib-src/lua-ecs/test11.lua | 17 + code/framework/lualib-src/lua-ecs/test2.lua | 50 +- code/framework/lualib-src/lua-ecs/test3.lua | 71 - code/framework/lualib-src/lua-ecs/test5.lua | 2 +- code/framework/lualib-src/lua-ecs/test6.lua | 24 + code/framework/lualib/3rd/misc/argparse.lua | 2099 ++++++++++++++++++ code/framework/lualib/3rd/misc/ecs.lua | 761 ++++--- docs/NoSQLBooster.md | 15 + 15 files changed, 3526 insertions(+), 1036 deletions(-) delete mode 160000 code/framework/3rd/goscon create mode 100644 code/framework/lualib/3rd/misc/argparse.lua create mode 100644 docs/NoSQLBooster.md diff --git a/.gitmodules b/.gitmodules index e004090..695297a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "code/framework/3rd/ffi-lua"] path = code/framework/3rd/ffi-lua url = git@github.com:cloudfreexiao/cffi-lua.git -[submodule "code/framework/3rd/goscon"] - path = code/framework/3rd/goscon - url = git@github.com:cloudfreexiao/goscon.git [submodule "code/framework/3rd/glm"] path = code/framework/3rd/glm url = git@github.com:g-truc/glm.git diff --git a/code/framework/3rd/goscon b/code/framework/3rd/goscon deleted file mode 160000 index 2d6546b..0000000 --- a/code/framework/3rd/goscon +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2d6546b99b7f42b53824fdd89a62780e94252a46 diff --git a/code/framework/lualib-src/lua-aoi/lua_aoi.c b/code/framework/lualib-src/lua-aoi/lua_aoi.c index 59b1c73..87b20a0 100644 --- a/code/framework/lualib-src/lua-aoi/lua_aoi.c +++ b/code/framework/lualib-src/lua-aoi/lua_aoi.c @@ -10,6 +10,7 @@ #include "aoi.h" // https://github.com/ssnobin/c_lua_aoi +// 非实时抛出事件,lua层定时aoi_update获得aoi事件 #define GET_INTEGER(L, index, isnum) \ (int)lua_tointegerx(L, index, &isnum); \ diff --git a/code/framework/lualib-src/lua-ecs/luaecs.c b/code/framework/lualib-src/lua-ecs/luaecs.c index 351d989..69c8acd 100644 --- a/code/framework/lualib-src/lua-ecs/luaecs.c +++ b/code/framework/lualib-src/lua-ecs/luaecs.c @@ -19,53 +19,64 @@ #define DUMMY_PTR (void *)(uintptr_t)(~0) #define REARRANGE_THRESHOLD 0x80000000 -struct component_pool { +struct component_pool +{ int cap; int n; - int stride; // -1 means lua object + int stride; // -1 means lua object int last_lookup; unsigned int *id; void *buffer; }; -struct entity_world { +struct entity_world +{ unsigned int max_id; struct component_pool c[MAX_COMPONENT]; }; static void -init_component_pool(struct entity_world *w, int index, int stride, int opt_size) { +init_component_pool(struct entity_world *w, int index, int stride, int opt_size) +{ struct component_pool *c = &w->c[index]; c->cap = opt_size; c->n = 0; c->stride = stride; c->id = NULL; c->last_lookup = 0; - if (stride > 0) { + if (stride > 0) + { c->buffer = NULL; - } else { + } + else + { c->buffer = DUMMY_PTR; } } static void -entity_new_type(lua_State *L, struct entity_world *w, int cid, int stride, int opt_size) { - if (opt_size <= 0) { +entity_new_type(lua_State *L, struct entity_world *w, int cid, int stride, int opt_size) +{ + if (opt_size <= 0) + { opt_size = DEFAULT_SIZE; } - if (cid < 0 || cid >=MAX_COMPONENT || w->c[cid].cap != 0) { + if (cid < 0 || cid >= MAX_COMPONENT || w->c[cid].cap != 0) + { luaL_error(L, "Can't new type %d", cid); } init_component_pool(w, cid, stride, opt_size); } static inline struct entity_world * -getW(lua_State *L) { +getW(lua_State *L) +{ return (struct entity_world *)luaL_checkudata(L, 1, "ENTITY_WORLD"); } static int -lnew_type(lua_State *L) { +lnew_type(lua_State *L) +{ struct entity_world *w = getW(L); int cid = luaL_checkinteger(L, 2); int stride = luaL_checkinteger(L, 3); @@ -75,18 +86,22 @@ lnew_type(lua_State *L) { } static int -lcount_memory(lua_State *L) { +lcount_memory(lua_State *L) +{ struct entity_world *w = getW(L); size_t sz = sizeof(*w); int i; size_t msz = sz; - for (i=0;ic[i]; - if (c->id) { + if (c->id) + { sz += c->cap * sizeof(unsigned int); msz += c->n * sizeof(unsigned int); } - if (c->buffer != DUMMY_PTR) { + if (c->buffer != DUMMY_PTR) + { sz += c->cap * c->stride; msz += c->cap * c->stride; } @@ -97,10 +112,12 @@ lcount_memory(lua_State *L) { } static void -shrink_component_pool(lua_State *L, struct component_pool *c, int id) { +shrink_component_pool(lua_State *L, struct component_pool *c, int id) +{ if (c->id == NULL) return; - if (c->n == 0) { + if (c->n == 0) + { c->id = NULL; if (c->stride > 0) c->buffer = NULL; @@ -108,7 +125,9 @@ shrink_component_pool(lua_State *L, struct component_pool *c, int id) { lua_setiuservalue(L, 1, id * 2 + 1); lua_pushnil(L); lua_setiuservalue(L, 1, id * 2 + 2); - } else if (c->stride > 0 && c->n < c->cap) { + } + else if (c->stride > 0 && c->n < c->cap) + { c->cap = c->n; c->id = (unsigned int *)lua_newuserdatauv(L, c->n * sizeof(unsigned int), 0); lua_setiuservalue(L, 1, id * 2 + 1); @@ -118,41 +137,52 @@ shrink_component_pool(lua_State *L, struct component_pool *c, int id) { } static int -lcollect_memory(lua_State *L) { +lcollect_memory(lua_State *L) +{ struct entity_world *w = getW(L); int i; - for (i=0;ic[i], i); } return 0; } static int -add_component_id_(lua_State *L, int world_index, struct entity_world *w, int cid, unsigned int eid) { +add_component_id_(lua_State *L, int world_index, struct entity_world *w, int cid, unsigned int eid) +{ struct component_pool *pool = &w->c[cid]; int cap = pool->cap; int index = pool->n; - if (pool->n == 0) { - if (pool->id == NULL) { + if (pool->n == 0) + { + if (pool->id == NULL) + { pool->id = (unsigned int *)lua_newuserdatauv(L, cap * sizeof(unsigned int), 0); lua_setiuservalue(L, world_index, cid * 2 + 1); } - if (pool->buffer == NULL) { + if (pool->buffer == NULL) + { pool->buffer = lua_newuserdatauv(L, cap * pool->stride, 0); lua_setiuservalue(L, world_index, cid * 2 + 2); - } else if (pool->stride == STRIDE_LUA) { + } + else if (pool->stride == STRIDE_LUA) + { lua_newtable(L); lua_setiuservalue(L, world_index, cid * 2 + 2); } - } else if (pool->n >= pool->cap) { + } + else if (pool->n >= pool->cap) + { // expand pool int newcap = cap * 3 / 2; unsigned int *newid = (unsigned int *)lua_newuserdatauv(L, newcap * sizeof(unsigned int), 0); lua_setiuservalue(L, world_index, cid * 2 + 1); - memcpy(newid, pool->id, cap * sizeof(unsigned int)); + memcpy(newid, pool->id, cap * sizeof(unsigned int)); pool->id = newid; int stride = pool->stride; - if (stride > 0) { + if (stride > 0) + { void *newbuffer = lua_newuserdatauv(L, newcap * stride, 0); lua_setiuservalue(L, world_index, cid * 2 + 2); memcpy(newbuffer, pool->buffer, cap * stride); @@ -162,14 +192,16 @@ add_component_id_(lua_State *L, int world_index, struct entity_world *w, int cid } ++pool->n; pool->id[index] = eid; - if (pool->stride != STRIDE_ORDER && index > 0 && eid < pool->id[index-1]) { + if (pool->stride != STRIDE_ORDER && index > 0 && eid < pool->id[index - 1]) + { luaL_error(L, "Add component %d fail", cid); } return index; } static inline void * -get_ptr(struct component_pool *c, int index) { +get_ptr(struct component_pool *c, int index) +{ if (c->stride > 0) return (void *)((char *)c->buffer + c->stride * index); else @@ -177,28 +209,34 @@ get_ptr(struct component_pool *c, int index) { } static void * -add_component_(lua_State *L, int world_index, struct entity_world *w, int cid, unsigned int eid, const void *buffer) { +add_component_(lua_State *L, int world_index, struct entity_world *w, int cid, unsigned int eid, const void *buffer) +{ int index = add_component_id_(L, world_index, w, cid, eid); struct component_pool *pool = &w->c[cid]; - assert(pool->stride >= 0); void *ret = get_ptr(pool, index); if (buffer) + { + assert(pool->stride >= 0); memcpy(ret, buffer, pool->stride); + } return ret; } static inline int -check_cid(lua_State *L, struct entity_world *w, int index) { +check_cid(lua_State *L, struct entity_world *w, int index) +{ int cid = luaL_checkinteger(L, index); struct component_pool *c = &w->c[cid]; - if (cid < 0 || cid >=MAX_COMPONENT || c->cap == 0) { + if (cid < 0 || cid >= MAX_COMPONENT || c->cap == 0) + { luaL_error(L, "Invalid type %d", cid); } return cid; } static int -ladd_component(lua_State *L) { +ladd_component(lua_State *L) +{ struct entity_world *w = getW(L); unsigned int eid = luaL_checkinteger(L, 2); int cid = check_cid(L, w, 3); @@ -208,7 +246,8 @@ ladd_component(lua_State *L) { } static int -lnew_entity(lua_State *L) { +lnew_entity(lua_State *L) +{ struct entity_world *w = getW(L); unsigned int eid = ++w->max_id; assert(eid != 0); @@ -217,28 +256,36 @@ lnew_entity(lua_State *L) { } static void -insert_id(lua_State *L, int world_index, struct entity_world *w, int cid, unsigned int eid) { +insert_id(lua_State *L, int world_index, struct entity_world *w, int cid, unsigned int eid) +{ struct component_pool *c = &w->c[cid]; assert(c->stride == STRIDE_TAG); int from = 0; int to = c->n; - while(from < to) { - int mid = (from + to)/2; + while (from < to) + { + int mid = (from + to) / 2; int aa = c->id[mid]; if (aa == eid) return; - else if (aa < eid) { + else if (aa < eid) + { from = mid + 1; - } else { + } + else + { to = mid; } } // insert eid at [from] - if (from < c->n - 1) { + if (from < c->n - 1) + { int i; // Any dup id ? - for (i=from;in-1;i++) { - if (c->id[i] == c->id[i+1]) { + for (i = from; i < c->n - 1; i++) + { + if (c->id[i] == c->id[i + 1]) + { memmove(c->id + from + 1, c->id + from, sizeof(unsigned int) * (i - from)); c->id[from] = eid; return; @@ -252,23 +299,29 @@ insert_id(lua_State *L, int world_index, struct entity_world *w, int cid, unsign } static void -entity_enable_tag_(struct entity_world *w, int cid, int index, int tag_id, void *L, int world_index) { +entity_enable_tag_(struct entity_world *w, int cid, int index, int tag_id, void *L, int world_index) +{ struct component_pool *c = &w->c[cid]; - assert(index >=0 && index < c->n); + assert(index >= 0 && index < c->n); unsigned int eid = c->id[index]; insert_id((lua_State *)L, world_index, w, tag_id, eid); } static int -binary_search(unsigned int *a, int from, int to, unsigned int v) { - while(from < to) { - int mid = (from + to)/2; +binary_search(unsigned int *a, int from, int to, unsigned int v) +{ + while (from < to) + { + int mid = (from + to) / 2; int aa = a[mid]; if (aa == v) return mid; - else if (aa < v) { + else if (aa < v) + { from = mid + 1; - } else { + } + else + { to = mid; } } @@ -278,7 +331,8 @@ binary_search(unsigned int *a, int from, int to, unsigned int v) { #define GUESS_RANGE 64 static inline int -lookup_component(struct component_pool *pool, unsigned int eid, int guess_index) { +lookup_component(struct component_pool *pool, unsigned int eid, int guess_index) +{ int n = pool->n; if (n == 0) return -1; @@ -286,56 +340,67 @@ lookup_component(struct component_pool *pool, unsigned int eid, int guess_index) return binary_search(pool->id, 0, pool->n, eid); unsigned int *a = pool->id; int lower = a[guess_index]; - if (eid <= lower) { + if (eid <= lower) + { if (eid == lower) return guess_index; return binary_search(a, 0, guess_index, eid); } - if (guess_index + GUESS_RANGE*2 >= pool->n) { + if (guess_index + GUESS_RANGE * 2 >= pool->n) + { return binary_search(a, guess_index + 1, pool->n, eid); } int higher = a[guess_index + GUESS_RANGE]; - if (eid > higher) { + if (eid > higher) + { return binary_search(a, guess_index + GUESS_RANGE + 1, pool->n, eid); } return binary_search(a, guess_index + 1, guess_index + GUESS_RANGE + 1, eid); } static inline void -replace_id(struct component_pool *c, int from, int to, unsigned int eid) { +replace_id(struct component_pool *c, int from, int to, unsigned int eid) +{ int i; - for (i=from;iid[i] = eid; } } static void -entity_disable_tag_(struct entity_world *w, int cid, int index, int tag_id) { +entity_disable_tag_(struct entity_world *w, int cid, int index, int tag_id) +{ struct component_pool *c = &w->c[cid]; - assert(index >=0 && index < c->n); + assert(index >= 0 && index < c->n); unsigned int eid = c->id[index]; - if (cid != tag_id) { + if (cid != tag_id) + { c = &w->c[tag_id]; index = lookup_component(c, eid, c->last_lookup); if (index < 0) return; } - int from,to; + int from, to; // find next tag. You may disable subsquent tags in iteration. // For example, The sequence is 1 3 5 7 9 . We are now on 5 , and disable 7 . // We should change 7 to 9 ( 1 3 5 9 9 ) rather than 7 to 5 ( 1 3 5 5 9 ) // iterator -> ^ ^ - for (to = index+1; ton; to++) { - if (c->id[to] != eid) { - for (from = index-1; from>=0; from--) { + for (to = index + 1; to < c->n; to++) + { + if (c->id[to] != eid) + { + for (from = index - 1; from >= 0; from--) + { if (c->id[from] != eid) break; } - replace_id(c, from+1, to, c->id[to]); + replace_id(c, from + 1, to, c->id[to]); return; } } - for (from = index-1; from>=0; from--) { + for (from = index - 1; from >= 0; from--) + { if (c->id[from] != eid) break; } @@ -343,25 +408,31 @@ entity_disable_tag_(struct entity_world *w, int cid, int index, int tag_id) { } static void -entity_remove_(struct entity_world *w, int cid, int index, void *L, int world_index) { +entity_remove_(struct entity_world *w, int cid, int index, void *L, int world_index) +{ entity_enable_tag_(w, cid, index, ENTITY_REMOVED, L, world_index); } -struct rearrange_context { +struct rearrange_context +{ struct entity_world *w; - unsigned int ptr[MAX_COMPONENT-1]; + unsigned int ptr[MAX_COMPONENT - 1]; }; static int -find_min(struct rearrange_context *ctx) { +find_min(struct rearrange_context *ctx) +{ unsigned int m = ~0; int i; int r = -1; struct entity_world *w = ctx->w; - for (i=1;iptr[i-1]; - if (index < w->c[i].n) { - if (w->c[i].id[index] <= m) { + for (i = 1; i < MAX_COMPONENT; i++) + { + int index = ctx->ptr[i - 1]; + if (index < w->c[i].n) + { + if (w->c[i].id[index] <= m) + { m = w->c[i].id[index]; r = i; } @@ -371,37 +442,44 @@ find_min(struct rearrange_context *ctx) { } static void -rearrange(struct entity_world *w) { +rearrange(struct entity_world *w) +{ struct rearrange_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.w = w; int cid; unsigned int new_id = 1; unsigned int last_id = 0; - while ((cid = find_min(&ctx)) >= 0) { - int index = ctx.ptr[cid-1]; + while ((cid = find_min(&ctx)) >= 0) + { + int index = ctx.ptr[cid - 1]; unsigned int current_id = w->c[cid].id[index]; -// printf("arrange %d <- %d\n", new_id, w->c[cid].id[index]); + // printf("arrange %d <- %d\n", new_id, w->c[cid].id[index]); w->c[cid].id[index] = new_id; - if (current_id != last_id) { + if (current_id != last_id) + { ++new_id; last_id = current_id; } - ++ctx.ptr[cid-1]; + ++ctx.ptr[cid - 1]; } w->max_id = new_id; } static inline void -move_tag(struct component_pool *pool, int from, int to) { - if (from != to) { +move_tag(struct component_pool *pool, int from, int to) +{ + if (from != to) + { pool->id[to] = pool->id[from]; } } static inline void -move_item(struct component_pool *pool, int from, int to) { - if (from != to) { +move_item(struct component_pool *pool, int from, int to) +{ + if (from != to) + { pool->id[to] = pool->id[from]; int stride = pool->stride; memcpy((char *)pool->buffer + to * stride, (char *)pool->buffer + from * stride, stride); @@ -409,70 +487,90 @@ move_item(struct component_pool *pool, int from, int to) { } static void -move_object(lua_State *L, struct component_pool *pool, int from, int to) { - if (from != to) { +move_object(lua_State *L, struct component_pool *pool, int from, int to) +{ + if (from != to) + { pool->id[to] = pool->id[from]; - lua_rawgeti(L, -1, from+1); - lua_rawseti(L, -2, to+1); + lua_rawgeti(L, -1, from + 1); + lua_rawseti(L, -2, to + 1); } } static void -remove_all(lua_State *L, struct component_pool *pool, struct component_pool *removed, int cid) { +remove_all(lua_State *L, struct component_pool *pool, struct component_pool *removed, int cid) +{ int index = 0; int count = 0; int i; - if (pool->stride != STRIDE_ORDER) { + if (pool->stride != STRIDE_ORDER) + { unsigned int *id = removed->id; unsigned int last_id = 0; - for (i=0;in;i++) { - if (id[i] != last_id) { + for (i = 0; i < removed->n; i++) + { + if (id[i] != last_id) + { // todo : order int r = lookup_component(pool, id[i], index); - if (r >= 0) { + if (r >= 0) + { index = r; pool->id[r] = 0; ++count; } } } - } else { + } + else + { unsigned int *id = pool->id; - for (i=0;in;i++) { + for (i = 0; i < pool->n; i++) + { int r = lookup_component(removed, id[i], 0); - if (r >= 0) { + if (r >= 0) + { id[i] = 0; ++count; } } } - if (count > 0) { + if (count > 0) + { index = 0; - switch (pool->stride) { + switch (pool->stride) + { case STRIDE_LUA: - if (lua_getiuservalue(L, 1, cid * 2 + 2) != LUA_TTABLE) { + if (lua_getiuservalue(L, 1, cid * 2 + 2) != LUA_TTABLE) + { luaL_error(L, "Missing lua object table for type %d", cid); } - for (i=0;in;i++) { - if (pool->id[i] != 0) { + for (i = 0; i < pool->n; i++) + { + if (pool->id[i] != 0) + { move_object(L, pool, i, index); ++index; } } - lua_pop(L, 1); // pop lua object table + lua_pop(L, 1); // pop lua object table break; case STRIDE_TAG: case STRIDE_ORDER: - for (i=0;in;i++) { - if (pool->id[i] != 0) { + for (i = 0; i < pool->n; i++) + { + if (pool->id[i] != 0) + { move_tag(pool, i, index); ++index; } } break; default: - for (i=0;in;i++) { - if (pool->id[i] != 0) { + for (i = 0; i < pool->n; i++) + { + if (pool->id[i] != 0) + { move_item(pool, i, index); ++index; } @@ -484,14 +582,17 @@ remove_all(lua_State *L, struct component_pool *pool, struct component_pool *rem } static int -lupdate(lua_State *L) { +lupdate(lua_State *L) +{ struct entity_world *w = getW(L); struct component_pool *removed = &w->c[ENTITY_REMOVED]; int i; - if (removed->n > 0) { + if (removed->n > 0) + { // mark removed assert(ENTITY_REMOVED == 0); - for (i=1;ic[i]; if (pool->n > 0) remove_all(L, pool, removed, i); @@ -499,7 +600,8 @@ lupdate(lua_State *L) { removed->n = 0; } - if (w->max_id > REARRANGE_THRESHOLD) { + if (w->max_id > REARRANGE_THRESHOLD) + { rearrange(w); } @@ -507,12 +609,15 @@ lupdate(lua_State *L) { } static void -remove_dup(struct component_pool *c, int index) { +remove_dup(struct component_pool *c, int index) +{ int i; unsigned int eid = c->id[index]; int to = index; - for (i=index+1;in;i++) { - if (c->id[i] != eid) { + for (i = index + 1; i < c->n; i++) + { + if (c->id[i] != eid) + { eid = c->id[i]; c->id[to] = eid; ++to; @@ -522,16 +627,19 @@ remove_dup(struct component_pool *c, int index) { } static void * -entity_iter_(struct entity_world *w, int cid, int index) { +entity_iter_(struct entity_world *w, int cid, int index) +{ struct component_pool *c = &w->c[cid]; assert(index >= 0); if (index >= c->n) return NULL; - if (c->stride == STRIDE_TAG) { + if (c->stride == STRIDE_TAG) + { // it's a tag unsigned int eid = c->id[index]; - if (index < c->n - 1 && eid == c->id[index+1]) { - remove_dup(c, index+1); + if (index < c->n - 1 && eid == c->id[index + 1]) + { + remove_dup(c, index + 1); } return DUMMY_PTR; } @@ -539,16 +647,19 @@ entity_iter_(struct entity_world *w, int cid, int index) { } static void * -entity_iter_lua_(struct entity_world *w, int cid, int index, void *L, int world_index) { - void * ret = entity_iter_(w, cid, index); +entity_iter_lua_(struct entity_world *w, int cid, int index, void *L, int world_index) +{ + void *ret = entity_iter_(w, cid, index); if (ret != DUMMY_PTR) return ret; - if (lua_getiuservalue(L, world_index, cid * 2 + 2) != LUA_TTABLE) { + if (lua_getiuservalue(L, world_index, cid * 2 + 2) != LUA_TTABLE) + { lua_pop(L, 1); return NULL; } - int t = lua_rawgeti(L, -1, index+1); - switch(t) { + int t = lua_rawgeti(L, -1, index + 1); + switch (t) + { case LUA_TSTRING: ret = (void *)lua_tostring(L, -1); break; @@ -565,15 +676,18 @@ entity_iter_lua_(struct entity_world *w, int cid, int index, void *L, int world_ } static int -entity_assign_lua_(struct entity_world *w, int cid, int index, void *L, int world_index) { +entity_assign_lua_(struct entity_world *w, int cid, int index, void *L, int world_index) +{ struct component_pool *c = &w->c[cid]; ++index; assert(lua_gettop(L) > 1); - if (c->stride != STRIDE_LUA || index <=0 || index > c->n) { + if (c->stride != STRIDE_LUA || index <= 0 || index > c->n) + { lua_pop(L, 1); return 0; } - if (lua_getiuservalue(L, world_index, cid * 2 + 2) != LUA_TTABLE) { + if (lua_getiuservalue(L, world_index, cid * 2 + 2) != LUA_TTABLE) + { lua_pop(L, 2); return 0; } @@ -585,21 +699,24 @@ entity_assign_lua_(struct entity_world *w, int cid, int index, void *L, int worl } static void -entity_clear_type_(struct entity_world *w, int cid) { +entity_clear_type_(struct entity_world *w, int cid) +{ struct component_pool *c = &w->c[cid]; c->n = 0; } static int -lclear_type(lua_State *L) { +lclear_type(lua_State *L) +{ struct entity_world *w = getW(L); - int cid = check_cid(L,w, 2); + int cid = check_cid(L, w, 2); entity_clear_type_(w, cid); return 0; } static int -entity_sibling_index_(struct entity_world *w, int cid, int index, int silbling_id) { +entity_sibling_index_(struct entity_world *w, int cid, int index, int silbling_id) +{ struct component_pool *c = &w->c[cid]; if (index < 0 || index >= c->n) return 0; @@ -607,7 +724,8 @@ entity_sibling_index_(struct entity_world *w, int cid, int index, int silbling_i c = &w->c[silbling_id]; assert(c->stride != STRIDE_ORDER); int result_index = lookup_component(c, eid, c->last_lookup); - if (result_index >= 0) { + if (result_index >= 0) + { c->last_lookup = result_index; return result_index + 1; } @@ -615,23 +733,28 @@ entity_sibling_index_(struct entity_world *w, int cid, int index, int silbling_i } static void * -entity_add_sibling_(struct entity_world *w, int cid, int index, int silbling_id, const void *buffer, void *L, int world_index) { +entity_add_sibling_(struct entity_world *w, int cid, int index, int silbling_id, const void *buffer, void *L, int world_index) +{ struct component_pool *c = &w->c[cid]; - assert(index >=0 && index < c->n); + assert(index >= 0 && index < c->n); unsigned int eid = c->id[index]; // todo: pcall add_component_ return add_component_((lua_State *)L, world_index, w, silbling_id, eid, buffer); } static int -entity_new_(struct entity_world *w, int cid, const void *buffer, void *L, int world_index) { +entity_new_(struct entity_world *w, int cid, const void *buffer, void *L, int world_index) +{ unsigned int eid = ++w->max_id; assert(eid != 0); struct component_pool *c = &w->c[cid]; assert(c->cap > 0); - if (buffer == NULL) { + if (buffer == NULL) + { return add_component_id_(L, world_index, w, cid, eid); - } else { + } + else + { assert(c->stride >= 0); int index = add_component_id_(L, world_index, w, cid, eid); void *ret = get_ptr(c, index); @@ -641,9 +764,10 @@ entity_new_(struct entity_world *w, int cid, const void *buffer, void *L, int wo } static int -entity_add_sibling_index_(lua_State *L, int world_index, struct entity_world *w, int cid, int index, int slibling_id) { +entity_add_sibling_index_(lua_State *L, int world_index, struct entity_world *w, int cid, int index, int slibling_id) +{ struct component_pool *c = &w->c[cid]; - assert(index >=0 && index < c->n); + assert(index >= 0 && index < c->n); unsigned int eid = c->id[index]; // todo: pcall add_component_ int ret = add_component_id_(L, world_index, w, slibling_id, eid); @@ -651,20 +775,22 @@ entity_add_sibling_index_(lua_State *L, int world_index, struct entity_world *w, } static int -lcontext(lua_State *L) { +lcontext(lua_State *L) +{ struct entity_world *w = getW(L); luaL_checktype(L, 2, LUA_TTABLE); lua_len(L, 2); int n = lua_tointeger(L, -1); lua_pop(L, 1); - if (n <= 0) { + if (n <= 0) + { return luaL_error(L, "Invalid length %d of table", n); } size_t sz = sizeof(struct ecs_context) + sizeof(int) * n; struct ecs_context *ctx = (struct ecs_context *)lua_newuserdatauv(L, sz, 1); ctx->L = (void *)lua_newthread(L); lua_pushvalue(L, 1); - lua_xmove(L, ctx->L, 1); // put world in the index 1 of newthread + lua_xmove(L, ctx->L, 1); // put world in the index 1 of newthread lua_setiuservalue(L, -2, 1); ctx->max_id = n; ctx->world = w; @@ -683,8 +809,10 @@ lcontext(lua_State *L) { ctx->api = &c_api; ctx->cid[0] = ENTITY_REMOVED; int i; - for (i=1;i<=n;i++) { - if (lua_geti(L, 2, i) != LUA_TNUMBER) { + for (i = 1; i <= n; i++) + { + if (lua_geti(L, 2, i) != LUA_TNUMBER) + { return luaL_error(L, "Invalid id at index %d", i); } ctx->cid[i] = lua_tointeger(L, -1); @@ -697,7 +825,8 @@ lcontext(lua_State *L) { } static int -lnew_world(lua_State *L) { +lnew_world(lua_State *L) +{ size_t sz = sizeof(struct entity_world); struct entity_world *w = (struct entity_world *)lua_newuserdatauv(L, sz, MAX_COMPONENT * 2); memset(w, 0, sz); @@ -719,16 +848,19 @@ lnew_world(lua_State *L) { #define TYPE_USERDATA 8 #define TYPE_COUNT 9 -struct field { +struct field +{ const char *key; int offset; int type; }; static int -check_type(lua_State *L) { +check_type(lua_State *L) +{ int type = lua_tointeger(L, -1); - if (type < 0 || type >= TYPE_COUNT) { + if (type < 0 || type >= TYPE_COUNT) + { luaL_error(L, "Invalid field type(%d)", type); } lua_pop(L, 1); @@ -736,19 +868,23 @@ check_type(lua_State *L) { } static void -get_field(lua_State *L, int i, struct field *f) { - if (lua_geti(L, -1, 1) != LUA_TNUMBER) { +get_field(lua_State *L, int i, struct field *f) +{ + if (lua_geti(L, -1, 1) != LUA_TNUMBER) + { luaL_error(L, "Invalid field %d [1] type", i); } f->type = check_type(L); - if (lua_geti(L, -1, 2) != LUA_TSTRING) { + if (lua_geti(L, -1, 2) != LUA_TSTRING) + { luaL_error(L, "Invalid field %d [2] key", i); } f->key = lua_tostring(L, -1); lua_pop(L, 1); - if (lua_geti(L, -1, 3) != LUA_TNUMBER) { + if (lua_geti(L, -1, 3) != LUA_TNUMBER) + { luaL_error(L, "Invalid field %d [3] offset", i); } f->offset = lua_tointeger(L, -1); @@ -758,90 +894,102 @@ get_field(lua_State *L, int i, struct field *f) { } static void -write_value(lua_State *L, struct field *f, char *buffer) { +write_value(lua_State *L, struct field *f, char *buffer) +{ int luat = lua_type(L, -1); char *ptr = buffer + f->offset; - switch (f->type) { - case TYPE_INT: - if (!lua_isinteger(L, -1)) - luaL_error(L, "Invalid .%s type %s (int)", f->key ? f->key : "*", lua_typename(L, luat)); - *(int *)ptr = lua_tointeger(L, -1); - break; - case TYPE_FLOAT: - if (luat != LUA_TNUMBER) - luaL_error(L, "Invalid .%s type %s (float)", f->key ? f->key : "*", lua_typename(L, luat)); - *(float *)ptr = lua_tonumber(L, -1); - break; - case TYPE_BOOL: - if (luat != LUA_TBOOLEAN) - luaL_error(L, "Invalid .%s type %s (bool)", f->key ? f->key : "*", lua_typename(L, luat)); - *(unsigned char *)ptr = lua_toboolean(L, -1); - break; - case TYPE_INT64: - if (!lua_isinteger(L, -1)) - luaL_error(L, "Invalid .%s type %s (int64)", f->key ? f->key : "*", lua_typename(L, luat)); - *(int64_t *)ptr = lua_tointeger(L, -1); - break; - case TYPE_DWORD: - if (!lua_isinteger(L, -1)) - luaL_error(L, "Invalid .%s type %s (uint32)", f->key ? f->key : "*", lua_typename(L, luat)); - else { - int64_t v = lua_tointeger(L, -1); - if (v < 0 || v > 0xffffffff) { - luaL_error(L, "Invalid DWORD %d", (int)v); - } - *(uint32_t *)ptr = v; + switch (f->type) + { + case TYPE_INT: + if (!lua_isinteger(L, -1)) + luaL_error(L, "Invalid .%s type %s (int)", f->key ? f->key : "*", lua_typename(L, luat)); + *(int *)ptr = lua_tointeger(L, -1); + break; + case TYPE_FLOAT: + if (luat != LUA_TNUMBER) + luaL_error(L, "Invalid .%s type %s (float)", f->key ? f->key : "*", lua_typename(L, luat)); + *(float *)ptr = lua_tonumber(L, -1); + break; + case TYPE_BOOL: + if (luat != LUA_TBOOLEAN) + luaL_error(L, "Invalid .%s type %s (bool)", f->key ? f->key : "*", lua_typename(L, luat)); + *(unsigned char *)ptr = lua_toboolean(L, -1); + break; + case TYPE_INT64: + if (!lua_isinteger(L, -1)) + luaL_error(L, "Invalid .%s type %s (int64)", f->key ? f->key : "*", lua_typename(L, luat)); + *(int64_t *)ptr = lua_tointeger(L, -1); + break; + case TYPE_DWORD: + if (!lua_isinteger(L, -1)) + luaL_error(L, "Invalid .%s type %s (uint32)", f->key ? f->key : "*", lua_typename(L, luat)); + else + { + int64_t v = lua_tointeger(L, -1); + if (v < 0 || v > 0xffffffff) + { + luaL_error(L, "Invalid DWORD %d", (int)v); } - break; - case TYPE_WORD: - if (!lua_isinteger(L, -1)) - luaL_error(L, "Invalid .%s type %s (uint16)", f->key ? f->key : "*", lua_typename(L, luat)); - else { - int v = lua_tointeger(L, -1); - if (v < 0 || v > 0xffff) { - luaL_error(L, "Invalid WORD %d", v); - } - *(uint16_t *)ptr = v; + *(uint32_t *)ptr = v; + } + break; + case TYPE_WORD: + if (!lua_isinteger(L, -1)) + luaL_error(L, "Invalid .%s type %s (uint16)", f->key ? f->key : "*", lua_typename(L, luat)); + else + { + int v = lua_tointeger(L, -1); + if (v < 0 || v > 0xffff) + { + luaL_error(L, "Invalid WORD %d", v); } - break; - case TYPE_BYTE: - if (!lua_isinteger(L, -1)) - luaL_error(L, "Invalid .%s type %s (uint8)", f->key ? f->key : "*", lua_typename(L, luat)); - else { - int v = lua_tointeger(L, -1); - if (v < 0 || v > 255) { - luaL_error(L, "Invalid BYTE %d", v); - } - *(uint16_t *)ptr = v; + *(uint16_t *)ptr = v; + } + break; + case TYPE_BYTE: + if (!lua_isinteger(L, -1)) + luaL_error(L, "Invalid .%s type %s (uint8)", f->key ? f->key : "*", lua_typename(L, luat)); + else + { + int v = lua_tointeger(L, -1); + if (v < 0 || v > 255) + { + luaL_error(L, "Invalid BYTE %d", v); } - break; - case TYPE_DOUBLE: - if (luat != LUA_TNUMBER) - luaL_error(L, "Invalid .%s type %s (double)", f->key ? f->key : "*", lua_typename(L, luat)); - *(double *)ptr = lua_tonumber(L, -1); - break; - case TYPE_USERDATA: - if (luat != LUA_TLIGHTUSERDATA) - luaL_error(L, "Invalid .%s type %s (pointer)", f->key ? f->key : "*", lua_typename(L, luat)); - *(void **)ptr = lua_touserdata(L, -1); - break; + *(uint16_t *)ptr = v; + } + break; + case TYPE_DOUBLE: + if (luat != LUA_TNUMBER) + luaL_error(L, "Invalid .%s type %s (double)", f->key ? f->key : "*", lua_typename(L, luat)); + *(double *)ptr = lua_tonumber(L, -1); + break; + case TYPE_USERDATA: + if (luat != LUA_TLIGHTUSERDATA) + luaL_error(L, "Invalid .%s type %s (pointer)", f->key ? f->key : "*", lua_typename(L, luat)); + *(void **)ptr = lua_touserdata(L, -1); + break; } lua_pop(L, 1); } static inline void -write_component(lua_State *L, int field_n, struct field *f, int index, char *buffer) { +write_component(lua_State *L, int field_n, struct field *f, int index, char *buffer) +{ int i; - for (i=0; i < field_n; i++) { + for (i = 0; i < field_n; i++) + { lua_getfield(L, index, f[i].key); write_value(L, &f[i], buffer); } } static void -read_value(lua_State *L, struct field *f, const char *buffer) { - const char * ptr = buffer + f->offset; - switch (f->type) { +read_value(lua_State *L, struct field *f, const char *buffer) +{ + const char *ptr = buffer + f->offset; + switch (f->type) + { case TYPE_INT: lua_pushinteger(L, *(const int *)ptr); break; @@ -877,22 +1025,27 @@ read_value(lua_State *L, struct field *f, const char *buffer) { } static void -read_component(lua_State *L, int field_n, struct field *f, int index, const char * buffer) { +read_component(lua_State *L, int field_n, struct field *f, int index, const char *buffer) +{ int i; - for (i=0; i < field_n; i++) { + for (i = 0; i < field_n; i++) + { read_value(L, &f[i], buffer); lua_setfield(L, index, f[i].key); } } static int -get_len(lua_State *L, int index) { +get_len(lua_State *L, int index) +{ lua_len(L, index); - if (lua_type(L, -1) != LUA_TNUMBER) { + if (lua_type(L, -1) != LUA_TNUMBER) + { return luaL_error(L, "Invalid table length"); } int n = lua_tointeger(L, -1); - if (n < 0) { + if (n < 0) + { return luaL_error(L, "Invalid table length %d", n); } lua_pop(L, 1); @@ -906,10 +1059,9 @@ get_len(lua_State *L, int index) { #define COMPONENT_EXIST 0x10 #define COMPONENT_ABSENT 0x20 #define COMPONENT_FILTER (COMPONENT_EXIST | COMPONENT_ABSENT) -#define COMPONENT_REFINDEX 0x40 -#define COMPONENT_REFOBJECT 0x80 -struct group_key { +struct group_key +{ const char *name; int id; int field_n; @@ -917,13 +1069,15 @@ struct group_key { }; static inline int -is_temporary(int attrib) { +is_temporary(int attrib) +{ if (attrib & COMPONENT_FILTER) return 0; return (attrib & COMPONENT_IN) == 0 && (attrib & COMPONENT_OUT) == 0; } -struct group_iter { +struct group_iter +{ struct entity_world *world; struct field *f; int nkey; @@ -932,25 +1086,30 @@ struct group_iter { }; static int -get_write_component(lua_State *L, int lua_index, const char *name, struct field *f, struct component_pool *c) { - switch (lua_getfield(L, lua_index, name)) { +get_write_component(lua_State *L, int lua_index, const char *name, struct field *f, struct component_pool *c) +{ + switch (lua_getfield(L, lua_index, name)) + { case LUA_TNIL: lua_pop(L, 1); // restore cache (metatable can be absent during sync) - if (lua_getmetatable(L, lua_index)) { + if (lua_getmetatable(L, lua_index)) + { lua_getfield(L, -1, name); lua_setfield(L, lua_index, name); - lua_pop(L, 1); // pop metatable + lua_pop(L, 1); // pop metatable } return 0; case LUA_TTABLE: return 1; default: - if (c->stride == STRIDE_LUA) { + if (c->stride == STRIDE_LUA) + { // lua object return 1; } - if (f->key == NULL) { + if (f->key == NULL) + { // value type return 1; } @@ -959,19 +1118,25 @@ get_write_component(lua_State *L, int lua_index, const char *name, struct field } static void -write_component_object(lua_State *L, int n, struct field *f, void *buffer) { - if (f->key == NULL) { +write_component_object(lua_State *L, int n, struct field *f, void *buffer) +{ + if (f->key == NULL) + { write_value(L, f, buffer); - } else { + } + else + { write_component(L, n, f, -1, (char *)buffer); lua_pop(L, 1); } } static int -remove_tag(lua_State *L, int lua_index, const char *name) { +remove_tag(lua_State *L, int lua_index, const char *name) +{ int r = 0; - switch (lua_getfield(L, lua_index, name)) { + switch (lua_getfield(L, lua_index, name)) + { case LUA_TNIL: r = 1; break; @@ -986,30 +1151,41 @@ remove_tag(lua_State *L, int lua_index, const char *name) { } static void -update_iter(lua_State *L, int world_index, int lua_index, struct group_iter *iter, int idx, int mainkey, int skip) { +update_iter(lua_State *L, int world_index, int lua_index, struct group_iter *iter, int idx, int mainkey, int skip) +{ struct field *f = iter->f; int i; - for (i=0;ik[i].field_n; } - for (i=skip;inkey;i++) { + for (i = skip; i < iter->nkey; i++) + { struct group_key *k = &iter->k[i]; - if (!(k->attrib & (COMPONENT_FILTER | COMPONENT_REFINDEX))) { + if (!(k->attrib & COMPONENT_FILTER)) + { struct component_pool *c = &iter->world->c[k->id]; - if (c->stride == STRIDE_TAG) { + if (c->stride == STRIDE_TAG) + { // It's a tag - if ((k->attrib & COMPONENT_OUT)) { - switch (lua_getfield(L, lua_index, k->name)) { + if ((k->attrib & COMPONENT_OUT)) + { + switch (lua_getfield(L, lua_index, k->name)) + { case LUA_TNIL: break; case LUA_TBOOLEAN: - if (lua_toboolean(L, -1)) { + if (lua_toboolean(L, -1)) + { entity_enable_tag_(iter->world, mainkey, idx, k->id, L, world_index); - } else { + } + else + { entity_disable_tag_(iter->world, mainkey, idx, k->id); } - if (!(k->attrib & COMPONENT_IN)) { + if (!(k->attrib & COMPONENT_IN)) + { // reset tag lua_pushnil(L); lua_setfield(L, lua_index, k->name); @@ -1020,66 +1196,56 @@ update_iter(lua_State *L, int world_index, int lua_index, struct group_iter *ite } lua_pop(L, 1); } - } else if ((k->attrib & COMPONENT_OUT) - && get_write_component(L, lua_index, k->name, f, c)) { - int index; - if (k->attrib & COMPONENT_REFOBJECT) { - struct group_key *index_k = &iter->k[i-1]; - index = entity_sibling_index_(iter->world, mainkey, idx, index_k->id); - if (index) { - int *ref = entity_iter_(iter->world, index_k->id, index-1); - index = *ref; - } - } else { - index = entity_sibling_index_(iter->world, mainkey, idx, k->id); + } + else if (c->stride == STRIDE_ORDER) + { + assert(is_temporary(k->attrib)); + if (lua_getfield(L, lua_index, k->name) != LUA_TNIL) + { + if (!lua_isboolean(L, -1) || lua_toboolean(L, -1) == 0) + luaL_error(L, "Only support true for order key .%s", k->name); + lua_pop(L, 1); + entity_add_sibling_(iter->world, mainkey, idx, k->id, NULL, L, world_index); + lua_pushnil(L); + lua_setfield(L, lua_index, k->name); } - if (index == 0) { + } + else if ((k->attrib & COMPONENT_OUT) && get_write_component(L, lua_index, k->name, f, c)) + { + int index = entity_sibling_index_(iter->world, mainkey, idx, k->id); + if (index == 0) + { luaL_error(L, "Can't find sibling %s of %s", k->name, iter->k[0].name); } - if (c->stride == STRIDE_LUA) { - if (lua_getiuservalue(L, world_index, k->id * 2 + 2) != LUA_TTABLE) { + if (c->stride == STRIDE_LUA) + { + if (lua_getiuservalue(L, world_index, k->id * 2 + 2) != LUA_TTABLE) + { luaL_error(L, "Missing lua table for %d", k->id); } lua_insert(L, -2); lua_rawseti(L, -2, index); - } else { + } + else + { void *buffer = get_ptr(c, index - 1); write_component_object(L, k->field_n, f, buffer); } - } else if (is_temporary(k->attrib) - && get_write_component(L, lua_index, k->name, f, c)) { - if (k->attrib & COMPONENT_REFOBJECT) { - // new ref object - struct group_key *index_k = &iter->k[i-1]; - int dead_tag = k->id + 1; - int id; - if (entity_iter_(iter->world, dead_tag, 0)) { - // reuse - id = entity_sibling_index_(iter->world, dead_tag, 0, k->id); - entity_disable_tag_(iter->world, dead_tag, 0, dead_tag); - } else { - id = entity_new_(iter->world, k->id, NULL, L, world_index) + 1; - } - if (c->stride == STRIDE_LUA) { - if (lua_getiuservalue(L, world_index, k->id * 2 + 2) != LUA_TTABLE) { - luaL_error(L, "Missing lua table for %d", k->id); - } - lua_insert(L, -2); - lua_rawseti(L, -2, id); - } else { - void *buffer = entity_iter_(iter->world, k->id, id - 1); - write_component_object(L, k->field_n, f, buffer); - } - // write ref id - entity_add_sibling_(iter->world, mainkey, idx, index_k->id, &id, L, world_index); - } else if (c->stride == STRIDE_LUA) { + } + else if (is_temporary(k->attrib) && get_write_component(L, lua_index, k->name, f, c)) + { + if (c->stride == STRIDE_LUA) + { int index = entity_add_sibling_index_(L, world_index, iter->world, mainkey, idx, k->id); - if (lua_getiuservalue(L, world_index, k->id * 2 + 2) != LUA_TTABLE) { + if (lua_getiuservalue(L, world_index, k->id * 2 + 2) != LUA_TTABLE) + { luaL_error(L, "Missing lua table for %d", k->id); } lua_insert(L, -2); lua_rawseti(L, -2, index + 1); - } else { + } + else + { void *buffer = entity_add_sibling_(iter->world, mainkey, idx, k->id, NULL, L, world_index); write_component_object(L, k->field_n, f, buffer); } @@ -1090,28 +1256,37 @@ update_iter(lua_State *L, int world_index, int lua_index, struct group_iter *ite } static void -update_last_index(lua_State *L, int world_index, int lua_index, struct group_iter *iter, int idx) { +update_last_index(lua_State *L, int world_index, int lua_index, struct group_iter *iter, int idx) +{ int mainkey = iter->k[0].id; struct component_pool *c = &iter->world->c[mainkey]; int disable_mainkey = 0; - if (!(iter->k[0].attrib & COMPONENT_FILTER)) { - if (c->stride == STRIDE_TAG) { + if (!(iter->k[0].attrib & COMPONENT_FILTER)) + { + if (c->stride == STRIDE_TAG) + { // The mainkey is a tag, delay disable disable_mainkey = ((iter->k[0].attrib & COMPONENT_OUT) && remove_tag(L, lua_index, iter->k[0].name)); - } else if ((iter->k[0].attrib & COMPONENT_OUT) - && get_write_component(L, lua_index, iter->k[0].name, iter->f, c)) { + } + else if ((iter->k[0].attrib & COMPONENT_OUT) && get_write_component(L, lua_index, iter->k[0].name, iter->f, c)) + { struct component_pool *c = &iter->world->c[mainkey]; - if (c->n <= idx) { + if (c->n <= idx) + { luaL_error(L, "Can't find component %s for index %d", iter->k[0].name, idx); } - if (c->stride == STRIDE_LUA) { - if (lua_getiuservalue(L, world_index, mainkey * 2 + 2) != LUA_TTABLE) { + if (c->stride == STRIDE_LUA) + { + if (lua_getiuservalue(L, world_index, mainkey * 2 + 2) != LUA_TTABLE) + { luaL_error(L, "Missing lua table for %d", mainkey); } lua_insert(L, -2); - lua_rawseti(L, -2, idx+1); - } else { - void * buffer = get_ptr(c, idx); + lua_rawseti(L, -2, idx + 1); + } + else + { + void *buffer = get_ptr(c, idx); write_component_object(L, iter->k[0].field_n, iter->f, buffer); } } @@ -1119,66 +1294,75 @@ update_last_index(lua_State *L, int world_index, int lua_index, struct group_ite update_iter(L, world_index, lua_index, iter, idx, mainkey, 1); - if (disable_mainkey) { + if (disable_mainkey) + { entity_disable_tag_(iter->world, mainkey, idx, mainkey); } } static void -read_component_in_field(lua_State *L, int lua_index, const char *name, int n, struct field *f, void *buffer) { - if (n == 0) { +read_component_in_field(lua_State *L, int lua_index, const char *name, int n, struct field *f, void *buffer) +{ + if (n == 0) + { // It's tag lua_pushboolean(L, buffer ? 1 : 0); lua_setfield(L, lua_index, name); return; } - if (f->key == NULL) { + if (f->key == NULL) + { // value type read_value(L, f, buffer); lua_setfield(L, lua_index, name); return; } - if (lua_getfield(L, lua_index, name) != LUA_TTABLE) { + if (lua_getfield(L, lua_index, name) != LUA_TTABLE) + { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -1); lua_setfield(L, lua_index, name); } - read_component(L, n , f, lua_gettop(L), buffer); + read_component(L, n, f, lua_gettop(L), buffer); lua_pop(L, 1); } // -1 : end ; 0 : next ; 1 : succ static int -query_index(struct group_iter *iter, int skip, int mainkey, int idx, unsigned int index[MAX_COMPONENT]) { - if (entity_iter_(iter->world, mainkey, idx) == NULL) { +query_index(struct group_iter *iter, int skip, int mainkey, int idx, unsigned int index[MAX_COMPONENT]) +{ + if (entity_iter_(iter->world, mainkey, idx) == NULL) + { return -1; } int j; - for (j=skip;jnkey;j++) { + for (j = skip; j < iter->nkey; j++) + { struct group_key *k = &iter->k[j]; - if (k->attrib & COMPONENT_ABSENT) { - if (entity_sibling_index_(iter->world, mainkey, idx, k->id)) { + if (k->attrib & COMPONENT_ABSENT) + { + if (entity_sibling_index_(iter->world, mainkey, idx, k->id)) + { // exist. try next return 0; } index[j] = 0; - } else if (k->attrib & COMPONENT_REFOBJECT) { - if (index[j-1]) { - struct group_key *index_k = &iter->k[j-1]; - int *ref = entity_iter_(iter->world, index_k->id, index[j-1]-1); - index[j] = *ref; - index[j-1] = 0; - } - } else if (!is_temporary(k->attrib)) { + } + else if (!is_temporary(k->attrib)) + { index[j] = entity_sibling_index_(iter->world, mainkey, idx, k->id); - if (index[j] == 0) { - if (!(k->attrib & COMPONENT_OPTIONAL)) { + if (index[j] == 0) + { + if (!(k->attrib & COMPONENT_OPTIONAL)) + { // required. try next return 0; } } - } else { + } + else + { index[j] = 0; } } @@ -1186,37 +1370,80 @@ query_index(struct group_iter *iter, int skip, int mainkey, int idx, unsigned in } static void -read_iter(lua_State *L, int world_index, int obj_index, struct group_iter *iter, unsigned int index[MAX_COMPONENT]) { +check_index(lua_State *L, struct group_iter *iter, int mainkey, int idx) +{ + int i; + for (i = 0; i < iter->nkey; i++) + { + struct group_key *k = &iter->k[i]; + if (k->attrib & COMPONENT_ABSENT) + { + if (entity_sibling_index_(iter->world, mainkey, idx, k->id)) + { + luaL_error(L, ".%s should be absent", k->name); + } + } + else if (!is_temporary(k->attrib)) + { + if (entity_sibling_index_(iter->world, mainkey, idx, k->id) == 0) + { + if (!(k->attrib & COMPONENT_OPTIONAL)) + { + luaL_error(L, ".%s not found", k->name); + } + } + } + } +} + +static void +read_iter(lua_State *L, int world_index, int obj_index, struct group_iter *iter, unsigned int index[MAX_COMPONENT]) +{ struct field *f = iter->f; int i; - for (i=0;inkey;i++) { + for (i = 0; i < iter->nkey; i++) + { struct group_key *k = &iter->k[i]; - if (!(k->attrib & COMPONENT_FILTER)) { + if (!(k->attrib & COMPONENT_FILTER)) + { struct component_pool *c = &iter->world->c[k->id]; - if (c->stride == STRIDE_LUA) { + if (c->stride == STRIDE_LUA) + { // lua object component - if (index[i]) { - if (lua_getiuservalue(L, world_index, k->id * 2 + 2) != LUA_TTABLE) { + if (index[i]) + { + if (lua_getiuservalue(L, world_index, k->id * 2 + 2) != LUA_TTABLE) + { luaL_error(L, "Missing lua table for %d", k->id); } lua_rawgeti(L, -1, index[i]); lua_setfield(L, obj_index, k->name); lua_pop(L, 1); - } else { + } + else + { lua_pushnil(L); lua_setfield(L, obj_index, k->name); } - } else if (c->stride != STRIDE_ORDER) { - if (k->attrib & COMPONENT_IN) { - if (index[i]) { - void *ptr = get_ptr(c, index[i]-1); + } + else if (c->stride != STRIDE_ORDER) + { + if (k->attrib & COMPONENT_IN) + { + if (index[i]) + { + void *ptr = get_ptr(c, index[i] - 1); read_component_in_field(L, obj_index, k->name, k->field_n, f, ptr); - } else { + } + else + { lua_pushnil(L); lua_setfield(L, obj_index, k->name); } - } else if (index[i] == 0 && !is_temporary(k->attrib)) { + } + else if (index[i] == 0 && !is_temporary(k->attrib)) + { lua_pushnil(L); lua_setfield(L, obj_index, k->name); } @@ -1227,8 +1454,10 @@ read_iter(lua_State *L, int world_index, int obj_index, struct group_iter *iter, } static int -get_integer(lua_State *L, int index, int i, const char *key) { - if (lua_rawgeti(L, index, i) != LUA_TNUMBER) { +get_integer(lua_State *L, int index, int i, const char *key) +{ + if (lua_rawgeti(L, index, i) != LUA_TNUMBER) + { return luaL_error(L, "Can't find %s in iterator", key); } int r = lua_tointeger(L, -1); @@ -1239,17 +1468,29 @@ get_integer(lua_State *L, int index, int i, const char *key) { } static int -lsync(lua_State *L) { +lsync(lua_State *L) +{ struct group_iter *iter = luaL_checkudata(L, 2, "ENTITY_GROUPITER"); luaL_checktype(L, 3, LUA_TTABLE); int idx = get_integer(L, 3, 1, "index") - 1; int mainkey = get_integer(L, 3, 2, "mainkey"); unsigned int index[MAX_COMPONENT]; - if (query_index(iter, 0, mainkey, idx, index) <=0) { - return luaL_error(L, "Read pattern fail"); + int r = query_index(iter, 0, mainkey, idx, index); + if (r <= 0) + { + if (r < 0) + { + return luaL_error(L, "Invalid iterator of mainkey (%d)", mainkey); + } + else + { + check_index(L, iter, mainkey, idx); // raise error + return 0; + } } - if (!iter->readonly) { + if (!iter->readonly) + { update_iter(L, 1, 3, iter, idx, mainkey, 0); } read_iter(L, 1, 3, iter, index); @@ -1257,10 +1498,35 @@ lsync(lua_State *L) { } static int -postpone(lua_State *L, struct group_iter *iter, struct component_pool *c) { +lread(lua_State *L) +{ + struct group_iter *iter = luaL_checkudata(L, 2, "ENTITY_GROUPITER"); + luaL_checktype(L, 3, LUA_TTABLE); + int idx = get_integer(L, 3, 1, "index") - 1; + int mainkey = get_integer(L, 3, 2, "mainkey"); + unsigned int index[MAX_COMPONENT]; + int r = query_index(iter, 0, mainkey, idx, index); + if (r <= 0) + { + return 0; + } + + if (!iter->readonly) + { + return luaL_error(L, "Pattern is not readonly"); + } + read_iter(L, 1, 3, iter, index); + return 1; +} + +static int +postpone(lua_State *L, struct group_iter *iter, struct component_pool *c) +{ int ret = 0; - if (c->stride == STRIDE_ORDER) { - if (lua_getfield(L, 2, iter->k[0].name) == LUA_TBOOLEAN) { + if (c->stride == STRIDE_ORDER) + { + if (lua_getfield(L, 2, iter->k[0].name) == LUA_TBOOLEAN) + { ret = (lua_toboolean(L, -1) == 0); lua_pushnil(L); lua_setfield(L, 2, iter->k[0].name); @@ -1271,9 +1537,11 @@ postpone(lua_State *L, struct group_iter *iter, struct component_pool *c) { } static int -leach_group(lua_State *L) { - struct group_iter *iter = lua_touserdata(L, 1); - if (lua_rawgeti(L, 2, 1) != LUA_TNUMBER) { +leach_group(lua_State *L) +{ + struct group_iter *iter = lua_touserdata(L, 1); + if (lua_rawgeti(L, 2, 1) != LUA_TNUMBER) + { return luaL_error(L, "Invalid group iterator"); } int i = lua_tointeger(L, -1); @@ -1281,7 +1549,8 @@ leach_group(lua_State *L) { return luaL_error(L, "Invalid iterator index %d", i); lua_pop(L, 1); - if (lua_getiuservalue(L, 1, 1) != LUA_TUSERDATA) { + if (lua_getiuservalue(L, 1, 1) != LUA_TUSERDATA) + { return luaL_error(L, "Missing world object for iterator"); } @@ -1291,17 +1560,22 @@ leach_group(lua_State *L) { int mainkey = iter->k[0].id; struct component_pool *c = &iter->world->c[mainkey]; - if (i>0) { - if (postpone(L, iter, c)) { + if (i > 0) + { + if (postpone(L, iter, c)) + { --i; unsigned int tmp = c->id[i]; - memmove(&c->id[i], &c->id[i+1], (c->n-i-1) * sizeof(c->id[0])); - c->id[c->n-1] = tmp; - } else if (!iter->readonly) { - update_last_index(L, world_index, 2, iter, i-1); + memmove(&c->id[i], &c->id[i + 1], (c->n - i - 1) * sizeof(c->id[0])); + c->id[c->n - 1] = tmp; + } + else if (!iter->readonly) + { + update_last_index(L, world_index, 2, iter, i - 1); } } - for (;;) { + for (;;) + { int idx = i++; index[0] = idx + 1; int ret = query_index(iter, 1, mainkey, idx, index); @@ -1321,14 +1595,18 @@ leach_group(lua_State *L) { } static void -create_key_cache(lua_State *L, struct group_key *k, struct field *f) { +create_key_cache(lua_State *L, struct group_key *k, struct field *f) +{ if (k->field_n == 0 // is tag or object? - || (k->attrib & COMPONENT_FILTER)) { // existence or ref + || (k->attrib & COMPONENT_FILTER)) + { // existence or ref return; } - if (k->field_n == 1 && f[0].key == NULL) { + if (k->field_n == 1 && f[0].key == NULL) + { // value type - switch (f[0].type) { + switch (f[0].type) + { case TYPE_INT: case TYPE_INT64: case TYPE_DWORD: @@ -1350,34 +1628,41 @@ create_key_cache(lua_State *L, struct group_key *k, struct field *f) { lua_pushnil(L); break; } - } else { + } + else + { lua_createtable(L, 0, k->field_n); } lua_setfield(L, -2, k->name); } static int -lpairs_group(lua_State *L) { - struct group_iter *iter = lua_touserdata(L, 1); +lpairs_group(lua_State *L) +{ + struct group_iter *iter = lua_touserdata(L, 1); lua_pushcfunction(L, leach_group); lua_pushvalue(L, 1); lua_createtable(L, 2, iter->nkey); int i; int opt = 0; struct field *f = iter->f; - for (i=0;inkey;i++) { + for (i = 0; i < iter->nkey; i++) + { struct group_key *k = &iter->k[i]; create_key_cache(L, k, f); f += k->field_n; if (k->attrib & COMPONENT_OPTIONAL) ++opt; } - if (opt) { + if (opt) + { // create backup table in metatable lua_createtable(L, 0, opt); - for (i=0;inkey;i++) { + for (i = 0; i < iter->nkey; i++) + { struct group_key *k = &iter->k[i]; - if (k->attrib & COMPONENT_OPTIONAL) { + if (k->attrib & COMPONENT_OPTIONAL) + { lua_getfield(L, -2, k->name); lua_setfield(L, -2, k->name); } @@ -1386,15 +1671,17 @@ lpairs_group(lua_State *L) { } lua_pushinteger(L, 0); lua_rawseti(L, -2, 1); - lua_pushinteger(L, iter->k[0].id); // mainkey + lua_pushinteger(L, iter->k[0].id); // mainkey lua_rawseti(L, -2, 2); - return 3; + return 3; } static int -check_boolean(lua_State *L, const char * key) { +check_boolean(lua_State *L, const char *key) +{ int r = 0; - switch (lua_getfield(L, -1, key)) { + switch (lua_getfield(L, -1, key)) + { case LUA_TNIL: break; case LUA_TBOOLEAN: @@ -1408,8 +1695,10 @@ check_boolean(lua_State *L, const char * key) { } static int -is_value(lua_State *L, struct field *f) { - switch (lua_getfield(L, -1, "type")) { +is_value(lua_State *L, struct field *f) +{ + switch (lua_getfield(L, -1, "type")) + { case LUA_TNIL: lua_pop(L, 1); return 0; @@ -1424,51 +1713,62 @@ is_value(lua_State *L, struct field *f) { } static int -get_key(struct entity_world *w, lua_State *L, struct group_key *key, struct field *f) { - if (lua_getfield(L, -1, "id") != LUA_TNUMBER) { +get_key(struct entity_world *w, lua_State *L, struct group_key *key, struct field *f) +{ + if (lua_getfield(L, -1, "id") != LUA_TNUMBER) + { return luaL_error(L, "Invalid id"); } key->id = lua_tointeger(L, -1); lua_pop(L, 1); - if (key->id < 0 || key->id >= MAX_COMPONENT || w->c[key->id].cap == 0) { + if (key->id < 0 || key->id >= MAX_COMPONENT || w->c[key->id].cap == 0) + { return luaL_error(L, "Invalid id %d", key->id); } - if (lua_getfield(L, -1, "name") != LUA_TSTRING) { + if (lua_getfield(L, -1, "name") != LUA_TSTRING) + { return luaL_error(L, "Invalid component name"); } key->name = lua_tostring(L, -1); lua_pop(L, 1); int attrib = 0; - if (check_boolean(L, "r")) { + if (check_boolean(L, "r")) + { attrib |= COMPONENT_IN; } - if (check_boolean(L, "w")) { + if (check_boolean(L, "w")) + { attrib |= COMPONENT_OUT; } - if (check_boolean(L, "opt")) { + if (check_boolean(L, "opt")) + { attrib |= COMPONENT_OPTIONAL; } - if (check_boolean(L, "exist")) { + if (check_boolean(L, "exist")) + { attrib |= COMPONENT_EXIST; } - if (check_boolean(L, "absent")) { + if (check_boolean(L, "absent")) + { attrib |= COMPONENT_ABSENT; } - if (check_boolean(L, "ref")) { - attrib |= COMPONENT_REFINDEX; - } key->attrib = attrib; - if (is_value(L, f)) { + if (is_value(L, f)) + { key->field_n = 1; return 1; - } else { + } + else + { int i = 0; int ttype; - while ((ttype = lua_geti(L, -1, i+1)) != LUA_TNIL) { - if (ttype != LUA_TTABLE) { - return luaL_error(L, "Invalid field %d", i+1); + while ((ttype = lua_geti(L, -1, i + 1)) != LUA_TNIL) + { + if (ttype != LUA_TTABLE) + { + return luaL_error(L, "Invalid field %d", i + 1); } - get_field(L, i+1, &f[i]); + get_field(L, i + 1, &f[i]); ++i; } key->field_n = i; @@ -1478,33 +1778,40 @@ get_key(struct entity_world *w, lua_State *L, struct group_key *key, struct fiel } static int -lgroupiter(lua_State *L) { +lgroupiter(lua_State *L) +{ struct entity_world *w = getW(L); luaL_checktype(L, 2, LUA_TTABLE); int nkey = get_len(L, 2); int field_n = 0; int i; - if (nkey == 0) { + if (nkey == 0) + { return luaL_error(L, "At least one key"); } - if (nkey > MAX_COMPONENT) { + if (nkey > MAX_COMPONENT) + { return luaL_error(L, "Too many keys"); } - for (i=0;ireadonly = 1; struct field *f = (struct field *)((char *)iter + header_size); iter->f = f; - for (i=0; i< nkey; i++) { - lua_geti(L, 2, i+1); + for (i = 0; i < nkey; i++) + { + lua_geti(L, 2, i + 1); int n = get_key(w, L, &iter->k[i], f); - if (i>0 && (iter->k[i-1].attrib & COMPONENT_REFINDEX)) { - iter->k[i].attrib |= COMPONENT_REFOBJECT; - } struct component_pool *c = &w->c[iter->k[i].id]; - if (c->stride == STRIDE_TAG && is_temporary(iter->k[i].attrib)) { + if (c->stride == STRIDE_TAG && is_temporary(iter->k[i].attrib)) + { return luaL_error(L, "%s is a tag, use %s?out instead", iter->k[i].name, iter->k[i].name); } f += n; lua_pop(L, 1); - if (c->stride == STRIDE_LUA) { + if (c->stride == STRIDE_LUA) + { if (n != 0) return luaL_error(L, ".%s is object component, no fields needed", iter->k[i].name); iter->k[i].attrib |= COMPONENT_OBJECT; - } else if (c->stride == STRIDE_ORDER) { - if (i != 0) { - return luaL_error(L, ".%s is an order key, must be main key", iter->k[i].name); - } else if (!(iter->k[0].attrib & COMPONENT_EXIST)) { - return luaL_error(L, ".%s is an order key, it can be exist", iter->k[0].name); + } + else if (c->stride == STRIDE_ORDER) + { + if (i != 0) + { + if (!is_temporary(iter->k[i].attrib)) + { + return luaL_error(L, ".%s is an order key, must be main key or temporary", iter->k[i].name); + } + } + else if (!(iter->k[0].attrib & COMPONENT_EXIST)) + { + return luaL_error(L, ".%s is an order key, it can only be exist", iter->k[0].name); } } int attrib = iter->k[i].attrib; - int readonly; - if (attrib & COMPONENT_FILTER) - readonly = 0; - else - readonly = (attrib & COMPONENT_IN) && !(attrib & COMPONENT_OUT); - if (!readonly) - iter->readonly = 0; + if (!(attrib & COMPONENT_FILTER)) + { + int readonly = (attrib & COMPONENT_IN) && !(attrib & COMPONENT_OUT); + if (!readonly) + iter->readonly = 0; + } } int mainkey_attrib = iter->k[0].attrib; - if (mainkey_attrib & COMPONENT_ABSENT) { + if (mainkey_attrib & COMPONENT_ABSENT) + { return luaL_error(L, "The main key can't be absent"); } - if (luaL_newmetatable(L, "ENTITY_GROUPITER")) { + if (luaL_newmetatable(L, "ENTITY_GROUPITER")) + { lua_pushcfunction(L, lpairs_group); lua_setfield(L, -2, "__call"); } @@ -1563,7 +1879,8 @@ lgroupiter(lua_State *L) { } static int -lremove(lua_State *L) { +lremove(lua_State *L) +{ struct entity_world *w = getW(L); luaL_checktype(L, 2, LUA_TTABLE); int iter = get_integer(L, 2, 1, "index") - 1; @@ -1573,58 +1890,78 @@ lremove(lua_State *L) { } static int -lobject(lua_State *L) { +lobject(lua_State *L) +{ struct group_iter *iter = luaL_checkudata(L, 1, "ENTITY_GROUPITER"); int index = luaL_checkinteger(L, 3) - 1; int cid = iter->k[0].id; - struct entity_world * w = iter->world; - if (cid < 0 || cid >=MAX_COMPONENT) { + struct entity_world *w = iter->world; + if (cid < 0 || cid >= MAX_COMPONENT) + { return luaL_error(L, "Invalid object %d", cid); } lua_settop(L, 2); struct component_pool *c = &w->c[cid]; - if (c->n <= index) { + if (c->n <= index) + { return luaL_error(L, "No object %d", cid); } - if (c->stride == STRIDE_LUA) { + if (c->stride == STRIDE_LUA) + { // lua object - if (lua_getiuservalue(L, 1, 1) != LUA_TUSERDATA) { + if (lua_getiuservalue(L, 1, 1) != LUA_TUSERDATA) + { return luaL_error(L, "No world"); } - if (lua_getiuservalue(L, -1, cid * 2 + 2) != LUA_TTABLE) { + if (lua_getiuservalue(L, -1, cid * 2 + 2) != LUA_TTABLE) + { return luaL_error(L, "Missing lua table for %d", cid); } - if (lua_isnil(L, 2)) { + if (lua_isnil(L, 2)) + { lua_rawgeti(L, -1, index + 1); - } else { + } + else + { lua_pushvalue(L, 2); lua_rawseti(L, -2, index + 1); lua_settop(L, 2); } return 1; - } else if (c->stride == 0) { + } + else if (c->stride == 0) + { if (lua_type(L, 2) != LUA_TBOOLEAN) return luaL_error(L, "%s is a tag, need boolean", iter->k[0].name); - if (!lua_toboolean(L, 2)) { + if (!lua_toboolean(L, 2)) + { entity_disable_tag_(w, cid, index, cid); } return 1; - } else if (c->stride < 0) { + } + else if (c->stride < 0) + { return luaL_error(L, "Invalid object %d", cid); } struct field *f = iter->f; - void * buffer = get_ptr(c, index); - if (lua_isnoneornil(L, 2)) { + void *buffer = get_ptr(c, index); + if (lua_isnoneornil(L, 2)) + { // read object - if (f->key == NULL) { + if (f->key == NULL) + { // value type read_value(L, f, buffer); - } else { + } + else + { lua_createtable(L, 0, iter->k[0].field_n); int lua_index = lua_gettop(L); read_component(L, iter->k[0].field_n, f, lua_index, buffer); } - } else { + } + else + { // write object lua_pushvalue(L, 2); write_component_object(L, iter->k[0].field_n, f, buffer); @@ -1633,7 +1970,8 @@ lobject(lua_State *L) { } static int -lrelease(lua_State *L) { +lrelease(lua_State *L) +{ struct entity_world *w = getW(L); int cid = check_cid(L, w, 2); int refid = luaL_checkinteger(L, 3) - 1; @@ -1643,12 +1981,14 @@ lrelease(lua_State *L) { } static int -lreuse(lua_State *L) { +lreuse(lua_State *L) +{ struct entity_world *w = getW(L); int cid = check_cid(L, w, 2); int dead_tagid = cid + 1; struct component_pool *c = &w->c[dead_tagid]; - if (c->stride != STRIDE_TAG) { + if (c->stride != STRIDE_TAG) + { return luaL_error(L, "%d is not a tag", dead_tagid); } if (c->n == 0) @@ -1662,15 +2002,20 @@ lreuse(lua_State *L) { } static int -find_boundary(int from, int to, unsigned int *a, unsigned int eid) { - while(from < to) { - int mid = (from + to)/2; +find_boundary(int from, int to, unsigned int *a, unsigned int eid) +{ + while (from < to) + { + int mid = (from + to) / 2; unsigned int aa = a[mid]; if (aa == eid) return mid; - else if (aa < eid) { + else if (aa < eid) + { from = mid + 1; - } else { + } + else + { to = mid; } } @@ -1678,10 +2023,13 @@ find_boundary(int from, int to, unsigned int *a, unsigned int eid) { } static inline int -next_removed_index(int removed_index, struct component_pool *removed, unsigned int *removed_eid) { - for (;;) { +next_removed_index(int removed_index, struct component_pool *removed, unsigned int *removed_eid) +{ + for (;;) + { ++removed_index; - if (removed_index >= removed->n) { + if (removed_index >= removed->n) + { *removed_eid = 0; break; } @@ -1695,35 +2043,41 @@ next_removed_index(int removed_index, struct component_pool *removed, unsigned i // remove reference object where id == 0 static void -remove_unused_reference(lua_State *L, struct component_pool *c, int from) { +remove_unused_reference(lua_State *L, struct component_pool *c, int from) +{ int i; int to = from; - for (i=from; i < c->n; i++) { - if (c->id[i]) { + for (i = from; i < c->n; i++) + { + if (c->id[i]) + { c->id[to] = c->id[i]; - lua_geti(L, -1, i+1); - lua_seti(L, -2, to+1); + lua_geti(L, -1, i + 1); + lua_seti(L, -2, to + 1); ++to; } } - for (i=to;i<=c->n;i++) { + for (i = to; i <= c->n; i++) + { lua_pushnil(L); - lua_seti(L, -2, i+1); + lua_seti(L, -2, i + 1); } c->n = to; } static int -lupdate_reference(lua_State *L) { +lupdate_reference(lua_State *L) +{ struct entity_world *w = getW(L); struct component_pool *removed = &w->c[ENTITY_REMOVED]; - if (removed->n == 0) // no removed entity + if (removed->n == 0) // no removed entity return 0; int cid = check_cid(L, w, 2); struct component_pool *reference = &w->c[cid]; if (reference->n == 0) - return 0; // no reference - if (lua_getiuservalue(L, 1, cid * 2 + 2) != LUA_TTABLE) { + return 0; // no reference + if (lua_getiuservalue(L, 1, cid * 2 + 2) != LUA_TTABLE) + { return luaL_error(L, "Invalid reference component %d", cid); } int i; @@ -1732,94 +2086,113 @@ lupdate_reference(lua_State *L) { int index = find_boundary(0, reference->n, reference->id, removed_eid); int reference_index = index + 1; int removed_reference = 0; - for (i=index; i< reference->n; i++) { - int rtype = lua_geti(L, -1, i+1); - if (rtype != LUA_TBOOLEAN && rtype != LUA_TTABLE) { + for (i = index; i < reference->n; i++) + { + int rtype = lua_geti(L, -1, i + 1); + if (rtype != LUA_TBOOLEAN && rtype != LUA_TTABLE) + { // false means removed reference return luaL_error(L, "Invalid reference object"); } - if (removed_eid) { - while (removed_eid < reference->id[i]) { + if (removed_eid) + { + while (removed_eid < reference->id[i]) + { removed_index = next_removed_index(removed_index, removed, &removed_eid); } } - if (removed_eid == reference->id[i]) { + if (removed_eid == reference->id[i]) + { // removed reference, clear reference id lua_pushnil(L); removed_index = next_removed_index(removed_index, removed, &removed_eid); - } else { + } + else + { // update reference id lua_pushinteger(L, reference_index); ++reference_index; } - if (rtype == LUA_TBOOLEAN) { + if (rtype == LUA_TBOOLEAN) + { // set id = 0, so removed_reference() can remove them --reference_index; reference->id[i] = 0; - if (removed_reference == 0) { + if (removed_reference == 0) + { removed_reference = i + 1; } lua_pop(L, 2); - } else { + } + else + { lua_seti(L, -2, 1); lua_pop(L, 1); } } - if (removed_reference) { + if (removed_reference) + { remove_unused_reference(L, reference, removed_reference - 1); } return 0; } static int -ldumpid(lua_State *L) { +ldumpid(lua_State *L) +{ struct entity_world *w = getW(L); int cid = check_cid(L, w, 2); struct component_pool *c = &w->c[cid]; lua_createtable(L, c->n, 0); int i; - for (i=0;in;i++) { + for (i = 0; i < c->n; i++) + { lua_pushinteger(L, c->id[i]); - lua_rawseti(L, -2, i+1); + lua_rawseti(L, -2, i + 1); } return 1; } LUAMOD_API int -luaopen_ecs_core(lua_State *L) { +luaopen_ecs_core(lua_State *L) +{ luaL_checkversion(L); luaL_Reg l[] = { - { "_world", lnew_world }, - { NULL, NULL }, + {"_world", lnew_world}, + {NULL, NULL}, }; - luaL_newlib(L,l); - lua_pushinteger(L, MAX_COMPONENT-1); + luaL_newlib(L, l); + lua_pushinteger(L, MAX_COMPONENT - 1); lua_setfield(L, -2, "_MAXTYPE"); - if (luaL_newmetatable(L, "ENTITY_WORLD")) { + if (luaL_newmetatable(L, "ENTITY_WORLD")) + { luaL_Reg l[] = { - { "__index", NULL }, - { "memory", lcount_memory }, - { "collect", lcollect_memory }, - { "_newtype",lnew_type }, - { "_newentity", lnew_entity }, - { "_addcomponent", ladd_component }, - { "_update", lupdate }, - { "_clear", lclear_type }, - { "_context", lcontext }, - { "_groupiter", lgroupiter }, - { "remove", lremove }, - { "_object", lobject }, - { "_sync", lsync }, - { "_release", lrelease }, - { "_reuse", lreuse }, - { "_update_reference", lupdate_reference }, - { "_dumpid", ldumpid }, - { NULL, NULL }, + {"__index", NULL}, + {"memory", lcount_memory}, + {"collect", lcollect_memory}, + {"_newtype", lnew_type}, + {"_newentity", lnew_entity}, + {"_addcomponent", ladd_component}, + {"_update", lupdate}, + {"_clear", lclear_type}, + {"_context", lcontext}, + {"_groupiter", lgroupiter}, + {"remove", lremove}, + {"_object", lobject}, + {"_sync", lsync}, + {"_read", lread}, + {"_release", lrelease}, + {"_reuse", lreuse}, + {"_update_reference", lupdate_reference}, + {"_dumpid", ldumpid}, + {NULL, NULL}, }; - luaL_setfuncs(L,l,0); + luaL_setfuncs(L, l, 0); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); - } else { + } + else + { return luaL_error(L, "ENTITY_WORLD exist"); } lua_setfield(L, -2, "_METHODS"); @@ -1862,28 +2235,34 @@ luaopen_ecs_core(lua_State *L) { #define COMPONENT_ID 3 #define SINGLETON_STRING 4 -struct vector2 { +struct vector2 +{ float x; float y; }; -struct id { +struct id +{ int v; }; static int -ltest(lua_State *L) { +ltest(lua_State *L) +{ struct ecs_context *ctx = lua_touserdata(L, 1); struct vector2 *v; int i; - for (i=0;(v=(struct vector2 *)entity_iter(ctx, COMPONENT_VECTOR2, i));i++) { + for (i = 0; (v = (struct vector2 *)entity_iter(ctx, COMPONENT_VECTOR2, i)); i++) + { printf("vector2 %d: x=%f y=%f\n", i, v->x, v->y); - struct id * id = (struct id *)entity_sibling(ctx, COMPONENT_VECTOR2, i, COMPONENT_ID); - if (id) { + struct id *id = (struct id *)entity_sibling(ctx, COMPONENT_VECTOR2, i, COMPONENT_ID); + if (id) + { printf("\tid = %d\n", id->v); } - void * mark = entity_sibling(ctx, COMPONENT_VECTOR2, i, TAG_MARK); - if (mark) { + void *mark = entity_sibling(ctx, COMPONENT_VECTOR2, i, TAG_MARK); + if (mark) + { printf("\tMARK\n"); } } @@ -1892,12 +2271,14 @@ ltest(lua_State *L) { } static int -lsum(lua_State *L) { +lsum(lua_State *L) +{ struct ecs_context *ctx = lua_touserdata(L, 1); struct vector2 *v; int i; float s = 0; - for (i=0;(v=(struct vector2 *)entity_iter(ctx, COMPONENT_VECTOR2, i));i++) { + for (i = 0; (v = (struct vector2 *)entity_iter(ctx, COMPONENT_VECTOR2, i)); i++) + { s += v->x + v->y; } lua_pushnumber(L, s); @@ -1905,22 +2286,26 @@ lsum(lua_State *L) { } static int -lget(lua_State *L) { +lget(lua_State *L) +{ struct ecs_context *ctx = lua_touserdata(L, 1); const char *ret = (const char *)entity_ref_object(ctx, SINGLETON_STRING, 1); - if (ret) { + if (ret) + { lua_pushfstring(L, "[string:%s]", ret); } return 1; } -struct userdata_t { +struct userdata_t +{ unsigned char a; void *b; }; static int -ltestuserdata(lua_State *L) { +ltestuserdata(lua_State *L) +{ struct ecs_context *ctx = lua_touserdata(L, 1); struct userdata_t *ud = entity_iter(ctx, 1, 0); ud->a = 1 - ud->a; @@ -1929,17 +2314,18 @@ ltestuserdata(lua_State *L) { } LUAMOD_API int -luaopen_ecs_ctest(lua_State *L) { +luaopen_ecs_ctest(lua_State *L) +{ luaL_checkversion(L); luaL_Reg l[] = { - { "test", ltest }, - { "sum", lsum }, - { "get", lget }, - { "testuserdata", ltestuserdata }, - { NULL, NULL }, + {"test", ltest}, + {"sum", lsum}, + {"get", lget}, + {"testuserdata", ltestuserdata}, + {NULL, NULL}, }; luaL_newlib(L, l); return 1; } -#endif +#endif \ No newline at end of file diff --git a/code/framework/lualib-src/lua-ecs/luaecs.h b/code/framework/lualib-src/lua-ecs/luaecs.h index ad56212..d35c0c4 100644 --- a/code/framework/lualib-src/lua-ecs/luaecs.h +++ b/code/framework/lualib-src/lua-ecs/luaecs.h @@ -5,40 +5,45 @@ struct entity_world; -struct ecs_capi { - void * (*iter)(struct entity_world *w, int cid, int index); +struct ecs_capi +{ + void *(*iter)(struct entity_world *w, int cid, int index); void (*clear_type)(struct entity_world *w, int cid); int (*sibling_id)(struct entity_world *w, int cid, int index, int slibling_id); - void* (*add_sibling)(struct entity_world *w, int cid, int index, int slibling_id, const void *buffer, void *L, int world_index); + void *(*add_sibling)(struct entity_world *w, int cid, int index, int slibling_id, const void *buffer, void *L, int world_index); int (*new_entity)(struct entity_world *w, int cid, const void *buffer, void *L, int world_index); void (*remove)(struct entity_world *w, int cid, int index, void *L, int world_index); void (*enable_tag)(struct entity_world *w, int cid, int index, int tag_id, void *L, int world_index); void (*disable_tag)(struct entity_world *w, int cid, int index, int tag_id); - void * (*iter_lua)(struct entity_world *w, int cid, int index, void *L, int world_index); + void *(*iter_lua)(struct entity_world *w, int cid, int index, void *L, int world_index); int (*assign_lua)(struct entity_world *w, int cid, int index, void *L, int world_index); }; -struct ecs_context { +struct ecs_context +{ struct ecs_capi *api; struct entity_world *world; - void *L; // for memory allocator + void *L; // for memory allocator int max_id; int cid[1]; }; static inline void -check_id_(struct ecs_context *ctx, int cid) { +check_id_(struct ecs_context *ctx, int cid) +{ assert(cid >= 0 && cid <= ctx->max_id); } static inline void * -entity_iter(struct ecs_context *ctx, int cid, int index) { +entity_iter(struct ecs_context *ctx, int cid, int index) +{ check_id_(ctx, cid); return ctx->api->iter(ctx->world, ctx->cid[cid], index); } static inline void * -entity_ref_object(struct ecs_context *ctx, int cid, int index) { +entity_ref_object(struct ecs_context *ctx, int cid, int index) +{ if (index <= 0) return NULL; check_id_(ctx, cid); @@ -46,96 +51,113 @@ entity_ref_object(struct ecs_context *ctx, int cid, int index) { } static inline void -entity_clear_type(struct ecs_context *ctx, int cid) { +entity_clear_type(struct ecs_context *ctx, int cid) +{ check_id_(ctx, cid); ctx->api->clear_type(ctx->world, ctx->cid[cid]); } static inline int -entity_sibling_id(struct ecs_context *ctx, int cid, int index, int sibling_id) { +entity_sibling_id(struct ecs_context *ctx, int cid, int index, int sibling_id) +{ check_id_(ctx, cid); check_id_(ctx, sibling_id); - return ctx->api->sibling_id(ctx->world, ctx->cid[cid], index, ctx->cid[sibling_id]); + return ctx->api->sibling_id(ctx->world, ctx->cid[cid], index, ctx->cid[sibling_id]); } static inline void * -entity_sibling(struct ecs_context *ctx, int cid, int index, int sibling_id) { +entity_sibling(struct ecs_context *ctx, int cid, int index, int sibling_id) +{ check_id_(ctx, cid); check_id_(ctx, sibling_id); - int id = ctx->api->sibling_id(ctx->world, ctx->cid[cid], index, ctx->cid[sibling_id]); - if (id == 0) { + int id = ctx->api->sibling_id(ctx->world, ctx->cid[cid], index, ctx->cid[sibling_id]); + if (id == 0) + { return NULL; - } else { - return ctx->api->iter(ctx->world, ctx->cid[sibling_id], id-1); + } + else + { + return ctx->api->iter(ctx->world, ctx->cid[sibling_id], id - 1); } } static inline void * -entity_add_sibling(struct ecs_context *ctx, int cid, int index, int sibling_id, const void *buffer) { +entity_add_sibling(struct ecs_context *ctx, int cid, int index, int sibling_id, const void *buffer) +{ check_id_(ctx, cid); check_id_(ctx, sibling_id); return ctx->api->add_sibling(ctx->world, ctx->cid[cid], index, ctx->cid[sibling_id], buffer, ctx->L, 1); } static inline int -entity_new(struct ecs_context *ctx, int cid, const void *buffer) { +entity_new(struct ecs_context *ctx, int cid, const void *buffer) +{ check_id_(ctx, cid); return ctx->api->new_entity(ctx->world, ctx->cid[cid], buffer, ctx->L, 1); } static inline void -entity_remove(struct ecs_context *ctx, int cid, int index) { +entity_remove(struct ecs_context *ctx, int cid, int index) +{ check_id_(ctx, cid); ctx->api->remove(ctx->world, ctx->cid[cid], index, ctx->L, 1); } static inline void -entity_enable_tag(struct ecs_context *ctx, int cid, int index, int tag_id) { +entity_enable_tag(struct ecs_context *ctx, int cid, int index, int tag_id) +{ check_id_(ctx, cid); check_id_(ctx, tag_id); ctx->api->enable_tag(ctx->world, ctx->cid[cid], index, ctx->cid[tag_id], ctx->L, 1); } static inline void -entity_disable_tag(struct ecs_context *ctx, int cid, int index, int tag_id) { +entity_disable_tag(struct ecs_context *ctx, int cid, int index, int tag_id) +{ check_id_(ctx, cid); check_id_(ctx, tag_id); ctx->api->disable_tag(ctx->world, ctx->cid[cid], index, ctx->cid[tag_id]); } static inline int -entity_assign_lua(struct ecs_context *ctx, int cid, int index) { +entity_assign_lua(struct ecs_context *ctx, int cid, int index) +{ check_id_(ctx, cid); assert(index > 0); - return ctx->api->assign_lua(ctx->world, ctx->cid[cid], index-1, ctx->L, 1); + return ctx->api->assign_lua(ctx->world, ctx->cid[cid], index - 1, ctx->L, 1); } static inline int -entity_new_ref(struct ecs_context *ctx, int cid) { +entity_new_ref(struct ecs_context *ctx, int cid) +{ check_id_(ctx, cid); int object_id = ctx->cid[cid]; int dead_tag = object_id + 1; int id; - if (ctx->api->iter(ctx->world, dead_tag, 0)) { + if (ctx->api->iter(ctx->world, dead_tag, 0)) + { // reuse id = ctx->api->sibling_id(ctx->world, dead_tag, 0, object_id); assert(id > 0); --id; ctx->api->disable_tag(ctx->world, dead_tag, 0, dead_tag); - } else { + } + else + { id = ctx->api->new_entity(ctx->world, object_id, NULL, ctx->L, 1); } return id + 1; } static inline void -entity_release_ref(struct ecs_context *ctx, int cid, int id) { +entity_release_ref(struct ecs_context *ctx, int cid, int id) +{ if (id == 0) return; check_id_(ctx, cid); int object_id = ctx->cid[cid]; int dead_tag = object_id + 1; - ctx->api->enable_tag(ctx->world, object_id, id-1, dead_tag, ctx->L, 1); + ctx->api->enable_tag(ctx->world, object_id, id - 1, dead_tag, ctx->L, 1); } #endif diff --git a/code/framework/lualib-src/lua-ecs/test.lua b/code/framework/lualib-src/lua-ecs/test.lua index 09e08b3..28bf32c 100644 --- a/code/framework/lualib-src/lua-ecs/test.lua +++ b/code/framework/lualib-src/lua-ecs/test.lua @@ -218,7 +218,7 @@ w:register { type = "float", } -for v in w:select "vector:in sum:temp" do +for v in w:select "vector:in sum:new" do print(v.vector.x, "+", v.vector.y) v.sum = v.vector.x + v.vector.y end diff --git a/code/framework/lualib-src/lua-ecs/test10.lua b/code/framework/lualib-src/lua-ecs/test10.lua index f6aa67a..cf705c0 100644 --- a/code/framework/lualib-src/lua-ecs/test10.lua +++ b/code/framework/lualib-src/lua-ecs/test10.lua @@ -17,7 +17,7 @@ for i = 1, 10 do end local function add_temp(i) - for v in w:select "key:in temp?temp" do + for v in w:select "key:in temp?new" do if v.key == i then v.temp = "Temp" end diff --git a/code/framework/lualib-src/lua-ecs/test11.lua b/code/framework/lualib-src/lua-ecs/test11.lua index a7c7c91..72c88fe 100644 --- a/code/framework/lualib-src/lua-ecs/test11.lua +++ b/code/framework/lualib-src/lua-ecs/test11.lua @@ -18,11 +18,28 @@ w:new { order = true, } +w:new { + node = { id = 2, parent = 0 }, + order = true, +} + w:new { node = { id = 0, parent = -1 }, +} + +w:new { + node = { id = 3, parent = 0 }, order = true, } +for v in w:select "node:in order?new" do + assert(v.order == nil) + if v.node.parent < 0 then + -- add order + v.order = true + end +end + local cache = {} for v in w:select "order node:update" do diff --git a/code/framework/lualib-src/lua-ecs/test2.lua b/code/framework/lualib-src/lua-ecs/test2.lua index c797f8a..7b33daf 100644 --- a/code/framework/lualib-src/lua-ecs/test2.lua +++ b/code/framework/lualib-src/lua-ecs/test2.lua @@ -1,51 +1,31 @@ --- test sort - local ecs = require "ecs" local w = ecs.world() w:register { - name = "data", - type = "float", + name = "value", + type = "int", } w:register { - name = "index", - type = "int", + name = "mark" } -local tmp = { 10,9,8,7,6,5,4,3,2,1 } - -for i = 1, 10, 2 do - w:new { data = tmp[i], index = tmp[i] } - w:new { index = tmp[i+1] } -end - -w:update() - -for v in w:select "index data?in" do - print(v.data) -end - -w:sort("sort", "index") - -print "sorted" - -for v in w:select "sort data:in index:in" do - print(v.data, v.index) -end +w:new { + value = 1, + mark = true, +} -w:register { - name = "sorted_index", - type = "int", +w:new { + mark = true, } -for i = 1, 10 do - w:new { data = i * 0.5 , sorted_index = i * 2 } -end -for v in w:select "sorted_index:in data?in" do - print(v.sorted_index, "=>", v.data) +for v in w:select "mark" do + w:sync("value?in", v) + print(v.value) end ---w:remove(iter) +for v in w:select "mark" do + print(pcall(w.sync, w, "value:in", v)) +end diff --git a/code/framework/lualib-src/lua-ecs/test3.lua b/code/framework/lualib-src/lua-ecs/test3.lua index f922c60..a8c9e76 100644 --- a/code/framework/lualib-src/lua-ecs/test3.lua +++ b/code/framework/lualib-src/lua-ecs/test3.lua @@ -53,74 +53,3 @@ for v in w:select "index:in" do print(v.index) end -print "Index refobject" - -for v in w:select "refobject(index):in" do - print(v.refobject) -end - -w:register { - name = "name", - type = "lua", -} - -w:new { - name = "Hello" -} - -for v in w:select "index:in" do - print(v.index) -end - - -for v in w:select "name refobject(index):temp" do - v.refobject = 42 -end - -for v in w:select "refobject:in" do - print(v.refobject) -end - -for v in w:select "refobject(index):update" do - v.refobject = v.refobject + 1 -end - -for v in w:select "refobject:in" do - print(v.refobject) -end - -w:register { - name = "mark" -} - -local ref = w:object_ref("refobject", id4) -ref.mark = true -w:sync("mark?out", ref) - - -w:ref ("refobject", { - refobject = 42, - mark = true -}) - -print "Marked refobject" - -for v in w:select "mark refobject?in" do - print(v.refobject) -end - -w:register { - name = "refobject2", - type = "int", - ref = true, -} - -w:register { - name = "mark2" -} - -w:ref ("refobject2", { - refobject2 = 42, - mark = true, - mark2 = false, -}) diff --git a/code/framework/lualib-src/lua-ecs/test5.lua b/code/framework/lualib-src/lua-ecs/test5.lua index f7495a5..4f930a4 100644 --- a/code/framework/lualib-src/lua-ecs/test5.lua +++ b/code/framework/lualib-src/lua-ecs/test5.lua @@ -20,7 +20,7 @@ for v in w:select "a:in" do if v.a %2 == 0 then v.a = -v.a v.temp = 42 - w:sync("a:out temp:temp", v) + w:sync("a:out temp:new", v) end end diff --git a/code/framework/lualib-src/lua-ecs/test6.lua b/code/framework/lualib-src/lua-ecs/test6.lua index b1e3d55..4cf3050 100644 --- a/code/framework/lualib-src/lua-ecs/test6.lua +++ b/code/framework/lualib-src/lua-ecs/test6.lua @@ -8,6 +8,10 @@ w:register { "b:userdata", } +w:register { + name = "flag", +} + w:new { t = { a = false, @@ -36,3 +40,23 @@ print_v() test.testuserdata(ctx) print_v() + +local v = w:singleton("t", "flag t:in") +assert(v == nil) + +-- remove singleton of t +local v = w:singleton "t" +w:remove(v) +w:update() + + +w:new { + t = { + a = true, + b = ecs.NULL, + }, + flag = true, +} + +local v = w:singleton("t", "flag t:in") +assert(v.t.a == true) diff --git a/code/framework/lualib/3rd/misc/argparse.lua b/code/framework/lualib/3rd/misc/argparse.lua new file mode 100644 index 0000000..80c8da6 --- /dev/null +++ b/code/framework/lualib/3rd/misc/argparse.lua @@ -0,0 +1,2099 @@ +-- The MIT License (MIT) +-- Copyright (c) 2013 - 2018 Peter Melnichenko +-- 2019 Paul Ouellette +-- 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. +local function deep_update(t1, t2) + for k, v in pairs(t2) do + if type(v) == "table" then + v = deep_update({}, v) + end + + t1[k] = v + end + + return t1 +end + +-- A property is a tuple {name, callback}. +-- properties.args is number of properties that can be set as arguments +-- when calling an object. +local function class(prototype, properties, parent) + -- Class is the metatable of its instances. + local cl = {} + cl.__index = cl + + if parent then + cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype) + else + cl.__prototype = prototype + end + + if properties then + local names = {} + + -- Create setter methods and fill set of property names. + for _, property in ipairs(properties) do + local name, callback = property[1], property[2] + + cl[name] = function(self, value) + if not callback(self, value) then + self["_" .. name] = value + end + + return self + end + + names[name] = true + end + + function cl.__call(self, ...) + -- When calling an object, if the first argument is a table, + -- interpret keys as property names, else delegate arguments + -- to corresponding setters in order. + if type((...)) == "table" then + for name, value in pairs((...)) do + if names[name] then + self[name](self, value) + end + end + else + local nargs = select("#", ...) + + for i, property in ipairs(properties) do + if i > nargs or i > properties.args then + break + end + + local arg = select(i, ...) + + if arg ~= nil then + self[property[1]](self, arg) + end + end + end + + return self + end + end + + -- If indexing class fails, fallback to its parent. + local class_metatable = {} + class_metatable.__index = parent + + function class_metatable.__call(self, ...) + -- Calling a class returns its instance. + -- Arguments are delegated to the instance. + local object = deep_update({}, self.__prototype) + setmetatable(object, self) + return object(...) + end + + return setmetatable(cl, class_metatable) +end + +local function typecheck(name, types, value) + for _, type_ in ipairs(types) do + if type(value) == type_ then + return true + end + end + + error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value))) +end + +local function typechecked(name, ...) + local types = {...} + return {name, function(_, value) + typecheck(name, types, value) + end} +end + +local multiname = {"name", function(self, value) + typecheck("name", {"string"}, value) + + for alias in value:gmatch("%S+") do + self._name = self._name or alias + table.insert(self._aliases, alias) + table.insert(self._public_aliases, alias) + -- If alias contains '_', accept '-' also. + if alias:find("_", 1, true) then + table.insert(self._aliases, (alias:gsub("_", "-"))) + end + end + + -- Do not set _name as with other properties. + return true +end} + +local multiname_hidden = {"hidden_name", function(self, value) + typecheck("hidden_name", {"string"}, value) + + for alias in value:gmatch("%S+") do + table.insert(self._aliases, alias) + if alias:find("_", 1, true) then + table.insert(self._aliases, (alias:gsub("_", "-"))) + end + end + + return true +end} + +local function parse_boundaries(str) + if tonumber(str) then + return tonumber(str), tonumber(str) + end + + if str == "*" then + return 0, math.huge + end + + if str == "+" then + return 1, math.huge + end + + if str == "?" then + return 0, 1 + end + + if str:match "^%d+%-%d+$" then + local min, max = str:match "^(%d+)%-(%d+)$" + return tonumber(min), tonumber(max) + end + + if str:match "^%d+%+$" then + local min = str:match "^(%d+)%+$" + return tonumber(min), math.huge + end +end + +local function boundaries(name) + return {name, function(self, value) + typecheck(name, {"number", "string"}, value) + + local min, max = parse_boundaries(value) + + if not min then + error(("bad property '%s'"):format(name)) + end + + self["_min" .. name], self["_max" .. name] = min, max + end} +end + +local actions = {} + +local option_action = {"action", function(_, value) + typecheck("action", {"function", "string"}, value) + + if type(value) == "string" and not actions[value] then + error(("unknown action '%s'"):format(value)) + end +end} + +local option_init = {"init", function(self) + self._has_init = true +end} + +local option_default = {"default", function(self, value) + if type(value) ~= "string" then + self._init = value + self._has_init = true + return true + end +end} + +local add_help = {"add_help", function(self, value) + typecheck("add_help", {"boolean", "string", "table"}, value) + + if self._help_option_idx then + table.remove(self._options, self._help_option_idx) + self._help_option_idx = nil + end + + if value then + local help = self:flag():description "Show this help message and exit.":action(function() + print(self:get_help()) + os.exit(0) + end) + + if value ~= true then + help = help(value) + end + + if not help._name then + help "-h" "--help" + end + + self._help_option_idx = #self._options + end +end} + +local Parser = class({ + _arguments = {}, + _options = {}, + _commands = {}, + _mutexes = {}, + _groups = {}, + _require_command = true, + _handle_options = true, +}, { + args = 3, + typechecked("name", "string"), + typechecked("description", "string"), + typechecked("epilog", "string"), + typechecked("usage", "string"), + typechecked("help", "string"), + typechecked("require_command", "boolean"), + typechecked("handle_options", "boolean"), + typechecked("action", "function"), + typechecked("command_target", "string"), + typechecked("help_vertical_space", "number"), + typechecked("usage_margin", "number"), + typechecked("usage_max_width", "number"), + typechecked("help_usage_margin", "number"), + typechecked("help_description_margin", "number"), + typechecked("help_max_width", "number"), + add_help, +}) + +local Command = class({ + _aliases = {}, + _public_aliases = {}, +}, { + args = 3, + multiname, + typechecked("description", "string"), + typechecked("epilog", "string"), + multiname_hidden, + typechecked("summary", "string"), + typechecked("target", "string"), + typechecked("usage", "string"), + typechecked("help", "string"), + typechecked("require_command", "boolean"), + typechecked("handle_options", "boolean"), + typechecked("action", "function"), + typechecked("command_target", "string"), + typechecked("help_vertical_space", "number"), + typechecked("usage_margin", "number"), + typechecked("usage_max_width", "number"), + typechecked("help_usage_margin", "number"), + typechecked("help_description_margin", "number"), + typechecked("help_max_width", "number"), + typechecked("hidden", "boolean"), + add_help, +}, Parser) + +local Argument = class({ + _minargs = 1, + _maxargs = 1, + _mincount = 1, + _maxcount = 1, + _defmode = "unused", + _show_default = true, +}, { + args = 5, + typechecked("name", "string"), + typechecked("description", "string"), + option_default, + typechecked("convert", "function", "table"), + boundaries("args"), + typechecked("target", "string"), + typechecked("defmode", "string"), + typechecked("show_default", "boolean"), + typechecked("argname", "string", "table"), + typechecked("choices", "table"), + typechecked("hidden", "boolean"), + option_action, + option_init, +}) + +local Option = class({ + _aliases = {}, + _public_aliases = {}, + _mincount = 0, + _overwrite = true, +}, { + args = 6, + multiname, + typechecked("description", "string"), + option_default, + typechecked("convert", "function", "table"), + boundaries("args"), + boundaries("count"), + multiname_hidden, + typechecked("target", "string"), + typechecked("defmode", "string"), + typechecked("show_default", "boolean"), + typechecked("overwrite", "boolean"), + typechecked("argname", "string", "table"), + typechecked("choices", "table"), + typechecked("hidden", "boolean"), + option_action, + option_init, +}, Argument) + +function Parser:_inherit_property(name, default) + local element = self + + while true do + local value = element["_" .. name] + + if value ~= nil then + return value + end + + if not element._parent then + return default + end + + element = element._parent + end +end + +function Argument:_get_argument_list() + local buf = {} + local i = 1 + + while i <= math.min(self._minargs, 3) do + local argname = self:_get_argname(i) + + if self._default and self._defmode:find "a" then + argname = "[" .. argname .. "]" + end + + table.insert(buf, argname) + i = i + 1 + end + + while i <= math.min(self._maxargs, 3) do + table.insert(buf, "[" .. self:_get_argname(i) .. "]") + i = i + 1 + + if self._maxargs == math.huge then + break + end + end + + if i < self._maxargs then + table.insert(buf, "...") + end + + return buf +end + +function Argument:_get_usage() + local usage = table.concat(self:_get_argument_list(), " ") + + if self._default and self._defmode:find "u" then + if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then + usage = "[" .. usage .. "]" + end + end + + return usage +end + +function actions.store_true(result, target) + result[target] = true +end + +function actions.store_false(result, target) + result[target] = false +end + +function actions.store(result, target, argument) + result[target] = argument +end + +function actions.count(result, target, _, overwrite) + if not overwrite then + result[target] = result[target] + 1 + end +end + +function actions.append(result, target, argument, overwrite) + result[target] = result[target] or {} + table.insert(result[target], argument) + + if overwrite then + table.remove(result[target], 1) + end +end + +function actions.concat(result, target, arguments, overwrite) + if overwrite then + error("'concat' action can't handle too many invocations") + end + + result[target] = result[target] or {} + + for _, argument in ipairs(arguments) do + table.insert(result[target], argument) + end +end + +function Argument:_get_action() + local action, init + + if self._maxcount == 1 then + if self._maxargs == 0 then + action, init = "store_true", nil + else + action, init = "store", nil + end + else + if self._maxargs == 0 then + action, init = "count", 0 + else + action, init = "append", {} + end + end + + if self._action then + action = self._action + end + + if self._has_init then + init = self._init + end + + if type(action) == "string" then + action = actions[action] + end + + return action, init +end + +-- Returns placeholder for `narg`-th argument. +function Argument:_get_argname(narg) + local argname = self._argname or self:_get_default_argname() + + if type(argname) == "table" then + return argname[narg] + else + return argname + end +end + +function Argument:_get_choices_list() + return "{" .. table.concat(self._choices, ",") .. "}" +end + +function Argument:_get_default_argname() + if self._choices then + return self:_get_choices_list() + else + return "<" .. self._name .. ">" + end +end + +function Option:_get_default_argname() + if self._choices then + return self:_get_choices_list() + else + return "<" .. self:_get_default_target() .. ">" + end +end + +-- Returns labels to be shown in the help message. +function Argument:_get_label_lines() + if self._choices then + return {self:_get_choices_list()} + else + return {self._name} + end +end + +function Option:_get_label_lines() + local argument_list = self:_get_argument_list() + + if #argument_list == 0 then + -- Don't put aliases for simple flags like `-h` on different lines. + return {table.concat(self._public_aliases, ", ")} + end + + local longest_alias_length = -1 + + for _, alias in ipairs(self._public_aliases) do + longest_alias_length = math.max(longest_alias_length, #alias) + end + + local argument_list_repr = table.concat(argument_list, " ") + local lines = {} + + for i, alias in ipairs(self._public_aliases) do + local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr + + if i ~= #self._public_aliases then + line = line .. "," + end + + table.insert(lines, line) + end + + return lines +end + +function Command:_get_label_lines() + return {table.concat(self._public_aliases, ", ")} +end + +function Argument:_get_description() + if self._default and self._show_default then + if self._description then + return ("%s (default: %s)"):format(self._description, self._default) + else + return ("default: %s"):format(self._default) + end + else + return self._description or "" + end +end + +function Command:_get_description() + return self._summary or self._description or "" +end + +function Option:_get_usage() + local usage = self:_get_argument_list() + table.insert(usage, 1, self._name) + usage = table.concat(usage, " ") + + if self._mincount == 0 or self._default then + usage = "[" .. usage .. "]" + end + + return usage +end + +function Argument:_get_default_target() + return self._name +end + +function Option:_get_default_target() + local res + + for _, alias in ipairs(self._public_aliases) do + if alias:sub(1, 1) == alias:sub(2, 2) then + res = alias:sub(3) + break + end + end + + res = res or self._name:sub(2) + return (res:gsub("-", "_")) +end + +function Option:_is_vararg() + return self._maxargs ~= self._minargs +end + +function Parser:_get_fullname(exclude_root) + local parent = self._parent + if exclude_root and not parent then + return "" + end + local buf = {self._name} + + while parent do + if not exclude_root or parent._parent then + table.insert(buf, 1, parent._name) + end + parent = parent._parent + end + + return table.concat(buf, " ") +end + +function Parser:_update_charset(charset) + charset = charset or {} + + for _, command in ipairs(self._commands) do + command:_update_charset(charset) + end + + for _, option in ipairs(self._options) do + for _, alias in ipairs(option._aliases) do + charset[alias:sub(1, 1)] = true + end + end + + return charset +end + +function Parser:argument(...) + local argument = Argument(...) + table.insert(self._arguments, argument) + return argument +end + +function Parser:option(...) + local option = Option(...) + table.insert(self._options, option) + return option +end + +function Parser:flag(...) + return self:option():args(0)(...) +end + +function Parser:command(...) + local command = Command():add_help(true)(...) + command._parent = self + table.insert(self._commands, command) + return command +end + +function Parser:mutex(...) + local elements = {...} + + for i, element in ipairs(elements) do + local mt = getmetatable(element) + assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) + end + + table.insert(self._mutexes, elements) + return self +end + +function Parser:group(name, ...) + assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) + + local group = { + name = name, + ..., + } + + for i, element in ipairs(group) do + local mt = getmetatable(element) + assert(mt == Option or mt == Argument or mt == Command, + ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) + end + + table.insert(self._groups, group) + return self +end + +local usage_welcome = "Usage: " + +function Parser:get_usage() + if self._usage then + return self._usage + end + + local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) + local max_usage_width = self:_inherit_property("usage_max_width", 70) + local lines = {usage_welcome .. self:_get_fullname()} + + local function add(s) + if #lines[#lines] + 1 + #s <= max_usage_width then + lines[#lines] = lines[#lines] .. " " .. s + else + lines[#lines + 1] = (" "):rep(usage_margin) .. s + end + end + + -- Normally options are before positional arguments in usage messages. + -- However, vararg options should be after, because they can't be reliable used + -- before a positional argument. + -- Mutexes come into play, too, and are shown as soon as possible. + -- Overall, output usages in the following order: + -- 1. Mutexes that don't have positional arguments or vararg options. + -- 2. Options that are not in any mutexes and are not vararg. + -- 3. Positional arguments - on their own or as a part of a mutex. + -- 4. Remaining mutexes. + -- 5. Remaining options. + + local elements_in_mutexes = {} + local added_elements = {} + local added_mutexes = {} + local argument_to_mutexes = {} + + local function add_mutex(mutex, main_argument) + if added_mutexes[mutex] then + return + end + + added_mutexes[mutex] = true + local buf = {} + + for _, element in ipairs(mutex) do + if not element._hidden and not added_elements[element] then + if getmetatable(element) == Option or element == main_argument then + table.insert(buf, element:_get_usage()) + added_elements[element] = true + end + end + end + + if #buf == 1 then + add(buf[1]) + elseif #buf > 1 then + add("(" .. table.concat(buf, " | ") .. ")") + end + end + + local function add_element(element) + if not element._hidden and not added_elements[element] then + add(element:_get_usage()) + added_elements[element] = true + end + end + + for _, mutex in ipairs(self._mutexes) do + local is_vararg = false + local has_argument = false + + for _, element in ipairs(mutex) do + if getmetatable(element) == Option then + if element:_is_vararg() then + is_vararg = true + end + else + has_argument = true + argument_to_mutexes[element] = argument_to_mutexes[element] or {} + table.insert(argument_to_mutexes[element], mutex) + end + + elements_in_mutexes[element] = true + end + + if not is_vararg and not has_argument then + add_mutex(mutex) + end + end + + for _, option in ipairs(self._options) do + if not elements_in_mutexes[option] and not option:_is_vararg() then + add_element(option) + end + end + + -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. + for _, argument in ipairs(self._arguments) do + -- Pick a mutex as a part of which to show this argument, take the first one that's still available. + local mutex + + if elements_in_mutexes[argument] then + for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do + if not added_mutexes[argument_mutex] then + mutex = argument_mutex + end + end + end + + if mutex then + add_mutex(mutex, argument) + else + add_element(argument) + end + end + + for _, mutex in ipairs(self._mutexes) do + add_mutex(mutex) + end + + for _, option in ipairs(self._options) do + add_element(option) + end + + if #self._commands > 0 then + if self._require_command then + add("") + else + add("[]") + end + + add("...") + end + + return table.concat(lines, "\n") +end + +local function split_lines(s) + if s == "" then + return {} + end + + local lines = {} + + if s:sub(-1) ~= "\n" then + s = s .. "\n" + end + + for line in s:gmatch("([^\n]*)\n") do + table.insert(lines, line) + end + + return lines +end + +local function autowrap_line(line, max_length) + -- Algorithm for splitting lines is simple and greedy. + local result_lines = {} + + -- Preserve original indentation of the line, put this at the beginning of each result line. + -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts + -- of the second and the following lines vertically align with the start of the second word. + local indentation = line:match("^ *") + + if line:find("^ *[%*%+%-]") then + indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") + end + + -- Parts of the last line being assembled. + local line_parts = {} + + -- Length of the current line. + local line_length = 0 + + -- Index of the next character to consider. + local index = 1 + + while true do + local word_start, word_finish, word = line:find("([^ ]+)", index) + + if not word_start then + -- Ignore trailing spaces, if any. + break + end + + local preceding_spaces = line:sub(index, word_start - 1) + index = word_finish + 1 + + if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then + -- Either this is the very first word or it fits as an addition to the current line, add it. + table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. + table.insert(line_parts, word) + line_length = line_length + #preceding_spaces + #word + else + -- Does not fit, finish current line and put the word into a new one. + table.insert(result_lines, table.concat(line_parts)) + line_parts = {indentation, word} + line_length = #indentation + #word + end + end + + if #line_parts > 0 then + table.insert(result_lines, table.concat(line_parts)) + end + + if #result_lines == 0 then + -- Preserve empty lines. + result_lines[1] = "" + end + + return result_lines +end + +-- Automatically wraps lines within given array, +-- attempting to limit line length to `max_length`. +-- Existing line splits are preserved. +local function autowrap(lines, max_length) + local result_lines = {} + + for _, line in ipairs(lines) do + local autowrapped_lines = autowrap_line(line, max_length) + + for _, autowrapped_line in ipairs(autowrapped_lines) do + table.insert(result_lines, autowrapped_line) + end + end + + return result_lines +end + +function Parser:_get_element_help(element) + local label_lines = element:_get_label_lines() + local description_lines = split_lines(element:_get_description()) + + local result_lines = {} + + -- All label lines should have the same length (except the last one, it has no comma). + -- If too long, start description after all the label lines. + -- Otherwise, combine label and description lines. + + local usage_margin_len = self:_inherit_property("help_usage_margin", 3) + local usage_margin = (" "):rep(usage_margin_len) + local description_margin_len = self:_inherit_property("help_description_margin", 25) + local description_margin = (" "):rep(description_margin_len) + + local help_max_width = self:_inherit_property("help_max_width") + + if help_max_width then + local description_max_width = math.max(help_max_width - description_margin_len, 10) + description_lines = autowrap(description_lines, description_max_width) + end + + if #label_lines[1] >= (description_margin_len - usage_margin_len) then + for _, label_line in ipairs(label_lines) do + table.insert(result_lines, usage_margin .. label_line) + end + + for _, description_line in ipairs(description_lines) do + table.insert(result_lines, description_margin .. description_line) + end + else + for i = 1, math.max(#label_lines, #description_lines) do + local label_line = label_lines[i] + local description_line = description_lines[i] + + local line = "" + + if label_line then + line = usage_margin .. label_line + end + + if description_line and description_line ~= "" then + line = line .. (" "):rep(description_margin_len - #line) .. description_line + end + + table.insert(result_lines, line) + end + end + + return table.concat(result_lines, "\n") +end + +local function get_group_types(group) + local types = {} + + for _, element in ipairs(group) do + types[getmetatable(element)] = true + end + + return types +end + +function Parser:_add_group_help(blocks, added_elements, label, elements) + local buf = {label} + + for _, element in ipairs(elements) do + if not element._hidden and not added_elements[element] then + added_elements[element] = true + table.insert(buf, self:_get_element_help(element)) + end + end + + if #buf > 1 then + table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) + end +end + +function Parser:get_help() + if self._help then + return self._help + end + + local blocks = {self:get_usage()} + + local help_max_width = self:_inherit_property("help_max_width") + + if self._description then + local description = self._description + + if help_max_width then + description = table.concat(autowrap(split_lines(description), help_max_width), "\n") + end + + table.insert(blocks, description) + end + + -- 1. Put groups containing arguments first, then other arguments. + -- 2. Put remaining groups containing options, then other options. + -- 3. Put remaining groups containing commands, then other commands. + -- Assume that an element can't be in several groups. + local groups_by_type = { + [Argument] = {}, + [Option] = {}, + [Command] = {}, + } + + for _, group in ipairs(self._groups) do + local group_types = get_group_types(group) + + for _, mt in ipairs({Argument, Option, Command}) do + if group_types[mt] then + table.insert(groups_by_type[mt], group) + break + end + end + end + + local default_groups = {{ + name = "Arguments", + type = Argument, + elements = self._arguments, + }, { + name = "Options", + type = Option, + elements = self._options, + }, { + name = "Commands", + type = Command, + elements = self._commands, + }} + + local added_elements = {} + + for _, default_group in ipairs(default_groups) do + local type_groups = groups_by_type[default_group.type] + + for _, group in ipairs(type_groups) do + self:_add_group_help(blocks, added_elements, group.name .. ":", group) + end + + local default_label = default_group.name .. ":" + + if #type_groups > 0 then + default_label = "Other " .. default_label:gsub("^.", string.lower) + end + + self:_add_group_help(blocks, added_elements, default_label, default_group.elements) + end + + if self._epilog then + local epilog = self._epilog + + if help_max_width then + epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") + end + + table.insert(blocks, epilog) + end + + return table.concat(blocks, "\n\n") +end + +function Parser:add_help_command(value) + if value then + assert(type(value) == "string" or type(value) == "table", + ("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(value))) + end + + local help = self:command():description "Show help for commands." + help:argument "command":description "The command to show help for.":args "?":action(function(_, _, cmd) + if not cmd then + print(self:get_help()) + os.exit(0) + else + for _, command in ipairs(self._commands) do + for _, alias in ipairs(command._aliases) do + if alias == cmd then + print(command:get_help()) + os.exit(0) + end + end + end + end + help:error(("unknown command '%s'"):format(cmd)) + end) + + if value then + help = help(value) + end + + if not help._name then + help "help" + end + + help._is_help_command = true + return self +end + +function Parser:_is_shell_safe() + if self._basename then + if self._basename:find("[^%w_%-%+%.]") then + return false + end + else + for _, alias in ipairs(self._aliases) do + if alias:find("[^%w_%-%+%.]") then + return false + end + end + end + for _, option in ipairs(self._options) do + for _, alias in ipairs(option._aliases) do + if alias:find("[^%w_%-%+%.]") then + return false + end + end + if option._choices then + for _, choice in ipairs(option._choices) do + if choice:find("[%s'\"]") then + return false + end + end + end + end + for _, argument in ipairs(self._arguments) do + if argument._choices then + for _, choice in ipairs(argument._choices) do + if choice:find("[%s'\"]") then + return false + end + end + end + end + for _, command in ipairs(self._commands) do + if not command:_is_shell_safe() then + return false + end + end + return true +end + +function Parser:add_complete(value) + if value then + assert(type(value) == "string" or type(value) == "table", + ("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(value))) + end + + local complete = self:option():description "Output a shell completion script for the specified shell.":args(1) + :choices{"bash", "zsh", "fish"}:action(function(_, _, shell) + io.write(self["get_" .. shell .. "_complete"](self)) + os.exit(0) + end) + + if value then + complete = complete(value) + end + + if not complete._name then + complete "--completion" + end + + return self +end + +function Parser:add_complete_command(value) + if value then + assert(type(value) == "string" or type(value) == "table", + ("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(value))) + end + + local complete = self:command():description "Output a shell completion script." + complete:argument "shell":description "The shell to output a completion script for.":choices{"bash", "zsh", "fish"} + :action(function(_, _, shell) + io.write(self["get_" .. shell .. "_complete"](self)) + os.exit(0) + end) + + if value then + complete = complete(value) + end + + if not complete._name then + complete "completion" + end + + return self +end + +local function base_name(pathname) + return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname +end + +local function get_short_description(element) + local short = element:_get_description():match("^(.-)%.%s") + return short or element:_get_description():match("^(.-)%.?$") +end + +function Parser:_get_options() + local options = {} + for _, option in ipairs(self._options) do + for _, alias in ipairs(option._aliases) do + table.insert(options, alias) + end + end + return table.concat(options, " ") +end + +function Parser:_get_commands() + local commands = {} + for _, command in ipairs(self._commands) do + for _, alias in ipairs(command._aliases) do + table.insert(commands, alias) + end + end + return table.concat(commands, " ") +end + +function Parser:_bash_option_args(buf, indent) + local opts = {} + for _, option in ipairs(self._options) do + if option._choices or option._minargs > 0 then + local compreply + if option._choices then + compreply = 'COMPREPLY=($(compgen -W "' .. table.concat(option._choices, " ") .. '" -- "$cur"))' + else + compreply = 'COMPREPLY=($(compgen -f -- "$cur"))' + end + table.insert(opts, (" "):rep(indent + 4) .. table.concat(option._aliases, "|") .. ")") + table.insert(opts, (" "):rep(indent + 8) .. compreply) + table.insert(opts, (" "):rep(indent + 8) .. "return 0") + table.insert(opts, (" "):rep(indent + 8) .. ";;") + end + end + + if #opts > 0 then + table.insert(buf, (" "):rep(indent) .. 'case "$prev" in') + table.insert(buf, table.concat(opts, "\n")) + table.insert(buf, (" "):rep(indent) .. "esac\n") + end +end + +function Parser:_bash_get_cmd(buf, indent) + if #self._commands == 0 then + return + end + + table.insert(buf, (" "):rep(indent) .. 'args=("${args[@]:1}")') + table.insert(buf, (" "):rep(indent) .. 'for arg in "${args[@]}"; do') + table.insert(buf, (" "):rep(indent + 4) .. 'case "$arg" in') + + for _, command in ipairs(self._commands) do + table.insert(buf, (" "):rep(indent + 8) .. table.concat(command._aliases, "|") .. ")") + if self._parent then + table.insert(buf, (" "):rep(indent + 12) .. 'cmd="$cmd ' .. command._name .. '"') + else + table.insert(buf, (" "):rep(indent + 12) .. 'cmd="' .. command._name .. '"') + end + table.insert(buf, (" "):rep(indent + 12) .. 'opts="$opts ' .. command:_get_options() .. '"') + command:_bash_get_cmd(buf, indent + 12) + table.insert(buf, (" "):rep(indent + 12) .. "break") + table.insert(buf, (" "):rep(indent + 12) .. ";;") + end + + table.insert(buf, (" "):rep(indent + 4) .. "esac") + table.insert(buf, (" "):rep(indent) .. "done") +end + +function Parser:_bash_cmd_completions(buf) + local cmd_buf = {} + if self._parent then + self:_bash_option_args(cmd_buf, 12) + end + if #self._commands > 0 then + table.insert(cmd_buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self:_get_commands() .. '" -- "$cur"))') + elseif self._is_help_command then + table.insert(cmd_buf, + (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self._parent:_get_commands() .. '" -- "$cur"))') + end + if #cmd_buf > 0 then + table.insert(buf, (" "):rep(8) .. "'" .. self:_get_fullname(true) .. "')") + table.insert(buf, table.concat(cmd_buf, "\n")) + table.insert(buf, (" "):rep(12) .. ";;") + end + + for _, command in ipairs(self._commands) do + command:_bash_cmd_completions(buf) + end +end + +function Parser:get_bash_complete() + self._basename = base_name(self._name) + assert(self:_is_shell_safe()) + local buf = {([[ + _%s() { + local IFS=$' \t\n' + local args cur prev cmd opts arg + args=("${COMP_WORDS[@]}") + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="%s" + ]]):format(self._basename, self:_get_options())} + + self:_bash_option_args(buf, 4) + self:_bash_get_cmd(buf, 4) + if #self._commands > 0 then + table.insert(buf, "") + table.insert(buf, (" "):rep(4) .. 'case "$cmd" in') + self:_bash_cmd_completions(buf) + table.insert(buf, (" "):rep(4) .. "esac\n") + end + + table.insert(buf, ([=[ + if [[ "$cur" = -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- "$cur")) + fi + } + + complete -F _%s -o bashdefault -o default %s + ]=]):format(self._basename, self._basename)) + + return table.concat(buf, "\n") +end + +function Parser:_zsh_arguments(buf, cmd_name, indent) + if self._parent then + table.insert(buf, (" "):rep(indent) .. "options=(") + table.insert(buf, (" "):rep(indent + 2) .. "$options") + else + table.insert(buf, (" "):rep(indent) .. "local -a options=(") + end + + for _, option in ipairs(self._options) do + local line = {} + if #option._aliases > 1 then + if option._maxcount > 1 then + table.insert(line, '"*"') + end + table.insert(line, "{" .. table.concat(option._aliases, ",") .. '}"') + else + table.insert(line, '"') + if option._maxcount > 1 then + table.insert(line, "*") + end + table.insert(line, option._name) + end + if option._description then + local description = get_short_description(option):gsub('["%]:`$]', "\\%0") + table.insert(line, "[" .. description .. "]") + end + if option._maxargs == math.huge then + table.insert(line, ":*") + end + if option._choices then + table.insert(line, ": :(" .. table.concat(option._choices, " ") .. ")") + elseif option._maxargs > 0 then + table.insert(line, ": :_files") + end + table.insert(line, '"') + table.insert(buf, (" "):rep(indent + 2) .. table.concat(line)) + end + + table.insert(buf, (" "):rep(indent) .. ")") + table.insert(buf, (" "):rep(indent) .. "_arguments -s -S \\") + table.insert(buf, (" "):rep(indent + 2) .. "$options \\") + + if self._is_help_command then + table.insert(buf, (" "):rep(indent + 2) .. '": :(' .. self._parent:_get_commands() .. ')" \\') + else + for _, argument in ipairs(self._arguments) do + local spec + if argument._choices then + spec = ": :(" .. table.concat(argument._choices, " ") .. ")" + else + spec = ": :_files" + end + if argument._maxargs == math.huge then + table.insert(buf, (" "):rep(indent + 2) .. '"*' .. spec .. '" \\') + break + end + for _ = 1, argument._maxargs do + table.insert(buf, (" "):rep(indent + 2) .. '"' .. spec .. '" \\') + end + end + + if #self._commands > 0 then + table.insert(buf, (" "):rep(indent + 2) .. '": :_' .. cmd_name .. '_cmds" \\') + table.insert(buf, (" "):rep(indent + 2) .. '"*:: :->args" \\') + end + end + + table.insert(buf, (" "):rep(indent + 2) .. "&& return 0") +end + +function Parser:_zsh_cmds(buf, cmd_name) + table.insert(buf, "\n_" .. cmd_name .. "_cmds() {") + table.insert(buf, " local -a commands=(") + + for _, command in ipairs(self._commands) do + local line = {} + if #command._aliases > 1 then + table.insert(line, "{" .. table.concat(command._aliases, ",") .. '}"') + else + table.insert(line, '"' .. command._name) + end + if command._description then + table.insert(line, ":" .. get_short_description(command):gsub('["`$]', "\\%0")) + end + table.insert(buf, " " .. table.concat(line) .. '"') + end + + table.insert(buf, ' )\n _describe "command" commands\n}') +end + +function Parser:_zsh_complete_help(buf, cmds_buf, cmd_name, indent) + if #self._commands == 0 then + return + end + + self:_zsh_cmds(cmds_buf, cmd_name) + table.insert(buf, "\n" .. (" "):rep(indent) .. "case $words[1] in") + + for _, command in ipairs(self._commands) do + local name = cmd_name .. "_" .. command._name + table.insert(buf, (" "):rep(indent + 2) .. table.concat(command._aliases, "|") .. ")") + command:_zsh_arguments(buf, name, indent + 4) + command:_zsh_complete_help(buf, cmds_buf, name, indent + 4) + table.insert(buf, (" "):rep(indent + 4) .. ";;\n") + end + + table.insert(buf, (" "):rep(indent) .. "esac") +end + +function Parser:get_zsh_complete() + self._basename = base_name(self._name) + assert(self:_is_shell_safe()) + local buf = {("#compdef %s\n"):format(self._basename)} + local cmds_buf = {} + table.insert(buf, "_" .. self._basename .. "() {") + if #self._commands > 0 then + table.insert(buf, " local context state state_descr line") + table.insert(buf, " typeset -A opt_args\n") + end + self:_zsh_arguments(buf, self._basename, 2) + self:_zsh_complete_help(buf, cmds_buf, self._basename, 2) + table.insert(buf, "\n return 1") + table.insert(buf, "}") + + local result = table.concat(buf, "\n") + if #cmds_buf > 0 then + result = result .. "\n" .. table.concat(cmds_buf, "\n") + end + return result .. "\n\n_" .. self._basename .. "\n" +end + +local function fish_escape(string) + return string:gsub("[\\']", "\\%0") +end + +function Parser:_fish_get_cmd(buf, indent) + if #self._commands == 0 then + return + end + + table.insert(buf, (" "):rep(indent) .. "set -e cmdline[1]") + table.insert(buf, (" "):rep(indent) .. "for arg in $cmdline") + table.insert(buf, (" "):rep(indent + 4) .. "switch $arg") + + for _, command in ipairs(self._commands) do + table.insert(buf, (" "):rep(indent + 8) .. "case " .. table.concat(command._aliases, " ")) + table.insert(buf, (" "):rep(indent + 12) .. "set cmd $cmd " .. command._name) + command:_fish_get_cmd(buf, indent + 12) + table.insert(buf, (" "):rep(indent + 12) .. "break") + end + + table.insert(buf, (" "):rep(indent + 4) .. "end") + table.insert(buf, (" "):rep(indent) .. "end") +end + +function Parser:_fish_complete_help(buf, basename) + local prefix = "complete -c " .. basename + table.insert(buf, "") + + for _, command in ipairs(self._commands) do + local aliases = table.concat(command._aliases, " ") + local line + if self._parent then + line = ("%s -n '__fish_%s_using_command %s' -xa '%s'"):format(prefix, basename, self:_get_fullname(true), + aliases) + else + line = ("%s -n '__fish_%s_using_command' -xa '%s'"):format(prefix, basename, aliases) + end + if command._description then + line = ("%s -d '%s'"):format(line, fish_escape(get_short_description(command))) + end + table.insert(buf, line) + end + + if self._is_help_command then + local line = ("%s -n '__fish_%s_using_command %s' -xa '%s'"):format(prefix, basename, self:_get_fullname(true), + self._parent:_get_commands()) + table.insert(buf, line) + end + + for _, option in ipairs(self._options) do + local parts = {prefix} + + if self._parent then + table.insert(parts, "-n '__fish_" .. basename .. "_seen_command " .. self:_get_fullname(true) .. "'") + end + + for _, alias in ipairs(option._aliases) do + if alias:match("^%-.$") then + table.insert(parts, "-s " .. alias:sub(2)) + elseif alias:match("^%-%-.+") then + table.insert(parts, "-l " .. alias:sub(3)) + end + end + + if option._choices then + table.insert(parts, "-xa '" .. table.concat(option._choices, " ") .. "'") + elseif option._minargs > 0 then + table.insert(parts, "-r") + end + + if option._description then + table.insert(parts, "-d '" .. fish_escape(get_short_description(option)) .. "'") + end + + table.insert(buf, table.concat(parts, " ")) + end + + for _, command in ipairs(self._commands) do + command:_fish_complete_help(buf, basename) + end +end + +function Parser:get_fish_complete() + self._basename = base_name(self._name) + assert(self:_is_shell_safe()) + local buf = {} + + if #self._commands > 0 then + table.insert(buf, ([[ + function __fish_%s_print_command + set -l cmdline (commandline -poc) + set -l cmd]]):format(self._basename)) + self:_fish_get_cmd(buf, 4) + table.insert(buf, ([[ + echo "$cmd" + end + + function __fish_%s_using_command + test (__fish_%s_print_command) = "$argv" + and return 0 + or return 1 + end + + function __fish_%s_seen_command + string match -q "$argv*" (__fish_%s_print_command) + and return 0 + or return 1 + end]]):format(self._basename, self._basename, self._basename, self._basename)) + end + + self:_fish_complete_help(buf, self._basename) + return table.concat(buf, "\n") .. "\n" +end + +local function get_tip(context, wrong_name) + local context_pool = {} + local possible_name + local possible_names = {} + + for name in pairs(context) do + if type(name) == "string" then + for i = 1, #name do + possible_name = name:sub(1, i - 1) .. name:sub(i + 1) + + if not context_pool[possible_name] then + context_pool[possible_name] = {} + end + + table.insert(context_pool[possible_name], name) + end + end + end + + for i = 1, #wrong_name + 1 do + possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) + + if context[possible_name] then + possible_names[possible_name] = true + elseif context_pool[possible_name] then + for _, name in ipairs(context_pool[possible_name]) do + possible_names[name] = true + end + end + end + + local first = next(possible_names) + + if first then + if next(possible_names, first) then + local possible_names_arr = {} + + for name in pairs(possible_names) do + table.insert(possible_names_arr, "'" .. name .. "'") + end + + table.sort(possible_names_arr) + return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" + else + return "\nDid you mean '" .. first .. "'?" + end + else + return "" + end +end + +local ElementState = class({ + invocations = 0, +}) + +function ElementState:__call(state, element) + self.state = state + self.result = state.result + self.element = element + self.target = element._target or element:_get_default_target() + self.action, self.result[self.target] = element:_get_action() + return self +end + +function ElementState:error(fmt, ...) + self.state:error(fmt, ...) +end + +function ElementState:convert(argument, index) + local converter = self.element._convert + + if converter then + local ok, err + + if type(converter) == "function" then + ok, err = converter(argument) + elseif type(converter[index]) == "function" then + ok, err = converter[index](argument) + else + ok = converter[argument] + end + + if ok == nil then + self:error(err and "%s" or "malformed argument '%s'", err or argument) + end + + argument = ok + end + + return argument +end + +function ElementState:default(mode) + return self.element._defmode:find(mode) and self.element._default +end + +local function bound(noun, min, max, is_max) + local res = "" + + if min ~= max then + res = "at " .. (is_max and "most" or "least") .. " " + end + + local number = is_max and max or min + return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") +end + +function ElementState:set_name(alias) + self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) +end + +function ElementState:invoke() + self.open = true + self.overwrite = false + + if self.invocations >= self.element._maxcount then + if self.element._overwrite then + self.overwrite = true + else + local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) + self:error("%s must be used %s", self.name, num_times_repr) + end + else + self.invocations = self.invocations + 1 + end + + self.args = {} + + if self.element._maxargs <= 0 then + self:close() + end + + return self.open +end + +function ElementState:check_choices(argument) + if self.element._choices then + for _, choice in ipairs(self.element._choices) do + if argument == choice then + return + end + end + local choices_list = "'" .. table.concat(self.element._choices, "', '") .. "'" + local is_option = getmetatable(self.element) == Option + self:error("%s%s must be one of %s", is_option and "argument for " or "", self.name, choices_list) + end +end + +function ElementState:pass(argument) + self:check_choices(argument) + argument = self:convert(argument, #self.args + 1) + table.insert(self.args, argument) + + if #self.args >= self.element._maxargs then + self:close() + end + + return self.open +end + +function ElementState:complete_invocation() + while #self.args < self.element._minargs do + self:pass(self.element._default) + end +end + +function ElementState:close() + if self.open then + self.open = false + + if #self.args < self.element._minargs then + if self:default("a") then + self:complete_invocation() + else + if #self.args == 0 then + if getmetatable(self.element) == Argument then + self:error("missing %s", self.name) + elseif self.element._maxargs == 1 then + self:error("%s requires an argument", self.name) + end + end + + self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) + end + end + + local args + + if self.element._maxargs == 0 then + args = self.args[1] + elseif self.element._maxargs == 1 then + if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then + args = self.args + else + args = self.args[1] + end + else + args = self.args + end + + self.action(self.result, self.target, args, self.overwrite) + end +end + +local ParseState = class({ + result = {}, + options = {}, + arguments = {}, + argument_i = 1, + element_to_mutexes = {}, + mutex_to_element_state = {}, + command_actions = {}, +}) + +function ParseState:__call(parser, error_handler) + self.parser = parser + self.error_handler = error_handler + self.charset = parser:_update_charset() + self:switch(parser) + return self +end + +function ParseState:error(fmt, ...) + self.error_handler(self.parser, fmt:format(...)) +end + +function ParseState:switch(parser) + self.parser = parser + + if parser._action then + table.insert(self.command_actions, { + action = parser._action, + name = parser._name, + }) + end + + for _, option in ipairs(parser._options) do + option = ElementState(self, option) + table.insert(self.options, option) + + for _, alias in ipairs(option.element._aliases) do + self.options[alias] = option + end + end + + for _, mutex in ipairs(parser._mutexes) do + for _, element in ipairs(mutex) do + if not self.element_to_mutexes[element] then + self.element_to_mutexes[element] = {} + end + + table.insert(self.element_to_mutexes[element], mutex) + end + end + + for _, argument in ipairs(parser._arguments) do + argument = ElementState(self, argument) + table.insert(self.arguments, argument) + argument:set_name() + argument:invoke() + end + + self.handle_options = parser._handle_options + self.argument = self.arguments[self.argument_i] + self.commands = parser._commands + + for _, command in ipairs(self.commands) do + for _, alias in ipairs(command._aliases) do + self.commands[alias] = command + end + end +end + +function ParseState:get_option(name) + local option = self.options[name] + + if not option then + self:error("unknown option '%s'%s", name, get_tip(self.options, name)) + else + return option + end +end + +function ParseState:get_command(name) + local command = self.commands[name] + + if not command then + if #self.commands > 0 then + self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) + else + self:error("too many arguments") + end + else + return command + end +end + +function ParseState:check_mutexes(element_state) + if self.element_to_mutexes[element_state.element] then + for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do + local used_element_state = self.mutex_to_element_state[mutex] + + if used_element_state and used_element_state ~= element_state then + self:error("%s can not be used together with %s", element_state.name, used_element_state.name) + else + self.mutex_to_element_state[mutex] = element_state + end + end + end +end + +function ParseState:invoke(option, name) + self:close() + option:set_name(name) + self:check_mutexes(option, name) + + if option:invoke() then + self.option = option + end +end + +function ParseState:pass(arg) + if self.option then + if not self.option:pass(arg) then + self.option = nil + end + elseif self.argument then + self:check_mutexes(self.argument) + + if not self.argument:pass(arg) then + self.argument_i = self.argument_i + 1 + self.argument = self.arguments[self.argument_i] + end + else + local command = self:get_command(arg) + self.result[command._target or command._name] = true + + if self.parser._command_target then + self.result[self.parser._command_target] = command._name + end + + self:switch(command) + end +end + +function ParseState:close() + if self.option then + self.option:close() + self.option = nil + end +end + +function ParseState:finalize() + self:close() + + for i = self.argument_i, #self.arguments do + local argument = self.arguments[i] + if #argument.args == 0 and argument:default("u") then + argument:complete_invocation() + else + argument:close() + end + end + + if self.parser._require_command and #self.commands > 0 then + self:error("a command is required") + end + + for _, option in ipairs(self.options) do + option.name = option.name or ("option '%s'"):format(option.element._name) + + if option.invocations == 0 then + if option:default("u") then + option:invoke() + option:complete_invocation() + option:close() + end + end + + local mincount = option.element._mincount + + if option.invocations < mincount then + if option:default("a") then + while option.invocations < mincount do + option:invoke() + option:close() + end + elseif option.invocations == 0 then + self:error("missing %s", option.name) + else + self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) + end + end + end + + for i = #self.command_actions, 1, -1 do + self.command_actions[i].action(self.result, self.command_actions[i].name) + end +end + +function ParseState:parse(args) + for _, arg in ipairs(args) do + local plain = true + + if self.handle_options then + local first = arg:sub(1, 1) + + if self.charset[first] then + if #arg > 1 then + plain = false + + if arg:sub(2, 2) == first then + if #arg == 2 then + if self.options[arg] then + local option = self:get_option(arg) + self:invoke(option, arg) + else + self:close() + end + + self.handle_options = false + else + local equals = arg:find "=" + if equals then + local name = arg:sub(1, equals - 1) + local option = self:get_option(name) + + if option.element._maxargs <= 0 then + self:error("option '%s' does not take arguments", name) + end + + self:invoke(option, name) + self:pass(arg:sub(equals + 1)) + else + local option = self:get_option(arg) + self:invoke(option, arg) + end + end + else + for i = 2, #arg do + local name = first .. arg:sub(i, i) + local option = self:get_option(name) + self:invoke(option, name) + + if i ~= #arg and option.element._maxargs > 0 then + self:pass(arg:sub(i + 1)) + break + end + end + end + end + end + end + + if plain then + self:pass(arg) + end + end + + self:finalize() + return self.result +end + +function Parser:error(msg) + io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) + os.exit(1) +end + +-- Compatibility with strict.lua and other checkers: +local default_cmdline = rawget(_G, "arg") or {} + +function Parser:_parse(args, error_handler) + return ParseState(self, error_handler):parse(args or default_cmdline) +end + +function Parser:parse(args) + return self:_parse(args, self.error) +end + +local function xpcall_error_handler(err) + return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) +end + +function Parser:pparse(args) + local parse_error + + local ok, result = xpcall(function() + return self:_parse(args, function(_, err) + parse_error = err + error(err, 0) + end) + end, xpcall_error_handler) + + if ok then + return true, result + elseif not parse_error then + error(result, 0) + else + return false, parse_error + end +end + +local argparse = {} + +argparse.version = "0.7.1" + +setmetatable(argparse, { + __call = function(_, ...) + return Parser(default_cmdline[0]):add_help(true)(...) + end, +}) + +return argparse diff --git a/code/framework/lualib/3rd/misc/ecs.lua b/code/framework/lualib/3rd/misc/ecs.lua index 9892fa5..9072923 100644 --- a/code/framework/lualib/3rd/misc/ecs.lua +++ b/code/framework/lualib/3rd/misc/ecs.lua @@ -1,434 +1,455 @@ local ecs = require "ecs.core" -local REFERENCE_ID = 1 +local REFERENCE_ID = 1 + +local rawerror = error +local selfsource = debug.getinfo(1, "S").source +local function error(errmsg) + local level = 2 + while true do + local info = debug.getinfo(level, "S") + if not info then + rawerror(errmsg, 2) + return + end + if selfsource ~= info.source then + rawerror(errmsg, level) + return + end + level = level + 1 + end +end + +local function assert(cond, errmsg) + if not cond then + error(errmsg or "assertion failed!") + end + return cond +end local function get_attrib(opt, inout) - if opt == nil then - return { exist = true } - end - local desc = {} - if opt == "?" then - desc.opt = true - else - assert(opt == ":") - end - if inout == "in" then - desc.r = true - elseif inout == "out" then - desc.w = true - elseif inout == "update" then - desc.r = true - desc.w = true - elseif inout == "exist" then - desc.exist = true - assert(not desc.opt) - elseif inout == "absent" then - desc.absent = true - assert(not desc.opt) - else - assert(inout == "temp") - end - return desc + if opt == nil then + return { + exist = true, + } + end + local desc = {} + if opt == "?" then + desc.opt = true + else + assert(opt == ":") + end + if inout == "in" then + desc.r = true + elseif inout == "out" then + desc.w = true + elseif inout == "update" then + desc.r = true + desc.w = true + elseif inout == "exist" then + desc.exist = true + assert(not desc.opt) + elseif inout == "absent" then + desc.absent = true + assert(not desc.opt) + else + assert(inout == "new") + end + return desc end local function cache_world(obj, k) - local c = { - typenames = {}, - id = 0, - select = {}, - ref = {}, - } - - local function gen_ref_pat(key) - local typenames = c.typenames - local desc = {} - local tc = typenames[key] - if tc == nil then - error("Unknown type " .. key) - end - local a = { - exist = true, - name = tc.name, - id = tc.id, - type = tc.type, - } - local n = #tc - for i=1,#tc do - a[i] = tc[i] - end - desc[1] = a - return desc - end - - local function gen_all_pat() - local desc = {} - local i = 1 - for name,t in pairs(c.typenames) do - if t.tag ~= "ORDER" then - local a = { - name = t.name, - id = t.id, - type = t.type, - opt = true, - r = true, - } - table.move(t, 1, #t, 1, a) - desc[i] = a - i = i + 1 - end - end - return desc - end - - setmetatable(c, { __index = function(_, key) - if key == "all" then - local all = k:_groupiter(gen_all_pat()) - c.all = all - return all - end - end }) - - local function gen_select_pat(pat) - local typenames = c.typenames - local desc = {} - local idx = 1 - for token in pat:gmatch "[^ ]+" do - local key, index, padding = token:match "^([_%w]+)%(?([_%w]*)%)?(.*)" - assert(key, "Invalid pattern") - local opt, inout - if padding ~= "" then - opt, inout = padding:match "^([:?])(%l+)$" - assert(opt, "Invalid pattern") - end - local tc = typenames[key] - if tc == nil then - error("Unknown type " .. key) - end - if index ~= "" then - local indexc = typenames[index] - if indexc == nil then - error("Unknown index type "..index) - end - local a = get_attrib(opt, inout == "temp" and "temp" or "in") - a.name = index - a.id = indexc.id - a.type = indexc.type - a.ref = true - desc[idx] = a - idx = idx + 1 - end - local a = get_attrib(opt, inout) - a.name = tc.name - a.id = tc.id - a.type = tc.type - local n = #tc - for i=1,#tc do - a[i] = tc[i] - end - desc[idx] = a - idx = idx + 1 - if tc.ref and index == "" then - local dead = typenames[key .. "_dead"] - local a = { - absent = true, - name = dead.name, - id = dead.id, - } - desc[idx] = a - idx = idx + 1 - end - end - return desc - end - - local function cache_select(cache, pat) - local pat_desc = gen_select_pat(pat) - cache[pat] = k:_groupiter(pat_desc) - return cache[pat] - end - - setmetatable(c.select, { - __mode = "kv", - __index = cache_select, - }) - - local function cache_ref(cache, pat) - local pat_desc = gen_ref_pat(pat) - cache[pat] = k:_groupiter(pat_desc) - return cache[pat] - end - - setmetatable(c.ref, { - __mode = "kv", - __index = cache_ref, - }) - - obj[k] = c - return c + local c = { + typenames = {}, + id = 0, + select = {}, + ref = {}, + } + + local function gen_ref_pat(key) + local typenames = c.typenames + local desc = {} + local tc = typenames[key] + if tc == nil then + error("Unknown type " .. key) + end + local a = { + exist = true, + name = tc.name, + id = tc.id, + type = tc.type, + } + local n = #tc + for i = 1, #tc do + a[i] = tc[i] + end + desc[1] = a + return desc + end + + local function gen_all_pat() + local desc = {} + local i = 1 + for name, t in pairs(c.typenames) do + if t.tag ~= "ORDER" then + local a = { + name = t.name, + id = t.id, + type = t.type, + opt = true, + r = true, + } + table.move(t, 1, #t, 1, a) + desc[i] = a + i = i + 1 + end + end + return desc + end + + setmetatable(c, { + __index = function(_, key) + if key == "all" then + local all = k:_groupiter(gen_all_pat()) + c.all = all + return all + end + end, + }) + + local function gen_select_pat(pat) + local typenames = c.typenames + local desc = {} + local idx = 1 + for token in pat:gmatch "[^ ]+" do + local key, padding = token:match "^([_%w]+)(.*)" + assert(key, "Invalid pattern") + local opt, inout + if padding ~= "" then + opt, inout = padding:match "^([:?])(%l+)$" + assert(opt, "Invalid pattern") + end + local tc = typenames[key] + if tc == nil then + error("Unknown type " .. key) + end + local a = get_attrib(opt, inout) + a.name = tc.name + a.id = tc.id + a.type = tc.type + local n = #tc + for i = 1, #tc do + a[i] = tc[i] + end + desc[idx] = a + idx = idx + 1 + if tc.ref then + local dead = typenames[key .. "_dead"] + local a = { + absent = true, + name = dead.name, + id = dead.id, + } + desc[idx] = a + idx = idx + 1 + end + end + return desc + end + + local function cache_select(cache, pat) + local pat_desc = gen_select_pat(pat) + cache[pat] = k:_groupiter(pat_desc) + return cache[pat] + end + + setmetatable(c.select, { + __mode = "kv", + __index = cache_select, + }) + + local function cache_ref(cache, pat) + local pat_desc = gen_ref_pat(pat) + cache[pat] = k:_groupiter(pat_desc) + return cache[pat] + end + + setmetatable(c.ref, { + __mode = "kv", + __index = cache_ref, + }) + + obj[k] = c + return c end -local context = setmetatable({}, { __index = cache_world }) +local context = setmetatable({}, { + __index = cache_world, +}) local typeid = { - int = assert(ecs._TYPEINT), - float = assert(ecs._TYPEFLOAT), - bool = assert(ecs._TYPEBOOL), - int64 = assert(ecs._TYPEINT64), - dword = assert(ecs._TYPEDWORD), - word = assert(ecs._TYPEWORD), - byte = assert(ecs._TYPEBYTE), - double = assert(ecs._TYPEDOUBLE), - userdata = assert(ecs._TYPEUSERDATA), + int = assert(ecs._TYPEINT), + float = assert(ecs._TYPEFLOAT), + bool = assert(ecs._TYPEBOOL), + int64 = assert(ecs._TYPEINT64), + dword = assert(ecs._TYPEDWORD), + word = assert(ecs._TYPEWORD), + byte = assert(ecs._TYPEBYTE), + double = assert(ecs._TYPEDOUBLE), + userdata = assert(ecs._TYPEUSERDATA), } local typesize = { - [typeid.int] = 4, - [typeid.float] = 4, - [typeid.bool] = 1, - [typeid.int64] = 8, - [typeid.dword] = 4, - [typeid.word] = 2, - [typeid.byte] = 1, - [typeid.double] = 8, - [typeid.userdata] = 8, + [typeid.int] = 4, + [typeid.float] = 4, + [typeid.bool] = 1, + [typeid.int64] = 8, + [typeid.dword] = 4, + [typeid.word] = 2, + [typeid.byte] = 1, + [typeid.double] = 8, + [typeid.userdata] = 8, } local M = ecs._METHODS -do -- newtype - local function parse(s) - -- s is "name:typename" - local name, typename = s:match "^([%w_]+):(%w+)$" - local typeid = assert(typeid[typename]) - return { typeid, name } - end - - local function align(c, field) - local t = field[1] - local tsize = typesize[t] - local offset = ((c.size + tsize - 1) & ~(tsize-1)) - c.size = offset + tsize - field[3] = offset - return field - end - - local function align_struct(c, t) - if t then - local s = typesize[t] - 1 - c.size = ((c.size + s) & ~s) - end - end - - function M:register(typeclass) - local name = assert(typeclass.name) - local ctx = context[self] - ctx.all = nil -- clear all pattern - local typenames = ctx.typenames - local id = ctx.id + 1 - assert(typenames[name] == nil and id <= ecs._MAXTYPE) - ctx.id = id - local c = { - id = id, - name = name, - size = 0, - } - for i, v in ipairs(typeclass) do - c[i] = align(c, parse(v)) - end - local ttype = typeclass.type - if ttype == "lua" then - assert(c.size == 0) - c.size = ecs._LUAOBJECT - assert(c[1] == nil) - elseif c.size > 0 then - align_struct(c, typeclass[1][1]) - else - -- size == 0, one value - if ttype then - local t = assert(typeid[typeclass.type]) - c.type = t - c.size = typesize[t] - c[1] = { t, "v", 0 } - elseif typeclass.order then - c.size = ecs._ORDERKEY - c.tag = "ORDER" - else - c.tag = true - end - end - typenames[name] = c - self:_newtype(id, c.size) - if typeclass.ref then - c.ref = true - self:register { name = name .. "_dead" } - end - end +do -- newtype + local function parse(s) + -- s is "name:typename" + local name, typename = s:match "^([%w_]+):(%w+)$" + local typeid = assert(typeid[typename]) + return {typeid, name} + end + + local function align(c, field) + local t = field[1] + local tsize = typesize[t] + local offset = ((c.size + tsize - 1) & ~(tsize - 1)) + c.size = offset + tsize + field[3] = offset + return field + end + + local function align_struct(c, t) + if t then + local s = typesize[t] - 1 + c.size = ((c.size + s) & ~s) + end + end + + function M:register(typeclass) + local name = assert(typeclass.name) + local ctx = context[self] + ctx.all = nil -- clear all pattern + local typenames = ctx.typenames + local id = ctx.id + 1 + assert(typenames[name] == nil and id <= ecs._MAXTYPE) + ctx.id = id + local c = { + id = id, + name = name, + size = 0, + } + for i, v in ipairs(typeclass) do + c[i] = align(c, parse(v)) + end + local ttype = typeclass.type + if ttype == "lua" then + assert(c.size == 0) + c.size = ecs._LUAOBJECT + assert(c[1] == nil) + elseif c.size > 0 then + align_struct(c, c[1][1]) + else + -- size == 0, one value + if ttype then + local t = assert(typeid[typeclass.type]) + c.type = t + c.size = typesize[t] + c[1] = {t, "v", 0} + elseif typeclass.order then + c.size = ecs._ORDERKEY + c.tag = "ORDER" + else + c.tag = true + end + end + typenames[name] = c + self:_newtype(id, c.size) + if typeclass.ref then + c.ref = true + self:register{ + name = name .. "_dead", + } + end + end end local function dump(obj) - for e,v in pairs(obj) do - if type(v) == "table" then - for k,v in pairs(v) do - print(e,k,v) - end - else - print(e,v) - end - end + for e, v in pairs(obj) do + if type(v) == "table" then + for k, v in pairs(v) do + print(e, k, v) + end + else + print(e, v) + end + end end function M:new(obj) --- dump(obj) - local eid = self:_newentity() - local typenames = context[self].typenames - local reference = obj.reference - if reference then - obj.reference = nil - end - for k,v in pairs(obj) do - local tc = typenames[k] - if not tc then - error ("Invalid key : ".. k) - end - local id = self:_addcomponent(eid, tc.id) - if tc.tag ~= "ORDER" then - self:object(k, id, v) - end - end - if reference then - local id = self:_addcomponent(eid, REFERENCE_ID) - reference[1] = id - reference[2] = REFERENCE_ID - self:object("reference", id, reference) - obj.reference = reference - return reference - end + -- dump(obj) + local eid = self:_newentity() + local typenames = context[self].typenames + local reference = obj.reference + if reference then + obj.reference = nil + end + for k, v in pairs(obj) do + local tc = typenames[k] + if not tc then + error("Invalid key : " .. k) + end + local id = self:_addcomponent(eid, tc.id) + if tc.tag ~= "ORDER" then + self:object(k, id, v) + end + end + if reference then + local id = self:_addcomponent(eid, REFERENCE_ID) + reference[1] = id + reference[2] = REFERENCE_ID + self:object("reference", id, reference) + obj.reference = reference + return reference + end end function M:ref(name, refobj) - local obj = assert(refobj[name]) - local ctx = context[self] - local typenames = ctx.typenames - local tc = assert(typenames[name]) - local refid = self:_reuse(tc.id) - refobj[2] = tc.id - if refid then - local p = context[self].select[name .. ":out"] - refobj[1] = refid - self:_sync(p, refobj) - else - local eid = self:_newentity() - refid = self:_addcomponent(eid, tc.id) - refobj[1] = refid - self:object(name, refid, obj) - end - for k,v in pairs(refobj) do - if (v == true or v == false) and name ~= k then - local p = context[self].select[k .. "?out"] - self:_sync(p, refobj) - end - end - return refid + local obj = assert(refobj[name]) + local ctx = context[self] + local typenames = ctx.typenames + local tc = assert(typenames[name]) + local refid = self:_reuse(tc.id) + refobj[2] = tc.id + if refid then + local p = context[self].select[name .. ":out"] + refobj[1] = refid + self:_sync(p, refobj) + else + local eid = self:_newentity() + refid = self:_addcomponent(eid, tc.id) + refobj[1] = refid + self:object(name, refid, obj) + end + for k, v in pairs(refobj) do + if (v == true or v == false) and name ~= k then + local p = context[self].select[k .. "?out"] + self:_sync(p, refobj) + end + end + return refid end function M:object_ref(name, refid) - local typenames = context[self].typenames - return { refid, typenames[name].id } + local typenames = context[self].typenames + return {refid, typenames[name].id} end function M:release(name, refid) - local id = assert(context[self].typenames[name].id) - self:_release(id, refid) + local id = assert(context[self].typenames[name].id) + self:_release(id, refid) end function M:context(t) - local typenames = context[self].typenames - local id = {} - for i, name in ipairs(t) do - local tc = typenames[name] - if not tc then - error ("Invalid component name " .. name) - end - id[i] = tc.id - end - return self:_context(id) + local typenames = context[self].typenames + local id = {} + for i, name in ipairs(t) do + local tc = typenames[name] + if not tc then + error("Invalid component name " .. name) + end + id[i] = tc.id + end + return self:_context(id) end function M:select(pat) - return context[self].select[pat]() + return context[self].select[pat]() end function M:sync(pat, iter) - local p = context[self].select[pat] - self:_sync(p, iter) - return iter + local p = context[self].select[pat] + self:_sync(p, iter) + return iter end function M:readall(iter) - local p = context[self].all - self:_sync(p, iter) - return iter + local p = context[self].all + self:_sync(p, iter) + return iter end function M:clear(name) - local id = assert(context[self].typenames[name].id) - self:_clear(id) + local id = assert(context[self].typenames[name].id) + self:_clear(id) end function M:dumpid(name) - local typenames = context[self].typenames - return self:_dumpid(typenames[name].id) + local typenames = context[self].typenames + return self:_dumpid(typenames[name].id) end function M:update() - self:_update_reference(REFERENCE_ID) - self:_update() + self:_update_reference(REFERENCE_ID) + self:_update() end function M:remove_reference(ref) - ref.reference = false - self:sync("reference:out", ref) - ref[1] = nil + ref.reference = false + self:sync("reference:out", ref) + ref[1] = nil end do - local _object = M._object - function M:object(name, refid, v) - local pat = context[self].ref[name] - return _object(pat, v, refid) - end - - function M:singleton(name, pattern, iter) - local typenames = context[self].typenames - if iter == nil then - iter = { 1, typenames[name].id } - if pattern then - local p = context[self].select[pattern] - self:_sync(p, iter) - end - return iter - else - iter[1] = 1 - iter[2] = typenames[name].id - local p = context[self].select[pattern] - self:_sync(p, iter) - end - return iter - end + local _object = M._object + function M:object(name, refid, v) + local pat = context[self].ref[name] + return _object(pat, v, refid) + end + + function M:singleton(name, pattern, iter) + local typenames = context[self].typenames + if iter == nil then + iter = {1, typenames[name].id} + if pattern then + local p = context[self].select[pattern] + return self:_read(p, iter) + else + return iter + end + else + iter[1] = 1 + iter[2] = typenames[name].id + local p = context[self].select[pattern] + self:_sync(p, iter) + end + return iter + end end function ecs.world() - local w = ecs._world() - context[w].typenames.REMOVED = { - name = "REMOVED", - id = ecs._REMOVED, - size = 0, - tag = true, - } - w:register { - name = "reference", - type = "lua", - } - assert(context[w].typenames.reference.id == REFERENCE_ID) - return w + local w = ecs._world() + context[w].typenames.REMOVED = { + name = "REMOVED", + id = ecs._REMOVED, + size = 0, + tag = true, + } + w:register{ + name = "reference", + type = "lua", + } + assert(context[w].typenames.reference.id == REFERENCE_ID) + return w end return ecs diff --git a/docs/NoSQLBooster.md b/docs/NoSQLBooster.md new file mode 100644 index 0000000..e20f9ba --- /dev/null +++ b/docs/NoSQLBooster.md @@ -0,0 +1,15 @@ +https://www.cnblogs.com/lucky9322/p/14922842.html + +NoSQLBooster for MongoDB 基于 Electro 编写 asar 打包,所以我们能够解包修改代码并重新打包 + +1.安装工具 +npm install asar -g + +2.解包 +进入 /Applications/NoSQLBooster for MongoDB.app/Contents/Resources + +3.修改 app\shared\lmCore.js 里的 MAX_TRIAL_DAYS 和 TRIAL_DAYS 值 +MAX_TRIAL_DAYS=999999,TRIAL_DAYS=999999 + +4.打包,打包完之后删除 app 文件夹 +asar pack app app.asar \ No newline at end of file