diff --git a/luprex/core/cpp/globaldb.cpp b/luprex/core/cpp/globaldb.cpp index f494c01c..f7365cd7 100644 --- a/luprex/core/cpp/globaldb.cpp +++ b/luprex/core/cpp/globaldb.cpp @@ -1,16 +1,6 @@ #include "luastack.hpp" #include "globaldb.hpp" -LuaDefine(globaldb_enable, "c") { - LuaVar globaldb; - LuaStack LS(L, globaldb); - LS.rawget(globaldb, LuaRegistry, "globaldb"); - if (!LS.istable(globaldb)) { - LS.newtable(globaldb); - LS.rawset(LuaRegistry, "globaldb", globaldb); - } - return LS.result(); -} // Get a table from the global database. // @@ -25,7 +15,7 @@ LuaDefine(globaldb_global, "f") { LuaVar globaldb; LuaStack LS(L, globalname, globaltab, globaldb); - // Get a pointer to the globaldb. + // Get a pointer to the globaldb. LS.rawget(globaldb, LuaRegistry, "globaldb"); if (!LS.istable(globaldb)) { luaL_error(L, "globaldb is not enabled"); diff --git a/luprex/core/cpp/globaldb.hpp b/luprex/core/cpp/globaldb.hpp index cfbce727..3d003d3e 100644 --- a/luprex/core/cpp/globaldb.hpp +++ b/luprex/core/cpp/globaldb.hpp @@ -28,14 +28,6 @@ #include "luastack.hpp" -// globaldb_enable -// -// Enable the use of the global DB. This is meant to be invoked in -// the master world model only. In other world models, you should simply -// not call this function. -// -int globaldb_enable(lua_State *L); - // The lua 'global' operator. // // Given a global name, returns a table for that global. diff --git a/luprex/core/cpp/luastack.cpp b/luprex/core/cpp/luastack.cpp index 033ae3c8..5bee3ce8 100644 --- a/luprex/core/cpp/luastack.cpp +++ b/luprex/core/cpp/luastack.cpp @@ -243,7 +243,14 @@ void LuaStack::makeclass(LuaSlot classtab, LuaSlot classname) const { LS.result(); } -void LuaStack::makeclass(LuaSlot tab, const char *name) { +void LuaStack::makeclass(LuaSlot tab, const char *name) const { + push_any_value(name); + LuaSpecial classname(lua_gettop(L_)); + makeclass(tab, classname); + lua_pop(L_, 1); +} + +void LuaStack::makeclass(LuaSlot tab, const std::string &name) const { push_any_value(name); LuaSpecial classname(lua_gettop(L_)); makeclass(tab, classname); diff --git a/luprex/core/cpp/luastack.hpp b/luprex/core/cpp/luastack.hpp index f8d722de..dda9d91b 100644 --- a/luprex/core/cpp/luastack.hpp +++ b/luprex/core/cpp/luastack.hpp @@ -420,7 +420,8 @@ public: void getclass(LuaSlot tab, LuaSlot name) const; void makeclass(LuaSlot tab, LuaSlot name) const; - void makeclass(LuaSlot tab, const char *name); + void makeclass(LuaSlot tab, const char *name) const; + void makeclass(LuaSlot tab, const std::string &name) const; std::string classname(LuaSlot tab); void movesortablekey(LuaSlot val, LuaStack &other, LuaSlot otherslot); diff --git a/luprex/core/cpp/world-difftab.cpp b/luprex/core/cpp/world-difftab.cpp index 7cf60ab9..112f57b0 100644 --- a/luprex/core/cpp/world-difftab.cpp +++ b/luprex/core/cpp/world-difftab.cpp @@ -13,6 +13,7 @@ #include "luastack.hpp" #include "streambuffer.hpp" +#include "table.hpp" #include "world.hpp" // Given a table and an tnmap, return the table number of the table. @@ -52,11 +53,11 @@ static bool equivalent_values(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap, 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; + if (midx == 0) { + return SLS.isnil(sval); + } int sidx = get_table_number(SLS, sval, stnmap); - if (sidx == 0) return false; return midx == sidx; } case LUA_TT_CLASS: { @@ -95,8 +96,13 @@ static void transmit_value(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap, StreamBu return; } case LUA_TT_GENERAL: { - sb->write_uint8(LUA_TT_GENERAL); - sb->write_uint32(get_table_number(MLS, mval, mtnmap)); + int midx = get_table_number(MLS, mval, mtnmap); + if (midx == 0) { + sb->write_uint8(LUA_TNIL); + } else { + sb->write_uint8(LUA_TT_GENERAL); + sb->write_uint32(midx); + } return; } case LUA_TT_CLASS: { @@ -160,7 +166,7 @@ static void transmit_value_debug_string(StreamBuffer *sb, std::ostringstream &os } } -static bool compare_tables(lua_State *synch, lua_State *master, bool cmeta, StreamBuffer *sb) { +static bool diff_tables(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); @@ -212,7 +218,7 @@ static bool compare_tables(lua_State *synch, lua_State *master, bool cmeta, Stre return (nupdates > 0); } -static std::string compare_tables_debug_string(StreamBuffer *sb) { +static std::string diff_tables_debug_string(StreamBuffer *sb) { std::vector sorted; std::ostringstream oss; int ndiffs = sb->read_int32(); @@ -230,7 +236,65 @@ static std::string compare_tables_debug_string(StreamBuffer *sb) { return oss.str(); } -void World::diff_lua_tables(lua_State *master, StreamBuffer *sb) { +static void set_transmitted_value(LuaStack &LS, LuaSlot tangibles, LuaSlot ntmap, LuaSlot target, StreamBuffer *sb) { + int kind = sb->read_uint8(); + switch (kind) { + case LUA_TBOOLEAN: { + LS.set(target, sb->read_bool()); + return; + } + case LUA_TNUMBER: { + LS.set(target, sb->read_double()); + return; + } + case LUA_TSTRING: { + LS.set(target, sb->read_string()); + return; + } + case LUA_TT_GENERAL: { + LS.rawgeti(target, ntmap, sb->read_int32()); + return; + } + case LUA_TT_CLASS: { + LS.makeclass(target, sb->read_string()); + return; + } + case LUA_TT_TANGIBLE: { + int64_t id = sb->read_int64(); + LS.rawgeti(target, tangibles, id); + if (LS.isnil(target)) { + World *w = World::fetch_global_pointer(LS.state()); + w->tangible_make(LS.state(), id, true); + lua_replace(LS.state(), target.index()); + } + return; + } + case LUA_TT_GLOBALENV: { + LS.getglobaltable(target); + return; + } + case LUA_TNIL: { + LS.set(target, LuaNil); + return; + } + default: + assert(false); // Should not get here. + } +} + +static void patch_table(LuaStack &LS0, LuaSlot tangibles, LuaSlot ntmap, LuaSlot tab, StreamBuffer *sb) { + LuaVar key, val; + LuaStack LS(LS0.state(), key, val); + int ndiffs = sb->read_int32(); + for (int i = 0; i < ndiffs; i++) { + set_transmitted_value(LS, tangibles, ntmap, key, sb); + set_transmitted_value(LS, tangibles, ntmap, val, sb); + LS.rawset(tab, key, val); + } + LS.result(); +} + +void World::diff_numbered_tables(lua_State *master, StreamBuffer *sb) { lua_State *synch = state(); LuaVar sntmap, mntmap, stnmap, mtnmap; LuaStack SLS(synch, sntmap, stnmap); @@ -257,7 +321,7 @@ void World::diff_lua_tables(lua_State *master, StreamBuffer *sb) { int tw = sb->total_writes(); sb->write_int32(id); nmodified += 1; - if (!compare_tables(synch, master, true, sb)) { + if (!diff_tables(synch, master, true, sb)) { sb->unwrite_to(tw); nmodified -= 1; } @@ -294,7 +358,7 @@ void World::diff_tangible_databases(const IdVector &basis, lua_State *master, St int tw = sb->total_writes(); sb->write_int64(id); nmodified += 1; - if (!compare_tables(synch, master, false, sb)) { + if (!diff_tables(synch, master, false, sb)) { sb->unwrite_to(tw); nmodified -= 1; } @@ -309,6 +373,11 @@ LuaDefine(table_diffcompare, "c") { LuaRet dbgstring; LuaVar tthread; LuaStack LS(L, mtnmap, mtab, stnmap, stab, dbgstring, tthread); + // Check the arguments. + LS.checktable(mtnmap); + LS.checktable(stnmap); + LS.checktable(mtab); + LS.checktable(stab); // 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); @@ -320,9 +389,56 @@ LuaDefine(table_diffcompare, "c") { lua_xmove(L, synch, 2); lua_pushvalue(L, mtnmap.index()); lua_pushvalue(L, mtab.index()); - compare_tables(synch, L, true, &sb); + diff_tables(synch, L, true, &sb); // Convert the output to a debug string. - LS.set(dbgstring, compare_tables_debug_string(&sb)); + LS.set(dbgstring, diff_tables_debug_string(&sb)); + return LS.result(); +} + +LuaDefine(table_diffapply, "c") { + LuaArg tnmap, mtab, stab; + LuaRet eql, eqlstr, rtab; + LuaVar tthread, tangibles, ntmap, key, val; + LuaStack LS(L, tnmap, mtab, stab, eql, eqlstr, rtab, tthread, tangibles, ntmap, key, val); + // Check the arguments. + LS.checktable(tnmap); + LS.checktable(mtab); + LS.checktable(stab); + + // Get the tangibles map. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + + // Invert the tnmap to make the ntmap. + LS.set(ntmap, LuaNewTable); + LS.set(key, LuaNil); + while (LS.next(tnmap, key, val)) { + LS.rawset(ntmap, val, key); + } + + // 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, tnmap.index()); + lua_pushvalue(L, stab.index()); + lua_xmove(L, synch, 2); + lua_pushvalue(L, tnmap.index()); + lua_pushvalue(L, mtab.index()); + diff_tables(synch, L, true, &sb); + + patch_table(LS, tangibles, ntmap, stab, &sb); + + LS.call(eql, table_equal, stab, mtab); + bool e = LS.ckboolean(eql); + if (e) { + LS.set(eqlstr, "tables equal"); + } else { + LS.set(eqlstr, "tables were supposed to be equal"); + } + LS.set(rtab, stab); + return LS.result(); } diff --git a/luprex/core/cpp/world-pairtab.cpp b/luprex/core/cpp/world-pairtab.cpp index d87a6724..7a163d5b 100644 --- a/luprex/core/cpp/world-pairtab.cpp +++ b/luprex/core/cpp/world-pairtab.cpp @@ -5,10 +5,10 @@ // routines in this file: // // World::number_lua_tables -// World::unnumber_lua_tables // World::pair_lua_tables -// World::pair_new_tables +// World::number_remaining_tables // World::create_new_tables +// World::unnumber_lua_tables // //////////////////////////////////////////////////////////////////// @@ -76,13 +76,6 @@ int World::number_lua_tables(const IdVector &basis) { return nextid - 1; } -void World::unnumber_lua_tables() { - // All we have to do is remove these tables from the registry. - LuaStack LS(state()); - LS.rawset(LuaRegistry, "tnmap", LuaNil); - LS.rawset(LuaRegistry, "ntmap", LuaNil); -} - 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; @@ -182,7 +175,7 @@ void World::pair_lua_tables(const IdVector &basis, lua_State *master) { SLS.result(); } -int World::pair_new_tables(const IdVector &basis, lua_State *master) { +int World::number_remaining_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; @@ -268,4 +261,10 @@ void World::create_new_tables(int n) { assert(stack_is_clear()); } +void World::unnumber_lua_tables() { + // All we have to do is remove these tables from the registry. + LuaStack LS(state()); + LS.rawset(LuaRegistry, "tnmap", LuaNil); + LS.rawset(LuaRegistry, "ntmap", LuaNil); +} diff --git a/luprex/core/cpp/world.cpp b/luprex/core/cpp/world.cpp index cd06608c..b24cdf4d 100644 --- a/luprex/core/cpp/world.cpp +++ b/luprex/core/cpp/world.cpp @@ -57,6 +57,9 @@ World::World(util::WorldType wt) { // Create the tangibles table in the registry. LS.rawset(LuaRegistry, "tangibles", LuaNewTable); + // Create the globaldb in the registry. + LS.rawset(LuaRegistry, "globaldb", LuaNewTable); + // Initialize the SourceDB. At this stage, the sourcedb is // empty, so it's just populating the lua builtins. source_db_.init(state()); @@ -755,7 +758,7 @@ void World::difference_transmit(int64_t actor_id, World *master, StreamBuffer *s // Pair tables from the synchronous and master models. pair_lua_tables(closetans, master->state()); - int ncreate = pair_new_tables(closetans, master->state()); + int ncreate = number_remaining_tables(closetans, master->state()); sb->write_int32(ncreate); create_new_tables(ncreate); @@ -1113,7 +1116,7 @@ LuaDefine(unittests_world, "c") { // 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(util::id_vector_create(123), m->state()); + ncreate = m->number_remaining_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()), diff --git a/luprex/core/cpp/world.hpp b/luprex/core/cpp/world.hpp index dd7fe82b..b4548315 100644 --- a/luprex/core/cpp/world.hpp +++ b/luprex/core/cpp/world.hpp @@ -285,9 +285,9 @@ private: static void diff_visible_animations(const TanVector &mvis, const TanVector &svis, StreamBuffer *sb); void patch_visible_animations(StreamBuffer *sb); - // Compare the general tables. + // Compare the numbered general tables. // - void diff_lua_tables(lua_State *master, StreamBuffer *sb); + void diff_numbered_tables(lua_State *master, StreamBuffer *sb); // Compare the tangible databases. // @@ -298,36 +298,55 @@ public: // // Numbering and pairing of lua tables. // + // The following routines pair up tables in the synchronous + // model with tables in the master model, by assigning matching + // table numbers. This is not one subroutine but several, because + // some of the steps happen on the server, some on the client, + // and so forth. + // + // The goal of these routines is to build these data structures: + // // Table-to-number mapping is stored in registry.tnmap // Number-to-table mapping is stored in registry.ntmap // /////////////////////////////////////////////////////////// - // numbering of tables. + // In the synchronous models, number tables recursively. // - // Returns the total number of tables numbered. + // This is a simple recursive traversal, which numbers tables. + // This creates the initial ntmap in the synchronous models. // int number_lua_tables(const IdVector &basis); - // Deletes registry.tnmap and registry.ntmap + // Pair tables in the master model to tables in the synch model. // - void unnumber_lua_tables(); - - // Number tables in the master model to match already-numbered tables in the synch model. + // Recursively walk the master and synchronous model in parallel, + // copying table numbers from the synchronous ntmap into the master's ntmap. // void pair_lua_tables(const IdVector &basis, lua_State *master); - // Pairs every not-yet-paired master table to a virtually-created table in the synch model. + // Number previously unpaired tables in the master model. // - // Returns the number of new tables that need to be created in the synch model. + // This finds every not-yet-numbered table in the master model, + // and appends these tables to the master's ntmap. Once they're + // in the ntmap, they can be paired by simply creating new tables + // in the synchronous model. // - int pair_new_tables(const IdVector &basis, lua_State *master); + int number_remaining_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. + // Create new tables in the synchronous models. + // + // Creates new tables in the synchronous model and appends these + // new tables to the synchronous model's ntmap. // void create_new_tables(int n); + // Delete the table numbering. + // + // This simply removes registry.tnmap and registry.ntmap + // + void unnumber_lua_tables(); + private: // Type of model util::WorldType world_type_; diff --git a/luprex/core/lua/ut-globaldb.lua b/luprex/core/lua/ut-globaldb.lua index 9f476db6..b6e4e362 100644 --- a/luprex/core/lua/ut-globaldb.lua +++ b/luprex/core/lua/ut-globaldb.lua @@ -1,7 +1,6 @@ makeclass("unittests") function unittests.globaldb() - globaldb.enable() local g1a = global("unittest-g1") local g2a = global("unittest-g2") local g1b = global("unittest-g1") diff --git a/luprex/core/lua/ut-tablecmp.lua b/luprex/core/lua/ut-tablecmp.lua index 77af0202..c2136e4f 100644 --- a/luprex/core/lua/ut-tablecmp.lua +++ b/luprex/core/lua/ut-tablecmp.lua @@ -1,63 +1,67 @@ +-- the tdc function calculates diffs, and returns those +-- diffs as a human-readable string. local tdc = table.diffcompare -function unittests.tablecmp() +-- the tda function calculates diffs, applies the diffs to the second +-- table, and then returns true if the second table equals the first. +local tda = table.diffapply + +function unittests.diffcompare() + local rtab = nil + -- 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"}) == "") + assert(tdc({}, {a=true}, {}, {a=true}) == "") + assert(tdc({}, {a=5}, {}, {a=5}) == ""); + assert(tdc({}, {a="foo"}, {}, {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;") + assert(tdc({}, {a=true}, {}, {}) == "a=true;") + assert(tdc({}, {a=5}, {}, {}) == "a=5;"); + assert(tdc({}, {a="foo"}, {}, {}) == "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;") + assert(tdc({}, {a=true}, {}, {a=false}) == "a=true;") + assert(tdc({}, {a=5}, {}, {a=4}) == "a=5;"); + assert(tdc({}, {a="foo"}, {}, {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;") + assert(tdc({}, {}, {}, {a=true}) == "a=nil;") + assert(tdc({}, {}, {}, {a=5}) == "a=nil;"); + assert(tdc({}, {}, {}, {a="foo"}) == "a=nil;") -- Try boolean keys. - assert(tdc(nil, {[true]=3}, nil, {}) == "true=3;") - assert(tdc(nil, {}, nil, {[true]=3}) == "true=nil;") + assert(tdc({}, {[true]=3}, {}, {}) == "true=3;") + assert(tdc({}, {}, {}, {[true]=3}) == "true=nil;") -- Try number keys. - assert(tdc(nil, {[7]=3}, nil, {}) == "7=3;") - assert(tdc(nil, {}, nil, {[7]=3}) == "7=nil;") + assert(tdc({}, {[7]=3}, {}, {}) == "7=3;") + assert(tdc({}, {}, {}, {[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;") + assert(tdc({}, {a=4, b=5, c=6}, {}, {b=5, c=7, d=8}) == "a=4;c=6;d=nil;") -- Nonsortable keys should be ignored (no diffs). - assert(tdc(nil, {[{}]=3}, nil, {}) == "") + assert(tdc({}, {[{}]=3}, {}, {}) == "") -- Try a table containing a class. - assert(tdc(nil, {a=deque}, nil, {}) == "a=class deque;") + assert(tdc({}, {a=deque}, {}, {}) == "a=class deque;") -- Try a table containing a pointer to the global environment. - assert(tdc(nil, {a=_G}, nil, {}) == "a=globals;") + assert(tdc({}, {a=_G}, {}, {}) == "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, {}) == ""); + assert(tdc({}, {a=global("foo")}, {}, {a=global("foo")}) == "a=nil;"); + assert(tdc({}, {}, {}, {a=global("foo")}) == "a=nil;"); + assert(tdc({}, {a=global("foo")}, {}, {}) == ""); -- 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;") @@ -65,7 +69,51 @@ function unittests.tablecmp() assert(tdc(mtnmap, {a=3}, stnmap, {a=stab10}) == "a=3;") assert(tdc(mtnmap, {}, stnmap, {a=stab10}) == "a=nil;") + -- confirm that unnumbered tables are forced to nil. + assert(tdc(mtnmap, {a={}}, stnmap, {}) == "") + assert(tdc(mtnmap, {a={}}, stnmap, {a=3}) == "a=nil;") + -- we're not going to test tangibles -- creating tangibles here is too difficult. end +function unittests.diffapply() + local tab10={id=tab10} + local tab11={id=tab11} + local tnmap={} + tnmap[tab10] = 10 + tnmap[tab11] = 11 + + -- verify some simple values. + assert(tda(tnmap, {a=1}, {})) + assert(tda(tnmap, {[true]="foo"}, {})) + assert(tda(tnmap, {[3]=false}, {})) + + -- verify a table with multiple simple values. + assert(tda(tnmap, {a=1, b=2, c=3}, {})) + + -- verify that it can remove or replace wrong values. + assert(tda(tnmap, {a=1,b=2}, {b=3,c=4})) + + -- verify a table containing another table. + assert(tda(tnmap, {a=tab10, b=tab11}, {})) + + -- verify a table containing a class. + assert(tda(tnmap, {a=deque, b=table}, {})) + + -- verify a table containing the global environment. + assert(tda(tnmap, {a=_G}, {})) + + -- GlobalDB tables should be forced to NIL. + rtab={a=3} + assert(not tda({}, {a=global("foo")}, rtab)) + assert(rtab.a == nil) + + -- Unnumbered tables should be forced to NIL. + rtab={a=3} + assert(not tda({}, {a={}}, rtab)) + assert(rtab.a == nil) + + -- don't test tangibles. +end +