diff --git a/luprex/core/Makefile b/luprex/core/Makefile index 4a56e48d..13856a0f 100644 --- a/luprex/core/Makefile +++ b/luprex/core/Makefile @@ -13,6 +13,7 @@ CPP_FILES=\ cpp/globaldb.cpp\ cpp/sched.cpp\ cpp/table.cpp\ + cpp/tablecmp.cpp\ cpp/gui.cpp\ cpp/luasnap.cpp\ cpp/animqueue.cpp\ diff --git a/luprex/core/cpp/globaldb.cpp b/luprex/core/cpp/globaldb.cpp index 0b368a25..f494c01c 100644 --- a/luprex/core/cpp/globaldb.cpp +++ b/luprex/core/cpp/globaldb.cpp @@ -45,6 +45,6 @@ LuaDefine(globaldb_global, "f") { LS.newtable(globaltab); LS.rawset(globaldb, globalname, globaltab); LS.rawset(globaltab, "__global", globalname); - LS.settabletype(globaltab, LuaStack::TAB_GLOBALDB); + LS.settabletype(globaltab, LUA_TT_GLOBALDB); return LS.result(); } diff --git a/luprex/core/cpp/luastack.cpp b/luprex/core/cpp/luastack.cpp index ebc6db93..033ae3c8 100644 --- a/luprex/core/cpp/luastack.cpp +++ b/luprex/core/cpp/luastack.cpp @@ -1,5 +1,6 @@ #include "luastack.hpp" #include +#include LuaSpecial LuaRegistry(LUA_REGISTRYINDEX); LuaNilMarker LuaNil; @@ -234,7 +235,7 @@ void LuaStack::makeclass(LuaSlot classtab, LuaSlot classname) const { } // Repair the special fields. - LS.settabletype(classtab, TAB_CLASS); + LS.settabletype(classtab, LUA_TT_CLASS); LS.rawset(classtab, "__index", classtab); LS.rawset(classtab, "__class", classname); @@ -242,23 +243,48 @@ void LuaStack::makeclass(LuaSlot classtab, LuaSlot classname) const { LS.result(); } -void LuaStack::movesortablekey(LuaSlot key, lua_State *L) { +void LuaStack::makeclass(LuaSlot tab, const char *name) { + push_any_value(name); + LuaSpecial classname(lua_gettop(L_)); + makeclass(tab, classname); + lua_pop(L_, 1); +} + +std::string LuaStack::classname(LuaSlot tab) { + std::string result; + if (istable(tab)) { + lua_pushstring(L_, "__class"); + lua_rawget(L_, tab); + if (lua_type(L_, -1) == LUA_TSTRING) { + size_t len; + const char *s = lua_tolstring(L_, -1, &len); + result = std::string(s, len); + } + lua_pop(L_, 1); + } + return result; +} + +void LuaStack::movesortablekey(LuaSlot key, LuaStack &otherstack, LuaSlot otherslot) { int type = lua_type(L_, key); switch (type) { case LUA_TBOOLEAN: - lua_pushboolean(L, lua_toboolean(L_, key)); + lua_pushboolean(otherstack.L_, lua_toboolean(L_, key)); + lua_replace(otherstack.L_, otherslot); break; case LUA_TNUMBER: - lua_pushnumber(L, lua_tonumber(L_, key)); + lua_pushnumber(otherstack.L_, lua_tonumber(L_, key)); + lua_replace(otherstack.L_, otherslot); break; case LUA_TSTRING: { size_t len; const char *str = lua_tolstring(L_, key, &len); - lua_pushlstring(L, str, len); + lua_pushlstring(otherstack.L_, str, len); + lua_replace(otherstack.L_, otherslot); break; } default: - luaL_error(L, "movesortablekey: not a sortable key"); + luaL_error(L_, "movesortablekey: not a sortable key"); } } @@ -297,20 +323,29 @@ void LuaStack::check_nret(int xnret, int otop, int nret) const { } } -LuaStack::TableType LuaStack::gettabletype(LuaSlot tab) { +int LuaStack::gettabletype(LuaSlot tab) const { uint16_t bits = lua_getflagbits(L_, tab.index()); - return TableType(bits & 0x000F); + return LUA_TT_GENERAL + (bits & 0x000F); } -void LuaStack::settabletype(LuaSlot tab, TableType t) { - lua_modflagbits(L_, tab.index(), 0x000F, t); +void LuaStack::settabletype(LuaSlot tab, int t) const { + assert((t >= LUA_TT_GENERAL) && (t <= LUA_TT_CLASS)); + int offset = (t - LUA_TT_GENERAL); + lua_modflagbits(L_, tab.index(), 0x000F, offset); } -bool LuaStack::getvisited(LuaSlot tab) { +int LuaStack::xtype(LuaSlot slot) const { + int t = lua_type(L_, slot); + if (t != LUA_TTABLE) return t; + uint16_t bits = lua_getflagbits(L_, slot); + return LUA_TT_GENERAL + (bits & 0x000F); +} + +bool LuaStack::getvisited(LuaSlot tab) const { uint16_t bits = lua_getflagbits(L_, tab.index()); return (bits & 0x0010); } -void LuaStack::setvisited(LuaSlot tab, bool visited) { +void LuaStack::setvisited(LuaSlot tab, bool visited) const { lua_modflagbits(L_, tab.index(), 0x0010, visited ? 0x0010 : 0); } diff --git a/luprex/core/cpp/luastack.hpp b/luprex/core/cpp/luastack.hpp index 8668416b..f8d722de 100644 --- a/luprex/core/cpp/luastack.hpp +++ b/luprex/core/cpp/luastack.hpp @@ -215,6 +215,18 @@ using LuaTypeTag = lua_CFunction; template int LuaTypeTagValue(lua_State *L) { return 0; } +// Lua table types. These deliberately do not overlap +// with lua type values. +// +#define LUA_TT_GENERAL 16 +#define LUA_TT_REGISTRY 17 +#define LUA_TT_GLOBALENV 18 +#define LUA_TT_TANGIBLE 19 +#define LUA_TT_TANGIBLEMETA 20 +#define LUA_TT_DEADTANGIBLE 21 +#define LUA_TT_GLOBALDB 22 +#define LUA_TT_CLASS 23 + class LuaStack { private: int narg_; @@ -360,6 +372,8 @@ public: int result(); public: + lua_State *state() const { return L_; } + int type(LuaSlot s) const { return lua_type(L_, s); } void checktype(LuaSlot s, int type) const { luaL_checktype(L_, s, type); } @@ -404,17 +418,13 @@ public: int next(LuaSlot tab, LuaSlot key, LuaSlot value) const; - void makeclass(LuaSlot tab, LuaSlot name) const; void getclass(LuaSlot tab, LuaSlot name) const; + void makeclass(LuaSlot tab, LuaSlot name) const; + void makeclass(LuaSlot tab, const char *name); + std::string classname(LuaSlot tab); - void movesortablekey(LuaSlot val, lua_State *L); + void movesortablekey(LuaSlot val, LuaStack &other, LuaSlot otherslot); - void makeclass(LuaSlot tab, const char *name) const { - push_any_value(name); - LuaSpecial classname(lua_gettop(L_)); - makeclass(tab, classname); - lua_pop(L_, 1); - } bool rawequal(LuaSlot v1, LuaSlot v2) const { return lua_rawequal(L_, v1, v2); @@ -475,21 +485,16 @@ public: } // Lua flagbits manipulation: Table types. - enum TableType { - TAB_GENERAL, // A general-purpose table. - TAB_REGISTRY, // The registry table. - TAB_GLOBALENV, // The global environment table. - TAB_TANGIBLE, // A tangible's database. - TAB_TANGIBLEMETA, // A tangible's metatable. - TAB_GLOBALDB, // Part of the globaldb. - TAB_CLASS, // A class which is directly reachable from the global environment. - }; - TableType gettabletype(LuaSlot tab); - void settabletype(LuaSlot tab, TableType t); + int gettabletype(LuaSlot tab) const; + void settabletype(LuaSlot tab, int t) const; + + // If slot is a table, returns the LUA_TT_XXX table type. + // If slot is not a table, returns the LUA_TXXX general type. + int xtype(LuaSlot slot) const; // Lua flagbits manipulation: visited bit. - bool getvisited(LuaSlot tab); - void setvisited(LuaSlot tab, bool visited); + bool getvisited(LuaSlot tab) const; + void setvisited(LuaSlot tab, bool visited) const; }; diff --git a/luprex/core/cpp/tablecmp.cpp b/luprex/core/cpp/tablecmp.cpp new file mode 100644 index 00000000..e770f2dc --- /dev/null +++ b/luprex/core/cpp/tablecmp.cpp @@ -0,0 +1,259 @@ +#include "luastack.hpp" +#include "streambuffer.hpp" +#include "tablecmp.hpp" + +// Given a table and an tnmap, return the table number of the table. +// Returns zero if the table doesn't have a table number. +// +static int get_table_number(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap) { + lua_State *L = MLS.state(); + lua_pushvalue(L, mval.index()); + lua_rawget(L, mtnmap.index()); + int result = 0; + if (lua_type(L, -1) == LUA_TNUMBER) { + result = lua_tointeger(L, -1); + } + lua_pop(L, 1); + return result; +} + +// Get the tangible ID of a tangible. +static int64_t get_tangible_id(const LuaStack &LS, LuaSlot tab) { + int64_t id = 0; + if (LS.istable(tab) && LS.gettabletype(tab) == LUA_TT_TANGIBLE) { + lua_State *L = LS.state(); + if (lua_getmetatable(L, tab.index())) { + lua_pushstring(L, "id"); + lua_rawget(L, -2); + if (lua_type(L, -1) == LUA_TNUMBER) { + id = lua_tointeger(L, -1); + } + lua_pop(L, 2); + } + } + return id; +} + + +static bool equivalent_values(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap, + LuaStack &SLS, LuaSlot sval, LuaSlot stnmap) { + switch (MLS.xtype(mval)) { + case LUA_TBOOLEAN: { + if (SLS.type(sval) != LUA_TBOOLEAN) return false; + return MLS.ckboolean(mval) == SLS.ckboolean(sval); + } + case LUA_TNUMBER: { + if (SLS.type(sval) != LUA_TNUMBER) return false; + return MLS.cknumber(mval) == SLS.cknumber(sval); + } + case LUA_TSTRING: { + // This could be faster if I used lua_tolstring directly. + if (SLS.type(sval) != LUA_TSTRING) return false; + return MLS.ckstring(mval) == SLS.ckstring(sval); + } + case LUA_TFUNCTION: { + // Cannot really compare. Just return true if the types match. + return SLS.type(sval) == MLS.type(mval); + } + case LUA_TT_GENERAL: { + if (SLS.xtype(sval) != LUA_TT_GENERAL) return false; + int midx = get_table_number(MLS, mval, mtnmap); + if (midx == 0) return false; + int sidx = get_table_number(SLS, sval, stnmap); + if (sidx == 0) return false; + return midx == sidx; + } + case LUA_TT_CLASS: { + if (SLS.xtype(sval) != LUA_TT_CLASS) return false; + return MLS.classname(mval) == SLS.classname(sval); + } + case LUA_TT_TANGIBLE: { + if (SLS.xtype(sval) != LUA_TT_TANGIBLE) return false; + return get_tangible_id(MLS, mval) == get_tangible_id(SLS, sval); + } + case LUA_TT_GLOBALENV: { + return (SLS.xtype(sval) == LUA_TT_GLOBALENV); + } + default: + // We're forcing anything else to go to NIL, + // and once it's NIL, we consider it 'equal'. + return SLS.type(sval) == LUA_TNIL; + } +} + +static void transmit_value(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap, StreamBuffer *sb) { + switch (MLS.xtype(mval)) { + case LUA_TBOOLEAN: { + sb->write_uint8(LUA_TBOOLEAN); + sb->write_bool(MLS.ckboolean(mval)); + return; + } + case LUA_TNUMBER: { + sb->write_uint8(LUA_TNUMBER); + sb->write_double(MLS.cknumber(mval)); + return; + } + case LUA_TSTRING: { + sb->write_uint8(LUA_TSTRING); + sb->write_string(MLS.ckstring(mval)); + return; + } + case LUA_TT_GENERAL: { + sb->write_uint8(LUA_TT_GENERAL); + sb->write_uint32(get_table_number(MLS, mval, mtnmap)); + return; + } + case LUA_TT_CLASS: { + sb->write_uint8(LUA_TT_CLASS); + sb->write_string(MLS.classname(mval)); + return; + } + case LUA_TT_TANGIBLE: { + sb->write_uint8(LUA_TT_TANGIBLE); + sb->write_int64(get_tangible_id(MLS, mval)); + return; + } + case LUA_TT_GLOBALENV: { + sb->write_uint8(LUA_TT_GLOBALENV); + return; + } + default: + sb->write_uint8(LUA_TNIL); + return; + } +} + +bool tablecmp_diff(lua_State *synch, lua_State *master, bool cmeta, StreamBuffer *sb) { + LuaArg mtnmap, mtab, stnmap, stab; + LuaVar skey, mkey, sval, mval, mnil; + LuaStack SLS(synch, stnmap, stab, skey, sval); + LuaStack MLS(master, mtnmap, mtab, mkey, mval, mnil); + MLS.set(mnil, LuaNil); + int nupdates = 0; + + sb->write_int32(0); + int wc = sb->total_writes(); + + MLS.set(mkey, LuaNil); + while (MLS.next(mtab, mkey, mval)) { + if (!MLS.issortablekey(mkey)) continue; + MLS.movesortablekey(mkey, SLS, skey); + SLS.rawget(sval, stab, skey); + if (!equivalent_values(MLS, mval, mtnmap, SLS, sval, stnmap)) { + transmit_value(MLS, mkey, mtnmap, sb); + transmit_value(MLS, mval, mtnmap, sb); + nupdates += 1; + } + } + + SLS.set(skey, LuaNil); + while (SLS.next(stab, skey, sval)) { + if (!SLS.issortablekey(skey)) continue; + SLS.movesortablekey(skey, MLS, mkey); + MLS.rawget(mval, mtab, mkey); + if (MLS.isnil(mval)) { + transmit_value(MLS, mkey, mtnmap, sb); + transmit_value(MLS, mval, mtnmap, sb); + nupdates += 1; + } + } + + if (cmeta) { + SLS.getmetatable(sval, stab); + MLS.getmetatable(mval, mtab); + if (!equivalent_values(MLS, mval, mtnmap, SLS, sval, stnmap)) { + transmit_value(MLS, mnil, mtnmap, sb); + transmit_value(MLS, mval, mtnmap, sb); + nupdates += 1; + } + } + + sb->overwrite_int32(wc, nupdates); + + SLS.result(); + MLS.result(); + return (nupdates > 0); +} + +static void tablecmp_value_debug_string(StreamBuffer *sb, std::ostringstream &oss) { + int kind = sb->read_uint8(); + switch (kind) { + case LUA_TBOOLEAN: { + bool b = sb->read_bool(); + oss << (b ? "true":"false"); + return; + } + case LUA_TNUMBER: { + oss << sb->read_double(); + return; + } + case LUA_TSTRING: { + oss << sb->read_string(); + return; + } + case LUA_TT_GENERAL: { + oss << "table " << sb->read_int32(); + return; + } + case LUA_TT_CLASS: { + oss << "class " << sb->read_string(); + return; + } + case LUA_TT_TANGIBLE: { + oss << "tan " << sb->read_int64(); + return; + } + case LUA_TT_GLOBALENV: { + oss << "globals"; + return; + } + case LUA_TNIL: { + oss << "nil"; + return; + } + default: + assert(false); // Should not get here. + } +} + +std::string tablecmp_debug_string(StreamBuffer *sb) { + std::vector sorted; + std::ostringstream oss; + int ndiffs = sb->read_int32(); + for (int i = 0; i < ndiffs; i++) { + tablecmp_value_debug_string(sb, oss); + oss << "="; + tablecmp_value_debug_string(sb, oss); + sorted.push_back(oss.str()); + oss.str(""); + } + std::sort(sorted.begin(), sorted.end()); + for (const std::string &s : sorted) { + oss << s << ";"; + } + return oss.str(); +} + +LuaDefine(table_diffcompare, "c") { + LuaArg mtnmap, mtab, stnmap, stab; + LuaRet dbgstring; + LuaVar tthread; + LuaStack LS(L, mtnmap, mtab, stnmap, stab, dbgstring, tthread); + // Create a temporary thread to be the 'synch model'. We'll use the + // existing thread as the 'master model'. + lua_State *synch = lua_newthread(L); + lua_replace(L, tthread.index()); + // Call tablecmp_diff. + StreamBuffer sb; + lua_pushvalue(L, stnmap.index()); + lua_pushvalue(L, stab.index()); + lua_xmove(L, synch, 2); + lua_pushvalue(L, mtnmap.index()); + lua_pushvalue(L, mtab.index()); + tablecmp_diff(synch, L, true, &sb); + // Convert the output to a debug string. + LS.set(dbgstring, tablecmp_debug_string(&sb)); + return LS.result(); +} + + diff --git a/luprex/core/cpp/tablecmp.hpp b/luprex/core/cpp/tablecmp.hpp new file mode 100644 index 00000000..4c8f3352 --- /dev/null +++ b/luprex/core/cpp/tablecmp.hpp @@ -0,0 +1,29 @@ +////////////////////////////////////////////////////////////// +// +// tablecmp -- compare two tables nonrecursively. +// +////////////////////////////////////////////////////////////// + +#ifndef TABLECMP_HPP +#define TABLECMP_HPP + +#include "luastack.hpp" +#include "util.hpp" +#include "streambuffer.hpp" + + +// Compare two tables, generating a diff in the specified stream buffer. +// +// The synch stack must have stnmap, stab on top. +// The master stack must have mtnmap, mtab on top. +// If cmeta is true, the metatables of the two tables are compared. +// Returns true if there were any diffs. +// +bool tablecmp_diff(lua_State *synch, lua_State *master, bool cmeta, StreamBuffer *sb); + +// Given a tablecmp_diff output, convert it to a debug string. +// +std::string tablecmp_debug_string(StreamBuffer *sb); + +#endif // TABLECMP_HPP + diff --git a/luprex/core/cpp/world.cpp b/luprex/core/cpp/world.cpp index 35e57f14..bbba6544 100644 --- a/luprex/core/cpp/world.cpp +++ b/luprex/core/cpp/world.cpp @@ -4,6 +4,7 @@ #include "animqueue.hpp" #include "gui.hpp" #include "traceback.hpp" +#include "tablecmp.hpp" #include void World::store_global_pointer(lua_State *L, World *v) { @@ -48,11 +49,11 @@ World::World(util::WorldType wt) { Gui::store_global_pointer(state(), nullptr); // Set the tabletype of the registry. - LS.settabletype(LuaRegistry, LuaStack::TAB_REGISTRY); + LS.settabletype(LuaRegistry, LUA_TT_REGISTRY); // Set the tabletype of the global environment. LS.getglobaltable(globtab); - LS.settabletype(globtab, LuaStack::TAB_GLOBALENV); + LS.settabletype(globtab, LUA_TT_GLOBALENV); // Create the tangibles table in the registry. LS.rawset(LuaRegistry, "tangibles", LuaNewTable); @@ -114,25 +115,34 @@ World::TanVector World::tangible_get_all(const IdVector &ids) const { return result; } -Tangible *World::tangible_get(lua_State *L, int idx) { - Tangible *result = nullptr; - int top = lua_gettop(L); - if (lua_istable(L, idx)) { - lua_getmetatable(L, idx); - if (lua_istable(L, -1)) { - lua_pushstring(L, "id"); - lua_rawget(L, -2); - lua_Number id = lua_tonumber(L, -1); - result = tangible_get(int64_t(id)); - } +Tangible *World::tangible_get(const LuaStack &LS, LuaSlot tab) { + int64_t id = tangible_id(LS, tab); + if (id == 0) { + luaL_error(LS.state(), "parameter is not a tangible"); } - lua_settop(L, top); + Tangible *result = tangible_get(id); if (result == nullptr) { - luaL_error(L, "parameter is not a tangible"); + luaL_error(LS.state(), "parameter is not a tangible"); } return result; } +int64_t World::tangible_id(const LuaStack &LS, LuaSlot tab) { + int64_t id = 0; + if (LS.istable(tab) && LS.gettabletype(tab) == LUA_TT_TANGIBLE) { + lua_State *L = LS.state(); + if (lua_getmetatable(L, tab.index())) { + lua_pushstring(L, "id"); + lua_rawget(L, -2); + if (lua_type(L, -1) == LUA_TNUMBER) { + id = lua_tointeger(L, -1); + } + lua_pop(L, 2); + } + } + return id; +} + void World::tangible_delete(int64_t id) { lua_State *L = state(); LuaVar tangibles, database; @@ -153,6 +163,7 @@ void World::tangible_delete(int64_t id) { // Clear out the database. LS.clearmetatable(database); LS.cleartable(database); + LS.settabletype(database, LUA_TT_DEADTANGIBLE); // Remove the lua portion from the tangibles table. LS.rawset(tangibles, id, LuaNil); @@ -221,7 +232,7 @@ std::string World::numbered_tables_debug_string() const { return oss.str(); } -std::string World::paired_tables_debug_string(lua_State *master, const TablePairing *pairing) const { +std::string World::paired_tables_debug_string(lua_State *master) const { lua_State *synch = state(); LuaVar mntmap, sntmap, mtab, stab, mtid, stid; LuaStack MLS(master, mntmap, mtab, mtid); @@ -232,26 +243,26 @@ std::string World::paired_tables_debug_string(lua_State *master, const TablePair // Fetch the numbered tables map. MLS.rawget(mntmap, LuaRegistry, "ntmap"); SLS.rawget(sntmap, LuaRegistry, "ntmap"); + int m_ntables = MLS.rawlen(mntmap); + int s_ntables = MLS.rawlen(sntmap); + assert(m_ntables == s_ntables); - for (int i = 1; i < pairing->mpair.size(); i++) { - if (pairing->mpair[i] == 0) continue; + for (int i = 1; i <= m_ntables; i++) { MLS.rawget(mtab, mntmap, i); - SLS.rawget(stab, sntmap, pairing->mpair[i]); - std::string mname = "unknown"; - std::string sname = "unknown"; - if (MLS.istable(mtab)) { + SLS.rawget(stab, sntmap, i); + if (MLS.istable(mtab) && SLS.istable(stab)) { + std::string mname = "unknown"; + std::string sname = "unknown"; MLS.rawget(mtid, mtab, "TID"); if (MLS.isstring(mtid)) { mname = MLS.ckstring(mtid); } - } - if (SLS.istable(stab)) { SLS.rawget(stid, stab, "TID"); if (SLS.isstring(stid)) { sname = SLS.ckstring(stid); } + result.push_back(std::make_pair(mname, sname)); } - result.push_back(std::make_pair(mname, sname)); } MLS.result(); SLS.result(); @@ -376,8 +387,8 @@ Tangible *World::tangible_make(lua_State *L, int64_t id, bool pushdb) { LS.setmetatable(database, metatab); // Mark the tangible using the tabletype field. - LS.settabletype(database, LuaStack::TAB_TANGIBLE); - LS.settabletype(metatab, LuaStack::TAB_TANGIBLEMETA); + LS.settabletype(database, LUA_TT_TANGIBLE); + LS.settabletype(metatab, LUA_TT_TANGIBLEMETA); // Store the database into the tangibles table. LS.rawget(tangibles, LuaRegistry, "tangibles"); @@ -741,13 +752,11 @@ void World::difference_transmit(int64_t actor_id, World *master, StreamBuffer *s // Number tables in both the master and the synchronous model. // Meanwhile, the client will number tables in the client-synchronous model. master->number_lua_tables(closetans); - int s_ntables = number_lua_tables(closetans); + number_lua_tables(closetans); // Pair tables from the synchronous and master models. - TablePairing pairing; - pair_lua_tables(closetans, master->state(), &pairing); - int ncreate = pair_new_tables(&pairing); - sb->write_int32(s_ntables); + pair_lua_tables(closetans, master->state()); + int ncreate = pair_new_tables(closetans, master->state()); sb->write_int32(ncreate); create_new_tables(ncreate); @@ -761,8 +770,7 @@ void World::apply_differences(StreamBuffer *sb) { util::HashValue closehash = util::hash_id_vector(closetans); util::HashValue m_closehash = sb->read_hashvalue(); assert(closehash == m_closehash); - int s_ntables = number_lua_tables(closetans); - assert(s_ntables == sb->read_int32()); + number_lua_tables(closetans); int ncreate = sb->read_int32(); create_new_tables(ncreate); } @@ -890,19 +898,12 @@ int World::number_lua_tables(const IdVector &basis) { for (int64_t id : basis) { LS.rawget(tab, tangibles, id); assert(LS.istable(tab)); - // Maybe I should traverse the metatable? // Traverse subtables. LS.set(key, LuaNil); while (LS.next(tab, key, val)) { - // if (LS.isstring(key)) { - // std::cerr << "Visiting tangible " << LS.ckstring(key) << std::endl; - // } else { - // std::cerr << "Visiting tangible xxxx" << std::endl; - // } - if (LS.istable(val) && LS.gettabletype(val)==LuaStack::TAB_GENERAL) { + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { lua_checkstack(L, 10); lua_pushvalue(L, val.index()); - // std::cerr << "Stack: " << lua_gettop(L) - top << std::endl; } } } @@ -910,7 +911,6 @@ int World::number_lua_tables(const IdVector &basis) { // Pop tables from the stack one by one. If the table is not // already numbered, number it and push subtables onto the stack. while (lua_gettop(L) > top) { - //std::cerr << "Popping stack with " << lua_gettop(L) - top << std::endl; lua_replace(L, tab.index()); LS.rawget(xid, tnmap, tab); if (LS.isnil(xid)) { @@ -919,22 +919,16 @@ int World::number_lua_tables(const IdVector &basis) { LS.rawset(ntmap, id, tab); // Traverse the metatable. LS.getmetatable(val, tab); - if (LS.istable(val) && LS.gettabletype(val)==LuaStack::TAB_GENERAL) { + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { lua_checkstack(L, 10); lua_pushvalue(L, val.index()); } // Traverse the subtables. LS.set(key, LuaNil); while (LS.next(tab, key, val)) { - // if (LS.isstring(key)) { - // std::cerr << "Visiting table " << LS.ckstring(key) << std::endl; - // } else { - // std::cerr << "Visiting table xxxx" << std::endl; - // } - if (LS.istable(val) && LS.gettabletype(val)==LuaStack::TAB_GENERAL) { + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { lua_checkstack(L, 10); lua_pushvalue(L, val.index()); - // std::cerr << "Stack: " << lua_gettop(L) - top << std::endl; } } } @@ -952,24 +946,37 @@ void World::unnumber_lua_tables() { LS.rawset(LuaRegistry, "ntmap", LuaNil); } -void World::pair_lua_tables(const IdVector &basis, lua_State *master, TablePairing *pairing) { +void World::pair_lua_tables(const IdVector &basis, lua_State *master) { lua_State *synch = state(); LuaVar stangibles, mtangibles, sntmap, mntmap, stnmap, mtnmap, stab, mtab, skey, mkey, sval, mval, sidx, midx; LuaStack SLS(synch, stangibles, stab, skey, sval, sntmap, stnmap, sidx); LuaStack MLS(master, mtangibles, mtab, mkey, mval, mntmap, mtnmap, midx); + // Fetch the tangible databases SLS.rawget(stangibles, LuaRegistry, "tangibles"); MLS.rawget(mtangibles, LuaRegistry, "tangibles"); + + // Fetch the synchronous model tnmap and ntmap SLS.rawget(stnmap, LuaRegistry, "tnmap"); - MLS.rawget(mtnmap, LuaRegistry, "tnmap"); SLS.rawget(sntmap, LuaRegistry, "ntmap"); - MLS.rawget(mntmap, LuaRegistry, "ntmap"); assert(SLS.istable(stnmap)); - assert(MLS.istable(mtnmap)); assert(SLS.istable(sntmap)); - assert(MLS.istable(mntmap)); - pairing->mpair.assign(MLS.rawlen(mntmap) + 1, 0); - pairing->spair.assign(SLS.rawlen(sntmap) + 1, 0); + + // Initialize the master model tnmap and ntmap + MLS.set(mtnmap, LuaNewTable); + MLS.set(mntmap, LuaNewTable); + MLS.rawset(LuaRegistry, "tnmap", mtnmap); + MLS.rawset(LuaRegistry, "ntmap", mntmap); + int s_ntables = SLS.rawlen(sntmap); + for (int i = 1; i <= s_ntables; i++) { + MLS.rawset(mntmap, i, 0); + } + + // Keep track of which tables are already paired + std::vector paired; + paired.assign(s_ntables + 1, false); + + // This records the top of the stack. int mtop = lua_gettop(master); for (int64_t id : basis) { @@ -981,8 +988,7 @@ void World::pair_lua_tables(const IdVector &basis, lua_State *master, TablePairi while (MLS.next(mtab, mkey, mval)) { if (!MLS.issortablekey(mkey)) continue; if (!MLS.istable(mval)) continue; - MLS.movesortablekey(mkey, synch); - lua_replace(synch, skey.index()); + MLS.movesortablekey(mkey, SLS, skey); SLS.rawget(sval, stab, skey); if (!SLS.istable(sval)) continue; lua_checkstack(master, 20); @@ -995,17 +1001,23 @@ void World::pair_lua_tables(const IdVector &basis, lua_State *master, TablePairi while (lua_gettop(master) > mtop) { lua_replace(master, mtab.index()); lua_replace(synch, stab.index()); + // If the master table is not a general table, skip. + if (MLS.gettabletype(mtab) != LUA_TT_GENERAL) continue; + // If the master table is already paired, skip. MLS.rawget(midx, mtnmap, mtab); - if (!MLS.isnumber(midx)) continue; + if (MLS.isnumber(midx)) continue; + // If the synch table is not a table, skip. + if (!SLS.istable(stab)) continue; + // If the synch table doesn't have a number, skip. SLS.rawget(sidx, stnmap, stab); if (!SLS.isnumber(sidx)) continue; - int imidx = MLS.ckint(midx); - int isidx = SLS.ckint(sidx); - if (pairing->spair[isidx] != 0) continue; - if (pairing->mpair[imidx] != 0) continue; - pairing->spair[isidx] = imidx; - pairing->mpair[imidx] = isidx; - // Pair the metatables. + int idx = SLS.ckinteger(sidx); + assert((idx >= 1) && (idx <= s_ntables)); + // Pair the tables. + MLS.rawset(mtnmap, mtab, idx); + MLS.rawset(mntmap, idx, mtab); + paired[idx] = true; + // Potentially pair the metatables. MLS.getmetatable(mval, mtab); if (MLS.istable(mval)) { SLS.getmetatable(sval, stab); @@ -1019,8 +1031,7 @@ void World::pair_lua_tables(const IdVector &basis, lua_State *master, TablePairi while (MLS.next(mtab, mkey, mval)) { if (!MLS.issortablekey(mkey)) continue; if (!MLS.istable(mval)) continue; - MLS.movesortablekey(mkey, synch); - lua_replace(synch, skey.index()); + MLS.movesortablekey(mkey, SLS, skey); SLS.rawget(sval, stab, skey); if (!SLS.istable(sval)) continue; lua_checkstack(master, 20); @@ -1034,16 +1045,71 @@ void World::pair_lua_tables(const IdVector &basis, lua_State *master, TablePairi SLS.result(); } -int World::pair_new_tables(TablePairing *pairing) { - int orig = pairing->spair.size(); - for (int i = 1; i <= pairing->mpair.size(); i++) { - int id = pairing->mpair[i]; - if (id == 0) { - pairing->mpair[i] = pairing->spair.size(); - pairing->spair.push_back(i); +int World::pair_new_tables(const IdVector &basis, lua_State *master) { + // This is conceptually recursive, but we're going to use an + // explicit stack (the lua stack). + lua_State *L = master; + LuaVar tnmap, ntmap, tangibles, tab, key, val, xid; + LuaStack LS(L, tnmap, ntmap, tangibles, tab, key, val, xid); + LS.rawget(tnmap, LuaRegistry, "tnmap"); + LS.rawget(ntmap, LuaRegistry, "ntmap"); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + int ntables = LS.rawlen(ntmap); + std::vector visited; + visited.assign(ntables + 1, false); + int top = lua_gettop(L); + + // Push all subtables onto the stack. Note that we may push + // the same table twice, that's OK. + for (int64_t id : basis) { + LS.rawget(tab, tangibles, id); + assert(LS.istable(tab)); + LS.set(key, LuaNil); + while (LS.next(tab, key, val)) { + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { + lua_checkstack(L, 10); + lua_pushvalue(L, val.index()); + } } } - return pairing->spair.size() - orig; + + // Pop tables from the stack one by one. If the table is not + // numbered, number it. If it is not visited, visit it. + while (lua_gettop(L) > top) { + lua_replace(L, tab.index()); + int id = 0; + LS.rawget(xid, tnmap, tab); + if (!LS.isnumber(xid)) { + id = visited.size(); + LS.rawset(tnmap, tab, id); + LS.rawset(ntmap, id, tab); + visited.push_back(false); + } else { + id = LS.cknumber(xid); + assert((id >= 0) && (id < int(visited.size()))); + } + if (!visited[id]) { + visited[id] = true; + // Traverse the metatable. + LS.getmetatable(val, tab); + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { + lua_checkstack(L, 10); + lua_pushvalue(L, val.index()); + } + // Traverse the subtables. + LS.set(key, LuaNil); + while (LS.next(tab, key, val)) { + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { + lua_checkstack(L, 10); + lua_pushvalue(L, val.index()); + } + } + } + } + + LS.result(); + assert(stack_is_clear()); + return visited.size() - 1 - ntables; } void World::create_new_tables(int n) { @@ -1065,12 +1131,87 @@ void World::create_new_tables(int n) { assert(stack_is_clear()); } +void World::diff_lua_tables(lua_State *master, StreamBuffer *sb) { + lua_State *synch = state(); + LuaVar sntmap, mntmap, stnmap, mtnmap; + LuaStack SLS(synch, sntmap, stnmap); + LuaStack MLS(master, mntmap, mtnmap); + SLS.rawget(sntmap, LuaRegistry, "ntmap"); + MLS.rawget(mntmap, LuaRegistry, "ntmap"); + SLS.rawget(stnmap, LuaRegistry, "tnmap"); + MLS.rawget(mtnmap, LuaRegistry, "tnmap"); + int m_ntables = MLS.rawlen(mntmap); + int s_ntables = SLS.rawlen(sntmap); + assert(m_ntables == s_ntables); + + sb->write_int32(0); + int write_count_after = sb->total_writes(); + int nmodified = 0; + int s_top = lua_gettop(synch); + int m_top = lua_gettop(master); + for (int id = 1; id <= m_ntables; id++) { + lua_pushvalue(master, mtnmap.index()); + lua_rawgeti(master, mtnmap.index(), id); + if (lua_type(master, -1) == LUA_TTABLE) { + lua_pushvalue(synch, stnmap.index()); + lua_rawgeti(synch, sntmap.index(), id); + int tw = sb->total_writes(); + sb->write_int32(id); + nmodified += 1; + if (!tablecmp_diff(synch, master, true, sb)) { + sb->unwrite_to(tw); + nmodified -= 1; + } + assert(lua_gettop(synch) == s_top); + assert(lua_gettop(master) == m_top); + } else { + lua_pop(master, 2); + } + } + sb->overwrite_int32(write_count_after, nmodified); +} + + +void World::diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb) { + lua_State *synch = state(); + LuaVar stnmap, mtnmap, stangibles, mtangibles; + LuaStack SLS(synch, stnmap, stangibles); + LuaStack MLS(master, mtnmap, mtangibles); + SLS.rawget(stnmap, LuaRegistry, "tnmap"); + MLS.rawget(mtnmap, LuaRegistry, "tnmap"); + SLS.rawget(stangibles, LuaRegistry, "tangibles"); + MLS.rawget(mtangibles, LuaRegistry, "tangibles"); + + sb->write_int32(0); + int write_count_after = sb->total_writes(); + int nmodified = 0; + int s_top = lua_gettop(synch); + int m_top = lua_gettop(master); + for (int64_t id : basis) { + lua_pushvalue(master, mtnmap.index()); + lua_rawgeti(master, mtangibles.index(), id); + lua_pushvalue(synch, stnmap.index()); + lua_rawgeti(synch, stangibles.index(), id); + int tw = sb->total_writes(); + sb->write_int64(id); + nmodified += 1; + if (!tablecmp_diff(synch, master, false, sb)) { + sb->unwrite_to(tw); + nmodified -= 1; + } + assert(lua_gettop(synch) == s_top); + assert(lua_gettop(master) == m_top); + } + sb->overwrite_int32(write_count_after, nmodified); +} + + LuaDefine(tangible_animstate, "c") { LuaArg tanobj; LuaRet graphic, plane, x, y, z, facing; LuaStack LS(L, tanobj, graphic, plane, x, y, z, facing); World *w = World::fetch_global_pointer(L); - Tangible *tan = w->tangible_get(L, tanobj.index()); + Tangible *tan = w->tangible_get(LS, tanobj); const AnimStep &aqback = tan->anim_queue_.back(); LS.set(graphic, aqback.graphic()); LS.set(plane, aqback.plane()); @@ -1085,7 +1226,7 @@ LuaDefine(tangible_animate, "c") { LuaArg tanobj, config; LuaStack LS(L, tanobj, config); World *w = World::fetch_global_pointer(L); - Tangible *tan = w->tangible_get(L, tanobj.index()); + Tangible *tan = w->tangible_get(LS, tanobj); int64_t id = w->id_global_pool_.alloc_id_for_thread(L); const AnimStep &prev = tan->anim_queue_.back(); AnimStep step; @@ -1103,7 +1244,7 @@ LuaDefine(tangible_setclass, "c") { LuaVar classtab, mt; LuaStack LS(L, tanobj, classname, classtab, mt); World *w = World::fetch_global_pointer(L); - w->tangible_get(L, tanobj.index()); + w->tangible_get(LS, tanobj); LS.getclass(classtab, classname); LS.getmetatable(mt, tanobj); LS.rawset(mt, "__index", classtab); @@ -1114,7 +1255,7 @@ LuaDefine(tangible_delete, "c") { LuaArg tanobj; LuaStack LS(L, tanobj); World *w = World::fetch_global_pointer(L); - Tangible *tan = w->tangible_get(L, tanobj.index()); + Tangible *tan = w->tangible_get(LS, tanobj); assert(tan != nullptr); // this should be checked above. if (tan->is_an_actor()) { luaL_error(L, "Cannot delete a player using tangible.delete, use tangible.redirect instead."); @@ -1188,14 +1329,14 @@ LuaDefine(tangible_redirect, "c") { LuaStack LS(L, actor1, actor2, bldz); World *w = World::fetch_global_pointer(L); bool bulldoze = LS.ckboolean(bldz); - Tangible *tan1 = w->tangible_get(L, actor1.index()); + Tangible *tan1 = w->tangible_get(LS, actor1); if (!tan1->is_an_actor()) { luaL_error(L, "redirect source is not an actor"); } if (LS.isnil(actor2)) { w->redirects_[tan1->id()] = 0; } else { - Tangible *tan2 = w->tangible_get(L, actor2.index()); + Tangible *tan2 = w->tangible_get(LS, actor2); tan2->configure_id_pool_for_actor(); w->redirects_[tan1->id()] = tan2->id(); } @@ -1205,6 +1346,14 @@ LuaDefine(tangible_redirect, "c") { return LS.result(); } +LuaDefine(tangible_id, "c") { + LuaArg tanobj; + LuaRet id; + LuaStack LS(L, tanobj, id); + LS.set(id, World::tangible_id(LS, tanobj)); + return LS.result(); +} + LuaDefine(world_wait, "f") { if ((lua_gettop(L) != 1) || (lua_type(L, -1) != LUA_TNUMBER)) { luaL_error(L, "Argument to wait must be a number."); @@ -1217,16 +1366,12 @@ LuaDefine(world_getregistry, "f") { return 1; } -LuaDefine(world_gettabletype, "f") { +LuaDefine(world_xtype, "f") { LuaArg tab; - LuaRet ttype; - LuaStack LS(L, tab, ttype); - if (LS.istable(tab)) { - LuaStack::TableType tt = LS.gettabletype(tab); - LS.set(ttype, int(tt)); - } else { - luaL_error(L, "Not a table"); - } + LuaRet rtype; + LuaStack LS(L, tab, rtype); + int xt = LS.xtype(tab); + LS.set(rtype, xt); return LS.result(); } @@ -1237,10 +1382,10 @@ LuaDefine(world_settabletype, "f") { luaL_error(L, "Not a table"); } int tt = LS.ckinteger(ttype); - if ((tt < 0) || (tt > 15)) { + if ((tt < LUA_TT_GENERAL) || (tt > LUA_TT_CLASS)) { luaL_error(L, "table type out of range"); } - LS.settabletype(tab, LuaStack::TableType(tt)); + LS.settabletype(tab, tt); return LS.result(); } @@ -1255,7 +1400,7 @@ static bool worlds_identical(const std::unique_ptr &w1, const std::unique LuaDefine(unittests_world, "c") { std::unique_ptr m, ss, cs; StreamBuffer sb; - int m_ntables, s_ntables, ncreate; + int ncreate; // Test the numbering of lua tables. We create some general // tables using tangible_set_string. Then we install some @@ -1270,8 +1415,7 @@ LuaDefine(unittests_world, "c") { m->tangible_set_string(123, "inventory.cplx.TID", "inventory.cplx"); m->tangible_copy_global(123, "math", "math"); m->tangible_copy_global(123, "gltab", "_G"); - m_ntables = m->number_lua_tables(util::id_vector_create(123)); -// LuaAssert(L, m_ntables == 7); + m->number_lua_tables(util::id_vector_create(123)); LuaAssertStrEq(L, m->numbered_tables_debug_string(), "inventory;inventory.cplx;skills;skills.leet;transactions;"); @@ -1286,23 +1430,21 @@ LuaDefine(unittests_world, "c") { ss->tangible_set_string(123, "skills.leet.TID", "skills.leet"); ss->tangible_set_string(123, "math.TID", "math"); ss->tangible_set_string(123, "gltab.TID", "gltab"); - s_ntables = ss->number_lua_tables(util::id_vector_create(123)); - LuaAssert(L, s_ntables == 6); + ss->number_lua_tables(util::id_vector_create(123)); LuaAssertStrEq(L, ss->numbered_tables_debug_string(), "gltab;inventory;math;skills;skills.crap;skills.leet;"); - World::TablePairing pairing; - ss->pair_lua_tables(util::id_vector_create(123), m->state(), &pairing); - LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state(), &pairing), + ss->pair_lua_tables(util::id_vector_create(123), m->state()); + LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state()), "inventory=inventory;skills=skills;skills.leet=skills.leet;"); // Test the creation of new tables during difference transmission. // The master world model above has two tables that couldn't be paired // to the client: inventory.cplx, and transactions. These two tables // should be paired to new, created tables. - ncreate = m->pair_new_tables(&pairing); + ncreate = m->pair_new_tables(util::id_vector_create(123), m->state()); LuaAssert(L, ncreate == 2); ss->create_new_tables(ncreate); - LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state(), &pairing), + LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state()), "inventory=inventory;inventory.cplx=unknown;skills=skills;skills.leet=skills.leet;transactions=unknown;"); // Create new clean world models. diff --git a/luprex/core/cpp/world.hpp b/luprex/core/cpp/world.hpp index ab8a4216..14e5fc8d 100644 --- a/luprex/core/cpp/world.hpp +++ b/luprex/core/cpp/world.hpp @@ -80,10 +80,6 @@ public: using IdVector = util::IdVector; using TanVector = std::vector; using Redirects = std::map; - struct TablePairing { - std::vector mpair; - std::vector spair; - }; const float RadiusVisibility = 100.0; const float RadiusClose = 10.0; @@ -123,6 +119,12 @@ public: // Tangible *tangible_make(lua_State *L, int64_t id, bool pushdb); + // Get the tangible ID of the specified LUA tangible database. + // + // Return zero if the item is not a tangible database. + // + static int64_t tangible_id(const LuaStack &LS, LuaSlot slot); + // Get a pointer to the specified tangible. // // If there's no such tangible, returns nullptr. @@ -135,7 +137,7 @@ public: // The value on the lua stack should be a valid lua tangible. If not, // a lua error is generated. // - Tangible *tangible_get(lua_State *L, int idx); + Tangible *tangible_get(const LuaStack &LS, LuaSlot slot); // Get pointers to many tangibles. // @@ -240,7 +242,7 @@ public: // Paired tables debug string. Shows TID=TID pairs, sorted alphabetically. // - std::string paired_tables_debug_string(lua_State *master, const TablePairing *pairing) const; + std::string paired_tables_debug_string(lua_State *master) const; // Store a string in the tangible's database. // @@ -296,21 +298,29 @@ public: // void unnumber_lua_tables(); - // Associates numbered tables in the master model with numbered tables in the synch model. + // Number tables in the master model to match already-numbered tables in the synch model. // - void pair_lua_tables(const IdVector &basis, lua_State *master, TablePairing *pair); + void pair_lua_tables(const IdVector &basis, lua_State *master); - // Pairs any not-yet-paired master table to a virtually-created table in the synch model. + // Pairs every not-yet-paired master table to a virtually-created table in the synch model. // // Returns the number of new tables that need to be created in the synch model. // - int pair_new_tables(TablePairing *pair); + int pair_new_tables(const IdVector &basis, lua_State *master); // This is followup for pair_new_tables: actually create the new tables that // were virtually created in the pair_new_tables step. // void create_new_tables(int n); + // Compare the general tables. + // + void diff_lua_tables(lua_State *master, StreamBuffer *sb); + + // Compare the tangible databases. + // + void diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb); + private: // Type of model util::WorldType world_type_; diff --git a/luprex/core/lua/control.lst b/luprex/core/lua/control.lst index 4bbe9bb8..89c34177 100644 --- a/luprex/core/lua/control.lst +++ b/luprex/core/lua/control.lst @@ -6,5 +6,6 @@ inspect.lua ut-table.lua ut-globaldb.lua +ut-tablecmp.lua player.lua login.lua diff --git a/luprex/core/lua/ut-tablecmp.lua b/luprex/core/lua/ut-tablecmp.lua new file mode 100644 index 00000000..77af0202 --- /dev/null +++ b/luprex/core/lua/ut-tablecmp.lua @@ -0,0 +1,71 @@ + +local tdc = table.diffcompare + +function unittests.tablecmp() + -- No differences in these simple-valued tables. + assert(tdc(nil, {a=true}, nil, {a=true}) == "") + assert(tdc(nil, {a=5}, nil, {a=5}) == ""); + assert(tdc(nil, {a="foo"}, nil, {a="foo"}) == "") + + -- Test transmission of missing simple values. + assert(tdc(nil, {a=true}, nil, {}) == "a=true;") + assert(tdc(nil, {a=5}, nil, {}) == "a=5;"); + assert(tdc(nil, {a="foo"}, nil, {}) == "a=foo;") + + -- Test the replacement of simple values. + assert(tdc(nil, {a=true}, nil, {a=false}) == "a=true;") + assert(tdc(nil, {a=5}, nil, {a=4}) == "a=5;"); + assert(tdc(nil, {a="foo"}, nil, {a="bar"}) == "a=foo;") + + -- Test the clearing of values. + assert(tdc(nil, {}, nil, {a=true}) == "a=nil;") + assert(tdc(nil, {}, nil, {a=5}) == "a=nil;"); + assert(tdc(nil, {}, nil, {a="foo"}) == "a=nil;") + + -- Try boolean keys. + assert(tdc(nil, {[true]=3}, nil, {}) == "true=3;") + assert(tdc(nil, {}, nil, {[true]=3}) == "true=nil;") + + -- Try number keys. + assert(tdc(nil, {[7]=3}, nil, {}) == "7=3;") + assert(tdc(nil, {}, nil, {[7]=3}) == "7=nil;") + + -- Try a table with multiple keys. + assert(tdc(nil, {a=4, b=5, c=6}, nil, {b=5, c=7, d=8}) == "a=4;c=6;d=nil;") + + -- Nonsortable keys should be ignored (no diffs). + assert(tdc(nil, {[{}]=3}, nil, {}) == "") + + -- Try a table containing a class. + assert(tdc(nil, {a=deque}, nil, {}) == "a=class deque;") + + -- Try a table containing a pointer to the global environment. + assert(tdc(nil, {a=_G}, nil, {}) == "a=globals;") + + -- GlobalDB tables should be forced to NIL. + assert(tdc(nil, {a=global("foo")}, nil, {a=global("foo")}) == "a=nil;"); + assert(tdc(nil, {}, nil, {a=global("foo")}) == "a=nil;"); + assert(tdc(nil, {a=global("foo")}, nil, {}) == ""); + + -- Set up some numbered tables for tests involving such. + local mtab10 = {} + local stab10 = {} + local mtab11 = {} + local stab11 = {} + local mtnmap = {} + local stnmap = {} + mtnmap[mtab10] = 10 + stnmap[stab10] = 10 + mtnmap[mtab11] = 11 + stnmap[stab11] = 11 + + -- confirm that numbered tables are being transmitted. + assert(tdc(mtnmap, {a=mtab10}, stnmap, {}) == "a=table 10;") + assert(tdc(mtnmap, {a=mtab10}, stnmap, {a=stab10}) == "") + assert(tdc(mtnmap, {a=3}, stnmap, {a=stab10}) == "a=3;") + assert(tdc(mtnmap, {}, stnmap, {a=stab10}) == "a=nil;") + + -- we're not going to test tangibles + -- creating tangibles here is too difficult. +end +