diff_tables and patch_table are working and tested

This commit is contained in:
2021-08-31 20:03:33 -04:00
parent 82f1f69c25
commit 013992400e
10 changed files with 261 additions and 87 deletions

View File

@@ -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");

View File

@@ -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.

View File

@@ -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);

View File

@@ -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);

View File

@@ -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<std::string> 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();
}

View File

@@ -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);
}

View File

@@ -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()),

View File

@@ -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_;

View File

@@ -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")

View File

@@ -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