diff --git a/luprex/core/cpp/animqueue.cpp b/luprex/core/cpp/animqueue.cpp index fad0e178..98b3ecb1 100644 --- a/luprex/core/cpp/animqueue.cpp +++ b/luprex/core/cpp/animqueue.cpp @@ -425,7 +425,7 @@ std::string AnimQueue::debug_string() const { return oss.str(); } -void AnimQueue::serialize_size_and_steps(StreamBuffer *sb) const { +void AnimQueue::serialize(StreamBuffer *sb) const { assert(valid()); // can't serialize an invalid animqueue. sb->write_uint8(size_limit_); sb->write_uint8(steps_.size()); @@ -434,12 +434,7 @@ void AnimQueue::serialize_size_and_steps(StreamBuffer *sb) const { } } -void AnimQueue::serialize(StreamBuffer *sb) const { - serialize_size_and_steps(sb); - sb->write_int64(version_number_); -} - -void AnimQueue::deserialize_size_and_steps(StreamBuffer *sb) { +void AnimQueue::deserialize(StreamBuffer *sb) { size_limit_ = sb->read_uint8(); size_t nsteps = sb->read_uint8(); steps_.resize(nsteps); @@ -448,11 +443,7 @@ void AnimQueue::deserialize_size_and_steps(StreamBuffer *sb) { step.read_from(sb); if (i > 0) step.echo(steps_[i - 1]); } -} - -void AnimQueue::deserialize(StreamBuffer *sb) { - deserialize_size_and_steps(sb); - version_number_ = sb->read_int64(); + version_number_ = 0; } bool AnimQueue::need_patch(const AnimQueue &auth) const { @@ -624,7 +615,7 @@ LuaDefine(unittests_animqueue, "c") { aqds.deserialize(&sb); LuaAssertStrEq(L, aqds.debug_string(), - "version=4; limit=5; " + "version=0; limit=5; " "id=0 action= plane= x=0 y=0 z=0 facing=0 graphic=; " "id=12345 action=walk x=3 y=4 z=5; " "id=12346 action=setgraphic graphic=banana; " diff --git a/luprex/core/cpp/animqueue.hpp b/luprex/core/cpp/animqueue.hpp index ab7edc4e..d202f553 100644 --- a/luprex/core/cpp/animqueue.hpp +++ b/luprex/core/cpp/animqueue.hpp @@ -182,8 +182,10 @@ public: void add(int64_t id, const AnimStep &step); // Serialize or deserialize to a StreamBuffer - void serialize_size_and_steps(StreamBuffer *sb) const; - void deserialize_size_and_steps(StreamBuffer *sb); + // + // Caution: version numbers are not stored. On deserialize, + // version number is set to zero. + // void serialize(StreamBuffer *sb) const; void deserialize(StreamBuffer *sb); @@ -196,11 +198,21 @@ public: // Get the final resting place after all animations are complete. const AnimStep &back() const; - // (For testing): change the size limit. +public: + //////////////////////////////////////////////////////////////////////////// + // + // TESTING SUPPORT + // + // The following functions are not designed to be useful for production + // code, they're designed to be helpful for unit testing. + // + //////////////////////////////////////////////////////////////////////////// + + // Change the size limit. void full_clear_and_set_limit(int szl); void set_limit(int szl); - // (For testing): make sure the invariants are preserved. + // Make sure the invariants are preserved. bool valid() const; // Convert to a string for debugging purposes. diff --git a/luprex/core/cpp/idalloc.hpp b/luprex/core/cpp/idalloc.hpp index a570e528..eb449e50 100644 --- a/luprex/core/cpp/idalloc.hpp +++ b/luprex/core/cpp/idalloc.hpp @@ -37,7 +37,7 @@ // // THE NUMERIC RANGES // -// * 0x0000+ : reserved for future expansion. +// * 0x0000+ : reserved for manually-created tangible IDs. // * 0x0001+ : used by master model's IdGlobalPool to create batches. // * 0x0010+ : used by master model's IdGlobalPool to create individual IDs. // * 0x001E+ : used by sync model's IdGlobalPool to create individual IDs. diff --git a/luprex/core/cpp/luasnap.cpp b/luprex/core/cpp/luasnap.cpp index 363b310b..a04b99f0 100644 --- a/luprex/core/cpp/luasnap.cpp +++ b/luprex/core/cpp/luasnap.cpp @@ -69,7 +69,7 @@ void LuaSnap::serialize(StreamBuffer *sb) { int64_t pos2 = sb->total_writes(); sb->overwrite_int64(pos1, pos2 - pos1); lua_settop(state_, 0); - std::cerr << "Eris dump is " << pos2-pos1 << " bytes." << std::endl; + // std::cerr << "Eris dump is " << pos2-pos1 << " bytes." << std::endl; } void LuaSnap::deserialize(StreamBuffer *sb) { diff --git a/luprex/core/cpp/planemap.cpp b/luprex/core/cpp/planemap.cpp index ea56a7c4..29474442 100644 --- a/luprex/core/cpp/planemap.cpp +++ b/luprex/core/cpp/planemap.cpp @@ -12,7 +12,7 @@ #define CELL_SCALE 10.0 // Round a float and return as integer. Clamp result to the specified range. -static int round_and_clamp(double x, int lo, int hi) { +static int round_and_clamp(float x, int lo, int hi) { x = round(x); if (x < lo) return lo; if (x > hi) return hi; @@ -37,7 +37,7 @@ struct CellRange { // beyond the maximum cell range. In that case, it clamps to the edge // of the cell range. // -static CellRange rect_cell_range(double x1, double y1, double x2, double y2) { +static CellRange rect_cell_range(float x1, float y1, float x2, float y2) { CellRange result; result.xlo = round_and_clamp(x1 / CELL_SCALE, -CELL_LIMIT, CELL_LIMIT); result.ylo = round_and_clamp(y1 / CELL_SCALE, -CELL_LIMIT, CELL_LIMIT); @@ -53,9 +53,9 @@ static int64_t cell_id(int64_t cellx, int64_t celly) { } // Get the cell ID of the specified point -static int64_t point_cell_id(double x, double y) { - double cellx = round_and_clamp(x / CELL_SCALE, -CELL_LIMIT, CELL_LIMIT); - double celly = round_and_clamp(y / CELL_SCALE, -CELL_LIMIT, CELL_LIMIT); +static int64_t point_cell_id(float x, float y) { + float cellx = round_and_clamp(x / CELL_SCALE, -CELL_LIMIT, CELL_LIMIT); + float celly = round_and_clamp(y / CELL_SCALE, -CELL_LIMIT, CELL_LIMIT); return cell_id(int64_t(cellx), int64_t(celly)); } @@ -85,7 +85,7 @@ void PlaneMap::insert(const std::string &plane, int64_t cellid, PlaneItem *clien l.push_back(client); } -void PlaneItem::set_pos(const std::string &plane, double x, double y, double z) { +void PlaneItem::set_pos(const std::string &plane, float x, float y, float z) { int64_t old_cell = point_cell_id(x_, y_); int64_t new_cell = point_cell_id(x, y); @@ -160,7 +160,7 @@ int PlaneMap::total_cells() const { return total; } -PlaneMap::IdVector PlaneMap::scan_radius(const std::string &plane, double x, double y, double radius, int64_t prepend) const { +PlaneMap::IdVector PlaneMap::scan_radius(const std::string &plane, float x, float y, float radius, int64_t prepend) const { PlaneMap::IdVector result; if (prepend != 0) { result.push_back(prepend); @@ -169,7 +169,7 @@ PlaneMap::IdVector PlaneMap::scan_radius(const std::string &plane, double x, dou if (piter != planes_.end()) { const Plane &p = piter->second; CellRange range = rect_cell_range(x - radius, y - radius, x + radius, y + radius); - double radsq = radius*radius; + float radsq = radius*radius; for (int cy = range.ylo; cy <= range.yhi; cy++) { for (int cx = range.xlo; cx <= range.xhi; cx++) { auto liter = p.find(cell_id(cx, cy)); @@ -189,8 +189,8 @@ PlaneMap::IdVector PlaneMap::scan_radius(const std::string &plane, double x, dou } LuaDefine(unittests_planemap, "c") { - double SC = CELL_SCALE; - double E = CELL_SCALE * 0.4; + float SC = CELL_SCALE; + float E = CELL_SCALE * 0.4; int LO = -CELL_LIMIT; int HI = CELL_LIMIT; PlaneMap pm; diff --git a/luprex/core/cpp/planemap.hpp b/luprex/core/cpp/planemap.hpp index 84a48bb9..069ea8f1 100644 --- a/luprex/core/cpp/planemap.hpp +++ b/luprex/core/cpp/planemap.hpp @@ -86,7 +86,7 @@ class PlaneItem { private: PlaneMap *pmap_; std::string plane_; - double x_, y_, z_; + float x_, y_, z_; int64_t id_; public: @@ -98,15 +98,15 @@ public: int64_t id() const { return id_; } const std::string &plane() const { return plane_; } - const double x() const { return x_; } - const double y() const { return y_; } - const double z() const { return z_; } + const float x() const { return x_; } + const float y() const { return y_; } + const float z() const { return z_; } void untrack(); void track(PlaneMap *pmap); - void set_pos(const std::string &plane, double x, double y, double z); - void set_xyz(double x, double y, double z) { set_pos(plane_, x, y, z); } + void set_pos(const std::string &plane, float x, float y, float z); + void set_xyz(float x, float y, float z) { set_pos(plane_, x, y, z); } }; class PlaneMap { @@ -124,7 +124,7 @@ public: ~PlaneMap(); // Caution: scan_radius is not deterministically ordered. - IdVector scan_radius(const std::string &plane, double x, double y, double radius, int64_t prepend) const; + IdVector scan_radius(const std::string &plane, float x, float y, float radius, int64_t prepend) const; private: // unit testing stuff. diff --git a/luprex/core/cpp/streambuffer.cpp b/luprex/core/cpp/streambuffer.cpp index e3df441c..a555820c 100644 --- a/luprex/core/cpp/streambuffer.cpp +++ b/luprex/core/cpp/streambuffer.cpp @@ -41,6 +41,10 @@ int64_t StreamBuffer::total_writes() const { return (write_cursor_ - buf_lo_) + pre_read_count_; } +int64_t StreamBuffer::fill() const { + return write_cursor_ - read_cursor_; +} + bool StreamBuffer::layout_is(int64_t a, int64_t b, int64_t c) { if (read_cursor_ - buf_lo_ != a) return false; if (write_cursor_ - read_cursor_ != b) return false; @@ -400,6 +404,14 @@ void StreamBuffer::copy_into(StreamBuffer *sb) { sb->write_bytes(read_cursor_, write_cursor_ - read_cursor_); } +bool StreamBuffer::contents_equal(const StreamBuffer *other) const { + int64_t len = fill(); + if (len != other->fill()) { + return false; + } + return memcmp(read_cursor_, other->read_cursor_, len) == 0; +} + util::HashValue StreamBuffer::hash() const { uint64_t hash1 = 0x82A7912E7893AC87; uint64_t hash2 = 0x81D402740DE458F3; @@ -448,90 +460,90 @@ LuaDefine(unittests_streambuffer, "c") { StreamBuffer sb11(11, true); // Check the initial state. - assert(sb11.layout_is(0, 0, 11)); + LuaAssert(L, sb11.layout_is(0, 0, 11)); // Write a few bytes. write_ztbytes(&sb11, "abcdef"); - assert(sb11.layout_is(0, 6, 5)); + LuaAssert(L, sb11.layout_is(0, 6, 5)); // Try reading some bytes. - assert(streq("abcd", sb11.read_bytes(4))); - assert(sb11.layout_is(4, 2, 5)); + LuaAssert(L, streq("abcd", sb11.read_bytes(4))); + LuaAssert(L, sb11.layout_is(4, 2, 5)); // Put back two bytes. sb11.unread_to(2); - assert(sb11.layout_is(2, 4, 5)); + LuaAssert(L, sb11.layout_is(2, 4, 5)); // Read some more bytes. - assert(streq("cdef", sb11.read_bytes(4))); - assert(sb11.layout_is(6, 0, 5)); + LuaAssert(L, streq("cdef", sb11.read_bytes(4))); + LuaAssert(L, sb11.layout_is(6, 0, 5)); // Reading bytes now should raise an EOF and should not alter layout try { sb11.read_bytes(1); - assert(false && "This should have thrown an exception"); + LuaAssert(L, false && "This should have thrown an exception"); } catch (StreamEof) {} - assert(sb11.layout_is(6, 0, 5)); + LuaAssert(L, sb11.layout_is(6, 0, 5)); // Write some more bytes into the stream, forcing a shift-left write_ztbytes(&sb11, "ghijkl"); - assert(sb11.layout_is(0, 6, 5)); + LuaAssert(L, sb11.layout_is(0, 6, 5)); // Test buffer wrapping a little more. - assert(streq("ghi", sb11.read_bytes(3))); - assert(sb11.layout_is(3, 3, 5)); + LuaAssert(L, streq("ghi", sb11.read_bytes(3))); + LuaAssert(L, sb11.layout_is(3, 3, 5)); write_ztbytes(&sb11, "mnopqr"); - assert(sb11.layout_is(0, 9, 2)); - assert(streq("jklmnopqr", sb11.read_bytes(9))); - assert(sb11.layout_is(9, 0, 2)); + LuaAssert(L, sb11.layout_is(0, 9, 2)); + LuaAssert(L, streq("jklmnopqr", sb11.read_bytes(9))); + LuaAssert(L, sb11.layout_is(9, 0, 2)); // Test 1-byte integer ops. sb11.clear(); for (int i = 0; i < 10; i++) { sb11.write_int8(i); sb11.write_int8(i+100); - assert(sb11.read_int8() == i); - assert(sb11.read_int8() == i+100); + LuaAssert(L, sb11.read_int8() == i); + LuaAssert(L, sb11.read_int8() == i+100); } // Test 2-byte integer ops. for (int i = 0; i < 10; i++) { sb11.write_int16(i); sb11.write_int16(i+10000); - assert(sb11.read_int16() == i); - assert(sb11.read_int16() == i+10000); + LuaAssert(L, sb11.read_int16() == i); + LuaAssert(L, sb11.read_int16() == i+10000); } // Test 4-byte integer ops. for (int i = 0; i < 10; i++) { sb11.write_int32(i); sb11.write_int32(i+1000000); - assert(sb11.read_int32() == i); - assert(sb11.read_int32() == i+1000000); + LuaAssert(L, sb11.read_int32() == i); + LuaAssert(L, sb11.read_int32() == i+1000000); } // Test 8-byte integer ops. for (int i = 0; i < 10; i++) { sb11.write_int64(i + 1000000); - assert(sb11.read_int64() == i + 1000000); + LuaAssert(L, sb11.read_int64() == i + 1000000); } // Check the write count and read count accumulator ability. sb11.clear(); - assert(sb11.total_writes() == 0); - assert(sb11.total_reads() == 0); + LuaAssert(L, sb11.total_writes() == 0); + LuaAssert(L, sb11.total_reads() == 0); for (int i = 0; i < 10; i++) { - assert(sb11.total_writes() == i * 8); + LuaAssert(L, sb11.total_writes() == i * 8); sb11.write_int32(i); sb11.write_int32(i+1000000); - assert(sb11.total_reads() == i * 8); - assert(sb11.read_int32() == i); - assert(sb11.read_int32() == i+1000000); + LuaAssert(L, sb11.total_reads() == i * 8); + LuaAssert(L, sb11.read_int32() == i); + LuaAssert(L, sb11.read_int32() == i+1000000); } // Try clearing the buffer. sb11.clear(); - assert(sb11.layout_is(0, 0, 11)); + LuaAssert(L, sb11.layout_is(0, 0, 11)); // Write a number then overwrite. for (int i = 0; i < 2; i++) { @@ -541,10 +553,10 @@ LuaDefine(unittests_streambuffer, "c") { sb11.write_int16(56); sb11.write_int16(78); sb11.overwrite_int16(wc, 90); - assert(sb11.read_int16() == 12); - assert(sb11.read_int16() == 90); - assert(sb11.read_int16() == 56); - assert(sb11.read_int16() == 78); + LuaAssert(L, sb11.read_int16() == 12); + LuaAssert(L, sb11.read_int16() == 90); + LuaAssert(L, sb11.read_int16() == 56); + LuaAssert(L, sb11.read_int16() == 78); } // Try compact string encoding. @@ -552,10 +564,21 @@ LuaDefine(unittests_streambuffer, "c") { sb11.write_string("abc"); sb11.write_string(""); sb11.write_string("de"); - assert(sb11.layout_is(0, 8, 3)); - assert(sb11.read_string() == "abc"); - assert(sb11.read_string() == ""); - assert(sb11.read_string() == "de"); + LuaAssert(L, sb11.layout_is(0, 8, 3)); + LuaAssert(L, sb11.read_string() == "abc"); + LuaAssert(L, sb11.read_string() == ""); + LuaAssert(L, sb11.read_string() == "de"); + // Make sure that contents_equal is not obviously broken. + StreamBuffer eqsb1, eqsb2; + eqsb1.write_int32(12); + eqsb1.write_int32(34); + eqsb2.write_int32(34); + LuaAssert(L, !eqsb1.contents_equal(&eqsb2)); + eqsb1.read_int32(); + LuaAssert(L, eqsb1.contents_equal(&eqsb2)); + eqsb1.write_int32(34); + LuaAssert(L, !eqsb1.contents_equal(&eqsb2)); + return 0; } diff --git a/luprex/core/cpp/streambuffer.hpp b/luprex/core/cpp/streambuffer.hpp index 454fad4e..47899579 100644 --- a/luprex/core/cpp/streambuffer.hpp +++ b/luprex/core/cpp/streambuffer.hpp @@ -256,6 +256,9 @@ public: // Get the total number of bytes ever written to this buffer. int64_t total_writes() const; + // Amount of data inside the buffer. + int64_t fill() const; + // Discard all data. Reset total read and write counts. // Frees up as much space as possible. void clear(); @@ -353,6 +356,9 @@ public: // Copy the entire contents of this streambuffer into another one. void copy_into(StreamBuffer *sb); + // Compare the contents of this streambuffer to another one. + bool contents_equal(const StreamBuffer *sb) const; + // Calculate a noncryptographic but good hash of what's in the buffer. util::HashValue hash() const; diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index 6f16f9b4..b73b913c 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -180,6 +180,8 @@ void TextGame::check_redirects() { void TextGame::run() { world_.reset(new World(util::WORLD_TYPE_STANDALONE)); + world_->update_source(); + world_->run_unittests(); actor_id_ = world_->create_login_actor(); std::cerr << "Login actor ID: " << actor_id_ << std::endl; console_.clear(); diff --git a/luprex/core/cpp/util.cpp b/luprex/core/cpp/util.cpp index 1e00df04..c8514448 100644 --- a/luprex/core/cpp/util.cpp +++ b/luprex/core/cpp/util.cpp @@ -15,7 +15,20 @@ #endif namespace util { - + + +std::string id_vector_debug_string(const IdVector &idv) { + std::ostringstream oss; + bool first = true; + for (int64_t id : idv) { + if (!first) oss << ","; + oss << id; + first = false; + } + return oss.str(); +} + + IdVector sort_union_id_vectors(const IdVector &v1, const IdVector &v2) { IdVector result(v1.size() + v2.size()); int next = 0; diff --git a/luprex/core/cpp/util.hpp b/luprex/core/cpp/util.hpp index 53696e74..e18dbca3 100644 --- a/luprex/core/cpp/util.hpp +++ b/luprex/core/cpp/util.hpp @@ -23,6 +23,9 @@ using StringVec = std::vector; using HashValue = std::pair; using IdVector = std::vector; +// ID vector debug string. +std::string id_vector_debug_string(const IdVector &idv); + // Unions and sorts two ID vectors. IdVector sort_union_id_vectors(const IdVector &v1, const IdVector &v2); diff --git a/luprex/core/cpp/world.cpp b/luprex/core/cpp/world.cpp index d2a90322..fb3cbf89 100644 --- a/luprex/core/cpp/world.cpp +++ b/luprex/core/cpp/world.cpp @@ -31,7 +31,7 @@ World::World(util::WorldType wt) { world_type_ = wt; // Initialize the ID allocator in master mode. - if (wt == util::WORLD_TYPE_MASTER) { + if (wt == util::WORLD_TYPE_MASTER || wt == util::WORLD_TYPE_STANDALONE) { id_global_pool_.init_master(); } else { id_global_pool_.init_synch(); @@ -50,19 +50,11 @@ World::World(util::WorldType wt) { // Create the tangibles table in the registry. LS.rawset(LuaRegistry, "tangibles", LuaNewTable); - // Initialize the SourceDB + // 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(); - // Do standalone initializations. - if (world_type_ == util::WORLD_TYPE_STANDALONE) { - // Load the lua source from disk then rebuild the environment. - source_db_.update(); - source_db_.rebuild(); - - // Run unit tests. - source_db_.run_unittests(); - } LS.result(); assert (stack_is_clear()); } @@ -161,6 +153,29 @@ void World::tangible_delete(lua_State *L, int64_t id) { 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_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); +} + + util::IdVector World::get_near(int64_t player_id, float radius, bool exclude_nowhere) const { const Tangible *player = tangible_get(player_id); if (player == nullptr) { @@ -177,6 +192,12 @@ util::IdVector World::get_near(int64_t player_id, float radius, bool exclude_now } 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); @@ -446,7 +467,7 @@ void World::run_scheduled_threads(int64_t clk) { void World::serialize(StreamBuffer *sb) { assert(stack_is_clear()); assert(redirects_.empty()); - int64_t wc0 = sb->total_writes(); + // int64_t wc0 = sb->total_writes(); lua_snap_.serialize(sb); id_global_pool_.serialize(sb); thread_sched_.serialize(sb); @@ -455,8 +476,8 @@ void World::serialize(StreamBuffer *sb) { 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; + // int64_t wc1 = sb->total_writes(); + // std::cerr << "World serialized to " << wc1-wc0 << " bytes." << std::endl; assert(stack_is_clear()); } @@ -506,13 +527,12 @@ void World::rollback() { void World::difference_transmit(int64_t actor_id, const World *master, StreamBuffer *sb) { StreamBuffer tsb; - // Get the actor in both models. + // 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(s_actor != nullptr); assert(m_actor != nullptr); - // Pass one: update the actor essentials. + // Pass one: update the actor essentials. Create actor if doesn't exist. assert(tsb.at_eof()); diff_actor_essentials(m_actor, s_actor, &tsb); tsb.copy_into(sb); @@ -523,7 +543,7 @@ void World::difference_transmit(int64_t actor_id, const World *master, StreamBuf util::IdVector visible = util::sort_union_id_vectors( master->get_near(actor_id, 100.0, true), this->get_near(actor_id, 100.0, true)); - TanVector m_visible = tangible_get_all(visible); + TanVector m_visible = master->tangible_get_all(visible); TanVector s_visible = tangible_get_all(visible); assert(m_visible.size() == s_visible.size()); @@ -549,17 +569,33 @@ void World::difference_transmit(int64_t actor_id, const World *master, StreamBuf assert(tsb.at_eof()); } +void World::apply_differences(StreamBuffer *sb) { + patch_actor_essentials(sb); + patch_visible_animations(sb); +} + void World::diff_actor_essentials(const Tangible *m_actor, const Tangible *s_actor, StreamBuffer *sb) { sb->write_int64(m_actor->id()); - s_actor->id_player_pool_.diff(m_actor->id_player_pool_, sb); - s_actor->anim_queue_.diff(m_actor->anim_queue_, sb); + if (s_actor == nullptr) { + m_actor->id_player_pool_.serialize(sb); + m_actor->anim_queue_.serialize(sb); + } else { + s_actor->id_player_pool_.diff(m_actor->id_player_pool_, sb); + s_actor->anim_queue_.diff(m_actor->anim_queue_, sb); + } } void World::patch_actor_essentials(StreamBuffer *sb) { int64_t actor_id = sb->read_int64(); Tangible *s_actor = tangible_get(actor_id); - s_actor->id_player_pool_.patch(sb); - s_actor->anim_queue_.patch(sb); + 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(); } @@ -575,7 +611,7 @@ void World::diff_visible_animations(const TanVector &mvis, const TanVector &svis if (st == nullptr) { count += 1; sb->write_int64(mt->id()); - mt->anim_queue_.serialize_size_and_steps(sb); + mt->anim_queue_.serialize(sb); } } sb->overwrite_int32(count_pos, count); @@ -620,7 +656,7 @@ void World::patch_visible_animations(StreamBuffer *sb) { for (int i = 0; i < count; i++) { int64_t id = sb->read_int64(); Tangible *t = tangible_make(state(), id, false); - t->anim_queue_.deserialize_size_and_steps(sb); + t->anim_queue_.deserialize(sb); t->update_plane_item(); } @@ -791,3 +827,30 @@ LuaDefine(world_getregistry, "f") { lua_pushvalue(L, LUA_REGISTRYINDEX); return 1; } + +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_world, "c") { + std::unique_ptr m, ss, cs; + StreamBuffer sb; + + m.reset(new World(util::WORLD_TYPE_MASTER)); + ss.reset(new World(util::WORLD_TYPE_S_SYNC)); + cs.reset(new World(util::WORLD_TYPE_C_SYNC)); + + m->tangible_make(0, 123, false); + m->tangible_make(0, 345, false); + LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123,345"); + + ss->difference_transmit(123, m.get(), &sb); + cs->apply_differences(&sb); + LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123,345"); + LuaAssert(L, worlds_identical(ss, cs)); + + return 0; +} \ No newline at end of file diff --git a/luprex/core/cpp/world.hpp b/luprex/core/cpp/world.hpp index 4e51df90..7ec2b77f 100644 --- a/luprex/core/cpp/world.hpp +++ b/luprex/core/cpp/world.hpp @@ -84,9 +84,7 @@ public: // Constructor. // // The constructor also calls 'lua_open' to create a new - // lua interpreter for this world model. The lua interpreter - // is stored in world::lua_state_. A significant amount of - // initial setup is done by this constructor. + // lua interpreter for this world model. // World(util::WorldType wt); @@ -136,7 +134,7 @@ public: // Delete the specified tangible. // void tangible_delete(lua_State *L, int64_t id); - + // Create a login actor. // // Creates a tangible of class 'login' and returns its ID. @@ -160,6 +158,14 @@ public: // void invoke(const Invocation &inv); + // Update the source database from disk. + // + void update_source() { source_db_.update(); source_db_.rebuild(); } + + // Run all unit tests. + // + void run_unittests() { source_db_.run_unittests(); } + // fetch_global_pointer // // Given a lua state, fetch the world model associated with @@ -190,8 +196,26 @@ public: // Note that difference_transmit applies the differences to the server // synchronous model, so this is only used by the client synchronous model. // - void apply_differences(int64_t actor, StreamBuffer *sb); + void apply_differences(StreamBuffer *sb); +public: + //////////////////////////////////////////////////////////////////////////// + // + // TESTING SUPPORT + // + // The following functions are not designed to be useful for production + // code, they're designed to be helpful for unit testing. + // + //////////////////////////////////////////////////////////////////////////// + + // Add a 'walkto' animation to the specified tangible. + // + void tangible_walkto(int64_t id, int64_t animid, float x, float y); + + // Get a list of all existing tangibles as a comma-separated string. + // + std::string tangible_ids_debug_string() const; + private: // Run any threads which according to the scheduler queue are ready. //