Working on diff xmission

This commit is contained in:
2021-08-23 23:34:30 -04:00
parent 7581ac7278
commit 2be6c2c58e
10 changed files with 702 additions and 149 deletions

View File

@@ -13,6 +13,7 @@ CPP_FILES=\
cpp/globaldb.cpp\ cpp/globaldb.cpp\
cpp/sched.cpp\ cpp/sched.cpp\
cpp/table.cpp\ cpp/table.cpp\
cpp/tablecmp.cpp\
cpp/gui.cpp\ cpp/gui.cpp\
cpp/luasnap.cpp\ cpp/luasnap.cpp\
cpp/animqueue.cpp\ cpp/animqueue.cpp\

View File

@@ -45,6 +45,6 @@ LuaDefine(globaldb_global, "f") {
LS.newtable(globaltab); LS.newtable(globaltab);
LS.rawset(globaldb, globalname, globaltab); LS.rawset(globaldb, globalname, globaltab);
LS.rawset(globaltab, "__global", globalname); LS.rawset(globaltab, "__global", globalname);
LS.settabletype(globaltab, LuaStack::TAB_GLOBALDB); LS.settabletype(globaltab, LUA_TT_GLOBALDB);
return LS.result(); return LS.result();
} }

View File

@@ -1,5 +1,6 @@
#include "luastack.hpp" #include "luastack.hpp"
#include <iostream> #include <iostream>
#include <cassert>
LuaSpecial LuaRegistry(LUA_REGISTRYINDEX); LuaSpecial LuaRegistry(LUA_REGISTRYINDEX);
LuaNilMarker LuaNil; LuaNilMarker LuaNil;
@@ -234,7 +235,7 @@ void LuaStack::makeclass(LuaSlot classtab, LuaSlot classname) const {
} }
// Repair the special fields. // Repair the special fields.
LS.settabletype(classtab, TAB_CLASS); LS.settabletype(classtab, LUA_TT_CLASS);
LS.rawset(classtab, "__index", classtab); LS.rawset(classtab, "__index", classtab);
LS.rawset(classtab, "__class", classname); LS.rawset(classtab, "__class", classname);
@@ -242,23 +243,48 @@ void LuaStack::makeclass(LuaSlot classtab, LuaSlot classname) const {
LS.result(); 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); int type = lua_type(L_, key);
switch (type) { switch (type) {
case LUA_TBOOLEAN: case LUA_TBOOLEAN:
lua_pushboolean(L, lua_toboolean(L_, key)); lua_pushboolean(otherstack.L_, lua_toboolean(L_, key));
lua_replace(otherstack.L_, otherslot);
break; break;
case LUA_TNUMBER: case LUA_TNUMBER:
lua_pushnumber(L, lua_tonumber(L_, key)); lua_pushnumber(otherstack.L_, lua_tonumber(L_, key));
lua_replace(otherstack.L_, otherslot);
break; break;
case LUA_TSTRING: { case LUA_TSTRING: {
size_t len; size_t len;
const char *str = lua_tolstring(L_, key, &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; break;
} }
default: 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()); 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) { void LuaStack::settabletype(LuaSlot tab, int t) const {
lua_modflagbits(L_, tab.index(), 0x000F, t); 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()); uint16_t bits = lua_getflagbits(L_, tab.index());
return (bits & 0x0010); 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); lua_modflagbits(L_, tab.index(), 0x0010, visited ? 0x0010 : 0);
} }

View File

