Overhaul animation queues so the queue is stored as a single encoded string.

This commit is contained in:
2023-07-26 17:40:20 -04:00
parent 2dff145885
commit b459eedc82
10 changed files with 239 additions and 242 deletions

View File

@@ -9,6 +9,9 @@
#include <cmath>
#include <cstdlib>
util::SharedStdString make_shared_string(const StreamBuffer &sb) {
return std::make_shared<std::string>(sb.view());
}
static const char *vtname(AnimValueType vt) {
switch (vt) {
@@ -22,7 +25,7 @@ static const char *vtname(AnimValueType vt) {
}
uint64_t hash_encoding(uint64_t prev, std::string_view s) {
uint64_t hash_encstep(uint64_t prev, std::string_view s) {
return util::hash_string(util::HashValue(123, prev), s).first;
}
@@ -130,6 +133,31 @@ static void parse_value(std::string_view vstr, AnimValue *v) {
v->set_string(vstr);
}
static AnimValue parse_anim_value(LuaCoreStack &LS, LuaSlot val, LuaSlot tmp) {
AnimValue result;
if (LS.isboolean(val)) {
result.set_boolean(LS.ckboolean(val));
} else if (LS.isnumber(val)) {
result.set_number(LS.cknumber(val));
} else if (LS.isstring(val)) {
result.set_string(LS.ckstring(val));
} else if (LS.istable(val)) {
util::DXYZ xyz;
LS.rawget(tmp, val, 1);
if (!LS.isnumber(tmp)) return result;
xyz.x = LS.cknumber(tmp);
LS.rawget(tmp, val, 2);
if (!LS.isnumber(tmp)) return result;
xyz.y = LS.cknumber(tmp);
LS.rawget(tmp, val, 3);
if (!LS.isnumber(tmp)) return result;
xyz.z = LS.cknumber(tmp);
result.set_xyz(xyz);
}
return result;
}
void AnimState::set_persistent(const eng::string &name) {
map_[name].persistent = true;
}
@@ -303,31 +331,6 @@ eng::string AnimState::add_defaults(const AnimState *other) {
}
static AnimValue parse_anim_value(LuaCoreStack &LS, LuaSlot val, LuaSlot tmp) {
AnimValue result;
if (LS.isboolean(val)) {
result.set_boolean(LS.ckboolean(val));
} else if (LS.isnumber(val)) {
result.set_number(LS.cknumber(val));
} else if (LS.isstring(val)) {
result.set_string(LS.ckstring(val));
} else if (LS.istable(val)) {
util::DXYZ xyz;
LS.rawget(tmp, val, 1);
if (!LS.isnumber(tmp)) return result;
xyz.x = LS.cknumber(tmp);
LS.rawget(tmp, val, 2);
if (!LS.isnumber(tmp)) return result;
xyz.y = LS.cknumber(tmp);
LS.rawget(tmp, val, 3);
if (!LS.isnumber(tmp)) return result;
xyz.z = LS.cknumber(tmp);
result.set_xyz(xyz);
}
return result;
}
eng::string AnimState::apply_lua(LuaCoreStack &LS0, LuaSlot tab, bool setpersist) {
LuaVar key, val, tmp;
LuaExtStack LS(LS0.state(), key, val, tmp);
@@ -420,6 +423,54 @@ void AnimCoreState::decode(std::string_view s) {
}
}
struct StepBreakout {
uint64_t hash;
std::string_view encstep;
StepBreakout(uint64_t h, std::string_view e) : hash(h), encstep(e) {}
};
using StepBreakoutVec = eng::vector<StepBreakout>;
StepBreakoutVec encqueue_breakout(std::string_view encqueue) {
StepBreakoutVec result;
StreamBuffer sb(encqueue);
while (!sb.empty()) {
uint64_t hash = sb.read_uint64();
std::string_view encstep = sb.read_string_view();
result.emplace_back(hash, encstep);
}
return result;
}
// Just return the hash of the very last step. (Steps are stored last to first).
static uint64_t encqueue_final_hash(std::string_view encqueue) {
StreamBuffer sb(encqueue);
uint64_t hash = sb.read_uint64();
return hash;
}
static std::string_view encqueue_final_encstep(std::string_view encqueue) {
StreamBuffer sb(encqueue);
sb.read_uint64();
std::string_view result = sb.read_string_view();
return result;
}
// Return the encqueue for the first N steps. If there aren't that
// many steps, then just return them all.
std::string_view encqueue_firstn(std::string_view encqueue, int n) {
StreamBuffer sb(encqueue);
while ((n > 0) && (!sb.empty())) {
sb.read_uint64();
uint64_t slen = sb.read_length();
sb.read_bytes(slen);
n -= 1;
}
return encqueue.substr(0, sb.total_reads());
}
AnimQueue::AnimQueue() {
size_limit_ = 10; // Default size limit.
clear();
@@ -431,53 +482,39 @@ void AnimQueue::clear() {
}
void AnimQueue::clear(const AnimState &state) {
steps_.clear();
eng::string encoding = state.encode();
uint64_t hash = hash_encoding(0, encoding);
steps_.emplace_back(encoding, hash);
StreamBuffer result;
eng::string encstep = state.encode();
uint64_t hash = hash_encstep(0, encstep);
result.write_uint64(hash);
result.write_string(encstep);
encqueue_ = make_shared_string(result);
}
void AnimQueue::set_limit(int n) {
assert((n >= 2) && (n <= 250));
size_limit_ = n;
if (int(steps_.size()) > n) {
while (int(steps_.size()) > n) {
steps_.pop_front();
}
}
void AnimQueue::set_limit(int nkeep) {
assert((nkeep >= 2) && (nkeep <= 250));
size_limit_ = nkeep;
encqueue_ = std::make_shared<std::string>(encqueue_firstn(*encqueue_, nkeep));
}
void AnimQueue::add(const AnimState &state) {
eng::string encoding = state.encode();
uint64_t hash = hash_encoding(steps_.back().hash, encoding);
steps_.emplace_back(encoding, hash);
set_limit(size_limit_);
uint64_t previoushash = encqueue_final_hash(*encqueue_);
eng::string encstep = state.encode();
uint64_t hash = hash_encstep(previoushash, encstep);
StreamBuffer result;
result.write_uint64(hash);
result.write_string(encstep);
result.write_bytes(encqueue_firstn(*encqueue_, size_limit_ - 1));
encqueue_ = make_shared_string(result);
}
void AnimQueue::serialize(StreamBuffer *sb) const {
sb->write_uint8(size_limit_);
sb->write_uint8(steps_.size());
sb->write_uint64(steps_[0].hash);
for (const AnimQueue::Step &step : steps_) {
sb->write_string(step.encoding);
}
sb->write_string(*encqueue_);
}
void AnimQueue::deserialize(StreamBuffer *sb) {
size_limit_ = sb->read_uint8();
int nsteps = sb->read_uint8();
uint64_t firsthash = sb->read_uint64();
assert ((nsteps >= 2) && (nsteps <= size_limit_) && (size_limit_ >= 2) && (size_limit_ <= 250));
steps_.resize(nsteps);
for (int i = 0; i < nsteps; i++) {
AnimQueue::Step &step = steps_[i];
step.encoding = sb->read_string();
if (i == 0) {
step.hash = firsthash;
} else {
step.hash = hash_encoding(steps_[i-1].hash, step.encoding);
}
}
encqueue_ = std::make_shared<std::string>(sb->read_string_view());
}
bool AnimQueue::diff(const AnimQueue &auth, StreamBuffer *sb) const {
@@ -488,28 +525,10 @@ bool AnimQueue::diff(const AnimQueue &auth, StreamBuffer *sb) const {
return false;
}
assert(auth.steps_.size() < 255);
sb->write_uint8(auth.steps_.size());
sb->write_uint8(auth.size_limit_);
sb->write_uint64(auth.steps_.front().hash);
// Index all known elements by text.
eng::map<eng::string, int> index;
for (int i = 0; i < int(steps_.size()); i++) {
index[steps_[i].encoding] = i;
}
// Write the encoded elements.
for (int i = 0; i < int(auth.steps_.size()); i++) {
const AnimQueue::Step &step = auth.steps_[i];
auto iter = index.find(step.encoding);
if (iter == index.end()) {
sb->write_uint8(255);
sb->write_string(step.encoding);
} else {
sb->write_uint8(iter->second);
}
}
// TODO: maybe send less data?
sb->write_uint8(0);
sb->write_uint32(auth.size_limit_);
sb->write_string(*auth.encqueue_);
return true;
}
@@ -520,68 +539,46 @@ void AnimQueue::patch(StreamBuffer *sb, DebugCollector *dbc) {
}
DebugLine(dbc) << "AnimQueue modified";
size_limit_ = sb->read_uint8();
uint64_t firsthash = sb->read_uint64();
assert ((nsteps >= 2) && (nsteps <= size_limit_) && (size_limit_ >= 2) && (size_limit_ <= 250));
// Decode the diff, stop at eof.
eng::deque<AnimQueue::Step> old = std::move(steps_);
steps_.resize(nsteps);
for (int i = 0; i < nsteps; i++) {
AnimQueue::Step &step = steps_[i];
uint8_t index = sb->read_uint8();
if (index < 255) {
assert(index < old.size());
step.encoding = old[index].encoding;
} else {
step.encoding = sb->read_string();
}
if (i == 0) {
step.hash = firsthash;
} else {
step.hash = hash_encoding(steps_[i-1].hash, step.encoding);
}
}
size_limit_ = sb->read_uint32();
std::string_view steps = sb->read_string_view();
encqueue_ = std::make_shared<std::string>(steps);
}
bool AnimQueue::exactly_equal(const AnimQueue &other) const {
if (size_limit_ != other.size_limit_) return false;
if (steps_.size() != other.steps_.size()) return false;
for (int i = 0; i < int(steps_.size()); i++) {
const AnimQueue::Step &step = steps_[i];
const AnimQueue::Step &otherstep = other.steps_[i];
if (step.hash != otherstep.hash) return false;
if (step.encoding != otherstep.encoding) return false;
}
if (*encqueue_ != *other.encqueue_) return false;
return true;
}
bool AnimQueue::exactly_equal_fast(const AnimQueue &other) const {
if (size_limit_ != other.size_limit_) return false;
if (steps_.size() != other.steps_.size()) return false;
if (steps_.back().hash != other.steps_.back().hash) return false;
if (encqueue_->size() != other.encqueue_->size()) return false;
if (encqueue_->compare(0, 8, *other.encqueue_) != 0) return false;
return true;
}
eng::string AnimQueue::steps_debug_string() const {
StepBreakoutVec breakout = encqueue_breakout(*encqueue_);
eng::ostringstream oss;
bool first = true;
for (const AnimQueue::Step &step : steps_) {
for (int i = breakout.size() - 1; i >= 0; i--) {
const StepBreakout &step = breakout[i];
if (!first) oss << "; ";
first = false;
AnimState state(step.encoding);
AnimState state(step.encstep);
state.print_debug_string(oss);
}
return oss.str();
}
eng::string AnimQueue::full_debug_string() const {
StepBreakoutVec breakout = encqueue_breakout(*encqueue_);
eng::ostringstream oss;
oss << "limit=" << size_limit();
for (const AnimQueue::Step &step : steps_) {
for (int i = breakout.size() - 1; i >= 0; i--) {
const StepBreakout &step = breakout[i];
oss << "; ";
AnimState state(step.encoding);
AnimState state(step.encstep);
state.print_debug_string(oss);
}
return oss.str();
@@ -590,38 +587,30 @@ eng::string AnimQueue::full_debug_string() const {
// Get the final entry, xyz and plane only.
//
AnimCoreState AnimQueue::get_final_core_state() const {
std::string_view encstep = encqueue_final_encstep(*encqueue_);
AnimCoreState result;
result.decode(steps_.back().encoding);
result.decode(encstep);
return result;
}
// Get the final entry, all persistent variables.
//
AnimState AnimQueue::get_final_persistent() const {
std::string_view encstep = encqueue_final_encstep(*encqueue_);
AnimState result;
result.decode_persistent(steps_.back().encoding);
result.decode_persistent(encstep);
return result;
}
// Get the final entry, everything persistent and non-persistent.
//
AnimState AnimQueue::get_final_everything() const {
std::string_view encstep = encqueue_final_encstep(*encqueue_);
AnimState result;
result.decode(steps_.back().encoding);
result.decode(encstep);
return result;
}
void AnimQueue::get_for_engine_wrapper(std::vector<EngineWrapper::AnimEntry> *into) const {
into->resize(steps_.size());
for (int i = 0; i < int(steps_.size()); i++) {
const AnimQueue::Step &step = steps_[i];
EngineWrapper::AnimEntry &entry = (*into)[i];
entry.hash = step.hash;
entry.data = step.encoding.c_str();
entry.size = step.encoding.size();
}
}
LuaDefine(unittests_animqueue, "", "some unit tests") {
// Useful objects.
@@ -757,7 +746,7 @@ LuaDefine(unittests_animqueue, "", "some unit tests") {
aqs.clear();
sb.clear();
aqs.diff(aq, &sb);
int difflen1 = sb.fill();
//int difflen1 = sb.fill();
LuaAssert(L, !aqs.exactly_equal(aq));
aqs.patch(&sb, nullptr);
LuaAssert(L, aqs.exactly_equal(aq));
@@ -775,12 +764,14 @@ LuaDefine(unittests_animqueue, "", "some unit tests") {
LuaAssertStrEq(L, aqs.full_debug_string(), "limit=10; action:jump xyz=4,5,6; plane=earth xyz=1,2,3; airline:southwest plane=moon");
sb.clear();
aqs.diff(aq, &sb);
int difflen2 = sb.fill();
//int difflen2 = sb.fill();
LuaAssert(L, !aqs.exactly_equal(aq));
aqs.patch(&sb, nullptr);
LuaAssert(L, aqs.exactly_equal(aq));
LuaAssertStrEq(L, aqs.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue");
LuaAssert(L, difflen2 < (difflen1 / 2));
// TODO: if we make the diff routine more efficient, this should be true.
// LuaAssert(L, difflen2 < (difflen1 / 2));
return 0;
}