From 1904f03dad4dcbf8649bdf0a5e3d410ab69633d7 Mon Sep 17 00:00:00 2001 From: Josh Yelon Date: Tue, 13 Jul 2021 15:18:37 -0400 Subject: [PATCH] diff xmit of animation queues. Also, StreamBuffer::unwrite_to --- luprex/core/cpp/animqueue.cpp | 272 +++++++++++++++++++++++++------ luprex/core/cpp/animqueue.hpp | 30 +++- luprex/core/cpp/streambuffer.cpp | 6 + luprex/core/cpp/streambuffer.hpp | 3 + 4 files changed, 260 insertions(+), 51 deletions(-) diff --git a/luprex/core/cpp/animqueue.cpp b/luprex/core/cpp/animqueue.cpp index b1f3d6ca..cb910a35 100644 --- a/luprex/core/cpp/animqueue.cpp +++ b/luprex/core/cpp/animqueue.cpp @@ -1,4 +1,5 @@ #include +#include #include "luastack.hpp" #include "animqueue.hpp" @@ -8,6 +9,8 @@ AnimStep::AnimStep() { AnimStep::~AnimStep() {} + + void AnimStep::clear() { id_ = 0; bits_ = 0; @@ -57,11 +60,88 @@ void AnimStep::set_plane(const std::string &p) { plane_ = p; } -AnimQueue::AnimQueue() { - size_limit_ = 10; // Default size limit. - clear_steps(); + +bool AnimStep::exactly_equal(const AnimStep &other) const { + if (id_ != other.id_) return false; + if (bits_ != other.bits_) return false; + if (action_ != other.action_) return false; + if (facing_ != other.facing_) return false; + if (xyz_ != other.xyz_) return false; + if (graphic_ != other.graphic_) return false; + if (plane_ != other.plane_) return false; + return true; } +bool AnimStep::logically_equal(const AnimStep &other) const { + if (id_ != other.id_) return false; + if (bits_ != other.bits_) return false; + if (action_ != other.action_) return false; + if (has_facing() && (facing_ != other.facing_)) return false; + if (has_x() && (xyz_.x != other.xyz_.x)) return false; + if (has_y() && (xyz_.y != other.xyz_.y)) return false; + if (has_z() && (xyz_.z != other.xyz_.z)) return false; + if (has_graphic() && (graphic_ != other.graphic_)) return false; + if (has_plane() && (plane_ != other.plane_)) return false; + return true; +} + +bool AnimStep::state_equal(const AnimStep &other) const { + if (facing_ != other.facing_) return false; + if (xyz_ != other.xyz_) return false; + if (graphic_ != other.graphic_) return false; + if (plane_ != other.plane_) return false; + return true; +} + +void AnimStep::write_into(StreamBuffer *sb) const { + sb->write_int64(id_); + sb->write_int16(bits_); + sb->write_string(action_); + if (has_facing()) { + sb->write_float(facing_); + } + if (has_x()) { + sb->write_float(xyz_.x); + } + if (has_y()) { + sb->write_float(xyz_.y); + } + if (has_z()) { + sb->write_float(xyz_.z); + } + if (has_graphic()) { + sb->write_string(graphic_); + } + if (has_plane()) { + sb->write_string(plane_); + } +} + +void AnimStep::read_from(StreamBuffer *sb) { + id_ = sb->read_int64(); + bits_ = sb->read_int16(); + action_ = sb->read_string(); + if (has_facing()) { + facing_ = sb->read_float(); + } + if (has_x()) { + xyz_.x = sb->read_float(); + } + if (has_y()) { + xyz_.y = sb->read_float(); + } + if (has_z()) { + xyz_.z = sb->read_float(); + } + if (has_graphic()) { + graphic_ = sb->read_string(); + } + if (has_plane()) { + plane_ = sb->read_string(); + } +} + + void AnimStep::from_lua_store_string(lua_State *L, int idx, std::string *target, int16_t bits, const char *name) { if ((bits_ & bits)||(*target != "")) { luaL_error(L, "specified %s twice", name); @@ -123,6 +203,12 @@ void AnimStep::from_lua(lua_State *L, int idx, const AnimStep &qback) { } } +void AnimStep::keep_state_only() { + bits_ = HAS_EVERYTHING; + id_ = 0; + action_ = ""; +} + void AnimStep::echo(const AnimStep &prev) { if (!has_facing()) { facing_ = prev.facing_; @@ -166,6 +252,23 @@ bool AnimStep::echoes(const AnimStep &prev) const { return true; } +AnimQueue::AnimQueue() { + size_limit_ = 10; // Default size limit. + clear_steps(); +} + +bool AnimQueue::exactly_equal(const AnimQueue &other) const { + if (steps_.size() != other.steps_.size()) { + return false; + } + for (int i = 0; i < int(steps_.size()); i++) { + if (!steps_[i].exactly_equal(other.steps_[i])) { + return false; + } + } + return true; +} + void AnimQueue::clear_steps() { steps_.clear(); steps_.emplace_back(); @@ -186,10 +289,7 @@ void AnimQueue::keep_only(int n) { while (int(steps_.size()) > n) { steps_.pop_front(); } - AnimStep &init = steps_.front(); - init.id_ = 0; - init.action_ = ""; - init.bits_ = AnimStep::HAS_EVERYTHING; + steps_.front().keep_state_only(); } void AnimQueue::add(int64_t id, const AnimStep &step) { @@ -200,7 +300,7 @@ void AnimQueue::add(int64_t id, const AnimStep &step) { keep_only(size_limit_); } -bool AnimQueue::valid() { +bool AnimQueue::valid() const { // Animqueue must have at least one step. if (steps_.empty()) { return false; @@ -227,27 +327,7 @@ void AnimQueue::serialize(StreamBuffer *sb) { 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_x()) { - sb->write_float(step.xyz_.x); - } - if (step.has_y()) { - sb->write_float(step.xyz_.y); - } - if (step.has_z()) { - sb->write_float(step.xyz_.z); - } - if (step.has_graphic()) { - sb->write_string(step.graphic_); - } - if (step.has_plane()) { - sb->write_string(step.plane_); - } + step.write_into(sb); } } @@ -257,31 +337,86 @@ void AnimQueue::deserialize(StreamBuffer *sb) { 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_x()) { - step.xyz_.x = sb->read_float(); - } - if (step.has_y()) { - step.xyz_.y = sb->read_float(); - } - if (step.has_z()) { - step.xyz_.z = sb->read_float(); - } - if (step.has_graphic()) { - step.graphic_ = sb->read_string(); - } - if (step.has_plane()) { - step.plane_ = sb->read_string(); + step.read_from(sb); + if (i > 0) step.echo(steps_[i - 1]); + } +} + +void AnimQueue::make_patch(const AnimQueue &auth, StreamBuffer *sb) const { + // Sanity check. + assert(valid()); + assert(auth.valid()); + + // Special case: if we're already a perfect match, output 0 bytes. + if (exactly_equal(auth)) { + return; + } + + // Write the first element. + const AnimStep &first = auth.steps_[0]; + int match = 0; + while ((match < int(steps_.size())) && (!steps_[match].state_equal(first))) { + match++; + } + if (match == int(steps_.size())) { + sb->write_uint8(255); + first.write_into(sb); + } else { + sb->write_uint8(match); + } + + // Index the remaining elements by id. + std::map index; + for (int i = 1; i < int(steps_.size()); i++) { + index[steps_[i].id_] = i; + } + + // Write the remaining elements. + for (int i = 1; i < int(auth.steps_.size()); i++) { + const AnimStep &step = auth.steps_[i]; + auto iter = index.find(step.id()); + if (iter == index.end()) { + sb->write_uint8(255); + step.write_into(sb); + } else { + const AnimStep &local = steps_[iter->second]; + if (local.exactly_equal(step)) { + sb->write_uint8(iter->second); + } else { + sb->write_uint8(255); + step.write_into(sb); + } } } } +void AnimQueue::apply_patch(StreamBuffer *sb) { + // Special case: if there are zero bytes, no patch is needed. + if (sb->at_eof()) { + return; + } + + // Decode the diff, stop at eof. + std::deque old = std::move(steps_); + steps_.clear(); + while (!sb->at_eof()) { + uint8_t index = sb->read_uint8(); + if (index < 255) { + assert(index < old.size()); + steps_.push_back(old[index]); + } else { + AnimStep step; + step.read_from(sb); + steps_.push_back(step); + } + int size = steps_.size(); + if (size > 1) { + steps_[size-1].echo(steps_[size-2]); + } + } + steps_.front().keep_state_only(); +} + const AnimStep &AnimQueue::back() const { return steps_.back(); } @@ -411,5 +546,42 @@ LuaDefine(unittests_animqueue, "c") { LuaAssert(L, aqds.nth(3).graphic() == "banana"); LuaAssert(L, aqds.nth(3).plane() == ""); + // Test difference transmission + // Start with an anim queue with an initial state and a single action. + aq.set_size_limit(10); + aqds.set_size_limit(10); + aq.clear_steps(); + aqds.clear_steps(); + + stp.clear(); + stp.set_action("walk"); + stp.set_xyz(util::XYZ(3,4,5)); + aq.add(12345, stp); + + sb.clear(); + aqds.make_patch(aq, &sb); + aqds.apply_patch(&sb); + LuaAssert(L, aqds.exactly_equal(aq)); + + // Add another action. + stp.clear(); + stp.set_action("fnord"); + stp.set_facing(123); + stp.set_plane("where"); + aq.add(232, stp); + + sb.clear(); + aqds.make_patch(aq, &sb); + aqds.apply_patch(&sb); + LuaAssert(L, aqds.exactly_equal(aq)); + + // Discard all but the last action. + aq.keep_only(1); + + sb.clear(); + aqds.make_patch(aq, &sb); + aqds.apply_patch(&sb); + LuaAssert(L, aqds.exactly_equal(aq)); + return 0; } diff --git a/luprex/core/cpp/animqueue.hpp b/luprex/core/cpp/animqueue.hpp index 469358ff..721005cd 100644 --- a/luprex/core/cpp/animqueue.hpp +++ b/luprex/core/cpp/animqueue.hpp @@ -95,6 +95,20 @@ public: void clear(); + // ExactlyEqual compares all fields. + bool exactly_equal(const AnimStep &other) const; + + // LogicallyEqual only compares fields whose HAS_XXX bits are set. + bool logically_equal(const AnimStep &other) const; + + // StateEqual is true if the plane, graphic, facing, and xyz match. + bool state_equal(const AnimStep &other) const; + + // read/write the step using a stream buffer. + // + void write_into(StreamBuffer *sb) const; + void read_from(StreamBuffer *sb); + // Create an AnimStep from a lua table. // // Lua stack must contain a table, which may contain: @@ -111,6 +125,9 @@ public: // void from_lua(lua_State *L, int idx, const AnimStep &qback); + // Make this step into a first-step of an anim queue. + void keep_state_only(); + // For any values that are unchanged in this step, // echo the values of the previous step. void echo(const AnimStep &prev); @@ -127,6 +144,8 @@ private: public: AnimQueue(); + bool exactly_equal(const AnimQueue &aq) const; + const AnimStep &nth(int n) const { return steps_[n]; } size_t size() const { return steps_.size(); } int32_t size_limit() const { return size_limit_; } @@ -147,6 +166,15 @@ public: void serialize(StreamBuffer *sb); void deserialize(StreamBuffer *sb); + // Difference transmission + // + // make_patch will emit zero bytes when there are no differences. + // make_patch does not put a 'length' field for the patch as a whole. + // apply_patch must receive a patch followed by eof. + // + void make_patch(const AnimQueue &auth, StreamBuffer *sb) const; + void apply_patch(StreamBuffer *sb); + // Get the final resting place after all animations are complete. const AnimStep &back() const; @@ -154,7 +182,7 @@ public: void set_size_limit(int32_t n); // (For testing): make sure the invariants are preserved. - bool valid(); + bool valid() const; }; #endif // ANIMQUEUE_HPP diff --git a/luprex/core/cpp/streambuffer.cpp b/luprex/core/cpp/streambuffer.cpp index eb69cbad..856e948e 100644 --- a/luprex/core/cpp/streambuffer.cpp +++ b/luprex/core/cpp/streambuffer.cpp @@ -290,6 +290,12 @@ void StreamBuffer::unread_to(int64_t rd_count) { read_cursor_ = buf_lo_ + (rd_count - pre_read_count_); } +void StreamBuffer::unwrite_to(int64_t wr_count) { + assert(wr_count >= read_count()); + assert(wr_count <= write_count()); + write_cursor_ = buf_lo_ + (wr_count - pre_read_count_); +} + int StreamBuffer::lua_writer(lua_State *L, const void* p, size_t sz, void* ud) { StreamBuffer *sb = (StreamBuffer *)ud; sb->write_bytes((const char *)p, sz); diff --git a/luprex/core/cpp/streambuffer.hpp b/luprex/core/cpp/streambuffer.hpp index f38152c5..3ef9aa5b 100644 --- a/luprex/core/cpp/streambuffer.hpp +++ b/luprex/core/cpp/streambuffer.hpp @@ -372,6 +372,9 @@ public: // Rewind the read cursor to a previous position. void unread_to(int64_t read_count); + // Rewind the write cursor to a previous position. + void unwrite_to(int64_t write_count); + // Use the stream buffer as a lua_Writer. static int lua_writer(lua_State *L, const void* p, size_t sz, void* ud); void *lua_writer_ud();