#include "world.hpp" #include "pprint.hpp" #include "json.hpp" #include void World::tangible_clear_anim_queue_to_empty(int64_t id) { Tangible *t = tangible_get(id); assert(t != nullptr); AnimState state; t->anim_queue_.clear(state); t->update_plane_item(); } void World::tangible_clear_plane_and_xyz(int64_t id, const eng::string &plane, const util::DXYZ &xyz) { Tangible *t = tangible_get(id); assert(t != nullptr); AnimState state; state.set_string("plane", plane); state.set_dxyz("xyz", xyz); state.set_persistent("plane"); state.set_persistent("xyz"); t->anim_queue_.clear(state); t->update_plane_item(); } void World::tangible_walkto(int64_t id, float x, float y) { Tangible *t = tangible_get(id); assert(t != nullptr); AnimState state = t->anim_queue_.get_final_persistent(); state.set_string("action", "walkto"); state.set_dxyz("xyz", util::DXYZ(x,y,0.0)); t->anim_queue_.add(state); } eng::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(); } eng::string World::tangible_id_pool_debug_string(int64_t id) const { const Tangible *t = tangible_get(id); if (t == 0) return "no such tangible"; return t->id_player_pool_.debug_string(); } eng::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); } eng::string World::tangibles_near_debug_string(int64_t actor, int64_t distance) { assert(stack_is_clear()); lua_State *L = state(); LuaVar tangibles, tanobj, classtab; LuaExtStack LS(L, tangibles, tanobj, classtab); LS.rawget(tangibles, LuaRegistry, "tangibles"); eng::ostringstream result; util::IdVector tans; get_near(actor, distance, true, false, true, &tans); for (int64_t id : tans) { const Tangible *tan = tangible_get(id); LS.rawget(tanobj, tangibles, id); LS.tangetclass(classtab, tanobj); eng::string cname = LS.classname(classtab); AnimState state = tan->anim_queue_.get_final_persistent(); result << id << " (" << cname << "): " << state.debug_string() << std::endl; } return result.str(); } eng::string World::tangible_pprint(int64_t id) const { lua_State *L = state(); LuaVar tangibles, tan, meta; LuaExtStack LS(L, tangibles, tan, meta); LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(tan, tangibles, id); eng::ostringstream oss; if (LS.istable(tan)) { PrettyPrintOptions opts; opts.indent = false; pprint(LS, tan, opts, &oss); } else { oss << "{}"; } return oss.str(); } eng::string World::numbered_tables_debug_string() const { lua_State *L = state(); LuaVar ntmap, tab, tid; LuaExtStack LS(L, ntmap, tab, tid); eng::vector result; eng::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.rawget(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"); } } std::sort(result.begin(), result.end()); for (const eng::string &s : result) { oss << s << ";"; } return oss.str(); } eng::string World::paired_tables_debug_string(lua_State *master) const { lua_State *synch = state(); LuaVar mntmap, sntmap, mtab, stab, mtid, stid; LuaExtStack MLS(master, mntmap, mtab, mtid); LuaExtStack SLS(synch, sntmap, stab, stid); eng::vector> result; eng::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)) { eng::string mname = "unknown"; eng::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)); } } 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 eng::string &path, const eng::string &value) { lua_State *L = state(); LuaVar tangibles, tab, subtab; LuaExtStack 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); eng::string finalpart = pathparts.back(); pathparts.pop_back(); // Create subtables as necessary. for (const eng::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); } void World::tangible_copy_global(int64_t id, const eng::string &path, const eng::string &global) { lua_State *L = state(); LuaVar tangibles, tab, subtab, globtab, value; LuaExtStack 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); eng::string finalpart = pathparts.back(); pathparts.pop_back(); // Create subtables as necessary. for (const eng::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); } void World::tangible_set_class(int64_t id, const eng::string &c) const { LuaVar tangibles, tan, meta, sclass; LuaExtStack LS(state(), tangibles, tan, meta, sclass); LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(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); } eng::string World::tangible_get_class(int64_t id) const { LuaVar tangibles, tan, meta, sclass; LuaExtStack LS(state(), tangibles, tan, meta, sclass); LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(tan, tangibles, id); assert(LS.istable(tan)); LS.getmetatable(meta, tan); LS.rawget(sclass, meta, "__index"); eng::string result = LS.classname(sclass); return result; } static bool worlds_identical(const UniqueWorld &w1, const UniqueWorld &w2) { StreamBuffer sbw1, sbw2; w1->serialize(&sbw1); w2->serialize(&sbw2); return sbw1.contents_equal(&sbw2); } LuaDefine(unittests_world1animdiff, "", "some unit tests") { UniqueWorld m(new World(WORLD_TYPE_MASTER)); UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE)); UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE)); StreamBuffer sb; util::IdVector ids = util::id_vector_create(123, 345); // Create some tangibles, and add some animations. m->tangible_make(123); m->tangible_make(345); m->tangible_clear_anim_queue_to_empty(123); m->tangible_clear_anim_queue_to_empty(345); m->tangible_walkto(123, 3, 4); m->tangible_walkto(345, 6, 2); LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123,345"); LuaAssertStrEq(L, m->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0"); LuaAssertStrEq(L, m->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0"); // Now difference transmit all that to the client. ss->diff_visible(ids, m.get(), &sb); cs->patch_visible(&sb, nullptr); LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123,345"); LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0"); LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0"); LuaAssert(L, worlds_identical(ss, cs)); // Now add some more animation records to the master. m->tangible_walkto(123, 7, 3); m->tangible_walkto(345, 2, 5); LuaAssertStrEq(L, m->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0; action:walkto xyz:7,3,0"); LuaAssertStrEq(L, m->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0; action:walkto xyz:2,5,0"); // Now difference transmit all that to the client again. ss->diff_visible(ids, m.get(), &sb); cs->patch_visible(&sb, nullptr); LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0; action:walkto xyz:7,3,0"); LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0; action:walkto xyz:2,5,0"); 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, nullptr); LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123"); LuaAssert(L, worlds_identical(ss, cs)); return 0; } LuaDefine(unittests_world2pairtab, "", "some unit tests") { UniqueWorld m(new World(WORLD_TYPE_MASTER)); UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE)); StreamBuffer sb; int ncreate; // Create a master model containing some general tables, and // some specialty tables (not numberable). m->tangible_make(123); 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(123); 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, "", "some unit tests") { UniqueWorld m(new World(WORLD_TYPE_MASTER)); UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE)); UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE)); StreamBuffer sb; // Initialize all three models so that a tangible exists. m->tangible_make(123); ss->tangible_make(123); cs->tangible_make(123); m->tangible_make(345); ss->tangible_make(345); cs->tangible_make(345); m->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0)); ss->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0)); cs->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0)); m->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0)); ss->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0)); cs->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0)); // 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, nullptr); // 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, "", "some unit tests") { UniqueWorld m(new World(WORLD_TYPE_MASTER)); UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE)); UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE)); StreamBuffer sb; // Initialize all three models so that a tangible exists. m->tangible_make(123); ss->tangible_make(123); cs->tangible_make(123); // 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, nullptr); // 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, nullptr); // 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; } LuaDefine(unittests_world5diffglobals, "", "some unit tests") { UniqueWorld m(new World(WORLD_TYPE_MASTER)); UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE)); UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE)); StreamBuffer sb; m->set_global("x", "3"); ss->diff_globals(m.get(), &sb); cs->patch_globals(&sb, nullptr); LuaAssertStrEq(L, cs->get_global("x"), "3"); m->set_global("x", "4"); m->set_global("x", "5"); m->set_global("y", "6"); ss->diff_globals(m.get(), &sb); cs->patch_globals(&sb, nullptr); LuaAssertStrEq(L, cs->get_global("x"), "5"); LuaAssertStrEq(L, cs->get_global("y"), "6"); cs->set_global("x", "2"); ss->set_global("x", "2"); ss->diff_globals(m.get(), &sb); cs->patch_globals(&sb, nullptr); LuaAssertStrEq(L, cs->get_global("x"), "5"); return 0; }