Spooky hash, smarter animqueue diffs

This commit is contained in:
2021-07-18 17:48:39 -04:00
parent 4357fd647f
commit a39eb4a218
14 changed files with 645 additions and 197 deletions

View File

@@ -2,6 +2,7 @@
#include <map>
#include "luastack.hpp"
#include "animqueue.hpp"
#include "streambuffer.hpp"
AnimStep::AnimStep() {
clear();
@@ -10,7 +11,6 @@ AnimStep::AnimStep() {
AnimStep::~AnimStep() {}
void AnimStep::clear() {
id_ = 0;
bits_ = 0;
@@ -252,12 +252,25 @@ bool AnimStep::echoes(const AnimStep &prev) const {
return true;
}
AnimQueue::AnimQueue() {
AnimQueue::AnimQueue(util::WorldType wt) {
world_type_ = wt;
size_limit_ = 10; // Default size limit.
clear_steps();
version_number_ = (wt == util::WORLD_TYPE_MASTER) ? 1 : 0;
}
bool AnimQueue::exactly_equal(const AnimQueue &other) const {
void AnimQueue::mutated() {
if (world_type_ == util::WORLD_TYPE_MASTER) {
version_number_ += 1;
} else {
version_number_ = 0;
}
}
bool AnimQueue::size_and_steps_equal(const AnimQueue &other) const {
if (size_limit_ != other.size_limit_) {
return false;
}
if (steps_.size() != other.steps_.size()) {
return false;
}
@@ -277,19 +290,18 @@ void AnimQueue::clear_steps() {
init.action_ = "";
init.graphic_ = "";
init.plane_ = "";
mutated();
}
void AnimQueue::set_size_limit(int n) {
assert(n >= 2);
assert(n >= 1);
if (size_limit_ == n) return;
size_limit_ = n;
}
void AnimQueue::keep_only(int n) {
if (n < 1) n = 1;
while (int(steps_.size()) > n) {
while (int(steps_.size()) > size_limit_) {
steps_.pop_front();
}
steps_.front().keep_state_only();
mutated();
}
void AnimQueue::add(int64_t id, const AnimStep &step) {
@@ -297,15 +309,23 @@ void AnimQueue::add(int64_t id, const AnimStep &step) {
copy.echo(steps_.back());
copy.id_ = id;
steps_.push_back(copy);
keep_only(size_limit_);
while (int(steps_.size()) > size_limit_) {
steps_.pop_front();
}
steps_.front().keep_state_only();
mutated();
}
bool AnimQueue::valid() const {
// Animqueue must have at least one step, and no more than 255.
// Size limit must be between 2 and 250
if ((size_limit_ < 1) || (size_limit_ > 250)) {
return false;
}
// Animqueue must have at least one step, and no more than 250.
if (steps_.empty()) {
return false;
}
if (steps_.size() > 255) {
if (steps_.size() > 250) {
return false;
}
// First action should be blank.
@@ -327,16 +347,18 @@ bool AnimQueue::valid() const {
void AnimQueue::serialize(StreamBuffer *sb) {
assert(valid()); // can't serialize an invalid animqueue.
sb->write_int32(size_limit_);
sb->write_size(steps_.size());
sb->write_int64(version_number_);
sb->write_uint8(size_limit_);
sb->write_uint8(steps_.size());
for (const AnimStep &step : steps_) {
step.write_into(sb);
}
}
void AnimQueue::deserialize(StreamBuffer *sb) {
size_limit_ = sb->read_int32();
size_t nsteps = sb->read_size();
version_number_ = sb->read_int64();
size_limit_ = sb->read_uint8();
size_t nsteps = sb->read_uint8();
steps_.resize(nsteps);
for (size_t i = 0; i < nsteps; i++) {
AnimStep &step = steps_[i];
@@ -349,15 +371,22 @@ bool AnimQueue::make_patch(const AnimQueue &auth, StreamBuffer *sb) const {
// Sanity check.
assert(valid());
assert(auth.valid());
// Fast path to detect equivalence.
if (version_number_ == auth.version_number_) {
sb->write_uint8(0);
return false;
}
// Special case: if we're already a perfect match, output 0 bytes.
if (exactly_equal(auth)) {
// Special case: if we're already a perfect match.
if (size_and_steps_equal(auth)) {
sb->write_uint8(0);
return false;
}
// Write the first element.
sb->write_uint8(auth.steps_.size());
sb->write_uint8(auth.size_limit_);
const AnimStep &first = auth.steps_[0];
int match = 0;
while ((match < int(steps_.size())) && (!steps_[match].state_equal(first))) {
@@ -401,7 +430,7 @@ void AnimQueue::apply_patch(StreamBuffer *sb) {
if (len == 0) {
return;
}
size_limit_ = sb->read_uint8();
// Decode the diff, stop at eof.
std::deque<AnimStep> old = std::move(steps_);
steps_.clear();
@@ -422,6 +451,12 @@ void AnimQueue::apply_patch(StreamBuffer *sb) {
steps_[0].keep_state_only();
}
}
mutated();
}
void AnimQueue::update_version(const AnimQueue &auth) {
assert(size_and_steps_equal(auth));
version_number_ = auth.version_number_;
}
const AnimStep &AnimQueue::back() const {
@@ -431,9 +466,9 @@ const AnimStep &AnimQueue::back() const {
LuaDefine(unittests_animqueue, "c") {
// Check initial state.
AnimStep stp;
AnimQueue aq;
AnimQueue aq(util::WORLD_TYPE_MASTER);
StreamBuffer sb;
AnimQueue aqds;
AnimQueue aqds(util::WORLD_TYPE_S_SYNC);
aq.set_size_limit(3);
LuaAssert(L, aq.valid());
@@ -568,7 +603,7 @@ LuaDefine(unittests_animqueue, "c") {
sb.clear();
LuaAssert(L, aqds.make_patch(aq, &sb));
aqds.apply_patch(&sb);
LuaAssert(L, aqds.exactly_equal(aq));
LuaAssert(L, aqds.size_and_steps_equal(aq));
// Add another action.
stp.clear();
@@ -585,21 +620,31 @@ LuaDefine(unittests_animqueue, "c") {
// Apply the diffs, 4 extra bytes should remain.
aqds.apply_patch(&sb);
LuaAssert(L, sb.write_count() - sb.read_count() == 4);
LuaAssert(L, aqds.exactly_equal(aq));
LuaAssert(L, aqds.size_and_steps_equal(aq));
// Change the queue size limit.
aq.set_size_limit(13);
// Generate diffs, but add 4 extra bytes.
sb.clear();
LuaAssert(L, !aqds.size_and_steps_equal(aq));
LuaAssert(L, aqds.make_patch(aq, &sb));
aqds.apply_patch(&sb);
LuaAssert(L, aqds.size_and_steps_equal(aq));
// compare again, should be no differences.
sb.clear();
LuaAssert(L, !aqds.make_patch(aq, &sb));
aqds.apply_patch(&sb);
LuaAssert(L, aqds.exactly_equal(aq));
LuaAssert(L, aqds.size_and_steps_equal(aq));
// Discard all but the last action.
aq.keep_only(1);
aq.set_size_limit(1);
sb.clear();
LuaAssert(L, aqds.make_patch(aq, &sb));
aqds.apply_patch(&sb);
LuaAssert(L, aqds.exactly_equal(aq));
LuaAssert(L, aqds.size_and_steps_equal(aq));
return 0;
}