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/sched.cpp\
cpp/table.cpp\
cpp/tablecmp.cpp\
cpp/gui.cpp\
cpp/luasnap.cpp\
cpp/animqueue.cpp\

View File

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

View File

@@ -1,5 +1,6 @@
#include "luastack.hpp"
#include <iostream>
#include <cassert>
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);
}

View File

@@ -215,6 +215,18 @@ using LuaTypeTag = lua_CFunction;
template<typename T>
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;
};

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 "gui.hpp"
#include "traceback.hpp"
#include "tablecmp.hpp"
#include <iostream>
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,27 +243,27 @@ 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]);
SLS.rawget(stab, sntmap, i);
if (MLS.istable(mtab) && SLS.istable(stab)) {
std::string mname = "unknown";
std::string sname = "unknown";
if (MLS.istable(mtab)) {
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));
}
}
MLS.result();
SLS.result();
std::sort(result.begin(), result.end());
@@ -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<bool> 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<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) {
@@ -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<World> &w1, const std::unique
LuaDefine(unittests_world, "c") {
std::unique_ptr<World> 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.

View File

@@ -80,10 +80,6 @@ public:
using IdVector = util::IdVector;
using TanVector = std::vector<const Tangible*>;
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 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_;

View File

@@ -6,5 +6,6 @@
inspect.lua
ut-table.lua
ut-globaldb.lua
ut-tablecmp.lua
player.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