diff --git a/luprex/core/Makefile b/luprex/core/Makefile index 9cad65d0..75775cc3 100644 --- a/luprex/core/Makefile +++ b/luprex/core/Makefile @@ -19,10 +19,12 @@ CPP_FILES=\ cpp/animqueue.cpp\ cpp/streambuffer.cpp\ cpp/source.cpp\ - cpp/world.cpp\ + cpp/world-core.cpp\ cpp/world-accessor.cpp\ cpp/world-difftab.cpp\ + cpp/world-diffxmit.cpp\ cpp/world-pairtab.cpp\ + cpp/world-testing.cpp\ cpp/textgame.cpp\ cpp/main.cpp diff --git a/luprex/core/cpp/animqueue.cpp b/luprex/core/cpp/animqueue.cpp index 9f563eb2..e2ddd98a 100644 --- a/luprex/core/cpp/animqueue.cpp +++ b/luprex/core/cpp/animqueue.cpp @@ -362,6 +362,15 @@ void AnimQueue::full_clear_and_set_limit(int n) { size_limit_ = n; version_number_ = version_autoinc_ ? 1 : 0; } + +void AnimQueue::clear(const std::string &plane) { + steps_.clear(); + steps_.emplace_back(); + steps_.front().set_plane(plane); + steps_.front().keep_state_only(); + mutated(); +} + void AnimQueue::set_limit(int n) { assert(n >= 1); size_limit_ = n; diff --git a/luprex/core/cpp/animqueue.hpp b/luprex/core/cpp/animqueue.hpp index fcf0bfcc..ab93045a 100644 --- a/luprex/core/cpp/animqueue.hpp +++ b/luprex/core/cpp/animqueue.hpp @@ -181,6 +181,9 @@ public: // Mutator to create a new step. void add(int64_t id, const AnimStep &step); + // Clear and set the plane. + void clear(const std::string &plane); + // Serialize or deserialize to a StreamBuffer // // Caution: version numbers are not stored. On deserialize, diff --git a/luprex/core/cpp/world-accessor.cpp b/luprex/core/cpp/world-accessor.cpp index 9b2d05f2..edd7e5ec 100644 --- a/luprex/core/cpp/world-accessor.cpp +++ b/luprex/core/cpp/world-accessor.cpp @@ -90,7 +90,7 @@ LuaDefine(tangible_build, "c") { // 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); + Tangible *tan = w->tangible_make(L, 0, "nowhere", true); lua_replace(L, database.index()); // Update the class of the new tangible. diff --git a/luprex/core/cpp/world-core.cpp b/luprex/core/cpp/world-core.cpp new file mode 100644 index 00000000..d28484b7 --- /dev/null +++ b/luprex/core/cpp/world-core.cpp @@ -0,0 +1,539 @@ + +#include "world.hpp" +#include "idalloc.hpp" +#include "animqueue.hpp" +#include "gui.hpp" +#include "traceback.hpp" +#include + +void World::store_global_pointer(lua_State *L, World *v) { + lua_pushstring(L, "world"); + lua_pushlightuserdata(L, v); + lua_rawset(L, LUA_REGISTRYINDEX); +} + +World *World::fetch_global_pointer(lua_State *L) { + lua_pushstring(L, "world"); + lua_rawget(L, LUA_REGISTRYINDEX); + World *result = (World *)lua_touserdata(L, -1); + if (result == nullptr) { + luaL_error(L, "No world pointer stored."); + } + lua_pop(L, 1); + return result; +} + +World::~World() { +} + +World::World(util::WorldType wt) { + // Master world model by default. + world_type_ = wt; + + // Initialize the ID allocator in master mode. + if (wt == util::WORLD_TYPE_MASTER || wt == util::WORLD_TYPE_STANDALONE) { + id_global_pool_.init_master(); + } else { + id_global_pool_.init_synch(); + } + + // Prepare to manipulate the lua state. + LuaVar world, globtab; + LuaStack LS(state(), world, globtab); + + // Put the world pointer into the lua registry. + World::store_global_pointer(state(), this); + + // Clear the global GUI pointer. + Gui::store_global_pointer(state(), nullptr); + + // Set the tabletype of the registry. + LS.settabletype(LuaRegistry, LUA_TT_REGISTRY); + + // Set the tabletype of the global environment. + LS.getglobaltable(globtab); + LS.settabletype(globtab, LUA_TT_GLOBALENV); + + // Create the tangibles table in the registry. + LS.rawset(LuaRegistry, "tangibles", LuaNewTable); + + // Create the globaldb in the registry. + LS.rawset(LuaRegistry, "globaldb", LuaNewTable); + + // Initialize the SourceDB. At this stage, the sourcedb is + // empty, so it's just populating the lua builtins. + source_db_.init(state()); + source_db_.rebuild(); + + LS.result(); + assert (stack_is_clear()); +} + +Tangible::Tangible(World *w, int64_t id) : world_(w), anim_queue_(w->world_type_), id_player_pool_(&w->id_global_pool_) { + plane_item_.set_id(id); + plane_item_.track(&w->plane_map_); +} + +void Tangible::update_plane_item() { + const AnimStep &aqback = anim_queue_.back(); + plane_item_.set_pos(aqback.plane(), aqback.xyz().x, aqback.xyz().y, aqback.xyz().z); +} + +void Tangible::serialize(StreamBuffer *sb) { + anim_queue_.serialize(sb); + id_player_pool_.serialize(sb); +} + +void Tangible::deserialize(StreamBuffer *sb) { + anim_queue_.deserialize(sb); + id_player_pool_.deserialize(sb); + update_plane_item(); +} + + +Tangible *World::tangible_get(int64_t id) { + auto iter = tangibles_.find(id); + if (iter == tangibles_.end()) { + return nullptr; + } else { + return iter->second.get(); + } +} + +const Tangible *World::tangible_get(int64_t id) const { + auto iter = tangibles_.find(id); + if (iter == tangibles_.end()) { + return nullptr; + } else { + return iter->second.get(); + } +} + +World::TanVector World::tangible_get_all(const IdVector &ids) const { + TanVector result(ids.size()); + for (int i = 0; i < int(ids.size()); i++) { + result[i] = tangible_get(ids[i]); + } + return result; +} + +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"); + } + Tangible *result = tangible_get(id); + if (result == nullptr) { + 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; +} + +Tangible *World::tangible_make(lua_State *L, int64_t id, const std::string &plane, 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)); + + // Set up initial animation state. + t->anim_queue_.clear(plane); + t->update_plane_item(); + + // 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; + LuaStack LS(L, tangibles, database); + + // Fetch the C++ side of the tangible. + auto iter = tangibles_.find(id); + if (iter == tangibles_.end()) { + LS.result(); + return; // Nothing to delete. + } + + // Fetch the lua side of the tangible. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(database, tangibles, id); + assert(LS.istable(database)); + + // 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); + + // Remove the C++ portion from the tangibles table. + tangibles_.erase(iter); + LS.result(); +} + +util::IdVector World::get_near_unsorted(int64_t player_id, float radius, bool exclude_nowhere) const { + const Tangible *player = tangible_get(player_id); + if (player == nullptr) { + return IdVector(); + } + + // Find out where's the center of the world. + const AnimStep &aqback = player->anim_queue_.back(); + if (exclude_nowhere && (aqback.plane() == "nowhere")) { + return IdVector(); + } + + return plane_map_.scan_radius(aqback.plane(), aqback.xyz().x, aqback.xyz().y, radius, player_id); +} + +util::IdVector World::get_near(int64_t player_id, float radius, bool exclude_nowhere) const { + util::IdVector v = get_near_unsorted(player_id, radius, exclude_nowhere); + std::sort(v.begin(), v.end()); + return v; +} + +World::Redirects World::fetch_redirects() { + World::Redirects result = std::move(redirects_); + redirects_.clear(); + return std::move(result); +} + +int64_t World::create_login_actor() { + Tangible *tan = tangible_make(state(), 0, "nowhere", true); + LuaArg database; + LuaVar classtab, mt; + LuaStack LS(state(), database, classtab, mt); + LS.makeclass(classtab, "login"); + LS.getmetatable(mt, database); + LS.rawset(mt, "__index", classtab); + LS.result(); + tan->configure_id_pool_for_actor(); + assert(stack_is_clear()); + return tan->id(); +} + +void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { + assert(stack_is_clear()); + gui->clear(); + lua_State *L = state(); + + LuaVar actor, place, ugui, func, tangibles, mt, index; + LuaStack LS(L, actor, place, ugui, func, tangibles, mt, index); + + // Get the actor and place. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(actor, tangibles, actor_id); + LS.rawget(place, tangibles, place_id); + if (!LS.istable(actor) || !LS.istable(place)) { + LS.result(); + return; + } + + // Get the interface closure. + LS.getmetatable(mt, place); + if (!LS.istable(mt)) { + LS.result(); + return; + } + LS.rawget(index, mt, "__index"); + if (!LS.istable(index)) { + LS.result(); + return; + } + LS.rawget(func, index, "interface"); + if (!LS.isfunction(func)) { + LS.result(); + return; + } + + // Call the interface function. + lua_pushvalue(L, func.index()); + lua_pushvalue(L, actor.index()); + lua_pushvalue(L, place.index()); + Gui::store_global_pointer(L, gui); + int status = traceback_pcall(L, 2, 0); + Gui::store_global_pointer(L, nullptr); + if (status != 0) { + gui->clear(); + std::cerr << lua_tostring(L, -1); + LS.result(); + return; + } + + // And we're done. + LS.result(); + assert(stack_is_clear()); +} + +void World::invoke(const Invocation &inv) { + switch (inv.kind()) { + case Invocation::KIND_PLAN: + invoke_plan(inv.actor(), inv.place(), inv.action(), inv.data()); + break; + default: + // Do nothing. Standard behavior for any invalid command is to + // simply do nothing at all. Perhaps eventually we may add a flag + // to the world model to indicate that we've detected an invalid + // command, to allow us to close the connection to a client that + // is misbehaving. + break; + } +} + +void World::invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &idata) { + assert(stack_is_clear()); + + // Validate that the action is legal. + Gui validation_gui; + update_gui(actor_id, place_id, &validation_gui); + if (!validation_gui.has_action(action)) { + return; + } + + // Get the actor and place. Make sure both exist. + Tangible *tactor = tangible_get(actor_id); + Tangible *tplace = tangible_get(place_id); + if ((tactor == nullptr) || (tplace == nullptr)) { + return; + } + + // Get an ID batch for the thread, and take one for the thread itself. + int64_t id_batch = tactor->id_player_pool_.get_batch(); + int64_t tid = id_batch++; + + // Set up for Lua manipulation. + lua_State *L = state(); + LuaVar actor, place, func, tangibles, mt, index, actions, thread, threads, message, invdata; + LuaStack LS(L, actor, place, func, tangibles, mt, index, actions, thread, threads, message, invdata); + + // Get the actor and place. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(actor, tangibles, actor_id); + LS.rawget(place, tangibles, place_id); + if (!LS.istable(actor) || !LS.istable(place)) { + LS.result(); + return; + } + + // Get the action closure. + LS.getmetatable(mt, place); + if (!LS.istable(mt)) { + LS.result(); + return; + } + LS.rawget(index, mt, "__index"); + if (!LS.istable(index)) { + LS.result(); + return; + } + LS.rawget(actions, index, "action"); + if (!LS.istable(actions)) { + LS.result(); + return; + } + LS.rawget(func, actions, action); + if (!LS.isfunction(func)) { + LS.result(); + return; + } + + // Convert the InvocationData into a lua table. + LS.newtable(invdata); + for (const auto &p : idata) { + LS.rawset(invdata, p.first, p.second); + } + + // Create a new thread, set up function and parameters. + lua_State *CO = LS.newthread(thread); + lua_pushvalue(L, func.index()); + lua_pushvalue(L, actor.index()); + lua_pushvalue(L, place.index()); + lua_pushvalue(L, invdata.index()); + lua_xmove(L, CO, 4); + + // Store the thread into place's thread table. + LS.rawget(threads, mt, "threads"); + if (!LS.istable(threads)) { + LS.result(); + return; + } + LS.rawset(threads, tid, thread); + LS.result(); + + // Push the thread's ID into the runnable thread queue, + // then run the thread queue. + thread_sched_.add(0, tid, place_id); + run_scheduled_threads(0); + assert(stack_is_clear()); +} + +void World::run_scheduled_threads(int64_t clk) { + assert(stack_is_clear()); + lua_State *L = state(); + LuaVar tangibles, place, mt, threads, thread; + LuaStack LS(L, tangibles, place, mt, threads, thread); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + + while (thread_sched_.ready(clk)) { + SchedEntry sched = thread_sched_.pop(); + LS.rawget(place, tangibles, sched.place_id()); + if (!LS.istable(place)) { + continue; + } + LS.getmetatable(mt, place); + if (!LS.istable(mt)) { + continue; + } + LS.rawget(threads, mt, "threads"); + if (!LS.istable(threads)) { + continue; + } + LS.rawget(thread, threads, sched.thread_id()); + if (!LS.isthread(thread)) { + continue; + } + + // Resume the coroutine. + lua_State *CO = LS.ckthread(thread); + int top = lua_gettop(CO); + int status = lua_resume(CO, nullptr, (top > 0) ? (top - 1) : 0); + + // Three possible outcomes: finished, yielded, or errored. + if (status == LUA_YIELD) { + // When the wait statement yields, it yields the desired timestamp. + if ((lua_gettop(CO) != 1) || (!lua_isnumber(CO, 1))) { + std::cerr << "Thread yielded incorrectly. Killing it." << std::endl; + LS.rawset(threads, sched.thread_id(), LuaNil); + } else { + lua_Number delay = lua_tonumber(CO, 1); + lua_settop(CO, 0); + std::cerr << "Thread wait = " << delay << std::endl; + thread_sched_.add(sched.clock() + int64_t(delay), sched.thread_id(), sched.place_id()); + std::cerr << "Added to schedule." << std::endl; + } + } else if (status == LUA_OK) { + // Successfully ran to completion. Remove from thread table. + std::cerr << "Thread ran to completion." << std::endl; + LS.rawset(threads, sched.thread_id(), LuaNil); + } else { + // Generated an error. Add a traceback, print, and kill the coroutine. + traceback_coroutine(CO); + std::cerr << lua_tostring(CO, -1); + LS.rawset(threads, sched.thread_id(), LuaNil); + } + } + LS.result(); + assert(stack_is_clear()); +} + +void World::serialize(StreamBuffer *sb) { + assert(stack_is_clear()); + assert(redirects_.empty()); + // int64_t wc0 = sb->total_writes(); + lua_snap_.serialize(sb); + id_global_pool_.serialize(sb); + thread_sched_.serialize(sb); + sb->write_uint32(tangibles_.size()); + for (const auto &p : tangibles_) { + sb->write_int64(p.first); + p.second->serialize(sb); + } + // int64_t wc1 = sb->total_writes(); + // std::cerr << "World serialized to " << wc1-wc0 << " bytes." << std::endl; + assert(stack_is_clear()); +} + +void World::deserialize(StreamBuffer *sb) { + assert(stack_is_clear()); + redirects_.clear(); + lua_snap_.deserialize(sb); + id_global_pool_.deserialize(sb); + thread_sched_.deserialize(sb); + // Mark all tangibles for deletion by setting ID to zero. + for (const auto &p : tangibles_) { + p.second->plane_item_.set_id(0); + } + // Deserialize tangibles. + size_t ntan = sb->read_uint32(); + for (size_t i = 0; i < ntan; i++) { + int64_t id = sb->read_int64(); + std::unique_ptr &t = tangibles_[id]; + if (t == nullptr) { + t.reset(new Tangible(this, id)); + } else { + t->plane_item_.set_id(id); + } + t->deserialize(sb); + } + // Delete tangibles that didn't get deserialized. + for (auto iter = tangibles_.begin(); iter != tangibles_.end(); ) { + if (iter->second->plane_item_.id() == 0) { + tangibles_.erase(iter++); + } else { + ++iter; + } + } + assert(stack_is_clear()); +} + +void World::snapshot() { + snapshot_.clear(); + serialize(&snapshot_); +} + +void World::rollback() { + assert(!snapshot_.at_eof()); + deserialize(&snapshot_); +} + diff --git a/luprex/core/cpp/world-difftab.cpp b/luprex/core/cpp/world-difftab.cpp index d2146075..191ea029 100644 --- a/luprex/core/cpp/world-difftab.cpp +++ b/luprex/core/cpp/world-difftab.cpp @@ -272,7 +272,7 @@ static void set_transmitted_value(LuaStack &LS, LuaSlot tangibles, LuaSlot ntmap LS.rawgeti(target, tangibles, id); if (LS.isnil(target)) { World *w = World::fetch_global_pointer(LS.state()); - w->tangible_make(LS.state(), id, true); + w->tangible_make(LS.state(), id, "nowhere", true); lua_replace(LS.state(), target.index()); } return; @@ -306,6 +306,25 @@ static void patch_table(LuaStack &LS0, LuaSlot tangibles, LuaSlot ntmap, LuaSlot LS.result(); } +void World::patch_numbered_tables(StreamBuffer *sb) { + lua_State *L = state(); + LuaVar tangibles, ntmap, tab; + LuaStack LS(L, tangibles, ntmap, tab); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(ntmap, LuaRegistry, "ntmap"); + assert(LS.istable(tangibles)); + assert(LS.istable(ntmap)); + + int nmodified = sb->read_int32(); + for (int i = 0; i < nmodified; i++) { + int index = sb->read_int32(); + LS.rawgeti(tab, ntmap, index); + assert(LS.istable(tab)); + patch_table(LS, tangibles, ntmap, tab, sb); + } + LS.result(); +} + void World::diff_numbered_tables(lua_State *master, StreamBuffer *sb) { lua_State *synch = state(); LuaVar sntmap, mntmap, stnmap, mtnmap, stab, mtab; @@ -345,6 +364,24 @@ void World::diff_numbered_tables(lua_State *master, StreamBuffer *sb) { MLS.result(); } +void World::patch_tangible_databases(StreamBuffer *sb) { + lua_State *L = state(); + LuaVar tangibles, ntmap, tab; + LuaStack LS(L, tangibles, ntmap, tab); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(ntmap, LuaRegistry, "ntmap"); + assert(LS.istable(tangibles)); + assert(LS.istable(ntmap)); + + int nmodified = sb->read_int32(); + for (int i = 0; i < nmodified; i++) { + int64_t id = sb->read_int64(); + LS.rawgeti(tab, tangibles, id); + assert(LS.istable(tab)); + patch_table(LS, tangibles, ntmap, tab, sb); + } + LS.result(); +} void World::diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb) { lua_State *synch = state(); @@ -379,44 +416,6 @@ void World::diff_tangible_databases(const IdVector &basis, lua_State *master, St MLS.result(); } -void World::patch_numbered_tables(StreamBuffer *sb) { - lua_State *L = state(); - LuaVar tangibles, ntmap, tab; - LuaStack LS(L, tangibles, ntmap, tab); - LS.rawget(tangibles, LuaRegistry, "tangibles"); - LS.rawget(ntmap, LuaRegistry, "ntmap"); - assert(LS.istable(tangibles)); - assert(LS.istable(ntmap)); - - int nmodified = sb->read_int32(); - for (int i = 0; i < nmodified; i++) { - int index = sb->read_int32(); - LS.rawgeti(tab, ntmap, index); - assert(LS.istable(tab)); - patch_table(LS, tangibles, ntmap, tab, sb); - } - LS.result(); -} - -void World::patch_tangible_databases(StreamBuffer *sb) { - lua_State *L = state(); - LuaVar tangibles, ntmap, tab; - LuaStack LS(L, tangibles, ntmap, tab); - LS.rawget(tangibles, LuaRegistry, "tangibles"); - LS.rawget(ntmap, LuaRegistry, "ntmap"); - assert(LS.istable(tangibles)); - assert(LS.istable(ntmap)); - - int nmodified = sb->read_int32(); - for (int i = 0; i < nmodified; i++) { - int64_t id = sb->read_int64(); - LS.rawgeti(tab, tangibles, id); - assert(LS.istable(tab)); - patch_table(LS, tangibles, ntmap, tab, sb); - } - LS.result(); -} - LuaDefine(table_diffcompare, "c") { LuaArg mtnmap, mtab, mstnmap, mstab, stnmap, stab; LuaRet dbgstring; diff --git a/luprex/core/cpp/world-diffxmit.cpp b/luprex/core/cpp/world-diffxmit.cpp new file mode 100644 index 00000000..fbab6a29 --- /dev/null +++ b/luprex/core/cpp/world-diffxmit.cpp @@ -0,0 +1,278 @@ +#include "world.hpp" + +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)); +} + +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) { + s_actor = tangible_make(nullptr, actor_id, "", false); + s_actor->id_player_pool_.deserialize(sb); + s_actor->anim_queue_.deserialize(sb); + } else { + s_actor->id_player_pool_.patch(sb); + s_actor->anim_queue_.patch(sb); + } + s_actor->update_plane_item(); + return actor_id; +} + +void World::diff_actor(int64_t actor_id, World *master, StreamBuffer *xsb) { + 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); + + // 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); + } + + // Forward to client, and apply to server-synchronous. + tsb.copy_into(xsb); + patch_actor(&tsb); + assert(tsb.at_eof()); +} + +void World::patch_visible(StreamBuffer *sb) { + // Receive create messages. + int count = sb->read_int32(); + for (int i = 0; i < count; i++) { + int64_t id = sb->read_int64(); + Tangible *t = tangible_make(state(), id, "", false); + t->anim_queue_.deserialize(sb); + t->update_plane_item(); + } + + // Receive delete messages + count = sb->read_int32(); + for (int i = 0; i < count; i++) { + int64_t id = sb->read_int64(); + tangible_delete(id); + } + + // Receive update messages + count = sb->read_int32(); + for (int i = 0; i < count; i++) { + int64_t id = sb->read_int64(); + Tangible *t = tangible_get(id); + assert(t != nullptr); + t->anim_queue_.patch(sb); + } +} + +void World::diff_visible(const util::IdVector &visible, World *master, StreamBuffer *xsb) { + StreamBuffer tsb; + + // 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()); + + // 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); + } + } + tsb.overwrite_int32(count_pos, count); + + // 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()); + } + } + 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); + } + } + } + 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_); + } + } +} + +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(); +} + +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); + + // Forward to client, and apply to server-synchronous. + 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(); +} + +void World::patch_tanclass(StreamBuffer *sb) { + lua_State *L = state(); + LuaVar tangibles, tab, meta, sclass; + LuaStack LS(L, tangibles, tab, meta, sclass); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + + int nmodified = sb->read_int32(); + for (int i = 0; i < nmodified; i++) { + int64_t id = sb->read_int64(); + LS.rawgeti(tab, tangibles, id); + assert(LS.istable(tab)); + LS.getmetatable(meta, tab); + std::string name = sb->read_string(); + if (name == "") { + LS.rawset(meta, "__index", LuaNil); + } else { + LS.makeclass(sclass, name); + LS.rawset(meta, "__index", sclass); + } + } + LS.result(); +} + +void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) { + StreamBuffer tsb; + + LuaVar stangibles, mtangibles, stab, mtab, smeta, mmeta, sclass, mclass; + LuaStack SLS(state(), stangibles, stab, smeta, sclass); + LuaStack MLS(master->state(), mtangibles, mtab, mmeta, mclass); + SLS.rawget(stangibles, LuaRegistry, "tangibles"); + MLS.rawget(mtangibles, LuaRegistry, "tangibles"); + + // Calculate the set of close tangibles. + // TODO: we've already calculated this in an earlier function. This is wasteful. + util::IdVector closetans = master->get_near(actor_id, RadiusClose, true); + + tsb.write_int32(0); + int write_count_after = tsb.total_writes(); + int nmodified = 0; + for (int64_t id : closetans) { + MLS.rawgeti(mtab, mtangibles, id); + SLS.rawgeti(stab, stangibles, id); + MLS.getmetatable(mmeta, mtab); + SLS.getmetatable(smeta, stab); + MLS.rawget(mclass, mmeta, "__index"); + SLS.rawget(sclass, smeta, "__index"); + std::string mname = MLS.classname(mclass); + std::string sname = SLS.classname(sclass); + if (mname != sname) { + tsb.write_int64(id); + tsb.write_string(mname); + nmodified += 1; + } + } + tsb.overwrite_int32(write_count_after, nmodified); + SLS.result(); + MLS.result(); + + // Forward to client, and apply to server-synchronous. + tsb.copy_into(xsb); + patch_tanclass(&tsb); + assert(tsb.at_eof()); +} + +void World::patch_everything(StreamBuffer *sb) { + patch_actor(sb); + patch_visible(sb); + patch_luatabs(sb); + patch_tanclass(sb); +} + +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); + diff_tanclass(actor_id, master, sb); +} diff --git a/luprex/core/cpp/world-testing.cpp b/luprex/core/cpp/world-testing.cpp new file mode 100644 index 00000000..55d18976 --- /dev/null +++ b/luprex/core/cpp/world-testing.cpp @@ -0,0 +1,437 @@ +#include "world.hpp" +#include "print.hpp" +#include + +void World::tangible_walkto(int64_t id, int64_t animid, float x, float y) { + Tangible *t = tangible_get(id); + assert(animid != 0); + assert(t != nullptr); + AnimStep step; + step.set_action("walkto"); + step.set_x(x); + step.set_y(y); + t->anim_queue_.add(animid, step); +} + + +std::string World::tangible_anim_debug_string(int64_t id) const { + const Tangible *t = tangible_get(id); + if (t == 0) return "no such tangible"; + return t->anim_queue_.steps_debug_string(); +} + + +std::string World::tangible_ids_debug_string() const { + util::IdVector idv; + for (const auto &pair : tangibles_) { + idv.push_back(pair.first); + } + std::sort(idv.begin(), idv.end()); + 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; + LuaStack LS(L, ntmap, tab, tid); + std::vector result; + std::ostringstream oss; + + // Fetch the numbered tables map. + LS.rawget(ntmap, LuaRegistry, "ntmap"); + + // Iterate over the map. For each table, if it has + // a TID, output that. Otherwise, output "unknown". + for (int i = 1; i < 10000; i++) { + LS.rawgeti(tab, ntmap, i); + if (!LS.istable(tab)) break; + LS.rawget(tid, tab, "TID"); + if (LS.isstring(tid)) { + result.push_back(LS.ckstring(tid)); + } else { + result.push_back("unknown"); + } + } + LS.result(); + std::sort(result.begin(), result.end()); + for (const std::string &s : result) { + oss << s << ";"; + } + return oss.str(); +} + +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); + LuaStack SLS(synch, sntmap, stab, stid); + std::vector> result; + std::ostringstream oss; + + // 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 <= m_ntables; i++) { + MLS.rawget(mtab, mntmap, i); + SLS.rawget(stab, sntmap, i); + if (MLS.istable(mtab) && SLS.istable(stab)) { + std::string mname = "unknown"; + std::string sname = "unknown"; + MLS.rawget(mtid, mtab, "TID"); + if (MLS.isstring(mtid)) { + mname = MLS.ckstring(mtid); + } + 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()); + for (const auto &pair : result) { + oss << pair.first << "=" << pair.second << ";"; + } + return oss.str(); +} + +void World::tangible_set_string(int64_t id, const std::string &path, const std::string &value) { + lua_State *L = state(); + LuaVar tangibles, tab, subtab; + LuaStack LS(L, tangibles, tab, subtab); + + // Fetch the lua side of the tangible. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(tab, tangibles, id); + assert(LS.istable(tab)); + + // Split the path parts into the table names and final part. + util::StringVec pathparts = util::split(path, '.'); + assert(pathparts.size() >= 1); + std::string finalpart = pathparts.back(); + pathparts.pop_back(); + + // Create subtables as necessary. + for (const std::string &subname : pathparts) { + LS.rawget(subtab, tab, subname); + if (LS.isnil(subtab)) { + LS.set(subtab, LuaNewTable); + LS.rawset(tab, subname, subtab); + } + assert(LS.istable(subtab)); + LS.set(tab, subtab); + } + + // Set the string value. + LS.rawset(tab, finalpart, value); + LS.result(); + assert(stack_is_clear()); +} + +void World::tangible_copy_global(int64_t id, const std::string &path, const std::string &global) { + lua_State *L = state(); + LuaVar tangibles, tab, subtab, globtab, value; + LuaStack LS(L, tangibles, tab, subtab, globtab, value); + + // Fetch the lua side of the tangible. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(tab, tangibles, id); + assert(LS.istable(tab)); + + // Split the path parts into the table names and final part. + util::StringVec pathparts = util::split(path, '.'); + assert(pathparts.size() >= 1); + std::string finalpart = pathparts.back(); + pathparts.pop_back(); + + // Create subtables as necessary. + for (const std::string &subname : pathparts) { + LS.rawget(subtab, tab, subname); + if (LS.isnil(subtab)) { + LS.set(subtab, LuaNewTable); + LS.rawset(tab, subname, subtab); + } + assert(LS.istable(subtab)); + LS.set(tab, subtab); + } + + // Copy the global value. + LS.getglobaltable(globtab); + LS.rawget(value, globtab, global); + LS.rawset(tab, finalpart, value); + LS.result(); +} + +void World::tangible_set_class(int64_t id, const std::string &c) const { + LuaVar tangibles, tan, meta, sclass; + LuaStack LS(state(), tangibles, tan, meta, sclass); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawgeti(tan, tangibles, id); + assert(LS.istable(tan)); + LS.getmetatable(meta, tan); + if (c == "") { + LS.set(sclass, LuaNil); + } else { + LS.makeclass(sclass, c); + } + LS.rawset(meta, "__index", sclass); + LS.result(); +} + +std::string World::tangible_get_class(int64_t id) const { + LuaVar tangibles, tan, meta, sclass; + LuaStack LS(state(), tangibles, tan, meta, sclass); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawgeti(tan, tangibles, id); + assert(LS.istable(tan)); + LS.getmetatable(meta, tan); + LS.rawget(sclass, meta, "__index"); + std::string result = LS.classname(sclass); + LS.result(); + return result; +} + +static bool worlds_identical(const std::unique_ptr &w1, const std::unique_ptr &w2) { + StreamBuffer sbw1, sbw2; + w1->serialize(&sbw1); + w2->serialize(&sbw2); + return sbw1.contents_equal(&sbw2); +} + + +LuaDefine(unittests_world1animdiff, "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, "somewhere", false); + m->tangible_make(0, 345, "somewhere", false); + m->tangible_walkto(123, 770, 3, 4); + m->tangible_walkto(345, 771, 6, 2); + LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123,345"); + LuaAssertStrEq(L, m->tangible_anim_debug_string(123), + "id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; " + "id=770 action=walkto x=3 y=4; "); + LuaAssertStrEq(L, m->tangible_anim_debug_string(345), + "id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; " + "id=771 action=walkto x=6 y=2; "); + + // Now difference transmit all that to the client. + 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=somewhere x=0 y=0 z=0 facing=0 graphic=; " + "id=770 action=walkto x=3 y=4; "); + LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), + "id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; " + "id=771 action=walkto x=6 y=2; "); + LuaAssert(L, worlds_identical(ss, cs)); + + // Now add some more animation records to the master. + m->tangible_walkto(123, 772, 7, 3); + m->tangible_walkto(345, 773, 2, 5); + LuaAssertStrEq(L, m->tangible_anim_debug_string(123), + "id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; " + "id=770 action=walkto x=3 y=4; " + "id=772 action=walkto x=7 y=3; "); + LuaAssertStrEq(L, m->tangible_anim_debug_string(345), + "id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; " + "id=771 action=walkto x=6 y=2; " + "id=773 action=walkto x=2 y=5; "); + + // Now difference transmit all that to the client again. + ss->diff_visible(ids, m.get(), &sb); + cs->patch_visible(&sb); + LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), + "id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; " + "id=770 action=walkto x=3 y=4; " + "id=772 action=walkto x=7 y=3; "); + LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), + "id=0 action= plane=somewhere x=0 y=0 z=0 facing=0 graphic=; " + "id=771 action=walkto x=6 y=2; " + "id=773 action=walkto x=2 y=5; "); + LuaAssert(L, worlds_identical(ss, cs)); + + // Delete tangible 345. + m->tangible_delete(345); + LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123"); + + // And difference transmit + 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)); + + return 0; +} + +LuaDefine(unittests_world2pairtab, "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; + + // Create a master model containing some general tables, and + // some specialty tables (not numberable). + m->tangible_make(0, 123, "somewhere", false); + m->tangible_set_string(123, "inventory.TID", "inventory"); + m->tangible_set_string(123, "transactions.TID", "transactions"); + m->tangible_set_string(123, "skills.TID", "skills"); + m->tangible_set_string(123, "skills.leet.TID", "skills.leet"); + m->tangible_set_string(123, "inventory.cplx.TID", "inventory.cplx"); + m->tangible_copy_global(123, "math", "math"); + m->tangible_copy_global(123, "gltab", "_G"); + + // Now we're going to create a synchronous model that's similar to, but not + // exactly the same as that master model. + ss->tangible_make(0, 123, "somewhere", false); + ss->tangible_set_string(123, "inventory.TID", "inventory"); + ss->tangible_set_string(123, "skills.TID", "skills"); + ss->tangible_set_string(123, "skills.crap.TID", "skills.crap"); + 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;"); + 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->number_remaining_tables(util::id_vector_create(123), m->state()); + LuaAssert(L, ncreate == 2); + ss->create_new_tables(ncreate); + LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state()), + "inventory=inventory;inventory.cplx=unknown;skills=skills;skills.leet=skills.leet;transactions=unknown;"); + return 0; +} + +LuaDefine(unittests_world3diffluatab, "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; + + // Initialize all three models so that a tangible exists. + m->tangible_make(0, 123, "somewhere", false); + ss->tangible_make(0, 123, "somewhere", false); + cs->tangible_make(0, 123, "somewhere", false); + m->tangible_make(0, 345, "somewhere", false); + ss->tangible_make(0, 345, "somewhere", false); + cs->tangible_make(0, 345, "somewhere", false); + + // Put some data into the master model. + m->tangible_set_string(123, "bacon", "crispy"); + m->tangible_set_string(123, "inventory.gold", "wealthy"); + m->tangible_set_string(123, "skills.hunting", "leet"); + m->tangible_set_string(123, "skills.magic.fireball", "weak"); + m->tangible_set_string(345, "inventory.gold", "poor"); + m->tangible_set_string(345, "phone", "867-5309"); + + // The data in the master model should now look like this: + const char *expect_123 = + "{ " + "bacon='crispy', " + "inventory={ gold='wealthy' }, " + "skills={ " + "hunting='leet', " + "magic={ fireball='weak' } " + "} " + "}"; + const char *expect_345 = + "{ " + "inventory={ gold='poor' }, " + "phone='867-5309' " + "}"; + LuaAssertStrEq(L, m->tangible_pprint(123), expect_123); + LuaAssertStrEq(L, m->tangible_pprint(345), expect_345); + + // Difference transmit. + ss->diff_luatabs(123, m.get(), &sb); + cs->patch_luatabs(&sb); + + // Verify that the data was transmitted. + LuaAssertStrEq(L, ss->tangible_pprint(123), expect_123); + LuaAssertStrEq(L, cs->tangible_pprint(123), expect_123); + LuaAssertStrEq(L, ss->tangible_pprint(345), expect_345); + LuaAssertStrEq(L, cs->tangible_pprint(345), expect_345); + LuaAssert(L, worlds_identical(ss, cs)); + + return 0; +} + +LuaDefine(unittests_world4difftanclass, "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; + + // Initialize all three models so that a tangible exists. + m->tangible_make(0, 123, "somewhere", false); + ss->tangible_make(0, 123, "somewhere", false); + cs->tangible_make(0, 123, "somewhere", false); + + // Change the lua class of the tangible. + m->tangible_set_class(123, "chicken"); + LuaAssertStrEq(L, m->tangible_get_class(123), "chicken"); + + // Difference transmit. + ss->diff_tanclass(123, m.get(), &sb); + cs->patch_tanclass(&sb); + + // Verify that the data was transmitted. + LuaAssertStrEq(L, ss->tangible_get_class(123), "chicken"); + LuaAssertStrEq(L, cs->tangible_get_class(123), "chicken"); + LuaAssert(L, worlds_identical(ss, cs)); + + // Change the class again. + m->tangible_set_class(123, ""); + LuaAssertStrEq(L, m->tangible_get_class(123), ""); + + // Difference transmit. + ss->diff_tanclass(123, m.get(), &sb); + cs->patch_tanclass(&sb); + + // Verify that the data was transmitted. + LuaAssertStrEq(L, ss->tangible_get_class(123), ""); + LuaAssertStrEq(L, cs->tangible_get_class(123), ""); + LuaAssert(L, worlds_identical(ss, cs)); + + return 0; +} diff --git a/luprex/core/cpp/world.cpp b/luprex/core/cpp/world.cpp deleted file mode 100644 index 4566b4ca..00000000 --- a/luprex/core/cpp/world.cpp +++ /dev/null @@ -1,1059 +0,0 @@ - -#include "world.hpp" -#include "idalloc.hpp" -#include "animqueue.hpp" -#include "gui.hpp" -#include "traceback.hpp" -#include "print.hpp" -#include - -void World::store_global_pointer(lua_State *L, World *v) { - lua_pushstring(L, "world"); - lua_pushlightuserdata(L, v); - lua_rawset(L, LUA_REGISTRYINDEX); -} - -World *World::fetch_global_pointer(lua_State *L) { - lua_pushstring(L, "world"); - lua_rawget(L, LUA_REGISTRYINDEX); - World *result = (World *)lua_touserdata(L, -1); - if (result == nullptr) { - luaL_error(L, "No world pointer stored."); - } - lua_pop(L, 1); - return result; -} - -World::~World() { -} - -World::World(util::WorldType wt) { - // Master world model by default. - world_type_ = wt; - - // Initialize the ID allocator in master mode. - if (wt == util::WORLD_TYPE_MASTER || wt == util::WORLD_TYPE_STANDALONE) { - id_global_pool_.init_master(); - } else { - id_global_pool_.init_synch(); - } - - // Prepare to manipulate the lua state. - LuaVar world, globtab; - LuaStack LS(state(), world, globtab); - - // Put the world pointer into the lua registry. - World::store_global_pointer(state(), this); - - // Clear the global GUI pointer. - Gui::store_global_pointer(state(), nullptr); - - // Set the tabletype of the registry. - LS.settabletype(LuaRegistry, LUA_TT_REGISTRY); - - // Set the tabletype of the global environment. - LS.getglobaltable(globtab); - LS.settabletype(globtab, LUA_TT_GLOBALENV); - - // Create the tangibles table in the registry. - LS.rawset(LuaRegistry, "tangibles", LuaNewTable); - - // Create the globaldb in the registry. - LS.rawset(LuaRegistry, "globaldb", LuaNewTable); - - // Initialize the SourceDB. At this stage, the sourcedb is - // empty, so it's just populating the lua builtins. - source_db_.init(state()); - source_db_.rebuild(); - - LS.result(); - assert (stack_is_clear()); -} - -Tangible::Tangible(World *w, int64_t id) : world_(w), anim_queue_(w->world_type_), id_player_pool_(&w->id_global_pool_) { - plane_item_.set_id(id); - plane_item_.track(&w->plane_map_); -} - -void Tangible::update_plane_item() { - const AnimStep &aqback = anim_queue_.back(); - plane_item_.set_pos(aqback.plane(), aqback.xyz().x, aqback.xyz().y, aqback.xyz().z); -} - -void Tangible::serialize(StreamBuffer *sb) { - anim_queue_.serialize(sb); - id_player_pool_.serialize(sb); -} - -void Tangible::deserialize(StreamBuffer *sb) { - anim_queue_.deserialize(sb); - id_player_pool_.deserialize(sb); - update_plane_item(); -} - - -Tangible *World::tangible_get(int64_t id) { - auto iter = tangibles_.find(id); - if (iter == tangibles_.end()) { - return nullptr; - } else { - return iter->second.get(); - } -} - -const Tangible *World::tangible_get(int64_t id) const { - auto iter = tangibles_.find(id); - if (iter == tangibles_.end()) { - return nullptr; - } else { - return iter->second.get(); - } -} - -World::TanVector World::tangible_get_all(const IdVector &ids) const { - TanVector result(ids.size()); - for (int i = 0; i < int(ids.size()); i++) { - result[i] = tangible_get(ids[i]); - } - return result; -} - -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"); - } - Tangible *result = tangible_get(id); - if (result == nullptr) { - 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; -} - -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; - LuaStack LS(L, tangibles, database); - - // Fetch the C++ side of the tangible. - auto iter = tangibles_.find(id); - if (iter == tangibles_.end()) { - LS.result(); - return; // Nothing to delete. - } - - // Fetch the lua side of the tangible. - LS.rawget(tangibles, LuaRegistry, "tangibles"); - LS.rawget(database, tangibles, id); - assert(LS.istable(database)); - - // 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); - - // Remove the C++ portion from the tangibles table. - tangibles_.erase(iter); - LS.result(); -} - - -void World::tangible_walkto(int64_t id, int64_t animid, float x, float y) { - Tangible *t = tangible_get(id); - assert(animid != 0); - assert(t != nullptr); - AnimStep step; - step.set_action("walkto"); - step.set_x(x); - step.set_y(y); - t->anim_queue_.add(animid, step); -} - - -std::string World::tangible_anim_debug_string(int64_t id) const { - const Tangible *t = tangible_get(id); - if (t == 0) return "no such tangible"; - return t->anim_queue_.steps_debug_string(); -} - - -std::string World::tangible_ids_debug_string() const { - util::IdVector idv; - for (const auto &pair : tangibles_) { - idv.push_back(pair.first); - } - std::sort(idv.begin(), idv.end()); - 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; - LuaStack LS(L, ntmap, tab, tid); - std::vector result; - std::ostringstream oss; - - // Fetch the numbered tables map. - LS.rawget(ntmap, LuaRegistry, "ntmap"); - - // Iterate over the map. For each table, if it has - // a TID, output that. Otherwise, output "unknown". - for (int i = 1; i < 10000; i++) { - LS.rawgeti(tab, ntmap, i); - if (!LS.istable(tab)) break; - LS.rawget(tid, tab, "TID"); - if (LS.isstring(tid)) { - result.push_back(LS.ckstring(tid)); - } else { - result.push_back("unknown"); - } - } - LS.result(); - std::sort(result.begin(), result.end()); - for (const std::string &s : result) { - oss << s << ";"; - } - return oss.str(); -} - -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); - LuaStack SLS(synch, sntmap, stab, stid); - std::vector> result; - std::ostringstream oss; - - // 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 <= m_ntables; i++) { - MLS.rawget(mtab, mntmap, i); - SLS.rawget(stab, sntmap, i); - if (MLS.istable(mtab) && SLS.istable(stab)) { - std::string mname = "unknown"; - std::string sname = "unknown"; - MLS.rawget(mtid, mtab, "TID"); - if (MLS.isstring(mtid)) { - mname = MLS.ckstring(mtid); - } - 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()); - for (const auto &pair : result) { - oss << pair.first << "=" << pair.second << ";"; - } - return oss.str(); -} - -void World::tangible_set_string(int64_t id, const std::string &path, const std::string &value) { - lua_State *L = state(); - LuaVar tangibles, tab, subtab; - LuaStack LS(L, tangibles, tab, subtab); - - // Fetch the lua side of the tangible. - LS.rawget(tangibles, LuaRegistry, "tangibles"); - LS.rawget(tab, tangibles, id); - assert(LS.istable(tab)); - - // Split the path parts into the table names and final part. - util::StringVec pathparts = util::split(path, '.'); - assert(pathparts.size() >= 1); - std::string finalpart = pathparts.back(); - pathparts.pop_back(); - - // Create subtables as necessary. - for (const std::string &subname : pathparts) { - LS.rawget(subtab, tab, subname); - if (LS.isnil(subtab)) { - LS.set(subtab, LuaNewTable); - LS.rawset(tab, subname, subtab); - } - assert(LS.istable(subtab)); - LS.set(tab, subtab); - } - - // Set the string value. - LS.rawset(tab, finalpart, value); - LS.result(); - assert(stack_is_clear()); -} - -void World::tangible_copy_global(int64_t id, const std::string &path, const std::string &global) { - lua_State *L = state(); - LuaVar tangibles, tab, subtab, globtab, value; - LuaStack LS(L, tangibles, tab, subtab, globtab, value); - - // Fetch the lua side of the tangible. - LS.rawget(tangibles, LuaRegistry, "tangibles"); - LS.rawget(tab, tangibles, id); - assert(LS.istable(tab)); - - // Split the path parts into the table names and final part. - util::StringVec pathparts = util::split(path, '.'); - assert(pathparts.size() >= 1); - std::string finalpart = pathparts.back(); - pathparts.pop_back(); - - // Create subtables as necessary. - for (const std::string &subname : pathparts) { - LS.rawget(subtab, tab, subname); - if (LS.isnil(subtab)) { - LS.set(subtab, LuaNewTable); - LS.rawset(tab, subname, subtab); - } - assert(LS.istable(subtab)); - LS.set(tab, subtab); - } - - // Copy the global value. - LS.getglobaltable(globtab); - LS.rawget(value, globtab, global); - LS.rawset(tab, finalpart, value); - LS.result(); - //assert(stack_is_clear()); -} - -util::IdVector World::get_near_unsorted(int64_t player_id, float radius, bool exclude_nowhere) const { - const Tangible *player = tangible_get(player_id); - if (player == nullptr) { - return IdVector(); - } - - // Find out where's the center of the world. - const AnimStep &aqback = player->anim_queue_.back(); - if (exclude_nowhere && (aqback.plane() == "nowhere")) { - return IdVector(); - } - - return plane_map_.scan_radius(aqback.plane(), aqback.xyz().x, aqback.xyz().y, radius, player_id); -} - -util::IdVector World::get_near(int64_t player_id, float radius, bool exclude_nowhere) const { - util::IdVector v = get_near_unsorted(player_id, radius, exclude_nowhere); - std::sort(v.begin(), v.end()); - return v; -} - -World::Redirects World::fetch_redirects() { - World::Redirects result = std::move(redirects_); - redirects_.clear(); - return std::move(result); -} - -int64_t World::create_login_actor() { - Tangible *tan = tangible_make(state(), 0, true); - LuaArg database; - LuaVar classtab, mt; - LuaStack LS(state(), database, classtab, mt); - LS.makeclass(classtab, "login"); - LS.getmetatable(mt, database); - LS.rawset(mt, "__index", classtab); - LS.result(); - tan->configure_id_pool_for_actor(); - assert(stack_is_clear()); - return tan->id(); -} - -void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { - assert(stack_is_clear()); - gui->clear(); - lua_State *L = state(); - - LuaVar actor, place, ugui, func, tangibles, mt, index; - LuaStack LS(L, actor, place, ugui, func, tangibles, mt, index); - - // Get the actor and place. - LS.rawget(tangibles, LuaRegistry, "tangibles"); - LS.rawget(actor, tangibles, actor_id); - LS.rawget(place, tangibles, place_id); - if (!LS.istable(actor) || !LS.istable(place)) { - LS.result(); - return; - } - - // Get the interface closure. - LS.getmetatable(mt, place); - if (!LS.istable(mt)) { - LS.result(); - return; - } - LS.rawget(index, mt, "__index"); - if (!LS.istable(index)) { - LS.result(); - return; - } - LS.rawget(func, index, "interface"); - if (!LS.isfunction(func)) { - LS.result(); - return; - } - - // Call the interface function. - lua_pushvalue(L, func.index()); - lua_pushvalue(L, actor.index()); - lua_pushvalue(L, place.index()); - Gui::store_global_pointer(L, gui); - int status = traceback_pcall(L, 2, 0); - Gui::store_global_pointer(L, nullptr); - if (status != 0) { - gui->clear(); - std::cerr << lua_tostring(L, -1); - LS.result(); - return; - } - - // And we're done. - LS.result(); - assert(stack_is_clear()); -} - -void World::invoke(const Invocation &inv) { - switch (inv.kind()) { - case Invocation::KIND_PLAN: - invoke_plan(inv.actor(), inv.place(), inv.action(), inv.data()); - break; - default: - // Do nothing. Standard behavior for any invalid command is to - // simply do nothing at all. Perhaps eventually we may add a flag - // to the world model to indicate that we've detected an invalid - // command, to allow us to close the connection to a client that - // is misbehaving. - break; - } -} - -void World::invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &idata) { - assert(stack_is_clear()); - - // Validate that the action is legal. - Gui validation_gui; - update_gui(actor_id, place_id, &validation_gui); - if (!validation_gui.has_action(action)) { - return; - } - - // Get the actor and place. Make sure both exist. - Tangible *tactor = tangible_get(actor_id); - Tangible *tplace = tangible_get(place_id); - if ((tactor == nullptr) || (tplace == nullptr)) { - return; - } - - // Get an ID batch for the thread, and take one for the thread itself. - int64_t id_batch = tactor->id_player_pool_.get_batch(); - int64_t tid = id_batch++; - - // Set up for Lua manipulation. - lua_State *L = state(); - LuaVar actor, place, func, tangibles, mt, index, actions, thread, threads, message, invdata; - LuaStack LS(L, actor, place, func, tangibles, mt, index, actions, thread, threads, message, invdata); - - // Get the actor and place. - LS.rawget(tangibles, LuaRegistry, "tangibles"); - LS.rawget(actor, tangibles, actor_id); - LS.rawget(place, tangibles, place_id); - if (!LS.istable(actor) || !LS.istable(place)) { - LS.result(); - return; - } - - // Get the action closure. - LS.getmetatable(mt, place); - if (!LS.istable(mt)) { - LS.result(); - return; - } - LS.rawget(index, mt, "__index"); - if (!LS.istable(index)) { - LS.result(); - return; - } - LS.rawget(actions, index, "action"); - if (!LS.istable(actions)) { - LS.result(); - return; - } - LS.rawget(func, actions, action); - if (!LS.isfunction(func)) { - LS.result(); - return; - } - - // Convert the InvocationData into a lua table. - LS.newtable(invdata); - for (const auto &p : idata) { - LS.rawset(invdata, p.first, p.second); - } - - // Create a new thread, set up function and parameters. - lua_State *CO = LS.newthread(thread); - lua_pushvalue(L, func.index()); - lua_pushvalue(L, actor.index()); - lua_pushvalue(L, place.index()); - lua_pushvalue(L, invdata.index()); - lua_xmove(L, CO, 4); - - // Store the thread into place's thread table. - LS.rawget(threads, mt, "threads"); - if (!LS.istable(threads)) { - LS.result(); - return; - } - LS.rawset(threads, tid, thread); - LS.result(); - - // Push the thread's ID into the runnable thread queue, - // then run the thread queue. - thread_sched_.add(0, tid, place_id); - run_scheduled_threads(0); - assert(stack_is_clear()); -} - -void World::run_scheduled_threads(int64_t clk) { - assert(stack_is_clear()); - lua_State *L = state(); - LuaVar tangibles, place, mt, threads, thread; - LuaStack LS(L, tangibles, place, mt, threads, thread); - LS.rawget(tangibles, LuaRegistry, "tangibles"); - - while (thread_sched_.ready(clk)) { - SchedEntry sched = thread_sched_.pop(); - LS.rawget(place, tangibles, sched.place_id()); - if (!LS.istable(place)) { - continue; - } - LS.getmetatable(mt, place); - if (!LS.istable(mt)) { - continue; - } - LS.rawget(threads, mt, "threads"); - if (!LS.istable(threads)) { - continue; - } - LS.rawget(thread, threads, sched.thread_id()); - if (!LS.isthread(thread)) { - continue; - } - - // Resume the coroutine. - lua_State *CO = LS.ckthread(thread); - int top = lua_gettop(CO); - int status = lua_resume(CO, nullptr, (top > 0) ? (top - 1) : 0); - - // Three possible outcomes: finished, yielded, or errored. - if (status == LUA_YIELD) { - // When the wait statement yields, it yields the desired timestamp. - if ((lua_gettop(CO) != 1) || (!lua_isnumber(CO, 1))) { - std::cerr << "Thread yielded incorrectly. Killing it." << std::endl; - LS.rawset(threads, sched.thread_id(), LuaNil); - } else { - lua_Number delay = lua_tonumber(CO, 1); - lua_settop(CO, 0); - std::cerr << "Thread wait = " << delay << std::endl; - thread_sched_.add(sched.clock() + int64_t(delay), sched.thread_id(), sched.place_id()); - std::cerr << "Added to schedule." << std::endl; - } - } else if (status == LUA_OK) { - // Successfully ran to completion. Remove from thread table. - std::cerr << "Thread ran to completion." << std::endl; - LS.rawset(threads, sched.thread_id(), LuaNil); - } else { - // Generated an error. Add a traceback, print, and kill the coroutine. - traceback_coroutine(CO); - std::cerr << lua_tostring(CO, -1); - LS.rawset(threads, sched.thread_id(), LuaNil); - } - } - LS.result(); - assert(stack_is_clear()); -} - -void World::serialize(StreamBuffer *sb) { - assert(stack_is_clear()); - assert(redirects_.empty()); - // int64_t wc0 = sb->total_writes(); - lua_snap_.serialize(sb); - id_global_pool_.serialize(sb); - thread_sched_.serialize(sb); - sb->write_uint32(tangibles_.size()); - for (const auto &p : tangibles_) { - sb->write_int64(p.first); - p.second->serialize(sb); - } - // int64_t wc1 = sb->total_writes(); - // std::cerr << "World serialized to " << wc1-wc0 << " bytes." << std::endl; - assert(stack_is_clear()); -} - -void World::deserialize(StreamBuffer *sb) { - assert(stack_is_clear()); - redirects_.clear(); - lua_snap_.deserialize(sb); - id_global_pool_.deserialize(sb); - thread_sched_.deserialize(sb); - // Mark all tangibles for deletion by setting ID to zero. - for (const auto &p : tangibles_) { - p.second->plane_item_.set_id(0); - } - // Deserialize tangibles. - size_t ntan = sb->read_uint32(); - for (size_t i = 0; i < ntan; i++) { - int64_t id = sb->read_int64(); - std::unique_ptr &t = tangibles_[id]; - if (t == nullptr) { - t.reset(new Tangible(this, id)); - } else { - t->plane_item_.set_id(id); - } - t->deserialize(sb); - } - // Delete tangibles that didn't get deserialized. - for (auto iter = tangibles_.begin(); iter != tangibles_.end(); ) { - if (iter->second->plane_item_.id() == 0) { - tangibles_.erase(iter++); - } else { - ++iter; - } - } - assert(stack_is_clear()); -} - -void World::snapshot() { - snapshot_.clear(); - serialize(&snapshot_); -} - -void World::rollback() { - assert(!snapshot_.at_eof()); - deserialize(&snapshot_); -} - -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)); -} - -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) { - s_actor = tangible_make(nullptr, actor_id, false); - s_actor->id_player_pool_.deserialize(sb); - s_actor->anim_queue_.deserialize(sb); - } else { - s_actor->id_player_pool_.patch(sb); - s_actor->anim_queue_.patch(sb); - } - s_actor->update_plane_item(); - return actor_id; -} - -void World::diff_actor(int64_t actor_id, World *master, StreamBuffer *xsb) { - 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); - - // 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); - } - - // Forward to client, and apply to server-synchronous. - tsb.copy_into(xsb); - patch_actor(&tsb); - assert(tsb.at_eof()); -} - -void World::patch_visible(StreamBuffer *sb) { - // Receive create messages. - int count = sb->read_int32(); - for (int i = 0; i < count; i++) { - int64_t id = sb->read_int64(); - Tangible *t = tangible_make(state(), id, false); - t->anim_queue_.deserialize(sb); - t->update_plane_item(); - } - - // Receive delete messages - count = sb->read_int32(); - for (int i = 0; i < count; i++) { - int64_t id = sb->read_int64(); - tangible_delete(id); - } - - // Receive update messages - count = sb->read_int32(); - for (int i = 0; i < count; i++) { - int64_t id = sb->read_int64(); - Tangible *t = tangible_get(id); - assert(t != nullptr); - t->anim_queue_.patch(sb); - } -} - -void World::diff_visible(const util::IdVector &visible, World *master, StreamBuffer *xsb) { - StreamBuffer tsb; - - // 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()); - - // 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); - } - } - tsb.overwrite_int32(count_pos, count); - - // 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()); - } - } - 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); - } - } - } - 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_); - } - } -} - -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(); -} - -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(); -} - -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); -} - -void World::patch_everything(StreamBuffer *sb) { - patch_actor(sb); - patch_visible(sb); - patch_luatabs(sb); -} - - -static bool worlds_identical(const std::unique_ptr &w1, const std::unique_ptr &w2) { - StreamBuffer sbw1, sbw2; - w1->serialize(&sbw1); - w2->serialize(&sbw2); - return sbw1.contents_equal(&sbw2); -} - -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; - - // 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"); - m->tangible_set_string(123, "skills.TID", "skills"); - m->tangible_set_string(123, "skills.leet.TID", "skills.leet"); - m->tangible_set_string(123, "inventory.cplx.TID", "inventory.cplx"); - m->tangible_copy_global(123, "math", "math"); - m->tangible_copy_global(123, "gltab", "_G"); - - // Now we're going to create a synchronous model that's similar to, but not - // 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"); - ss->tangible_set_string(123, "skills.crap.TID", "skills.crap"); - 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;"); - 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->number_remaining_tables(util::id_vector_create(123), m->state()); - LuaAssert(L, ncreate == 2); - ss->create_new_tables(ncreate); - LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state()), - "inventory=inventory;inventory.cplx=unknown;skills=skills;skills.leet=skills.leet;transactions=unknown;"); - return 0; -} - -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); - m->tangible_make(0, 345, false); - m->tangible_walkto(123, 770, 3, 4); - m->tangible_walkto(345, 771, 6, 2); - LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123,345"); - LuaAssertStrEq(L, m->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; "); - LuaAssertStrEq(L, m->tangible_anim_debug_string(345), - "id=0 action= plane= x=0 y=0 z=0 facing=0 graphic=; " - "id=771 action=walkto x=6 y=2; "); - - // Now difference transmit all that to the client. - 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=; " - "id=770 action=walkto x=3 y=4; "); - LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), - "id=0 action= plane= x=0 y=0 z=0 facing=0 graphic=; " - "id=771 action=walkto x=6 y=2; "); - LuaAssert(L, worlds_identical(ss, cs)); - - // Now add some more animation records to the master. - m->tangible_walkto(123, 772, 7, 3); - m->tangible_walkto(345, 773, 2, 5); - LuaAssertStrEq(L, m->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; " - "id=772 action=walkto x=7 y=3; "); - LuaAssertStrEq(L, m->tangible_anim_debug_string(345), - "id=0 action= plane= x=0 y=0 z=0 facing=0 graphic=; " - "id=771 action=walkto x=6 y=2; " - "id=773 action=walkto x=2 y=5; "); - - // Now difference transmit all that to the client again. - 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; " - "id=772 action=walkto x=7 y=3; "); - LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), - "id=0 action= plane= x=0 y=0 z=0 facing=0 graphic=; " - "id=771 action=walkto x=6 y=2; " - "id=773 action=walkto x=2 y=5; "); - LuaAssert(L, worlds_identical(ss, cs)); - - // Delete tangible 345. - m->tangible_delete(345); - LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123"); - - // And difference transmit - 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)); - - return 0; -} diff --git a/luprex/core/cpp/world.hpp b/luprex/core/cpp/world.hpp index c4481299..bf0d2c1e 100644 --- a/luprex/core/cpp/world.hpp +++ b/luprex/core/cpp/world.hpp @@ -117,7 +117,7 @@ public: // pushdb is true, pushes the tangible's database onto the lua stack. // Otherwise, leaves the lua stack untouched. // - Tangible *tangible_make(lua_State *L, int64_t id, bool pushdb); + Tangible *tangible_make(lua_State *L, int64_t id, const std::string &plane, bool pushdb); // Get the tangible ID of the specified LUA tangible database. // @@ -197,7 +197,22 @@ public: void snapshot(); void rollback(); - util::IdVector get_visible_union(int64_t actor_id, World *master); +private: + // Run any threads which according to the scheduler queue are ready. + // + void run_scheduled_threads(int64_t clk); + + // Store a pointer to a world model into a lua registry. + // + static void store_global_pointer(lua_State *L, World *w); + + // Check that the main thread has nothing on the stack + // + bool stack_is_clear() const { return lua_gettop(state()) == 0; } + + // Invoke a plan. + // + void invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &idata); public: //////////////////////////////////////////////////////////////////////////// @@ -242,23 +257,35 @@ public: // Pretty-print the entire tangible database and return it as a string. // std::string tangible_pprint(int64_t id) const; + + // Set the tangible's lua class. + // + void tangible_set_class(int64_t id, const std::string &c) const; + + // Get the tangible's lua class (returns empty string if none). + // + std::string tangible_get_class(int64_t id) const; -private: - // Run any threads which according to the scheduler queue are ready. - // - void run_scheduled_threads(int64_t clk); - // Store a pointer to a world model into a lua registry. +public: + /////////////////////////////////////////////////////////// // - static void store_global_pointer(lua_State *L, World *w); + // world-difftab: Nonrecursive table comparison + // + // These routines compare tables in the master lua to the corresponding + // tables in the synchronous lua. This is a nonrecursive process, because + // the recursion has already been done during the table enumeration process. + // + /////////////////////////////////////////////////////////// - // Check that the main thread has nothing on the stack - // - bool stack_is_clear() const { return lua_gettop(state()) == 0; } + void patch_numbered_tables(StreamBuffer *sb); + void diff_numbered_tables(lua_State *master, StreamBuffer *sb); - // Invoke a plan. - // - void invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &idata); + void patch_tangible_databases(StreamBuffer *sb); + void diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb); + + void patch_tangible_classes(StreamBuffer *sb); + void diff_tangible_classes(const IdVector &basis, lua_State *master, StreamBuffer *sb); public: /////////////////////////////////////////////////////////// @@ -267,6 +294,8 @@ public: // /////////////////////////////////////////////////////////// + util::IdVector get_visible_union(int64_t actor_id, World *master); + int64_t patch_actor(StreamBuffer *sb); void diff_actor(int64_t actor_id, World *master, StreamBuffer *sb); @@ -276,6 +305,9 @@ public: void patch_luatabs(StreamBuffer *sb); void diff_luatabs(int64_t actor_id, World *master, StreamBuffer *sb); + void patch_tanclass(StreamBuffer *sb); + void diff_tanclass(int64_t actor_id, World *master, StreamBuffer *sb); + // This is the main entry point for difference transmission. // void patch_everything(StreamBuffer *sb); @@ -335,23 +367,6 @@ public: // void unnumber_lua_tables(); -public: - /////////////////////////////////////////////////////////// - // - // world-difftab: Nonrecursive table comparison - // - // These routines compare tables in the master lua to the corresponding - // tables in the synchronous lua. This is a nonrecursive process, because - // the recursion has already been done during the table enumeration process. - // - /////////////////////////////////////////////////////////// - - void patch_numbered_tables(StreamBuffer *sb); - void diff_numbered_tables(lua_State *master, StreamBuffer *sb); - - void patch_tangible_databases(StreamBuffer *sb); - void diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb); - private: // Type of model util::WorldType world_type_;