diff --git a/luprex/core/cpp/animqueue.cpp b/luprex/core/cpp/animqueue.cpp index b1f57cae..51890dcf 100644 --- a/luprex/core/cpp/animqueue.cpp +++ b/luprex/core/cpp/animqueue.cpp @@ -6,15 +6,21 @@ AnimStep::AnimStep() {} AnimStep::~AnimStep() {} AnimQueue::AnimQueue() { + id_ = 0; size_limit_ = 10; // Default size limit. + clear_steps(); +} + +void AnimQueue::clear_steps() { + steps_.clear(); steps_.emplace_back(); AnimStep &init = steps_.back(); init.id_ = 0; + init.bits_ = AnimStep::HAS_EVERYTHING; init.facing_ = 0; init.xyz_ = util::XYZ(0,0,0); init.graphic_ = "nothing"; init.plane_ = "nowhere"; - init.bits_ = AnimStep::HAS_EVERYTHING; } void AnimQueue::set_size_limit(int n) { @@ -163,6 +169,94 @@ void AnimQueue::set_plane(const std::string &p) { last.plane_ = p; } +bool AnimQueue::valid() { + // Animqueue must have at least one step. + if (steps_.empty()) { + return false; + } + // First action should be blank. + if (steps_[0].action_ != "") { + return false; + } + // First step should have all bits. + if (steps_[0].bits_ != AnimStep::HAS_EVERYTHING) { + return false; + } + // Any unset bit should correspond to a value copied from the previous step. + for (size_t i = 1; i < steps_.size(); i++) { + const AnimStep &prev = steps_[i - 1]; + const AnimStep &curr = steps_[i]; + if (!curr.has_facing() && (curr.facing_ != prev.facing_)) { + return false; + } + if (!curr.has_xyz() && (curr.xyz_ != prev.xyz_)) { + return false; + } + if (!curr.has_graphic() && (curr.graphic_ != prev.graphic_)) { + return false; + } + if (!curr.has_plane() && (curr.plane_ != prev.plane_)) { + return false; + } + } + return true; +} + +void AnimQueue::serialize(StreamBuffer *sb) { + assert(valid()); // can't serialize an invalid animqueue. + sb->write_int64(id_); + sb->write_int32(size_limit_); + sb->write_size(steps_.size()); + for (const AnimStep &step : steps_) { + sb->write_int64(step.id_); + sb->write_int16(step.bits_); + sb->write_string(step.action_); + if (step.has_facing()) { + sb->write_float(step.facing_); + } + if (step.has_xyz()) { + sb->write_float(step.xyz_.x); + sb->write_float(step.xyz_.y); + sb->write_float(step.xyz_.z); + } + if (step.has_graphic()) { + sb->write_string(step.graphic_); + } + if (step.has_plane()) { + sb->write_string(step.plane_); + } + } +} + +void AnimQueue::deserialize(StreamBuffer *sb) { + id_ = sb->read_int64(); + size_limit_ = sb->read_int32(); + size_t nsteps = sb->read_size(); + steps_.clear(); + steps_.resize(nsteps); + for (size_t i = 0; i < nsteps; i++) { + AnimStep &step = steps_[i]; + if (i > 0) step = steps_[i - 1]; + step.id_ = sb->read_int64(); + step.bits_ = sb->read_int16(); + step.action_ = sb->read_string(); + if (step.has_facing()) { + step.facing_ = sb->read_float(); + } + if (step.has_xyz()) { + step.xyz_.x = sb->read_float(); + step.xyz_.y = sb->read_float(); + step.xyz_.z = sb->read_float(); + } + if (step.has_graphic()) { + step.graphic_ = sb->read_string(); + } + if (step.has_plane()) { + step.plane_ = sb->read_string(); + } + } +} + const std::string &AnimQueue::get_graphic() const { const AnimStep &last = steps_.back(); return last.graphic_; @@ -181,8 +275,11 @@ const util::XYZ &AnimQueue::get_xyz() const { LuaDefine(unittests_animqueue, "c") { // Check initial state. AnimQueue aq; + StreamBuffer sb; + AnimQueue aqds; aq.set_size_limit(3); + LuaAssert(L, aq.valid()); LuaAssert(L, aq.size() == 1); const AnimStep *st = &aq.nth(0); LuaAssert(L, st->id() == 0); @@ -195,6 +292,7 @@ LuaDefine(unittests_animqueue, "c") { // Add a step. aq.add(12345, "walk"); + LuaAssert(L, aq.valid()); LuaAssert(L, aq.size() == 2); st = &aq.nth(1); LuaAssert(L, st->id() == 12345); @@ -218,6 +316,7 @@ LuaDefine(unittests_animqueue, "c") { aq.set_graphic("something"); LuaAssert(L, st->graphic() == "something"); LuaAssert(L, st->bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ | AnimStep::HAS_PLANE | AnimStep::HAS_GRAPHIC)); + LuaAssert(L, aq.valid()); // Exceed the length limit, dropping first element. aq.add(12346, "walk"); @@ -228,5 +327,56 @@ LuaDefine(unittests_animqueue, "c") { LuaAssert(L, aq.nth(0).bits() == AnimStep::HAS_EVERYTHING); LuaAssert(L, aq.nth(1).id() == 12346); LuaAssert(L, aq.nth(2).id() == 12347); + LuaAssert(L, aq.valid()); + + // Test serialization and deserialization. + aq.set_id(123); + aq.set_size_limit(5); + aq.clear_steps(); + aq.add(12345, "walk"); + aq.set_xyz(util::XYZ(3,4,5)); + aq.add(12346, "setgraphic"); + aq.set_graphic("banana"); + aq.add(12347, "setfacing"); + aq.set_facing(301.0); + aq.serialize(&sb); + aqds.deserialize(&sb); + + LuaAssert(L, aqds.get_id() == 123); + LuaAssert(L, aqds.size_limit() == 5); + LuaAssert(L, aqds.size() == 4); + + LuaAssert(L, aqds.nth(0).id() == 0); + LuaAssert(L, aqds.nth(0).bits() == AnimStep::HAS_EVERYTHING); + LuaAssert(L, aqds.nth(0).action() == ""); + LuaAssert(L, aqds.nth(0).facing() == 0.0); + LuaAssert(L, aqds.nth(0).xyz() == util::XYZ(0,0,0)); + LuaAssert(L, aqds.nth(0).graphic() == "nothing"); + LuaAssert(L, aqds.nth(0).plane() == "nowhere"); + + LuaAssert(L, aqds.nth(1).id() == 12345); + LuaAssert(L, aqds.nth(1).bits() == AnimStep::HAS_XYZ); + LuaAssert(L, aqds.nth(1).action() == "walk"); + LuaAssert(L, aqds.nth(1).facing() == 0.0); + LuaAssert(L, aqds.nth(1).xyz() == util::XYZ(3,4,5)); + LuaAssert(L, aqds.nth(1).graphic() == "nothing"); + LuaAssert(L, aqds.nth(1).plane() == "nowhere"); + + LuaAssert(L, aqds.nth(2).id() == 12346); + LuaAssert(L, aqds.nth(2).bits() == AnimStep::HAS_GRAPHIC); + LuaAssert(L, aqds.nth(2).action() == "setgraphic"); + LuaAssert(L, aqds.nth(2).facing() == 0.0); + LuaAssert(L, aqds.nth(2).xyz() == util::XYZ(3,4,5)); + LuaAssert(L, aqds.nth(2).graphic() == "banana"); + LuaAssert(L, aqds.nth(2).plane() == "nowhere"); + + LuaAssert(L, aqds.nth(3).id() == 12347); + LuaAssert(L, aqds.nth(3).bits() == AnimStep::HAS_FACING); + LuaAssert(L, aqds.nth(3).action() == "setfacing"); + LuaAssert(L, aqds.nth(3).facing() == 301.0); + LuaAssert(L, aqds.nth(3).xyz() == util::XYZ(3,4,5)); + LuaAssert(L, aqds.nth(3).graphic() == "banana"); + LuaAssert(L, aqds.nth(3).plane() == "nowhere"); + return 0; } diff --git a/luprex/core/cpp/animqueue.hpp b/luprex/core/cpp/animqueue.hpp index c2c559dc..bf6a7049 100644 --- a/luprex/core/cpp/animqueue.hpp +++ b/luprex/core/cpp/animqueue.hpp @@ -29,6 +29,7 @@ #include #include #include +#include "streambuffer.hpp" #include "util.hpp" @@ -45,8 +46,8 @@ public: private: int64_t id_; + int16_t bits_; std::string action_; - int bits_; float facing_; util::XYZ xyz_; @@ -58,8 +59,8 @@ public: ~AnimStep(); int64_t id() const { return id_; } - const std::string &action() const { return action_; } int bits() const { return bits_; } + const std::string &action() const { return action_; } double facing() const { return facing_; } util::XYZ xyz() const { return xyz_; } @@ -75,15 +76,21 @@ public: class AnimQueue { private: int64_t id_; - int size_limit_; + int32_t size_limit_; std::deque steps_; + public: AnimQueue(); + int64_t get_id() const { return id_; } void set_id(int64_t id) { id_ = id; } const AnimStep &nth(int n) const { return steps_[n]; } - int size() const { return steps_.size(); } + size_t size() const { return steps_.size(); } + int32_t size_limit() const { return size_limit_; } + // Clear the steps. Doesn't affect size_limit or id. + void clear_steps(); + // Mutators to create new steps from C++ // void add(int64_t id, const std::string &action); @@ -92,6 +99,10 @@ public: void set_graphic(const std::string &g); void set_plane(const std::string &p); + // Serialize or deserialize to a StreamBuffer + void serialize(StreamBuffer *sb); + void deserialize(StreamBuffer *sb); + // Mutator to create new steps from lua. // // Lua stack must contain a table, which may contain: @@ -113,8 +124,11 @@ public: const std::string &get_plane() const; const util::XYZ &get_xyz() const; - // Functions for unit testing. - void set_size_limit(int n); + // (For testing): change the size limit. + void set_size_limit(int32_t n); + + // (For testing): make sure the invariants are preserved. + bool valid(); }; #endif // ANIMQUEUE_HPP diff --git a/luprex/core/cpp/idalloc.cpp b/luprex/core/cpp/idalloc.cpp index 39fca25e..314f3284 100644 --- a/luprex/core/cpp/idalloc.cpp +++ b/luprex/core/cpp/idalloc.cpp @@ -84,24 +84,24 @@ int64_t IdGlobalPool::alloc_id_for_thread(lua_State *L) { } void IdGlobalPool::serialize(StreamBuffer *sb) { - sb->write_int64(salvaged_.size()); + sb->write_int64(next_batch_); + sb->write_int64(next_id_); + sb->write_int32(queue_fill_); + sb->write_size(salvaged_.size()); for (int64_t batch : salvaged_) { sb->write_int64(batch); } - sb->write_int64(next_batch_); - sb->write_int64(next_id_); - sb->write_int64(queue_fill_); } void IdGlobalPool::deserialize(StreamBuffer *sb) { - int64_t salvaged_size = sb->read_int64(); - salvaged_.resize(salvaged_size); - for (int i=0; i < salvaged_size; i++) { - salvaged_[i] = sb->read_int64(); - } next_batch_ = sb->read_int64(); next_id_ = sb->read_int64(); - queue_fill_ = sb->read_int64(); + queue_fill_ = sb->read_int32(); + size_t salvaged_size = sb->read_size(); + salvaged_.resize(salvaged_size); + for (int i=0; i < int(salvaged_size); i++) { + salvaged_[i] = sb->read_int64(); + } } IdPlayerPool::IdPlayerPool(IdGlobalPool *gp) { @@ -155,16 +155,16 @@ void IdPlayerPool::prepare_thread(lua_State *L) { } void IdPlayerPool::serialize(StreamBuffer *sb) { - sb->write_int64(ranges_.size()); + sb->write_size(ranges_.size()); for (int64_t batch : ranges_) { sb->write_int64(batch); } } void IdPlayerPool::deserialize(StreamBuffer *sb) { - int64_t ranges_size = sb->read_int64(); + size_t ranges_size = sb->read_size(); ranges_.resize(ranges_size); - for (int i=0; i < ranges_size; i++) { + for (int i=0; i < int(ranges_size); i++) { ranges_[i] = sb->read_int64(); } } @@ -172,6 +172,9 @@ void IdPlayerPool::deserialize(StreamBuffer *sb) { LuaDefine(unittests_idalloc, "c") { IdGlobalPool gp; IdPlayerPool pp(&gp); + IdGlobalPool gpds; + IdPlayerPool ppds(&gpds); + StreamBuffer sb; // Synchronous pools produce IDs starting at 0x001E000000000000 gp.init_synch(3); @@ -277,5 +280,35 @@ LuaDefine(unittests_idalloc, "c") { LuaAssert(L, gp.alloc_id_for_thread(L) == 0x0010000000000000); LuaAssert(L, lua_getnextid(L) == 0); + // Serialize and deserialize a global pool. + gp.init_master(3); + gpds.init_master(10); + LuaAssert(L, gp.get_one() == 0x0010000000000000); + LuaAssert(L, gp.get_batch() == nthbatch(0)); + gp.salvage(nthbatch(182)); + gp.salvage(nthbatch(183)); + gp.serialize(&sb); + gpds.deserialize(&sb); + LuaAssert(L, gpds.queue_fill() == 3); + LuaAssert(L, gpds.get_one() == 0x0010000000000001); + LuaAssert(L, gpds.get_batch() == nthbatch(183)); + LuaAssert(L, gpds.get_batch() == nthbatch(182)); + LuaAssert(L, gpds.get_batch() == nthbatch(1)); + + // Serialize and deserialize a player pool. + gp.init_master(3); + gpds.init_synch(5); + LuaAssert(L, gp.get_batch() == nthbatch(0)); + pp.purge(); + pp.refill(); + LuaAssert(L, pp.size() == 3); + ppds.purge(); + pp.serialize(&sb); + ppds.deserialize(&sb); + LuaAssert(L, ppds.size() == 3); + LuaAssert(L, ppds.get_batch() == nthbatch(1)); + LuaAssert(L, ppds.get_batch() == nthbatch(2)); + LuaAssert(L, ppds.get_batch() == nthbatch(3)); + return 0; } diff --git a/luprex/core/cpp/idalloc.hpp b/luprex/core/cpp/idalloc.hpp index 55225d27..30b95c85 100644 --- a/luprex/core/cpp/idalloc.hpp +++ b/luprex/core/cpp/idalloc.hpp @@ -76,7 +76,7 @@ private: std::vector salvaged_; int64_t next_batch_; int64_t next_id_; - int queue_fill_; + int32_t queue_fill_; friend int unittests_idalloc(lua_State *L); public: @@ -120,10 +120,8 @@ public: // batch if possible. If not, fetches one ID from the global pool. int64_t alloc_id_for_thread(lua_State *L); - // Serialize to a streambuffer. + // Serialize to or deserialize from a streambuffer. void serialize(StreamBuffer *sb); - - // Deserialize from a streambuffer. void deserialize(StreamBuffer *sb); }; @@ -163,10 +161,9 @@ public: // Return the size of the queue. int size() { return ranges_.size(); } - // Serialize to a streambuffer. + // Serialize to or deserialize from a streambuffer. + // Caution: the pointer to the global pool is not serialized or deserialized. void serialize(StreamBuffer *sb); - - // Deserialize from a streambuffer. void deserialize(StreamBuffer *sb); }; diff --git a/luprex/core/cpp/streambuffer.cpp b/luprex/core/cpp/streambuffer.cpp index 677aac64..eb69cbad 100644 --- a/luprex/core/cpp/streambuffer.cpp +++ b/luprex/core/cpp/streambuffer.cpp @@ -139,6 +139,18 @@ void StreamBuffer::write_int64(int64_t v) { write_cursor_ += 8; } +void StreamBuffer::write_float(float f) { + make_space(4); + memcpy(write_cursor_, &f, 4); + write_cursor_ += 4; +} + +void StreamBuffer::write_double(double d) { + make_space(8); + memcpy(write_cursor_, &d, 8); + write_cursor_ += 8; +} + void StreamBuffer::write_bytes(const char *s, int64_t len) { make_space(len); memcpy(write_cursor_, s, len); @@ -212,6 +224,34 @@ int64_t StreamBuffer::read_int64() { return v; } +float StreamBuffer::read_float() { + check_available(4); + float f; + memcpy(&f, read_cursor_, 4); + read_cursor_ += 4; + return f; +} + +double StreamBuffer::read_double() { + check_available(8); + double d; + memcpy(&d, read_cursor_, 8); + read_cursor_ += 8; + return d; +} + +size_t StreamBuffer::read_size() { + return read_size_limit(0xFFFFFFF); +} + +size_t StreamBuffer::read_size_limit(size_t limit) { + int64_t value = read_int64(); + if ((value < 0)||(value > int64_t(limit))) { + throw StreamCorruption(); + } + return size_t(value); +} + const char *StreamBuffer::read_bytes(int64_t bytes) { check_available(bytes); char *data = read_cursor_; @@ -219,7 +259,11 @@ const char *StreamBuffer::read_bytes(int64_t bytes) { return data; } -std::string StreamBuffer::read_string(int64_t max_allowed) { +std::string StreamBuffer::read_string() { + return read_string_limit(0xFFFFFFF); +} + +std::string StreamBuffer::read_string_limit(int64_t max_allowed) { int64_t len = read_uint8(); if (len == 255) { len = read_int64(); @@ -388,9 +432,9 @@ LuaDefine(unittests_streambuffer, "c") { sb11.write_string(""); sb11.write_string("de"); assert(sb11.layout_is(0, 8, 3)); - assert(sb11.read_string(1000) == "abc"); - assert(sb11.read_string(1000) == ""); - assert(sb11.read_string(1000) == "de"); + assert(sb11.read_string() == "abc"); + assert(sb11.read_string() == ""); + assert(sb11.read_string() == "de"); return 0; } diff --git a/luprex/core/cpp/streambuffer.hpp b/luprex/core/cpp/streambuffer.hpp index 616a50f5..f24a2173 100644 --- a/luprex/core/cpp/streambuffer.hpp +++ b/luprex/core/cpp/streambuffer.hpp @@ -308,18 +308,22 @@ public: char *alloc_space(int64_t bytes); void wrote_space(int64_t bytes); - // Write values into the buffer. + // Write numbers into the buffer. void write_int8(int8_t v); void write_int16(int16_t v); void write_int32(int32_t v); void write_int64(int64_t v); + void write_float(float f); + void write_double(double d); void write_uint8(uint8_t v) { write_int8(v); } void write_uint16(uint16_t v) { write_int16(v); } void write_uint32(uint32_t v) { write_int32(v); } void write_uint64(uint64_t v) { write_int64(v); } - - void write_bytes(const char *bytes, int64_t len); + void write_size(size_t sz) { write_int64(sz); } + + // Write strings or blocks of bytes into the buffer. void write_string(const std::string &s); + void write_bytes(const char *bytes, int64_t len); void write_ztbytes(const char *bytes); // Overwrite values previously written to the buffer. @@ -332,19 +336,26 @@ public: void overwrite_uint32(int64_t write_count_after, uint32_t v) { overwrite_int32(write_count_after, v); } void overwrite_uint64(int64_t write_count_after, uint64_t v) { overwrite_int64(write_count_after, v); } - // Read integers from the buffer. May throw StreamEof. + // Read numbers from the buffer. May throw StreamEof. int8_t read_int8(); int16_t read_int16(); int32_t read_int32(); int64_t read_int64(); + float read_float(); + double read_double(); uint8_t read_uint8() { return read_int8(); } uint16_t read_uint16() { return read_int16(); } uint32_t read_uint32() { return read_int32(); } uint64_t read_uint64() { return read_int64(); } - // Read a string of no more than the specified length. May throw StreamEof - // or StreamCorruption. - std::string read_string(int64_t max_allowed); + // May throw StreamEof or StreamCorruption. + size_t read_size(); + size_t read_size_limit(size_t limit); + + // Read a string of no more than the specified length. + // May throw StreamEof or StreamCorruption. + std::string read_string(); + std::string read_string_limit(int64_t max_allowed); // Read a block of bytes. May throw StreamEof. const char *read_bytes(int64_t bytes); diff --git a/luprex/core/cpp/util.hpp b/luprex/core/cpp/util.hpp index 347dc02e..5867024a 100644 --- a/luprex/core/cpp/util.hpp +++ b/luprex/core/cpp/util.hpp @@ -52,7 +52,8 @@ struct XYZ { float x, y, z; XYZ() { x=0; y=0; z=0; } XYZ(float ix, float iy, float iz) { x=ix; y=iy; z=iz; } - bool operator ==(const XYZ &o) { return x==o.x && y == o.y && z==o.z; } + bool operator ==(const XYZ &o) const { return x==o.x && y == o.y && z==o.z; } + bool operator !=(const XYZ &o) const { return x!=o.x || y != o.y || z!=o.z; } }; std::ostream & operator << (std::ostream &out, const XYZ &xyz);