@@ -215,6 +215,18 @@ using LuaTypeTag = lua_CFunction;
template<typename T> template<typename T>
int LuaTypeTagValue(lua_State *L) { return 0; } 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 { class LuaStack {
private: private:
int narg_; int narg_;
@@ -360,6 +372,8 @@ public:
int result(); int result();
public: public:
lua_State *state() const { return L_; }
int type(LuaSlot s) const { return lua_type(L_, s); } int type(LuaSlot s) const { return lua_type(L_, s); }
void checktype(LuaSlot s, int type) const { luaL_checktype(L_, s, type); } 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; int next(LuaSlot tab, LuaSlot key, LuaSlot value) const;
void makeclass(LuaSlot tab, LuaSlot name) const;
void getclass(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 { bool rawequal(LuaSlot v1, LuaSlot v2) const {
return lua_rawequal(L_, v1, v2); return lua_rawequal(L_, v1, v2);
@@ -475,21 +485,16 @@ public:
} }
// Lua flagbits manipulation: Table types. // Lua flagbits manipulation: Table types.
enum TableType { int gettabletype(LuaSlot tab) const;
TAB_GENERAL, // A general-purpose table. void settabletype(LuaSlot tab, int t) const;
TAB_REGISTRY, // The registry table.
TAB_GLOBALENV, // The global environment table. // If slot is a table, returns the LUA_TT_XXX table type.
TAB_TANGIBLE, // A tangible's database. // If slot is not a table, returns the LUA_TXXX general type.
TAB_TANGIBLEMETA, // A tangible's metatable. int xtype(LuaSlot slot) const;
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);
// Lua flagbits manipulation: visited bit. // Lua flagbits manipulation: visited bit.
bool getvisited(LuaSlot tab); bool getvisited(LuaSlot tab) const;
void setvisited(LuaSlot tab, bool visited); void setvisited(LuaSlot tab, bool visited) const;
}; };

View File

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

View File

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

View File

@@ -4,6 +4,7 @@
#include "animqueue.hpp" #include "animqueue.hpp"
#include "gui.hpp" #include "gui.hpp"
#include "traceback.hpp" #include "traceback.hpp"
#include "tablecmp.hpp"
#include <iostream> #include <iostream>
void World::store_global_pointer(lua_State *L, World *v) { 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); Gui::store_global_pointer(state(), nullptr);
// Set the tabletype of the registry. // 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. // Set the tabletype of the global environment.
LS.getglobaltable(globtab); LS.getglobaltable(globtab);
LS.settabletype(globtab, LuaStack::TAB_GLOBALENV); LS.settabletype(globtab, LUA_TT_GLOBALENV);
// Create the tangibles table in the registry. // Create the tangibles table in the registry.
LS.rawset(LuaRegistry, "tangibles", LuaNewTable); LS.rawset(LuaRegistry, "tangibles", LuaNewTable);
@@ -114,25 +115,34 @@ World::TanVector World::tangible_get_all(const IdVector &ids) const {
return result; return result;
} }
Tangible *World::tangible_get(lua_State *L, int idx) { Tangible *World::tangible_get(const LuaStack &LS, LuaSlot tab) {
Tangible *result = nullptr; int64_t id = tangible_id(LS, tab);
int top = lua_gettop(L); if (id == 0) {
if (lua_istable(L, idx)) { luaL_error(LS.state(), "parameter is not a tangible");
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));
}
} }
lua_settop(L, top); Tangible *result = tangible_get(id);
if (result == nullptr) { if (result == nullptr) {
luaL_error(L, "parameter is not a tangible"); luaL_error(LS.state(), "parameter is not a tangible");
} }
return result; 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) { void World::tangible_delete(int64_t id) {
lua_State *L = state(); lua_State *L = state();
LuaVar tangibles, database; LuaVar tangibles, database;
@@ -153,6 +163,7 @@ void World::tangible_delete(int64_t id) {
// Clear out the database. // Clear out the database.
LS.clearmetatable(database); LS.clearmetatable(database);
LS.cleartable(database); LS.cleartable(database);
LS.settabletype(database, LUA_TT_DEADTANGIBLE);
// Remove the lua portion from the tangibles table. // Remove the lua portion from the tangibles table.
LS.rawset(tangibles, id, LuaNil); LS.rawset(tangibles, id, LuaNil);
@@ -221,7 +232,7 @@ std::string World::numbered_tables_debug_string() const {
return oss.str(); 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(); lua_State *synch = state();
LuaVar mntmap, sntmap, mtab, stab, mtid, stid; LuaVar mntmap, sntmap, mtab, stab, mtid, stid;
LuaStack MLS(master, mntmap, mtab, mtid); 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. // Fetch the numbered tables map.
MLS.rawget(mntmap, LuaRegistry, "ntmap"); MLS.rawget(mntmap, LuaRegistry, "ntmap");
SLS.rawget(sntmap, 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++) { for (int i = 1; i <= m_ntables; i++) {
if (pairing->mpair[i] == 0) continue;
MLS.rawget(mtab, mntmap, i); MLS.rawget(mtab, mntmap, i);
SLS.rawget(stab, sntmap, pairing->mpair[i]); SLS.rawget(stab, sntmap, i);
std::string mname = "unknown"; if (MLS.istable(mtab) && SLS.istable(stab)) {
std::string sname = "unknown"; std::string mname = "unknown";
if (MLS.istable(mtab)) { std::string sname = "unknown";
MLS.rawget(mtid, mtab, "TID"); MLS.rawget(mtid, mtab, "TID");
if (MLS.isstring(mtid)) { if (MLS.isstring(mtid)) {
mname = MLS.ckstring(mtid); mname = MLS.ckstring(mtid);
} }
}
if (SLS.istable(stab)) {
SLS.rawget(stid, stab, "TID"); SLS.rawget(stid, stab, "TID");
if (SLS.isstring(stid)) { if (SLS.isstring(stid)) {
sname = SLS.ckstring(stid); sname = SLS.ckstring(stid);
} }
result.push_back(std::make_pair(mname, sname));
} }
result.push_back(std::make_pair(mname, sname));
} }
MLS.result(); MLS.result();
SLS.result(); SLS.result();
@@ -376,8 +387,8 @@ Tangible *World::tangible_make(lua_State *L, int64_t id, bool pushdb) {
LS.setmetatable(database, metatab); LS.setmetatable(database, metatab);
// Mark the tangible using the tabletype field. // Mark the tangible using the tabletype field.
LS.settabletype(database, LuaStack::TAB_TANGIBLE); LS.settabletype(database, LUA_TT_TANGIBLE);
LS.settabletype(metatab, LuaStack::TAB_TANGIBLEMETA); LS.settabletype(metatab, LUA_TT_TANGIBLEMETA);
// Store the database into the tangibles table. // Store the database into the tangibles table.
LS.rawget(tangibles, LuaRegistry, "tangibles"); 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. // Number tables in both the master and the synchronous model.
// Meanwhile, the client will number tables in the client-synchronous model. // Meanwhile, the client will number tables in the client-synchronous model.
master->number_lua_tables(closetans); master->number_lua_tables(closetans);
int s_ntables = number_lua_tables(closetans); number_lua_tables(closetans);
// Pair tables from the synchronous and master models. // Pair tables from the synchronous and master models.
TablePairing pairing; pair_lua_tables(closetans, master->state());
pair_lua_tables(closetans, master->state(), &pairing); int ncreate = pair_new_tables(closetans, master->state());
int ncreate = pair_new_tables(&pairing);
sb->write_int32(s_ntables);
sb->write_int32(ncreate); sb->write_int32(ncreate);
create_new_tables(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 closehash = util::hash_id_vector(closetans);
util::HashValue m_closehash = sb->read_hashvalue(); util::HashValue m_closehash = sb->read_hashvalue();
assert(closehash == m_closehash); assert(closehash == m_closehash);
int s_ntables = number_lua_tables(closetans); number_lua_tables(closetans);
assert(s_ntables == sb->read_int32());
int ncreate = sb->read_int32(); int ncreate = sb->read_int32();
create_new_tables(ncreate); create_new_tables(ncreate);
} }
@@ -890,19 +898,12 @@ int World::number_lua_tables(const IdVector &basis) {
for (int64_t id : basis) { for (int64_t id : basis) {
LS.rawget(tab, tangibles, id); LS.rawget(tab, tangibles, id);
assert(LS.istable(tab)); assert(LS.istable(tab));
// Maybe I should traverse the metatable?
// Traverse subtables. // Traverse subtables.
LS.set(key, LuaNil); LS.set(key, LuaNil);
while (LS.next(tab, key, val)) { while (LS.next(tab, key, val)) {
// if (LS.isstring(key)) { if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) {
// 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) {
lua_checkstack(L, 10); lua_checkstack(L, 10);
lua_pushvalue(L, val.index()); 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 // Pop tables from the stack one by one. If the table is not
// already numbered, number it and push subtables onto the stack. // already numbered, number it and push subtables onto the stack.
while (lua_gettop(L) > top) { while (lua_gettop(L) > top) {
//std::cerr << "Popping stack with " << lua_gettop(L) - top << std::endl;
lua_replace(L, tab.index()); lua_replace(L, tab.index());
LS.rawget(xid, tnmap, tab); LS.rawget(xid, tnmap, tab);
if (LS.isnil(xid)) { if (LS.isnil(xid)) {
@@ -919,22 +919,16 @@ int World::number_lua_tables(const IdVector &basis) {
LS.rawset(ntmap, id, tab); LS.rawset(ntmap, id, tab);
// Traverse the metatable. // Traverse the metatable.
LS.getmetatable(val, tab); 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_checkstack(L, 10);
lua_pushvalue(L, val.index()); lua_pushvalue(L, val.index());
} }
// Traverse the subtables. // Traverse the subtables.
LS.set(key, LuaNil); LS.set(key, LuaNil);
while (LS.next(tab, key, val)) { while (LS.next(tab, key, val)) {
// if (LS.isstring(key)) { if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) {
// 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) {
lua_checkstack(L, 10); lua_checkstack(L, 10);
lua_pushvalue(L, val.index()); 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); 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(); lua_State *synch = state();
LuaVar stangibles, mtangibles, sntmap, mntmap, stnmap, mtnmap, stab, mtab, skey, mkey, sval, mval, sidx, midx; 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 SLS(synch, stangibles, stab, skey, sval, sntmap, stnmap, sidx);
LuaStack MLS(master, mtangibles, mtab, mkey, mval, mntmap, mtnmap, midx); LuaStack MLS(master, mtangibles, mtab, mkey, mval, mntmap, mtnmap, midx);
// Fetch the tangible databases
SLS.rawget(stangibles, LuaRegistry, "tangibles"); SLS.rawget(stangibles, LuaRegistry, "tangibles");
MLS.rawget(mtangibles, LuaRegistry, "tangibles"); MLS.rawget(mtangibles, LuaRegistry, "tangibles");
// Fetch the synchronous model tnmap and ntmap
SLS.rawget(stnmap, LuaRegistry, "tnmap"); SLS.rawget(stnmap, LuaRegistry, "tnmap");
MLS.rawget(mtnmap, LuaRegistry, "tnmap");
SLS.rawget(sntmap, LuaRegistry, "ntmap"); SLS.rawget(sntmap, LuaRegistry, "ntmap");
MLS.rawget(mntmap, LuaRegistry, "ntmap");
assert(SLS.istable(stnmap)); assert(SLS.istable(stnmap));
assert(MLS.istable(mtnmap));
assert(SLS.istable(sntmap)); assert(SLS.istable(sntmap));
assert(MLS.istable(mntmap));
pairing->mpair.assign(MLS.rawlen(mntmap) + 1, 0); // Initialize the master model tnmap and ntmap
pairing->spair.assign(SLS.rawlen(sntmap) + 1, 0); 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<bool> paired;
paired.assign(s_ntables + 1, false);
// This records the top of the stack.
int mtop = lua_gettop(master); int mtop = lua_gettop(master);
for (int64_t id : basis) { 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)) { while (MLS.next(mtab, mkey, mval)) {
if (!MLS.issortablekey(mkey)) continue; if (!MLS.issortablekey(mkey)) continue;
if (!MLS.istable(mval)) continue; if (!MLS.istable(mval)) continue;
MLS.movesortablekey(mkey, synch); MLS.movesortablekey(mkey, SLS, skey);
lua_replace(synch, skey.index());
SLS.rawget(sval, stab, skey); SLS.rawget(sval, stab, skey);
if (!SLS.istable(sval)) continue; if (!SLS.istable(sval)) continue;
lua_checkstack(master, 20); 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) { while (lua_gettop(master) > mtop) {
lua_replace(master, mtab.index()); lua_replace(master, mtab.index());
lua_replace(synch, stab.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); 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); SLS.rawget(sidx, stnmap, stab);
if (!SLS.isnumber(sidx)) continue; if (!SLS.isnumber(sidx)) continue;
int imidx = MLS.ckint(midx); int idx = SLS.ckinteger(sidx);
int isidx = SLS.ckint(sidx); assert((idx >= 1) && (idx <= s_ntables));
if (pairing->spair[isidx] != 0) continue; // Pair the tables.
if (pairing->mpair[imidx] != 0) continue; MLS.rawset(mtnmap, mtab, idx);
pairing->spair[isidx] = imidx; MLS.rawset(mntmap, idx, mtab);
pairing->mpair[imidx] = isidx; paired[idx] = true;
// Pair the metatables. // Potentially pair the metatables.
MLS.getmetatable(mval, mtab); MLS.getmetatable(mval, mtab);
if (MLS.istable(mval)) { if (MLS.istable(mval)) {
SLS.getmetatable(sval, stab); 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)) { while (MLS.next(mtab, mkey, mval)) {
if (!MLS.issortablekey(mkey)) continue; if (!MLS.issortablekey(mkey)) continue;
if (!MLS.istable(mval)) continue; if (!MLS.istable(mval)) continue;
MLS.movesortablekey(mkey, synch); MLS.movesortablekey(mkey, SLS, skey);
lua_replace(synch, skey.index());
SLS.rawget(sval, stab, skey); SLS.rawget(sval, stab, skey);
if (!SLS.istable(sval)) continue; if (!SLS.istable(sval)) continue;
lua_checkstack(master, 20); lua_checkstack(master, 20);
@@ -1034,16 +1045,71 @@ void World::pair_lua_tables(const IdVector &basis, lua_State *master, TablePairi
SLS.result(); SLS.result();
} }
int World::pair_new_tables(TablePairing *pairing) { int World::pair_new_tables(const IdVector &basis, lua_State *master) {
int orig = pairing->spair.size(); // This is conceptually recursive, but we're going to use an
for (int i = 1; i <= pairing->mpair.size(); i++) { // explicit stack (the lua stack).
int id = pairing->mpair[i]; lua_State *L = master;
if (id == 0) { LuaVar tnmap, ntmap, tangibles, tab, key, val, xid;
pairing->mpair[i] = pairing->spair.size(); LuaStack LS(L, tnmap, ntmap, tangibles, tab, key, val, xid);
pairing->spair.push_back(i); LS.rawget(tnmap, LuaRegistry, "tnmap");
LS.rawget(ntmap, LuaRegistry, "ntmap");
LS.rawget(tangibles, LuaRegistry, "tangibles");
int ntables = LS.rawlen(ntmap);
std::vector<bool> 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) { void World::create_new_tables(int n) {
@@ -1065,12 +1131,87 @@ void World::create_new_tables(int n) {
assert(stack_is_clear()); 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") { LuaDefine(tangible_animstate, "c") {
LuaArg tanobj; LuaArg tanobj;
LuaRet graphic, plane, x, y, z, facing; LuaRet graphic, plane, x, y, z, facing;
LuaStack LS(L, tanobj, graphic, plane, x, y, z, facing); LuaStack LS(L, tanobj, graphic, plane, x, y, z, facing);
World *w = World::fetch_global_pointer(L); 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(); const AnimStep &aqback = tan->anim_queue_.back();
LS.set(graphic, aqback.graphic()); LS.set(graphic, aqback.graphic());
LS.set(plane, aqback.plane()); LS.set(plane, aqback.plane());
@@ -1085,7 +1226,7 @@ LuaDefine(tangible_animate, "c") {
LuaArg tanobj, config; LuaArg tanobj, config;
LuaStack LS(L, tanobj, config); LuaStack LS(L, tanobj, config);
World *w = World::fetch_global_pointer(L); 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); int64_t id = w->id_global_pool_.alloc_id_for_thread(L);
const AnimStep &prev = tan->anim_queue_.back(); const AnimStep &prev = tan->anim_queue_.back();
AnimStep step; AnimStep step;
@@ -1103,7 +1244,7 @@ LuaDefine(tangible_setclass, "c") {
LuaVar classtab, mt; LuaVar classtab, mt;
LuaStack LS(L, tanobj, classname, classtab, mt); LuaStack LS(L, tanobj, classname, classtab, mt);
World *w = World::fetch_global_pointer(L); World *w = World::fetch_global_pointer(L);
w->tangible_get(L, tanobj.index()); w->tangible_get(LS, tanobj);
LS.getclass(classtab, classname); LS.getclass(classtab, classname);
LS.getmetatable(mt, tanobj); LS.getmetatable(mt, tanobj);
LS.rawset(mt, "__index", classtab); LS.rawset(mt, "__index", classtab);
@@ -1114,7 +1255,7 @@ LuaDefine(tangible_delete, "c") {
LuaArg tanobj; LuaArg tanobj;
LuaStack LS(L, tanobj); LuaStack LS(L, tanobj);
World *w = World::fetch_global_pointer(L); 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. assert(tan != nullptr); // this should be checked above.
if (tan->is_an_actor()) { if (tan->is_an_actor()) {
luaL_error(L, "Cannot delete a player using tangible.delete, use tangible.redirect instead."); 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); LuaStack LS(L, actor1, actor2, bldz);
World *w = World::fetch_global_pointer(L); World *w = World::fetch_global_pointer(L);
bool bulldoze = LS.ckboolean(bldz); 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()) { if (!tan1->is_an_actor()) {
luaL_error(L, "redirect source is not an actor"); luaL_error(L, "redirect source is not an actor");
} }
if (LS.isnil(actor2)) { if (LS.isnil(actor2)) {
w->redirects_[tan1->id()] = 0; w->redirects_[tan1->id()] = 0;
} else { } else {
Tangible *tan2 = w->tangible_get(L, actor2.index()); Tangible *tan2 = w->tangible_get(LS, actor2);
tan2->configure_id_pool_for_actor(); tan2->configure_id_pool_for_actor();
w->redirects_[tan1->id()] = tan2->id(); w->redirects_[tan1->id()] = tan2->id();
} }
@@ -1205,6 +1346,14 @@ LuaDefine(tangible_redirect, "c") {
return LS.result(); 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") { LuaDefine(world_wait, "f") {
if ((lua_gettop(L) != 1) || (lua_type(L, -1) != LUA_TNUMBER)) { if ((lua_gettop(L) != 1) || (lua_type(L, -1) != LUA_TNUMBER)) {
luaL_error(L, "Argument to wait must be a number."); luaL_error(L, "Argument to wait must be a number.");
@@ -1217,16 +1366,12 @@ LuaDefine(world_getregistry, "f") {
return 1; return 1;
} }
LuaDefine(world_gettabletype, "f") { LuaDefine(world_xtype, "f") {
LuaArg tab; LuaArg tab;
LuaRet ttype; LuaRet rtype;
LuaStack LS(L, tab, ttype); LuaStack LS(L, tab, rtype);
if (LS.istable(tab)) { int xt = LS.xtype(tab);
LuaStack::TableType tt = LS.gettabletype(tab); LS.set(rtype, xt);
LS.set(ttype, int(tt));
} else {
luaL_error(L, "Not a table");
}
return LS.result(); return LS.result();
} }
@@ -1237,10 +1382,10 @@ LuaDefine(world_settabletype, "f") {
luaL_error(L, "Not a table"); luaL_error(L, "Not a table");
} }
int tt = LS.ckinteger(ttype); 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"); luaL_error(L, "table type out of range");
} }
LS.settabletype(tab, LuaStack::TableType(tt)); LS.settabletype(tab, tt);
return LS.result(); return LS.result();
} }
@@ -1255,7 +1400,7 @@ static bool worlds_identical(const std::unique_ptr<World> &w1, const std::unique
LuaDefine(unittests_world, "c") { LuaDefine(unittests_world, "c") {
std::unique_ptr<World> m, ss, cs; std::unique_ptr<World> m, ss, cs;
StreamBuffer sb; StreamBuffer sb;
int m_ntables, s_ntables, ncreate; int ncreate;
// Test the numbering of lua tables. We create some general // Test the numbering of lua tables. We create some general
// tables using tangible_set_string. Then we install some // 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_set_string(123, "inventory.cplx.TID", "inventory.cplx");
m->tangible_copy_global(123, "math", "math"); m->tangible_copy_global(123, "math", "math");
m->tangible_copy_global(123, "gltab", "_G"); m->tangible_copy_global(123, "gltab", "_G");
m_ntables = m->number_lua_tables(util::id_vector_create(123)); m->number_lua_tables(util::id_vector_create(123));
// LuaAssert(L, m_ntables == 7);
LuaAssertStrEq(L, m->numbered_tables_debug_string(), LuaAssertStrEq(L, m->numbered_tables_debug_string(),
"inventory;inventory.cplx;skills;skills.leet;transactions;"); "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, "skills.leet.TID", "skills.leet");
ss->tangible_set_string(123, "math.TID", "math"); ss->tangible_set_string(123, "math.TID", "math");
ss->tangible_set_string(123, "gltab.TID", "gltab"); ss->tangible_set_string(123, "gltab.TID", "gltab");
s_ntables = ss->number_lua_tables(util::id_vector_create(123)); ss->number_lua_tables(util::id_vector_create(123));
LuaAssert(L, s_ntables == 6);
LuaAssertStrEq(L, ss->numbered_tables_debug_string(), LuaAssertStrEq(L, ss->numbered_tables_debug_string(),
"gltab;inventory;math;skills;skills.crap;skills.leet;"); "gltab;inventory;math;skills;skills.crap;skills.leet;");
World::TablePairing pairing; ss->pair_lua_tables(util::id_vector_create(123), m->state());
ss->pair_lua_tables(util::id_vector_create(123), m->state(), &pairing); LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state()),
LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state(), &pairing),
"inventory=inventory;skills=skills;skills.leet=skills.leet;"); "inventory=inventory;skills=skills;skills.leet=skills.leet;");
// Test the creation of new tables during difference transmission. // Test the creation of new tables during difference transmission.
// The master world model above has two tables that couldn't be paired // The master world model above has two tables that couldn't be paired
// to the client: inventory.cplx, and transactions. These two tables // to the client: inventory.cplx, and transactions. These two tables
// should be paired to new, created 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); LuaAssert(L, ncreate == 2);
ss->create_new_tables(ncreate); 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;"); "inventory=inventory;inventory.cplx=unknown;skills=skills;skills.leet=skills.leet;transactions=unknown;");
// Create new clean world models. // Create new clean world models.

View File

@@ -80,10 +80,6 @@ public:
using IdVector = util::IdVector; using IdVector = util::IdVector;
using TanVector = std::vector<const Tangible*>; using TanVector = std::vector<const Tangible*>;
using Redirects = std::map<int64_t, int64_t>; using Redirects = std::map<int64_t, int64_t>;
struct TablePairing {
std::vector<int> mpair;
std::vector<int> spair;
};
const float RadiusVisibility = 100.0; const float RadiusVisibility = 100.0;
const float RadiusClose = 10.0; const float RadiusClose = 10.0;
@@ -123,6 +119,12 @@ public:
// //
Tangible *tangible_make(lua_State *L, int64_t id, bool pushdb); 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. // Get a pointer to the specified tangible.
// //
// If there's no such tangible, returns nullptr. // 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, // The value on the lua stack should be a valid lua tangible. If not,
// a lua error is generated. // 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. // Get pointers to many tangibles.
// //
@@ -240,7 +242,7 @@ public:
// Paired tables debug string. Shows TID=TID pairs, sorted alphabetically. // 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. // Store a string in the tangible's database.
// //
@@ -296,21 +298,29 @@ public:
// //
void unnumber_lua_tables(); 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. // 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 // This is followup for pair_new_tables: actually create the new tables that
// were virtually created in the pair_new_tables step. // were virtually created in the pair_new_tables step.
// //
void create_new_tables(int n); 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: private:
// Type of model // Type of model
util::WorldType world_type_; util::WorldType world_type_;

View File

@@ -6,5 +6,6 @@
inspect.lua inspect.lua
ut-table.lua ut-table.lua
ut-globaldb.lua ut-globaldb.lua
ut-tablecmp.lua
player.lua player.lua
login.lua login.lua

View File

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