From aedd8ca43ae808d331fd82537d117a80c2af5f9d Mon Sep 17 00:00:00 2001 From: Josh Yelon Date: Thu, 9 Sep 2021 18:23:17 -0400 Subject: [PATCH] Worked on code for difference transmission --- luprex/core/Makefile | 1 + luprex/core/cpp/print.cpp | 6 +- luprex/core/cpp/print.hpp | 2 +- luprex/core/cpp/util.cpp | 10 +- luprex/core/cpp/world-accessor.cpp | 186 +++++++++ luprex/core/cpp/world-difftab.cpp | 4 + luprex/core/cpp/world.cpp | 594 +++++++++++------------------ luprex/core/cpp/world.hpp | 56 ++- 8 files changed, 456 insertions(+), 403 deletions(-) create mode 100644 luprex/core/cpp/world-accessor.cpp diff --git a/luprex/core/Makefile b/luprex/core/Makefile index b1f701f2..9cad65d0 100644 --- a/luprex/core/Makefile +++ b/luprex/core/Makefile @@ -20,6 +20,7 @@ CPP_FILES=\ cpp/streambuffer.cpp\ cpp/source.cpp\ cpp/world.cpp\ + cpp/world-accessor.cpp\ cpp/world-difftab.cpp\ cpp/world-pairtab.cpp\ cpp/textgame.cpp\ diff --git a/luprex/core/cpp/print.cpp b/luprex/core/cpp/print.cpp index 23bcc24d..4ca5e598 100644 --- a/luprex/core/cpp/print.cpp +++ b/luprex/core/cpp/print.cpp @@ -200,7 +200,11 @@ static void pprint_r(Inspector &insp, int level, LuaSlot root) { pprint_r(insp, level + 1, key); (*insp.stream) << "]"; } - (*insp.stream) << " = "; + if (insp.indent) { + (*insp.stream) << " = "; + } else { + (*insp.stream) << "="; + } pprint_r(insp, level + 1, val); } } diff --git a/luprex/core/cpp/print.hpp b/luprex/core/cpp/print.hpp index e6660ab7..34f94ced 100644 --- a/luprex/core/cpp/print.hpp +++ b/luprex/core/cpp/print.hpp @@ -37,7 +37,7 @@ bool string_quote(LuaStack &LS, LuaSlot val, std::ostream *os); // Maxlen specifies the maximum number of characters output. If // this is exceeded, then the printout is truncated. // -void pprint(LuaStack &LS, LuaSlot val, int indent, int maxlen, std::ostream *os); +void pprint(LuaStack &LS, LuaSlot val, bool indent, std::ostream *os); // The following lua interfaces to this code are included: // diff --git a/luprex/core/cpp/util.cpp b/luprex/core/cpp/util.cpp index 607bdbdb..d109a5c6 100644 --- a/luprex/core/cpp/util.cpp +++ b/luprex/core/cpp/util.cpp @@ -36,13 +36,13 @@ bool is_identifier(const std::string &str) { } void quote_string(const std::string &s, std::ostream *os) { - bool usesinglequote = false; + bool anysq = false; + bool anydq = false; for (char c : s) { - if (c == '"') { - usesinglequote = true; - break; - } + if (c == '\'') anysq = true; + if (c == '"') anydq = true; } + bool usesinglequote = (!anysq)||(anydq); (*os) << (usesinglequote ? '\'' : '"'); for (char c : s) { if (c >= 32) { diff --git a/luprex/core/cpp/world-accessor.cpp b/luprex/core/cpp/world-accessor.cpp new file mode 100644 index 00000000..9b2d05f2 --- /dev/null +++ b/luprex/core/cpp/world-accessor.cpp @@ -0,0 +1,186 @@ + +#include "world.hpp" + +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(LS, tanobj); + const AnimStep &aqback = tan->anim_queue_.back(); + LS.set(graphic, aqback.graphic()); + LS.set(plane, aqback.plane()); + LS.set(x, aqback.xyz().x); + LS.set(y, aqback.xyz().y); + LS.set(z, aqback.xyz().z); + LS.set(facing, aqback.facing()); + return LS.result(); +} + +LuaDefine(tangible_animate, "c") { + LuaArg tanobj, config; + LuaStack LS(L, tanobj, config); + World *w = World::fetch_global_pointer(L); + 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; + step.from_lua(L, config.index(), prev); + if (step.action() == "") { + luaL_error(L, "animation action must be specified"); + } + tan->anim_queue_.add(id, step); + tan->update_plane_item(); + return LS.result(); +} + +LuaDefine(tangible_setclass, "c") { + LuaArg tanobj, classname; + LuaVar classtab, mt; + LuaStack LS(L, tanobj, classname, classtab, mt); + World *w = World::fetch_global_pointer(L); + w->tangible_get(LS, tanobj); + LS.getclass(classtab, classname); + LS.getmetatable(mt, tanobj); + LS.rawset(mt, "__index", classtab); + return LS.result(); +} + +LuaDefine(tangible_delete, "c") { + LuaArg tanobj; + LuaStack LS(L, tanobj); + World *w = World::fetch_global_pointer(L); + 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."); + return 0; + } + w->tangible_delete(tan->id()); + return LS.result(); +} + +LuaDefine(tangible_build, "c") { + LuaArg config; + LuaVar classname, classtab, mt; + LuaRet database; + LuaStack LS(L, config, classname, classtab, database, mt); + + LS.checktable(config); + // Get the class of the new tangible. + LS.rawget(classname, config, "class"); + if (LS.isnil(classname)) { + luaL_error(L, "must specify a class name"); + } + LS.getclass(classtab, classname); + + // Parse the initial animation step. + AnimStep initstep, blank; + initstep.from_lua(L, config.index(), blank); + if (!initstep.has_xyz()) { + luaL_error(L, "You must specify (X,Y,Z) for new tangible"); + } + if (!initstep.has_plane()) { + luaL_error(L, "You must specify plane for new tangible"); + } + if (!initstep.has_graphic()) { + luaL_error(L, "You must specify graphic for new tangible"); + } + + // TODO: generate error if there's extra crap in the config table. + + World *w = World::fetch_global_pointer(L); + Tangible *tan = w->tangible_make(L, 0, true); + lua_replace(L, database.index()); + + // Update the class of the new tangible. + LS.getmetatable(mt, database); + LS.rawset(mt, "__index", classtab); + + // Update the animation queue and planemap of the new tangible. + int64_t stepid = w->id_global_pool_.alloc_id_for_thread(L); + tan->anim_queue_.add(stepid, initstep); + tan->update_plane_item(); + + return LS.result(); +} + +LuaDefine(tangible_get, "c") { + LuaArg id; + LuaVar tangibles; + LuaRet database; + LuaStack LS(L, id, tangibles, database); + int64_t nid = LS.ckinteger(id); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(database, tangibles, id); + if (!LS.istable(database)) { + luaL_error(L, "Not a tangible ID: %d", nid); + } + return LS.result(); +} + +LuaDefine(tangible_redirect, "c") { + LuaArg actor1, actor2, bldz; + LuaStack LS(L, actor1, actor2, bldz); + World *w = World::fetch_global_pointer(L); + bool bulldoze = LS.ckboolean(bldz); + 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(LS, actor2); + tan2->configure_id_pool_for_actor(); + w->redirects_[tan1->id()] = tan2->id(); + } + if (bulldoze) { + w->tangible_delete(tan1->id()); + } + 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."); + } + return lua_yield(L, 1); +} + +LuaDefine(world_getregistry, "f") { + lua_pushvalue(L, LUA_REGISTRYINDEX); + return 1; +} + +LuaDefine(world_xtype, "f") { + LuaArg tab; + LuaRet rtype; + LuaStack LS(L, tab, rtype); + int xt = LS.xtype(tab); + LS.set(rtype, xt); + return LS.result(); +} + +LuaDefine(world_settabletype, "f") { + LuaArg tab, ttype; + LuaStack LS(L, tab, ttype); + if (!LS.istable(tab)) { + luaL_error(L, "Not a table"); + } + int tt = LS.ckinteger(ttype); + if ((tt < LUA_TT_GENERAL) || (tt > LUA_TT_CLASS)) { + luaL_error(L, "table type out of range"); + } + LS.settabletype(tab, tt); + return LS.result(); +} + diff --git a/luprex/core/cpp/world-difftab.cpp b/luprex/core/cpp/world-difftab.cpp index 762cf067..d2146075 100644 --- a/luprex/core/cpp/world-difftab.cpp +++ b/luprex/core/cpp/world-difftab.cpp @@ -341,6 +341,8 @@ void World::diff_numbered_tables(lua_State *master, StreamBuffer *sb) { assert(lua_gettop(master) == m_top); } sb->overwrite_int32(write_count_after, nmodified); + SLS.result(); + MLS.result(); } @@ -373,6 +375,8 @@ void World::diff_tangible_databases(const IdVector &basis, lua_State *master, St assert(lua_gettop(master) == m_top); } sb->overwrite_int32(write_count_after, nmodified); + SLS.result(); + MLS.result(); } void World::patch_numbered_tables(StreamBuffer *sb) { diff --git a/luprex/core/cpp/world.cpp b/luprex/core/cpp/world.cpp index 983c7d8b..4566b4ca 100644 --- a/luprex/core/cpp/world.cpp +++ b/luprex/core/cpp/world.cpp @@ -4,6 +4,7 @@ #include "animqueue.hpp" #include "gui.hpp" #include "traceback.hpp" +#include "print.hpp" #include void World::store_global_pointer(lua_State *L, World *v) { @@ -145,6 +146,49 @@ int64_t World::tangible_id(const LuaStack &LS, LuaSlot tab) { return id; } +Tangible *World::tangible_make(lua_State *L, int64_t id, bool pushdb) { + // Get a state if we don't already have one. + if (L == nullptr) { + L = state(); + assert(!pushdb); + } + + LuaVar tangibles, metatab; + LuaRet database; + LuaStack LS(L, tangibles, database, metatab); + + // Allocate an ID if we don't already have one. + if (id == 0) id = id_global_pool_.alloc_id_for_thread(L); + + // Create the C++ part of the structure. + std::unique_ptr &t = tangibles_[id]; + assert (t == nullptr); + t.reset(new Tangible(this, id)); + + // Create the tangible's database and metatable. + LS.set(database, LuaNewTable); + LS.set(metatab, LuaNewTable); + LS.setmetatable(database, metatab); + + // Mark the tangible using the tabletype field. + LS.settabletype(database, LUA_TT_TANGIBLE); + LS.settabletype(metatab, LUA_TT_TANGIBLEMETA); + + // Store the database into the tangibles table. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawset(tangibles, id, database); + + // Populate the database and metatable with initial stuff. + LS.rawset(database, "inventory", LuaNewTable); + LS.rawset(metatab, "id", id); + LS.rawset(metatab, "threads", LuaNewTable); + // LS.rawset(metatab, "__metatable", LuaNil); + + LS.result(); + if (!pushdb) lua_pop(L, 1); + return t.get(); +} + void World::tangible_delete(int64_t id) { lua_State *L = state(); LuaVar tangibles, database; @@ -204,6 +248,25 @@ std::string World::tangible_ids_debug_string() const { return util::id_vector_debug_string(idv); } +std::string World::tangible_pprint(int64_t id) const { + lua_State *L = state(); + LuaVar tangibles, tan, meta; + LuaStack LS(L, tangibles, tan, meta); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawgeti(tan, tangibles, id); + std::ostringstream oss; + if (LS.istable(tan)) { + LS.getmetatable(meta, tan); + LS.clearmetatable(tan); + pprint(LS, tan, false, &oss); + LS.setmetatable(tan, meta); + } else { + oss << ""; + } + LS.result(); + return oss.str(); +} + std::string World::numbered_tables_debug_string() const { lua_State *L = state(); LuaVar ntmap, tab, tid; @@ -364,49 +427,6 @@ util::IdVector World::get_near(int64_t player_id, float radius, bool exclude_now return v; } -Tangible *World::tangible_make(lua_State *L, int64_t id, bool pushdb) { - // Get a state if we don't already have one. - if (L == nullptr) { - L = state(); - assert(!pushdb); - } - - LuaVar tangibles, metatab; - LuaRet database; - LuaStack LS(L, tangibles, database, metatab); - - // Allocate an ID if we don't already have one. - if (id == 0) id = id_global_pool_.alloc_id_for_thread(L); - - // Create the C++ part of the structure. - std::unique_ptr &t = tangibles_[id]; - assert (t == nullptr); - t.reset(new Tangible(this, id)); - - // Create the tangible's database and metatable. - LS.set(database, LuaNewTable); - LS.set(metatab, LuaNewTable); - LS.setmetatable(database, metatab); - - // Mark the tangible using the tabletype field. - LS.settabletype(database, LUA_TT_TANGIBLE); - LS.settabletype(metatab, LUA_TT_TANGIBLEMETA); - - // Store the database into the tangibles table. - LS.rawget(tangibles, LuaRegistry, "tangibles"); - LS.rawset(tangibles, id, database); - - // Populate the database and metatable with initial stuff. - LS.rawset(database, "inventory", LuaNewTable); - LS.rawset(metatab, "id", id); - LS.rawset(metatab, "threads", LuaNewTable); - // LS.rawset(metatab, "__metatable", LuaNil); - - LS.result(); - if (!pushdb) lua_pop(L, 1); - return t.get(); -} - World::Redirects World::fetch_redirects() { World::Redirects result = std::move(redirects_); redirects_.clear(); @@ -701,94 +721,13 @@ void World::rollback() { deserialize(&snapshot_); } -void World::difference_transmit(int64_t actor_id, World *master, StreamBuffer *sb) { - StreamBuffer tsb; - - // Get the actor in both models. s_actor may be nil. - const Tangible *s_actor = tangible_get(actor_id); - const Tangible *m_actor = master->tangible_get(actor_id); - assert(m_actor != nullptr); - - // Pass one: update the actor essentials. Create actor if doesn't exist. - assert(tsb.at_eof()); - diff_actor_essentials(m_actor, s_actor, &tsb); - tsb.copy_into(sb); - patch_actor_essentials(&tsb); - - // Get the list of tangibles visible in either model. - // Some tangibles may be missing in the master, some may be missing in the sync. - util::IdVector visible = util::sort_union_id_vectors( +util::IdVector World::get_visible_union(int64_t actor_id, World *master) { + return util::sort_union_id_vectors( master->get_near_unsorted(actor_id, RadiusVisibility, true), get_near_unsorted(actor_id, RadiusVisibility, true)); - TanVector m_visible = master->tangible_get_all(visible); - TanVector s_visible = tangible_get_all(visible); - assert(m_visible.size() == s_visible.size()); - - // Pass two: update visible animations. - assert(tsb.at_eof()); - diff_visible_animations(m_visible, s_visible, &tsb); - tsb.copy_into(sb); - patch_visible_animations(&tsb); - - // Copy the version number from master animqueue to synch animqueue. - for (int i = 0; i < int(m_visible.size()); i++) { - const Tangible *m_tan = m_visible[i]; - if (m_tan != nullptr) { - Tangible *s_tan = const_cast(s_visible[i]); - if (s_tan == nullptr) { - s_tan = tangible_get(m_tan->id()); - assert(s_tan != nullptr); - } - s_tan->anim_queue_.update_version(m_tan->anim_queue_); - } - } - - // Obtain the list of tangibles whose tables we're going to transmit. - util::IdVector closetans = get_near(actor_id, RadiusClose, true); - assert(closetans == master->get_near(actor_id, RadiusClose, true)); - util::HashValue closehash = util::hash_id_vector(closetans); - - // Confirm the close tangibles with the client. - sb->write_hashvalue(closehash); - - // 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); - number_lua_tables(closetans); - - // Pair tables from the synchronous and master models. - pair_lua_tables(closetans, master->state()); - int ncreate = number_remaining_tables(closetans, master->state()); - sb->write_int32(ncreate); - create_new_tables(ncreate); - - assert(tsb.at_eof()); } -void World::apply_differences(StreamBuffer *sb) { - int64_t actor_id = patch_actor_essentials(sb); - patch_visible_animations(sb); - util::IdVector closetans = get_near(actor_id, RadiusClose, true); - util::HashValue closehash = util::hash_id_vector(closetans); - util::HashValue m_closehash = sb->read_hashvalue(); - assert(closehash == m_closehash); - number_lua_tables(closetans); - int ncreate = sb->read_int32(); - create_new_tables(ncreate); -} - -void World::diff_actor_essentials(const Tangible *m_actor, const Tangible *s_actor, StreamBuffer *sb) { - sb->write_int64(m_actor->id()); - if (s_actor == nullptr) { - m_actor->id_player_pool_.serialize(sb); - m_actor->anim_queue_.serialize(sb); - } else { - s_actor->id_player_pool_.diff(m_actor->id_player_pool_, sb); - s_actor->anim_queue_.diff(m_actor->anim_queue_, sb); - } -} - -int64_t World::patch_actor_essentials(StreamBuffer *sb) { +int64_t World::patch_actor(StreamBuffer *sb) { int64_t actor_id = sb->read_int64(); Tangible *s_actor = tangible_get(actor_id); if (s_actor == nullptr) { @@ -803,58 +742,31 @@ int64_t World::patch_actor_essentials(StreamBuffer *sb) { return actor_id; } -void World::diff_visible_animations(const TanVector &mvis, const TanVector &svis, StreamBuffer *sb) { - // For each tangible missing in the synchronous model, send the - // necessary information to create the tangible. - sb->write_int32(0); - int count_pos = sb->total_writes(); - int count = 0; - for (int i = 0; i < int(svis.size()); i++) { - const Tangible *mt = mvis[i]; - const Tangible *st = svis[i]; - if (st == nullptr) { - count += 1; - sb->write_int64(mt->id()); - mt->anim_queue_.serialize(sb); - } - } - sb->overwrite_int32(count_pos, count); +void World::diff_actor(int64_t actor_id, World *master, StreamBuffer *xsb) { + StreamBuffer tsb; - // For each tangible present in the synchronous model that doesn't - // exist in the master model, send command to delete it. - sb->write_int32(0); - count_pos = sb->total_writes(); - count = 0; - for (int i = 0; i < int(svis.size()); i++) { - const Tangible *mt = mvis[i]; - const Tangible *st = svis[i]; - if (mt == nullptr) { - count += 1; - sb->write_int64(st->id()); - } + // Get the actor in both models. s_actor may be nil. + const Tangible *s_actor = tangible_get(actor_id); + const Tangible *m_actor = master->tangible_get(actor_id); + assert(m_actor != nullptr); + + // Calculate diffs. + tsb.write_int64(actor_id); + if (s_actor == nullptr) { + m_actor->id_player_pool_.serialize(&tsb); + m_actor->anim_queue_.serialize(&tsb); + } else { + s_actor->id_player_pool_.diff(m_actor->id_player_pool_, &tsb); + s_actor->anim_queue_.diff(m_actor->anim_queue_, &tsb); } - sb->overwrite_int32(count_pos, count); - // For each tangible present in both models, compare - // the animation queues. - sb->write_int32(0); - count_pos = sb->total_writes(); - count = 0; - for (int i = 0; i < int(svis.size()); i++) { - const Tangible *mt = mvis[i]; - const Tangible *st = svis[i]; - if ((mt != nullptr) && (st != nullptr)) { - if (st->anim_queue_.need_patch(mt->anim_queue_)) { - count++; - sb->write_int64(st->id()); - st->anim_queue_.diff(mt->anim_queue_, sb); - } - } - } - sb->overwrite_int32(count_pos, count); + // Forward to client, and apply to server-synchronous. + tsb.copy_into(xsb); + patch_actor(&tsb); + assert(tsb.at_eof()); } -void World::patch_visible_animations(StreamBuffer *sb) { +void World::patch_visible(StreamBuffer *sb) { // Receive create messages. int count = sb->read_int32(); for (int i = 0; i < count; i++) { @@ -881,187 +793,143 @@ void World::patch_visible_animations(StreamBuffer *sb) { } } -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(LS, tanobj); - const AnimStep &aqback = tan->anim_queue_.back(); - LS.set(graphic, aqback.graphic()); - LS.set(plane, aqback.plane()); - LS.set(x, aqback.xyz().x); - LS.set(y, aqback.xyz().y); - LS.set(z, aqback.xyz().z); - LS.set(facing, aqback.facing()); - return LS.result(); -} +void World::diff_visible(const util::IdVector &visible, World *master, StreamBuffer *xsb) { + StreamBuffer tsb; -LuaDefine(tangible_animate, "c") { - LuaArg tanobj, config; - LuaStack LS(L, tanobj, config); - World *w = World::fetch_global_pointer(L); - 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; - step.from_lua(L, config.index(), prev); - if (step.action() == "") { - luaL_error(L, "animation action must be specified"); - } - tan->anim_queue_.add(id, step); - tan->update_plane_item(); - return LS.result(); -} - -LuaDefine(tangible_setclass, "c") { - LuaArg tanobj, classname; - LuaVar classtab, mt; - LuaStack LS(L, tanobj, classname, classtab, mt); - World *w = World::fetch_global_pointer(L); - w->tangible_get(LS, tanobj); - LS.getclass(classtab, classname); - LS.getmetatable(mt, tanobj); - LS.rawset(mt, "__index", classtab); - return LS.result(); -} - -LuaDefine(tangible_delete, "c") { - LuaArg tanobj; - LuaStack LS(L, tanobj); - World *w = World::fetch_global_pointer(L); - 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."); - return 0; - } - w->tangible_delete(tan->id()); - return LS.result(); -} - -LuaDefine(tangible_build, "c") { - LuaArg config; - LuaVar classname, classtab, mt; - LuaRet database; - LuaStack LS(L, config, classname, classtab, database, mt); + // Get the specified tangibles in both models. + // Some tangibles may be missing in the master, some may be missing in the sync. + TanVector mvis = master->tangible_get_all(visible); + TanVector svis = tangible_get_all(visible); + assert(mvis.size() == svis.size()); - LS.checktable(config); - // Get the class of the new tangible. - LS.rawget(classname, config, "class"); - if (LS.isnil(classname)) { - luaL_error(L, "must specify a class name"); + // For each tangible that exists in the master, but not + // in the synchronous model, send a create message. + tsb.write_int32(0); + int count_pos = tsb.total_writes(); + int count = 0; + for (int i = 0; i < int(svis.size()); i++) { + const Tangible *mt = mvis[i]; + const Tangible *st = svis[i]; + if ((st == nullptr) && (mt != nullptr)) { + count += 1; + tsb.write_int64(mt->id()); + mt->anim_queue_.serialize(&tsb); + } } - LS.getclass(classtab, classname); + tsb.overwrite_int32(count_pos, count); - // Parse the initial animation step. - AnimStep initstep, blank; - initstep.from_lua(L, config.index(), blank); - if (!initstep.has_xyz()) { - luaL_error(L, "You must specify (X,Y,Z) for new tangible"); + // For each tangible present in the synchronous model that doesn't + // exist in the master model, send command to delete it. + tsb.write_int32(0); + count_pos = tsb.total_writes(); + count = 0; + for (int i = 0; i < int(svis.size()); i++) { + const Tangible *mt = mvis[i]; + const Tangible *st = svis[i]; + if ((mt == nullptr) && (st != nullptr)) { + count += 1; + tsb.write_int64(st->id()); + } } - if (!initstep.has_plane()) { - luaL_error(L, "You must specify plane for new tangible"); + tsb.overwrite_int32(count_pos, count); + + // For each tangible present in both models, compare + // the animation queues. + tsb.write_int32(0); + count_pos = tsb.total_writes(); + count = 0; + for (int i = 0; i < int(svis.size()); i++) { + const Tangible *mt = mvis[i]; + const Tangible *st = svis[i]; + if ((mt != nullptr) && (st != nullptr)) { + if (st->anim_queue_.need_patch(mt->anim_queue_)) { + count++; + tsb.write_int64(st->id()); + st->anim_queue_.diff(mt->anim_queue_, &tsb); + } + } } - if (!initstep.has_graphic()) { - luaL_error(L, "You must specify graphic for new tangible"); + tsb.overwrite_int32(count_pos, count); + + // Forward to client, and apply to server-synchronous. + tsb.copy_into(xsb); + patch_visible(&tsb); + assert(tsb.at_eof()); + + // Copy the version number from master animqueue to synch animqueue. + for (int i = 0; i < int(mvis.size()); i++) { + const Tangible *m_tan = mvis[i]; + if (m_tan != nullptr) { + Tangible *s_tan = const_cast(svis[i]); + if (s_tan == nullptr) { + s_tan = tangible_get(m_tan->id()); + assert(s_tan != nullptr); + } + s_tan->anim_queue_.update_version(m_tan->anim_queue_); + } } - - // TODO: generate error if there's extra crap in the config table. - - World *w = World::fetch_global_pointer(L); - Tangible *tan = w->tangible_make(L, 0, true); - lua_replace(L, database.index()); - - // Update the class of the new tangible. - LS.getmetatable(mt, database); - LS.rawset(mt, "__index", classtab); - - // Update the animation queue and planemap of the new tangible. - int64_t stepid = w->id_global_pool_.alloc_id_for_thread(L); - tan->anim_queue_.add(stepid, initstep); - tan->update_plane_item(); - - return LS.result(); } -LuaDefine(tangible_get, "c") { - LuaArg id; - LuaVar tangibles; - LuaRet database; - LuaStack LS(L, id, tangibles, database); - int64_t nid = LS.ckinteger(id); - LS.rawget(tangibles, LuaRegistry, "tangibles"); - LS.rawget(database, tangibles, id); - if (!LS.istable(database)) { - luaL_error(L, "Not a tangible ID: %d", nid); - } - return LS.result(); +void World::patch_luatabs(StreamBuffer *sb) { + int64_t actor_id = sb->read_int64(); + util::HashValue closehash = sb->read_hashvalue(); + int ncreate = sb->read_int32(); + util::IdVector closetans = get_near(actor_id, RadiusClose, true); + assert(closehash == util::hash_id_vector(closetans)); + number_lua_tables(closetans); + create_new_tables(ncreate); + patch_tangible_databases(sb); + patch_numbered_tables(sb); + unnumber_lua_tables(); } -LuaDefine(tangible_redirect, "c") { - LuaArg actor1, actor2, bldz; - LuaStack LS(L, actor1, actor2, bldz); - World *w = World::fetch_global_pointer(L); - bool bulldoze = LS.ckboolean(bldz); - 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(LS, actor2); - tan2->configure_id_pool_for_actor(); - w->redirects_[tan1->id()] = tan2->id(); - } - if (bulldoze) { - w->tangible_delete(tan1->id()); - } - return LS.result(); +void World::diff_luatabs(int64_t actor_id, World *master, StreamBuffer *xsb) { + StreamBuffer tsb; + + // Calculate the set of close tangibles. + util::IdVector closetans = master->get_near(actor_id, RadiusClose, true); + assert(get_near(actor_id, RadiusClose, true) == closetans); + util::HashValue closehash = util::hash_id_vector(closetans); + + // Number and pair tables in the synchronous and master model. + number_lua_tables(closetans); + pair_lua_tables(closetans, master->state()); + int ncreate = number_remaining_tables(closetans, master->state()); + create_new_tables(ncreate); + + // Difference transmit. + tsb.write_int64(actor_id); + tsb.write_hashvalue(closehash); + tsb.write_int32(ncreate); + diff_tangible_databases(closetans, master->state(), &tsb); + diff_numbered_tables(master->state(), &tsb); + + // Patch + tsb.copy_into(xsb); + assert(tsb.read_int64() == actor_id); + assert(tsb.read_hashvalue() == closehash); + assert(tsb.read_int32() == ncreate); + patch_tangible_databases(&tsb); + patch_numbered_tables(&tsb); + unnumber_lua_tables(); + assert(tsb.at_eof()); + + // Unnumber tables in both models. + unnumber_lua_tables(); + master->unnumber_lua_tables(); } -LuaDefine(tangible_id, "c") { - LuaArg tanobj; - LuaRet id; - LuaStack LS(L, tanobj, id); - LS.set(id, World::tangible_id(LS, tanobj)); - return LS.result(); +void World::diff_everything(int64_t actor_id, World *master, StreamBuffer *sb) { + diff_actor(actor_id, master, sb); + util::IdVector visible = get_visible_union(actor_id, master); + diff_visible(visible, master, sb); + diff_luatabs(actor_id, master, sb); } -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."); - } - return lua_yield(L, 1); -} - -LuaDefine(world_getregistry, "f") { - lua_pushvalue(L, LUA_REGISTRYINDEX); - return 1; -} - -LuaDefine(world_xtype, "f") { - LuaArg tab; - LuaRet rtype; - LuaStack LS(L, tab, rtype); - int xt = LS.xtype(tab); - LS.set(rtype, xt); - return LS.result(); -} - -LuaDefine(world_settabletype, "f") { - LuaArg tab, ttype; - LuaStack LS(L, tab, ttype); - if (!LS.istable(tab)) { - luaL_error(L, "Not a table"); - } - int tt = LS.ckinteger(ttype); - if ((tt < LUA_TT_GENERAL) || (tt > LUA_TT_CLASS)) { - luaL_error(L, "table type out of range"); - } - LS.settabletype(tab, tt); - return LS.result(); +void World::patch_everything(StreamBuffer *sb) { + patch_actor(sb); + patch_visible(sb); + patch_luatabs(sb); } @@ -1072,16 +940,14 @@ static bool worlds_identical(const std::unique_ptr &w1, const std::unique return sbw1.contents_equal(&sbw2); } -LuaDefine(unittests_worlddiffs, "c") { - std::unique_ptr m, ss, cs; +LuaDefine(unittests_worldpairtab, "c") { + std::unique_ptr m(new World(util::WORLD_TYPE_MASTER)); + std::unique_ptr ss(new World(util::WORLD_TYPE_S_SYNC)); StreamBuffer sb; int ncreate; - // Test the numbering of lua tables. We create some general - // tables using tangible_set_string. Then we install some - // specialty tables (not numberable) using tangible_copy_global. - // Finally, we number and dump the list of numbered tables. - m.reset(new World(util::WORLD_TYPE_MASTER)); + // Create a master model containing some general tables, and + // some specialty tables (not numberable). m->tangible_make(0, 123, false); m->tangible_set_string(123, "inventory.TID", "inventory"); m->tangible_set_string(123, "transactions.TID", "transactions"); @@ -1090,14 +956,9 @@ LuaDefine(unittests_worlddiffs, "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->number_lua_tables(util::id_vector_create(123)); - LuaAssertStrEq(L, m->numbered_tables_debug_string(), - "inventory;inventory.cplx;skills;skills.leet;transactions;"); // Now we're going to create a synchronous model that's similar to, but not - // exactly the same as that master model, and we're going to pair the tables. - // Only some of these tables should pair: inventory, skills, and skills.leet - ss.reset(new World(util::WORLD_TYPE_S_SYNC)); + // exactly the same as that master model. ss->tangible_make(0, 123, false); ss->tangible_set_string(123, "inventory.TID", "inventory"); ss->tangible_set_string(123, "skills.TID", "skills"); @@ -1105,6 +966,9 @@ LuaDefine(unittests_worlddiffs, "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"); + + // Now we're going to test the numbering and pairing of tables. + // Only these tables should pair: inventory, skills, and skills.leet ss->number_lua_tables(util::id_vector_create(123)); LuaAssertStrEq(L, ss->numbered_tables_debug_string(), "gltab;inventory;math;skills;skills.crap;skills.leet;"); @@ -1121,11 +985,15 @@ LuaDefine(unittests_worlddiffs, "c") { ss->create_new_tables(ncreate); LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state()), "inventory=inventory;inventory.cplx=unknown;skills=skills;skills.leet=skills.leet;transactions=unknown;"); + return 0; +} - // Create new clean world models. - m.reset(new World(util::WORLD_TYPE_MASTER)); - ss.reset(new World(util::WORLD_TYPE_S_SYNC)); - cs.reset(new World(util::WORLD_TYPE_C_SYNC)); +LuaDefine(unittests_worldanimdiff, "c") { + std::unique_ptr m(new World(util::WORLD_TYPE_MASTER)); + std::unique_ptr ss(new World(util::WORLD_TYPE_S_SYNC)); + std::unique_ptr cs(new World(util::WORLD_TYPE_C_SYNC)); + StreamBuffer sb; + util::IdVector ids = util::id_vector_create(123, 345); // Create some tangibles, and add some animations. m->tangible_make(0, 123, false); @@ -1141,8 +1009,8 @@ LuaDefine(unittests_worlddiffs, "c") { "id=771 action=walkto x=6 y=2; "); // Now difference transmit all that to the client. - ss->difference_transmit(123, m.get(), &sb); - cs->apply_differences(&sb); + ss->diff_visible(ids, m.get(), &sb); + cs->patch_visible(&sb); LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123,345"); LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), "id=0 action= plane= x=0 y=0 z=0 facing=0 graphic=; " @@ -1165,8 +1033,8 @@ LuaDefine(unittests_worlddiffs, "c") { "id=773 action=walkto x=2 y=5; "); // Now difference transmit all that to the client again. - ss->difference_transmit(123, m.get(), &sb); - cs->apply_differences(&sb); + ss->diff_visible(ids, m.get(), &sb); + cs->patch_visible(&sb); LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), "id=0 action= plane= x=0 y=0 z=0 facing=0 graphic=; " "id=770 action=walkto x=3 y=4; " @@ -1182,8 +1050,8 @@ LuaDefine(unittests_worlddiffs, "c") { LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123"); // And difference transmit - ss->difference_transmit(123, m.get(), &sb); - cs->apply_differences(&sb); + ss->diff_visible(ids, m.get(), &sb); + cs->patch_visible(&sb); LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123"); LuaAssert(L, worlds_identical(ss, cs)); diff --git a/luprex/core/cpp/world.hpp b/luprex/core/cpp/world.hpp index 4f249017..c4481299 100644 --- a/luprex/core/cpp/world.hpp +++ b/luprex/core/cpp/world.hpp @@ -197,20 +197,7 @@ public: void snapshot(); void rollback(); - // Difference transmission. - // - // This generates diffs and stores them in the specified buffer, - // so that they can be sent to the client. It also applies the diffs - // to this model. - // - void difference_transmit(int64_t actor, World *master, StreamBuffer *sb); - - // Apply differences. - // - // Note that difference_transmit applies the differences to the server - // synchronous model, so this is only used by the client synchronous model. - // - void apply_differences(StreamBuffer *sb); + util::IdVector get_visible_union(int64_t actor_id, World *master); public: //////////////////////////////////////////////////////////////////////////// @@ -252,6 +239,10 @@ public: // void tangible_copy_global(int64_t id, const std::string &path, const std::string &global); + // Pretty-print the entire tangible database and return it as a string. + // + std::string tangible_pprint(int64_t id) const; + private: // Run any threads which according to the scheduler queue are ready. // @@ -269,21 +260,26 @@ private: // void invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &idata); - // pass 1 of difference transmission: actor essentials. +public: + /////////////////////////////////////////////////////////// // - // Before we do anything else, we need to get the actor in the right place. - // We also update the actor's ID allocation pipeline. + // Difference transmission // - static void diff_actor_essentials(const Tangible *mactor, const Tangible *sactor, StreamBuffer *sb); - int64_t patch_actor_essentials(StreamBuffer *sb); + /////////////////////////////////////////////////////////// - // Pass 2 of difference transmission: visible animations. + int64_t patch_actor(StreamBuffer *sb); + void diff_actor(int64_t actor_id, World *master, StreamBuffer *sb); + + void patch_visible(StreamBuffer *sb); + void diff_visible(const util::IdVector &ids, World *master, StreamBuffer *sb); + + void patch_luatabs(StreamBuffer *sb); + void diff_luatabs(int64_t actor_id, World *master, StreamBuffer *sb); + + // This is the main entry point for difference transmission. // - // Synchronizes the animation status of every tangible inside the visibility - // radius of either model. Creates missing tangibles and deletes excess tangibles. - // - static void diff_visible_animations(const TanVector &mvis, const TanVector &svis, StreamBuffer *sb); - void patch_visible_animations(StreamBuffer *sb); + void patch_everything(StreamBuffer *sb); + void diff_everything(int64_t actor, World *master, StreamBuffer *sb); public: /////////////////////////////////////////////////////////// @@ -350,17 +346,11 @@ public: // /////////////////////////////////////////////////////////// - // Compare the numbered general tables. - // + void patch_numbered_tables(StreamBuffer *sb); void diff_numbered_tables(lua_State *master, StreamBuffer *sb); - // Compare the tangible databases. - // - void diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb); - - void patch_numbered_tables(StreamBuffer *sb); - void patch_tangible_databases(StreamBuffer *sb); + void diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb); private: // Type of model