diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d35e774e9..87a0499b6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -276,6 +276,7 @@ set(SRB2_LUA_SOURCES lua_hudlib.c lua_infolib.c lua_maplib.c + lua_taglib.c lua_mathlib.c lua_mobjlib.c lua_playerlib.c diff --git a/src/blua/Makefile.cfg b/src/blua/Makefile.cfg index eae95ba3a..3a2962e65 100644 --- a/src/blua/Makefile.cfg +++ b/src/blua/Makefile.cfg @@ -47,6 +47,7 @@ OBJS:=$(OBJS) \ $(OBJDIR)/lua_skinlib.o \ $(OBJDIR)/lua_thinkerlib.o \ $(OBJDIR)/lua_maplib.o \ + $(OBJDIR)/lua_taglib.o \ $(OBJDIR)/lua_polyobjlib.o \ $(OBJDIR)/lua_blockmaplib.o \ $(OBJDIR)/lua_hudlib.o diff --git a/src/doomtype.h b/src/doomtype.h index c239c7b8e..950f50856 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -379,6 +379,28 @@ Needed for some lua shenanigans. #define FIELDFROM( type, field, have, want ) \ (void *)((intptr_t)(field) - offsetof (type, have) + offsetof (type, want)) +typedef UINT8 bitarray_t; + +#define BIT_ARRAY_SIZE(n) (((n) + 7) >> 3) + +static inline int +in_bit_array (const bitarray_t * const array, const int value) +{ + return (array[value >> 3] & (1<<(value & 7))); +} + +static inline void +set_bit_array (bitarray_t * const array, const int value) +{ + array[value >> 3] |= (1<<(value & 7)); +} + +static inline void +unset_bit_array (bitarray_t * const array, const int value) +{ + array[value >> 3] &= ~(1<<(value & 7)); +} + #ifdef HAVE_SDL typedef UINT64 precise_t; #endif diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 515f6f0ba..c5f847be6 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -155,6 +155,8 @@ static const struct { {META_PIVOTLIST, "spriteframepivot_t[]"}, {META_FRAMEPIVOT, "spriteframepivot_t"}, + {META_TAGLIST, "taglist"}, + {META_MOBJ, "mobj_t"}, {META_MAPTHING, "mapthing_t"}, @@ -186,6 +188,9 @@ static const struct { {META_CVAR, "consvar_t"}, {META_SECTORLINES, "sector_t.lines"}, +#ifdef MUTABLE_TAGS + {META_SECTORTAGLIST, "sector_t.taglist"}, +#endif {META_SIDENUM, "line_t.sidenum"}, {META_LINEARGS, "line_t.args"}, {META_LINESTRINGARGS, "line_t.stringargs"}, diff --git a/src/lua_libs.h b/src/lua_libs.h index 54ac89bcc..fbe8d4878 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -12,6 +12,8 @@ extern lua_State *gL; +#define MUTABLE_TAGS + #define LREG_VALID "VALID_USERDATA" #define LREG_EXTVARS "LUA_VARS" #define LREG_STATEACTION "STATE_ACTION" @@ -27,6 +29,8 @@ extern lua_State *gL; #define META_PIVOTLIST "SPRITEFRAMEPIVOT_T[]" #define META_FRAMEPIVOT "SPRITEFRAMEPIVOT_T*" +#define META_TAGLIST "TAGLIST" + #define META_MOBJ "MOBJ_T*" #define META_MAPTHING "MAPTHING_T*" @@ -58,6 +62,9 @@ extern lua_State *gL; #define META_CVAR "CONSVAR_T*" #define META_SECTORLINES "SECTOR_T*LINES" +#ifdef MUTABLE_TAGS +#define META_SECTORTAGLIST "sector_t.taglist" +#endif #define META_SIDENUM "LINE_T*SIDENUM" #define META_LINEARGS "LINE_T*ARGS" #define META_LINESTRINGARGS "LINE_T*STRINGARGS" @@ -95,6 +102,7 @@ int LUA_PlayerLib(lua_State *L); int LUA_SkinLib(lua_State *L); int LUA_ThinkerLib(lua_State *L); int LUA_MapLib(lua_State *L); +int LUA_TagLib(lua_State *L); int LUA_PolyObjLib(lua_State *L); int LUA_BlockmapLib(lua_State *L); int LUA_HudLib(lua_State *L); diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 83744c74d..6a9091cc9 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -37,6 +37,7 @@ enum sector_e { sector_lightlevel, sector_special, sector_tag, + sector_taglist, sector_thinglist, sector_heightsec, sector_camsec, @@ -55,6 +56,7 @@ static const char *const sector_opt[] = { "lightlevel", "special", "tag", + "taglist", "thinglist", "heightsec", "camsec", @@ -89,6 +91,7 @@ enum line_e { line_flags, line_special, line_tag, + line_taglist, line_args, line_stringargs, line_sidenum, @@ -113,6 +116,7 @@ static const char *const line_opt[] = { "flags", "special", "tag", + "taglist", "args", "stringargs", "sidenum", @@ -581,6 +585,9 @@ static int sector_get(lua_State *L) case sector_tag: lua_pushinteger(L, Tag_FGet(§or->tags)); return 1; + case sector_taglist: + LUA_PushUserdata(L, §or->tags, META_SECTORTAGLIST); + return 1; case sector_thinglist: // thinglist lua_pushcfunction(L, lib_iterateSectorThinglist); LUA_PushUserdata(L, sector->thinglist, META_MOBJ); @@ -682,6 +689,8 @@ static int sector_set(lua_State *L) case sector_tag: Tag_SectorFSet((UINT32)(sector - sectors), (INT16)luaL_checkinteger(L, 3)); break; + case sector_taglist: + return LUA_ErrSetDirectly(L, "sector_t", "taglist"); } return 0; } @@ -821,6 +830,9 @@ static int line_get(lua_State *L) case line_tag: lua_pushinteger(L, Tag_FGet(&line->tags)); return 1; + case line_taglist: + LUA_PushUserdata(L, &line->tags, META_TAGLIST); + return 1; case line_args: LUA_PushUserdata(L, line->args, META_LINEARGS); return 1; @@ -1385,25 +1397,15 @@ static int lib_iterateSectors(lua_State *L) static int lib_getSector(lua_State *L) { - int field; INLEVEL - lua_settop(L, 2); - lua_remove(L, 1); // dummy userdata table is unused. - if (lua_isnumber(L, 1)) + if (lua_isnumber(L, 2)) { - size_t i = lua_tointeger(L, 1); + size_t i = lua_tointeger(L, 2); if (i >= numsectors) return 0; LUA_PushUserdata(L, §ors[i], META_SECTOR); return 1; } - field = luaL_checkoption(L, 1, NULL, array_opt); - switch(field) - { - case 0: // iterate - lua_pushcfunction(L, lib_iterateSectors); - return 1; - } return 0; } @@ -1489,25 +1491,15 @@ static int lib_iterateLines(lua_State *L) static int lib_getLine(lua_State *L) { - int field; INLEVEL - lua_settop(L, 2); - lua_remove(L, 1); // dummy userdata table is unused. - if (lua_isnumber(L, 1)) + if (lua_isnumber(L, 2)) { - size_t i = lua_tointeger(L, 1); + size_t i = lua_tointeger(L, 2); if (i >= numlines) return 0; LUA_PushUserdata(L, &lines[i], META_LINE); return 1; } - field = luaL_checkoption(L, 1, NULL, array_opt); - switch(field) - { - case 0: // iterate - lua_pushcfunction(L, lib_iterateLines); - return 1; - } return 0; } @@ -2360,15 +2352,13 @@ int LUA_MapLib(lua_State *L) //lua_setfield(L, -2, "__len"); lua_pop(L, 1); - lua_newuserdata(L, 0); - lua_createtable(L, 0, 2); - lua_pushcfunction(L, lib_getSector); - lua_setfield(L, -2, "__index"); - - lua_pushcfunction(L, lib_numsectors); - lua_setfield(L, -2, "__len"); - lua_setmetatable(L, -2); - lua_setglobal(L, "sectors"); + LUA_PushTaggableObjectArray(L, "sectors", + lib_iterateSectors, + lib_getSector, + lib_numsectors, + tags_sectors, + &numsectors, §ors, + sizeof (sector_t), META_SECTOR); lua_newuserdata(L, 0); lua_createtable(L, 0, 2); @@ -2380,15 +2370,13 @@ int LUA_MapLib(lua_State *L) lua_setmetatable(L, -2); lua_setglobal(L, "subsectors"); - lua_newuserdata(L, 0); - lua_createtable(L, 0, 2); - lua_pushcfunction(L, lib_getLine); - lua_setfield(L, -2, "__index"); - - lua_pushcfunction(L, lib_numlines); - lua_setfield(L, -2, "__len"); - lua_setmetatable(L, -2); - lua_setglobal(L, "lines"); + LUA_PushTaggableObjectArray(L, "lines", + lib_iterateLines, + lib_getLine, + lib_numlines, + tags_lines, + &numlines, &lines, + sizeof (line_t), META_LINE); lua_newuserdata(L, 0); lua_createtable(L, 0, 2); diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index 7aae18c90..65adceb15 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -22,8 +22,6 @@ #include "lua_hud.h" // hud_running errors #include "lua_hook.h" // hook_cmd_running errors -static const char *const array_opt[] ={"iterate",NULL}; - enum mobj_e { mobj_valid = 0, mobj_x, @@ -904,6 +902,11 @@ static int mapthing_get(lua_State *L) number = mt->extrainfo; else if(fastcmp(field,"tag")) number = Tag_FGet(&mt->tags); + else if(fastcmp(field,"taglist")) + { + LUA_PushUserdata(L, &mt->tags, META_TAGLIST); + return 1; + } else if(fastcmp(field,"args")) { LUA_PushUserdata(L, mt->args, META_THINGARGS); @@ -966,6 +969,8 @@ static int mapthing_set(lua_State *L) } else if (fastcmp(field,"tag")) Tag_FSet(&mt->tags, (INT16)luaL_checkinteger(L, 3)); + else if (fastcmp(field,"taglist")) + return LUA_ErrSetDirectly(L, "mapthing_t", "taglist"); else if(fastcmp(field,"mobj")) mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)); else @@ -1003,25 +1008,15 @@ static int lib_iterateMapthings(lua_State *L) static int lib_getMapthing(lua_State *L) { - int field; INLEVEL - lua_settop(L, 2); - lua_remove(L, 1); // dummy userdata table is unused. - if (lua_isnumber(L, 1)) + if (lua_isnumber(L, 2)) { - size_t i = lua_tointeger(L, 1); + size_t i = lua_tointeger(L, 2); if (i >= nummapthings) return 0; LUA_PushUserdata(L, &mapthings[i], META_MAPTHING); return 1; } - field = luaL_checkoption(L, 1, NULL, array_opt); - switch(field) - { - case 0: // iterate - lua_pushcfunction(L, lib_iterateMapthings); - return 1; - } return 0; } @@ -1068,14 +1063,13 @@ int LUA_MobjLib(lua_State *L) lua_setfield(L, -2, "__len"); lua_pop(L,1); - lua_newuserdata(L, 0); - lua_createtable(L, 0, 2); - lua_pushcfunction(L, lib_getMapthing); - lua_setfield(L, -2, "__index"); + LUA_PushTaggableObjectArray(L, "mapthings", + lib_iterateMapthings, + lib_getMapthing, + lib_nummapthings, + tags_mapthings, + &nummapthings, &mapthings, + sizeof (mapthing_t), META_MAPTHING); - lua_pushcfunction(L, lib_nummapthings); - lua_setfield(L, -2, "__len"); - lua_setmetatable(L, -2); - lua_setglobal(L, "mapthings"); return 0; } diff --git a/src/lua_script.c b/src/lua_script.c index eb4737f76..bc88928f3 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -53,6 +53,7 @@ static lua_CFunction liblist[] = { LUA_SkinLib, // skin_t, skins[] LUA_ThinkerLib, // thinker_t LUA_MapLib, // line_t, side_t, sector_t, subsector_t + LUA_TagLib, // tags LUA_PolyObjLib, // polyobj_t LUA_BlockmapLib, // blockmap stuff LUA_HudLib, // HUD stuff @@ -739,25 +740,37 @@ void LUA_PushLightUserdata (lua_State *L, void *data, const char *meta) // Pushes it to the stack and stores it in the registry. void LUA_PushUserdata(lua_State *L, void *data, const char *meta) { + if (LUA_RawPushUserdata(L, data) == LPUSHED_NEW) + { + luaL_getmetatable(L, meta); + lua_setmetatable(L, -2); + } +} + +// Same as LUA_PushUserdata but don't set a metatable yet. +lpushed_t LUA_RawPushUserdata(lua_State *L, void *data) +{ + lpushed_t status = LPUSHED_NIL; + void **userdata; if (!data) { // push a NULL lua_pushnil(L); - return; + return status; } lua_getfield(L, LUA_REGISTRYINDEX, LREG_VALID); I_Assert(lua_istable(L, -1)); + lua_pushlightuserdata(L, data); lua_rawget(L, -2); + if (lua_isnil(L, -1)) { // no userdata? deary me, we'll have to make one. lua_pop(L, 1); // pop the nil // create the userdata userdata = lua_newuserdata(L, sizeof(void *)); *userdata = data; - luaL_getmetatable(L, meta); - lua_setmetatable(L, -2); // Set it in the registry so we can find it again lua_pushlightuserdata(L, data); // k (store the userdata via the data's pointer) @@ -765,8 +778,15 @@ void LUA_PushUserdata(lua_State *L, void *data, const char *meta) lua_rawset(L, -4); // stack is left with the userdata on top, as if getting it had originally succeeded. + + status = LPUSHED_NEW; } + else + status = LPUSHED_EXISTING; + lua_remove(L, -2); // remove LREG_VALID + + return status; } // When userdata is freed, use this function to remove it from Lua. @@ -826,6 +846,7 @@ void LUA_InvalidateLevel(void) { LUA_InvalidateUserdata(§ors[i]); LUA_InvalidateUserdata(§ors[i].lines); + LUA_InvalidateUserdata(§ors[i].tags); if (sectors[i].ffloors) { for (rover = sectors[i].ffloors; rover; rover = rover->next) @@ -835,6 +856,7 @@ void LUA_InvalidateLevel(void) for (i = 0; i < numlines; i++) { LUA_InvalidateUserdata(&lines[i]); + LUA_InvalidateUserdata(&lines[i].tags); LUA_InvalidateUserdata(lines[i].sidenum); } for (i = 0; i < numsides; i++) @@ -866,7 +888,10 @@ void LUA_InvalidateMapthings(void) return; for (i = 0; i < nummapthings; i++) + { LUA_InvalidateUserdata(&mapthings[i]); + LUA_InvalidateUserdata(&mapthings[i].tags); + } } void LUA_InvalidatePlayer(player_t *player) @@ -1681,3 +1706,36 @@ int Lua_optoption(lua_State *L, int narg, return i; return -1; } + +void LUA_PushTaggableObjectArray +( lua_State *L, + const char *field, + lua_CFunction iterator, + lua_CFunction indexer, + lua_CFunction counter, + taggroup_t *garray[], + size_t * max_elements, + void * element_array, + size_t sizeof_element, + const char *meta) +{ + lua_newuserdata(L, 0); + lua_createtable(L, 0, 2); + lua_createtable(L, 0, 2); + lua_pushcfunction(L, iterator); + lua_setfield(L, -2, "iterate"); + + LUA_InsertTaggroupIterator(L, garray, + max_elements, element_array, sizeof_element, meta); + + lua_createtable(L, 0, 1); + lua_pushcfunction(L, indexer); + lua_setfield(L, -2, "__index"); + lua_setmetatable(L, -2); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, counter); + lua_setfield(L, -2, "__len"); + lua_setmetatable(L, -2); + lua_setglobal(L, field); +} diff --git a/src/lua_script.h b/src/lua_script.h index 79ba0bb38..77fbb7c1d 100644 --- a/src/lua_script.h +++ b/src/lua_script.h @@ -10,10 +10,14 @@ /// \file lua_script.h /// \brief Lua scripting basics +#ifndef LUA_SCRIPT_H +#define LUA_SCRIPT_H + #include "m_fixed.h" #include "doomtype.h" #include "d_player.h" #include "g_state.h" +#include "taglist.h" #include "blua/lua.h" #include "blua/lualib.h" @@ -46,12 +50,6 @@ void LUA_LoadLump(UINT16 wad, UINT16 lump, boolean noresults); void LUA_DumpFile(const char *filename); #endif fixed_t LUA_EvalMath(const char *word); -void LUA_PushLightUserdata(lua_State *L, void *data, const char *meta); -void LUA_PushUserdata(lua_State *L, void *data, const char *meta); -void LUA_InvalidateUserdata(void *data); -void LUA_InvalidateLevel(void); -void LUA_InvalidateMapthings(void); -void LUA_InvalidatePlayer(player_t *player); void LUA_Step(void); void LUA_Archive(void); void LUA_UnArchive(void); @@ -63,11 +61,49 @@ int Lua_optoption(lua_State *L, int narg, const char *def, const char *const lst[]); void LUAh_NetArchiveHook(lua_CFunction archFunc); +void LUA_PushTaggableObjectArray +( lua_State *L, + const char *field, + lua_CFunction iterator, + lua_CFunction indexer, + lua_CFunction counter, + taggroup_t *garray[], + size_t * max_elements, + void * element_array, + size_t sizeof_element, + const char *meta); + +void LUA_InsertTaggroupIterator +( lua_State *L, + taggroup_t *garray[], + size_t * max_elements, + void * element_array, + size_t sizeof_element, + const char * meta); + +typedef enum { + LPUSHED_NIL, + LPUSHED_NEW, + LPUSHED_EXISTING, +} lpushed_t; + +void LUA_PushLightUserdata(lua_State *L, void *data, const char *meta); +void LUA_PushUserdata(lua_State *L, void *data, const char *meta); +lpushed_t LUA_RawPushUserdata(lua_State *L, void *data); + +void LUA_InvalidateUserdata(void *data); + +void LUA_InvalidateLevel(void); +void LUA_InvalidateMapthings(void); +void LUA_InvalidatePlayer(player_t *player); + // Console wrapper void COM_Lua_f(void); #define LUA_ErrInvalid(L, type) luaL_error(L, "accessed " type " doesn't exist anymore, please check 'valid' before using " type "."); +#define LUA_ErrSetDirectly(L, type, field) luaL_error(L, type " field " LUA_QL(field) " cannot be set directly.") + // Deprecation warnings // Shows once upon use. Then doesn't show again. #define LUA_Deprecated(L,this_func,use_instead)\ @@ -98,3 +134,5 @@ void COM_Lua_f(void); #define INLEVEL if (! ISINLEVEL)\ return luaL_error(L, "This can only be used in a level!"); + +#endif/*LUA_SCRIPT_H*/ diff --git a/src/lua_taglib.c b/src/lua_taglib.c new file mode 100644 index 000000000..c9f320fe8 --- /dev/null +++ b/src/lua_taglib.c @@ -0,0 +1,451 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2020 by James R. +// Copyright (C) 2020 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file lua_taglib.c +/// \brief tag list iterator for Lua scripting + +#include "doomdef.h" +#include "taglist.h" +#include "r_state.h" + +#include "lua_script.h" +#include "lua_libs.h" + +#ifdef MUTABLE_TAGS +#include "z_zone.h" +#endif + +static int tag_iterator(lua_State *L) +{ + INT32 tag = lua_isnil(L, 2) ? -1 : lua_tonumber(L, 2); + do + { + if (++tag >= MAXTAGS) + return 0; + } + while (! in_bit_array(tags_available, tag)) ; + lua_pushnumber(L, tag); + return 1; +} + +enum { +#define UPVALUE lua_upvalueindex + up_garray = UPVALUE(1), + up_max_elements = UPVALUE(2), + up_element_array = UPVALUE(3), + up_sizeof_element = UPVALUE(4), + up_meta = UPVALUE(5), +#undef UPVALUE +}; + +static INT32 next_element(lua_State *L, const mtag_t tag, const size_t p) +{ + taggroup_t ** garray = lua_touserdata(L, up_garray); + const size_t * max_elements = lua_touserdata(L, up_max_elements); + return Taggroup_Iterate(garray, *max_elements, tag, p); +} + +static void push_element(lua_State *L, void *element) +{ + if (LUA_RawPushUserdata(L, element) == LPUSHED_NEW) + { + lua_pushvalue(L, up_meta); + lua_setmetatable(L, -2); + } +} + +static void push_next_element(lua_State *L, const INT32 element) +{ + char * element_array = *(char **)lua_touserdata(L, up_element_array); + const size_t sizeof_element = lua_tonumber(L, up_sizeof_element); + push_element(L, &element_array[element * sizeof_element]); +} + +struct element_iterator_state { + mtag_t tag; + size_t p; +}; + +static int element_iterator(lua_State *L) +{ + struct element_iterator_state * state = lua_touserdata(L, 1); + if (lua_isnoneornil(L, 3)) + state->p = 0; + lua_pushnumber(L, ++state->p); + lua_gettable(L, 1); + return 1; +} + +static int lib_iterateTags(lua_State *L) +{ + if (lua_gettop(L) < 2) + { + lua_pushcfunction(L, tag_iterator); + return 1; + } + else + return tag_iterator(L); +} + +static int lib_numTags(lua_State *L) +{ + lua_pushnumber(L, num_tags); + return 1; +} + +static int lib_getTaggroup(lua_State *L) +{ + struct element_iterator_state *state; + + mtag_t tag; + + if (lua_gettop(L) > 1) + return luaL_error(L, "too many arguments"); + + if (lua_isnoneornil(L, 1)) + { + tag = MTAG_GLOBAL; + } + else + { + tag = lua_tonumber(L, 1); + luaL_argcheck(L, tag >= -1, 1, "tag out of range"); + } + + state = lua_newuserdata(L, sizeof *state); + state->tag = tag; + state->p = 0; + + lua_pushvalue(L, lua_upvalueindex(1)); + lua_setmetatable(L, -2); + + return 1; +} + +static int lib_getTaggroupElement(lua_State *L) +{ + const size_t p = luaL_checknumber(L, 2) - 1; + const mtag_t tag = *(mtag_t *)lua_touserdata(L, 1); + const INT32 element = next_element(L, tag, p); + + if (element == -1) + return 0; + else + { + push_next_element(L, element); + return 1; + } +} + +static int lib_numTaggroupElements(lua_State *L) +{ + const mtag_t tag = *(mtag_t *)lua_touserdata(L, 1); + if (tag == MTAG_GLOBAL) + lua_pushnumber(L, *(size_t *)lua_touserdata(L, up_max_elements)); + else + { + const taggroup_t ** garray = lua_touserdata(L, up_garray); + lua_pushnumber(L, Taggroup_Count(garray[tag])); + } + return 1; +} + +#ifdef MUTABLE_TAGS +static int meta_ref[2]; +#endif + +static int has_valid_field(lua_State *L) +{ + int equal; + lua_rawgeti(L, LUA_ENVIRONINDEX, 1); + equal = lua_rawequal(L, 2, -1); + lua_pop(L, 1); + return equal; +} + +static taglist_t * valid_taglist(lua_State *L, int idx, boolean getting) +{ + taglist_t *list = *(taglist_t **)lua_touserdata(L, idx); + + if (list == NULL) + { + if (getting && has_valid_field(L)) + lua_pushboolean(L, 0); + else + LUA_ErrInvalid(L, "taglist");/* doesn't actually return */ + return NULL; + } + else + return list; +} + +static taglist_t * check_taglist(lua_State *L, int idx) +{ + if (lua_isuserdata(L, idx) && lua_getmetatable(L, idx)) + { + lua_getref(L, meta_ref[0]); + lua_getref(L, meta_ref[1]); + + if (lua_rawequal(L, -3, -2) || lua_rawequal(L, -3, -1)) + { + lua_pop(L, 3); + return valid_taglist(L, idx, false); + } + } + + return luaL_argerror(L, idx, "must be a tag list"), NULL; +} + +static int taglist_get(lua_State *L) +{ + const taglist_t *list = valid_taglist(L, 1, true); + + if (list == NULL)/* valid check */ + return 1; + + if (lua_isnumber(L, 2)) + { + const size_t i = lua_tonumber(L, 2); + + if (list && i <= list->count) + { + lua_pushnumber(L, list->tags[i - 1]); + return 1; + } + else + return 0; + } + else if (has_valid_field(L)) + { + lua_pushboolean(L, 1); + return 1; + } + else + { + lua_getmetatable(L, 1); + lua_replace(L, 1); + lua_rawget(L, 1); + return 1; + } +} + +static int taglist_len(lua_State *L) +{ + const taglist_t *list = valid_taglist(L, 1, false); + lua_pushnumber(L, list->count); + return 1; +} + +static int taglist_equal(lua_State *L) +{ + const taglist_t *lhs = check_taglist(L, 1); + const taglist_t *rhs = check_taglist(L, 2); + lua_pushboolean(L, Tag_Compare(lhs, rhs)); + return 1; +} + +static int taglist_iterator(lua_State *L) +{ + const taglist_t *list = valid_taglist(L, 1, false); + const size_t i = 1 + lua_tonumber(L, lua_upvalueindex(1)); + if (i <= list->count) + { + lua_pushnumber(L, list->tags[i - 1]); + /* watch me exploit an upvalue as a control because + I want to use the control as the value */ + lua_pushnumber(L, i); + lua_replace(L, lua_upvalueindex(1)); + return 1; + } + else + return 0; +} + +static int taglist_iterate(lua_State *L) +{ + check_taglist(L, 1); + lua_pushnumber(L, 0); + lua_pushcclosure(L, taglist_iterator, 1); + lua_pushvalue(L, 1); + return 2; +} + +static int taglist_find(lua_State *L) +{ + const taglist_t *list = check_taglist(L, 1); + const mtag_t tag = luaL_checknumber(L, 2); + lua_pushboolean(L, Tag_Find(list, tag)); + return 1; +} + +static int taglist_shares(lua_State *L) +{ + const taglist_t *lhs = check_taglist(L, 1); + const taglist_t *rhs = check_taglist(L, 2); + lua_pushboolean(L, Tag_Share(lhs, rhs)); + return 1; +} + +/* only sector tags are mutable... */ + +#ifdef MUTABLE_TAGS +static size_t sector_of_taglist(taglist_t *list) +{ + return (sector_t *)((char *)list - offsetof (sector_t, tags)) - sectors; +} + +static int this_taglist(lua_State *L) +{ + lua_settop(L, 1); + return 1; +} + +static int taglist_add(lua_State *L) +{ + taglist_t *list = *(taglist_t **)luaL_checkudata(L, 1, META_SECTORTAGLIST); + const mtag_t tag = luaL_checknumber(L, 2); + + if (! Tag_Find(list, tag)) + { + Taggroup_Add(tags_sectors, tag, sector_of_taglist(list)); + Tag_Add(list, tag); + } + + return this_taglist(L); +} + +static int taglist_remove(lua_State *L) +{ + taglist_t *list = *(taglist_t **)luaL_checkudata(L, 1, META_SECTORTAGLIST); + const mtag_t tag = luaL_checknumber(L, 2); + + size_t i; + + for (i = 0; i < list->count; ++i) + { + if (list->tags[i] == tag) + { + if (list->count > 1) + { + memmove(&list->tags[i], &list->tags[i + 1], + (list->count - 1 - i) * sizeof (mtag_t)); + list->tags = Z_Realloc(list->tags, + (--list->count) * sizeof (mtag_t), PU_LEVEL, NULL); + Taggroup_Remove(tags_sectors, tag, sector_of_taglist(list)); + } + else/* reset to default tag */ + Tag_SectorFSet(sector_of_taglist(list), 0); + break; + } + } + + return this_taglist(L); +} +#endif/*MUTABLE_TAGS*/ + +void LUA_InsertTaggroupIterator +( lua_State *L, + taggroup_t *garray[], + size_t * max_elements, + void * element_array, + size_t sizeof_element, + const char * meta) +{ + lua_createtable(L, 0, 3); + lua_pushlightuserdata(L, garray); + lua_pushlightuserdata(L, max_elements); + + lua_pushvalue(L, -2); + lua_pushvalue(L, -2); + lua_pushlightuserdata(L, element_array); + lua_pushnumber(L, sizeof_element); + luaL_getmetatable(L, meta); + lua_pushcclosure(L, lib_getTaggroupElement, 5); + lua_setfield(L, -4, "__index"); + + lua_pushcclosure(L, lib_numTaggroupElements, 2); + lua_setfield(L, -2, "__len"); + + lua_pushcfunction(L, element_iterator); + lua_setfield(L, -2, "__call"); + lua_pushcclosure(L, lib_getTaggroup, 1); + lua_setfield(L, -2, "tagged"); +} + +static luaL_Reg taglist_lib[] = { + {"iterate", taglist_iterate}, + {"find", taglist_find}, + {"shares", taglist_shares}, +#ifdef MUTABLE_TAGS + {"add", taglist_add}, + {"remove", taglist_remove}, +#endif + {0} +}; + +static void open_taglist(lua_State *L) +{ + luaL_register(L, "taglist", taglist_lib); + + lua_getfield(L, -1, "find"); + lua_setfield(L, -2, "has"); +} + +#define new_literal(L, s) \ + (lua_pushliteral(L, s), luaL_ref(L, -2)) + +#ifdef MUTABLE_TAGS +static int +#else +static void +#endif +set_taglist_metatable(lua_State *L, const char *meta) +{ + luaL_newmetatable(L, meta); + lua_pushcfunction(L, taglist_get); + lua_createtable(L, 0, 1); + new_literal(L, "valid"); + lua_setfenv(L, -2); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, taglist_len); + lua_setfield(L, -2, "__len"); + + lua_pushcfunction(L, taglist_equal); + lua_setfield(L, -2, "__eq"); +#ifdef MUTABLE_TAGS + return luaL_ref(L, LUA_REGISTRYINDEX); +#endif +} + +int LUA_TagLib(lua_State *L) +{ + lua_newuserdata(L, 0); + lua_createtable(L, 0, 2); + lua_createtable(L, 0, 1); + lua_pushcfunction(L, lib_iterateTags); + lua_setfield(L, -2, "iterate"); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, lib_numTags); + lua_setfield(L, -2, "__len"); + lua_setmetatable(L, -2); + lua_setglobal(L, "tags"); + + open_taglist(L); + +#ifdef MUTABLE_TAGS + meta_ref[0] = set_taglist_metatable(L, META_TAGLIST); + meta_ref[1] = set_taglist_metatable(L, META_SECTORTAGLIST); +#else + set_taglist_metatable(L, META_TAGLIST); +#endif + + return 0; +} diff --git a/src/taglist.c b/src/taglist.c index b11216b6c..a759f4d02 100644 --- a/src/taglist.c +++ b/src/taglist.c @@ -15,6 +15,11 @@ #include "z_zone.h" #include "r_data.h" +// Bit array of whether a tag exists for sectors/lines/things. +bitarray_t tags_available[BIT_ARRAY_SIZE (MAXTAGS)]; + +size_t num_tags; + // Taggroups are used to list elements of the same tag, for iteration. // Since elements can now have multiple tags, it means an element may appear // in several taggroups at the same time. These are built on level load. @@ -105,6 +110,39 @@ size_t Taggroup_Find (const taggroup_t *group, const size_t id) return -1; } +/// group->count, but also checks for NULL +size_t Taggroup_Count (const taggroup_t *group) +{ + return group ? group->count : 0; +} + +/// Iterate thru elements in a global taggroup. +INT32 Taggroup_Iterate +( taggroup_t *garray[], + const size_t max_elements, + const mtag_t tag, + const size_t p) +{ + const taggroup_t *group; + + if (tag == MTAG_GLOBAL) + { + if (p < max_elements) + return p; + return -1; + } + + group = garray[(UINT16)tag]; + + if (group) + { + if (p < group->count) + return group->elements[p]; + return -1; + } + return -1; +} + /// Add an element to a global taggroup. void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id) { @@ -120,6 +158,12 @@ void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id) if (Taggroup_Find(group, id) != (size_t)-1) return; + if (! in_bit_array(tags_available, tag)) + { + num_tags++; + set_bit_array(tags_available, tag); + } + // Create group if empty. if (!group) { @@ -133,25 +177,34 @@ void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id) for (i = 0; i < group->count; i++) if (group->elements[i] > id) break; - - group->elements = Z_Realloc(group->elements, (group->count + 1) * sizeof(size_t), PU_LEVEL, NULL); - - // Offset existing elements to make room for the new one. - if (i < group->count) - memmove(&group->elements[i + 1], &group->elements[i], group->count - i); } + group->elements = Z_Realloc(group->elements, (group->count + 1) * sizeof(size_t), PU_LEVEL, NULL); + + // Offset existing elements to make room for the new one. + if (i < group->count) + memmove(&group->elements[i + 1], &group->elements[i], group->count - i); + group->count++; - group->elements = Z_Realloc(group->elements, group->count * sizeof(size_t), PU_LEVEL, NULL); group->elements[i] = id; } +static size_t total_elements_with_tag (const mtag_t tag) +{ + return + ( + Taggroup_Count(tags_sectors[tag]) + + Taggroup_Count(tags_lines[tag]) + + Taggroup_Count(tags_mapthings[tag]) + ); +} + /// Remove an element from a global taggroup. void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id) { taggroup_t *group; size_t rempos; - size_t newcount; + size_t oldcount; if (tag == MTAG_GLOBAL) return; @@ -161,8 +214,14 @@ void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id) if ((rempos = Taggroup_Find(group, id)) == (size_t)-1) return; + if (group->count == 1 && total_elements_with_tag(tag) == 1) + { + num_tags--; + unset_bit_array(tags_available, tag); + } + // Strip away taggroup if no elements left. - if (!(newcount = --group->count)) + if (!(oldcount = group->count--)) { Z_Free(group->elements); Z_Free(group); @@ -170,19 +229,18 @@ void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id) } else { - size_t *newelements = Z_Malloc(newcount * sizeof(size_t), PU_LEVEL, NULL); + size_t *newelements = Z_Malloc(group->count * sizeof(size_t), PU_LEVEL, NULL); size_t i; // Copy the previous entries save for the one to remove. for (i = 0; i < rempos; i++) newelements[i] = group->elements[i]; - for (i = rempos + 1; i < group->count; i++) + for (i = rempos + 1; i < oldcount; i++) newelements[i - 1] = group->elements[i]; Z_Free(group->elements); group->elements = newelements; - group->count = newcount; } } @@ -209,6 +267,9 @@ void Taglist_InitGlobalTables(void) { size_t i, j; + memset(tags_available, 0, sizeof tags_available); + num_tags = 0; + for (i = 0; i < MAXTAGS; i++) { tags_sectors[i] = NULL; @@ -236,56 +297,17 @@ void Taglist_InitGlobalTables(void) INT32 Tag_Iterate_Sectors (const mtag_t tag, const size_t p) { - if (tag == MTAG_GLOBAL) - { - if (p < numsectors) - return p; - return -1; - } - - if (tags_sectors[(UINT16)tag]) - { - if (p < tags_sectors[(UINT16)tag]->count) - return tags_sectors[(UINT16)tag]->elements[p]; - return -1; - } - return -1; + return Taggroup_Iterate(tags_sectors, numsectors, tag, p); } INT32 Tag_Iterate_Lines (const mtag_t tag, const size_t p) { - if (tag == MTAG_GLOBAL) - { - if (p < numlines) - return p; - return -1; - } - - if (tags_lines[(UINT16)tag]) - { - if (p < tags_lines[(UINT16)tag]->count) - return tags_lines[(UINT16)tag]->elements[p]; - return -1; - } - return -1; + return Taggroup_Iterate(tags_lines, numlines, tag, p); } INT32 Tag_Iterate_Things (const mtag_t tag, const size_t p) { - if (tag == MTAG_GLOBAL) - { - if (p < nummapthings) - return p; - return -1; - } - - if (tags_mapthings[(UINT16)tag]) - { - if (p < tags_mapthings[(UINT16)tag]->count) - return tags_mapthings[(UINT16)tag]->elements[p]; - return -1; - } - return -1; + return Taggroup_Iterate(tags_mapthings, nummapthings, tag, p); } INT32 Tag_FindLineSpecial(const INT16 special, const mtag_t tag) diff --git a/src/taglist.h b/src/taglist.h index 0e6d9f842..a0529ab6b 100644 --- a/src/taglist.h +++ b/src/taglist.h @@ -43,6 +43,10 @@ typedef struct size_t count; } taggroup_t; +extern bitarray_t tags_available[]; + +extern size_t num_tags; + extern taggroup_t* tags_sectors[]; extern taggroup_t* tags_lines[]; extern taggroup_t* tags_mapthings[]; @@ -50,6 +54,13 @@ extern taggroup_t* tags_mapthings[]; void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id); void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id); size_t Taggroup_Find (const taggroup_t *group, const size_t id); +size_t Taggroup_Count (const taggroup_t *group); + +INT32 Taggroup_Iterate +( taggroup_t *garray[], + const size_t max_elements, + const mtag_t tag, + const size_t p); void Taglist_InitGlobalTables(void);