#include "world.hpp" #include "pprint.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::tangibles_near_debug_string(int64_t actor, int64_t distance) { std::ostringstream result; for (int64_t id : get_near(actor, distance, true)) { const Tangible *tan = tangible_get(id); const AnimStep &aqback = tan->anim_queue_.back(); result << id << ": " << aqback.graphic() << " " << aqback.plane() << " " << aqback.xyz().debug_string() << std::endl; } return result.str(); } 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.rawget(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.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"); } } 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.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); 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.rawget(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 UniqueWorld &w1, const UniqueWorld &w2) { StreamBuffer sbw1, sbw2; w1->serialize(&sbw1); w2->serialize(&sbw2); return sbw1.contents_equal(&sbw2); } LuaDefine(unittests_world1animdiff, "c") { UniqueWorld m(new World(util::WORLD_TYPE_MASTER)); UniqueWorld ss(new World(util::WORLD_TYPE_S_SYNC)); UniqueWorld 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, nullptr); 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, nullptr); 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, nullptr); LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123"); LuaAssert(L, worlds_identical(ss, cs)); return 0; } LuaDefine(unittests_world2pairtab, "c") { UniqueWorld m(new World(util::WORLD_TYPE_MASTER)); UniqueWorld 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") { UniqueWorld m(new World(util::WORLD_TYPE_MASTER)); UniqueWorld ss(new World(util::WORLD_TYPE_S_SYNC)); UniqueWorld 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, 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, "c") { UniqueWorld m(new World(util::WORLD_TYPE_MASTER)); UniqueWorld ss(new World(util::WORLD_TYPE_S_SYNC)); UniqueWorld 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, 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; }