Files
integration/luprex/cpp/core/animqueue.cpp

787 lines
25 KiB
C++
Raw Normal View History

#include "wrap-sstream.hpp"
#include "wrap-map.hpp"
#include "util.hpp"
#include "animqueue.hpp"
2021-01-23 14:44:06 -05:00
#include "luastack.hpp"
2021-07-18 17:48:39 -04:00
#include "streambuffer.hpp"
2021-01-12 14:14:38 -05:00
#include <limits>
#include <cmath>
#include <cstdlib>
static const char *vtname(AnimValueType vt) {
switch (vt) {
case T_UNINITIALIZED: return "uninitialized";
case T_BOOLEAN: return "boolean";
case T_NUMBER: return "number";
case T_STRING: return "string";
case T_XYZ: return "xyz";
default: return "unknown";
}
}
2021-01-12 14:14:38 -05:00
uint64_t hash_encoding(uint64_t prev, std::string_view s) {
return util::hash_string(util::HashValue(123, prev), s).first;
}
void AnimValue::set_boolean(bool b) {
type = T_BOOLEAN;
str.clear();
xyz = (b ? 1.0:0.0);
2021-01-12 14:14:38 -05:00
}
void AnimValue::set_number(double n) {
type = T_NUMBER;
str.clear();
xyz = util::DXYZ(n, 0, 0);
}
void AnimValue::set_xyz(const util::DXYZ &v) {
type = T_XYZ;
str.clear();
xyz = v;
}
void AnimValue::set_string(std::string_view sv) {
type = T_STRING;
str = sv;
xyz = 0.0;
}
bool AnimValue::get_boolean() const {
if (type != T_BOOLEAN) return false;
return (xyz.x == 1.0);
}
double AnimValue::get_number() const {
if (type != T_NUMBER) return 0.0;
return xyz.x;
}
const util::DXYZ &AnimValue::get_xyz() const {
static util::DXYZ zero;
if (type != T_XYZ) return zero;
return xyz;
}
std::string_view AnimValue::get_string() const {
if (type != T_STRING) return std::string_view("");
return std::string_view(str);
2021-01-17 16:23:10 -05:00
}
void AnimValue::copy_value(const AnimValue &other) {
type = other.type;
str = other.str;
xyz = other.xyz;
}
static void encode_value(StreamBuffer *sb, const AnimValue *v) {
sb->write_uint8(uint8_t(v->type));
switch(v->type) {
case T_NUMBER: sb->write_double(v->xyz.x); break;
case T_BOOLEAN: sb->write_bool(v->xyz.x == 1.0); break;
case T_XYZ: sb->write_dxyz(v->xyz); break;
case T_STRING: sb->write_string(v->str); break;
default: assert(false);
}
}
static void decode_value(StreamBuffer *sb, AnimValue *v) {
AnimValueType type = AnimValueType(sb->read_uint8());
switch (type) {
case T_NUMBER: v->set_number(sb->read_double()); break;
case T_BOOLEAN: v->set_boolean(sb->read_bool()); break;
case T_XYZ: v->set_xyz(sb->read_dxyz()); break;
case T_STRING: v->set_string(sb->read_string()); break;
default: assert(false);
}
}
// Parse a value. This is meant for unit testing only. The
// parser isn't powerful enough to express all possible values.
static void parse_value(std::string_view vstr, AnimValue *v) {
// Try to interpret vstr as a boolean.
bool is_true = (vstr == "true");
bool is_false = (vstr == "false");
if (is_true || is_false) {
v->set_boolean(is_true);
return;
}
// Try to interpret vstr as a number.
if (sv::valid_number(vstr, true, true, true, false)) {
v->set_number(std::atof(std::string(vstr).c_str()));
return;
}
// Try to interpret vstr as a vector.
eng::vector<eng::string> parts = util::split(eng::string(vstr), ',');
if ((parts.size() == 3) &&
(sv::valid_number(parts[0], true, true, true, false)) &&
(sv::valid_number(parts[1], true, true, true, false)) &&
(sv::valid_number(parts[2], true, true, true, false))) {
double x = std::atof(parts[0].c_str());
double y = std::atof(parts[1].c_str());
double z = std::atof(parts[2].c_str());
v->set_xyz(util::DXYZ(x,y,z));
return;
}
// If it doesn't parse as any of the above, it's a string.
v->set_string(vstr);
}
void AnimState::set_persistent(const eng::string &name) {
map_[name].persistent = true;
}
bool AnimState::get_boolean(const eng::string &name) {
auto iter = map_.find(name);
if (iter == map_.end()) return false;
return iter->second.get_boolean();
}
double AnimState::get_number(const eng::string &name) {
auto iter = map_.find(name);
if (iter == map_.end()) return 0.0;
return iter->second.get_number();
}
2021-02-25 16:32:48 -05:00
util::DXYZ AnimState::get_xyz(const eng::string &name) {
static util::DXYZ zero;
auto iter = map_.find(name);
if (iter == map_.end()) return zero;
return iter->second.get_xyz();
}
2021-02-25 16:32:48 -05:00
std::string_view AnimState::get_string(const eng::string &name) {
auto iter = map_.find(name);
if (iter == map_.end()) return std::string_view("");
return iter->second.get_string();
}
void AnimState::set_boolean(const eng::string &name, bool v) {
AnimValue &value = map_[name];
value.set_boolean(v);
}
2021-02-25 16:32:48 -05:00
void AnimState::set_number(const eng::string &name, double v) {
AnimValue &value = map_[name];
value.set_number(v);
}
void AnimState::set_xyz(const eng::string &name, const util::DXYZ &v) {
AnimValue &value = map_[name];
value.set_xyz(v);
}
void AnimState::set_string(const eng::string &name, std::string_view v) {
AnimValue &value = map_[name];
value.set_string(v);
}
void AnimState::print_debug_string(eng::ostringstream &oss) {
bool first = true;
if (map_.empty()) {
oss << "[empty]";
}
for (const auto &pair : map_) {
if (!first) oss << " ";
const eng::string &name = pair.first;
const AnimValue &value = pair.second;
oss << name;
if (value.persistent) {
oss << "=";
} else {
oss << ":";
}
switch (value.type) {
case T_NUMBER: oss << value.xyz.x; break;
case T_BOOLEAN: oss << ((value.xyz.x == 1.0) ? "true":"false"); break;
case T_XYZ: oss << value.xyz; break;
case T_STRING: oss << value.str; break;
default: assert(false);
}
first = false;
}
}
eng::string AnimState::debug_string() {
eng::ostringstream oss;
print_debug_string(oss);
return oss.str();
}
eng::string AnimState::encode() const {
StreamBuffer sb;
for (const auto &pair : map_) {
const eng::string &name = pair.first;
const AnimValue &value = pair.second;
sb.write_string(name);
sb.write_bool(value.persistent);
encode_value(&sb, &value);
}
return sb.read_entire_contents();
}
void AnimState::decode(std::string_view s) {
map_.clear();
StreamBuffer sb(s);
while (!sb.empty()) {
eng::string name = sb.read_string();
bool persistent = sb.read_bool();
AnimValue &value = map_[name];
value.persistent = persistent;
decode_value(&sb, &value);
}
}
void AnimState::decode_persistent(std::string_view s) {
map_.clear();
StreamBuffer sb(s);
AnimValue dummy;
while (!sb.empty()) {
eng::string name = sb.read_string();
bool persistent = sb.read_bool();
if (persistent) {
AnimValue &value = map_[name];
value.persistent = persistent;
decode_value(&sb, &value);
} else {
decode_value(&sb, &dummy);
}
}
}
eng::string AnimState::add_default(const eng::string &name, const AnimValue &def, const AnimState *other) {
AnimValue &value = map_[name];
value.persistent = true;
if (value.type == T_UNINITIALIZED) {
if (other != nullptr) {
auto otheriter = other->map_.find(name);
if (otheriter != other->map_.end()) {
if (otheriter->second.persistent && otheriter->second.type == def.type) {
value.copy_value(otheriter->second);
return "";
}
}
}
value.copy_value(def);
return "";
2021-07-18 17:48:39 -04:00
}
if (value.type != def.type) {
return util::ss("Animation key ", name, " must be a ", vtname(def.type));
}
return "";
2021-07-18 17:48:39 -04:00
}
eng::string AnimState::add_defaults(const AnimState *other) {
eng::string err;
AnimValue defval;
defval.set_xyz(util::DXYZ(0,0,0));
err = add_default("xyz", defval, other);
if (!err.empty()) return err;
defval.set_string("nowhere");
err = add_default("plane", defval, other);
if (!err.empty()) return err;
defval.set_number(0.0);
err = add_default("facing", defval, other);
if (!err.empty()) return err;
defval.set_string("stdbp");
err = add_default("bp", defval, other);
if (!err.empty()) return err;
defval.set_string("stdmodel");
err = add_default("model", defval, other);
if (!err.empty()) return err;
return "";
}
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);
util::DXYZ xyz;
if (!LS.istable(tab)) {
return "An animstate must be a table.";
}
LS.set(key, LuaNil);
while (LS.next(tab, key, val)) {
if (!LS.isstring(key)) {
return "in animation key-value pairs, key must be a string.";
}
AnimValue parsedvalue = parse_anim_value(LS, val, tmp);
if (parsedvalue.type == T_UNINITIALIZED) {
return "in animation key-value pairs, val must be number, string, boolean, or xyz";
}
eng::string name = LS.ckstring(key);
AnimValue &mapentry = map_[name];
if ((mapentry.type != T_UNINITIALIZED) && (mapentry.type != parsedvalue.type)) {
return util::ss("animation '", name, "' must be a ", vtname(mapentry.type));
}
mapentry.copy_value(parsedvalue);
if (setpersist) mapentry.persistent = true;
}
return "";
}
void AnimState::to_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent) {
LuaVar name, val;
LuaExtStack LS(LS0.state(), name, val);
LS.newtable(tab);
for (const auto &pair : map_) {
if (pair.second.persistent != persistent) continue;
LS.set(name, pair.first);
const AnimValue &value = pair.second;
if (value.type == T_BOOLEAN) {
LS.set(val, value.get_boolean());
} else if (value.type == T_NUMBER) {
LS.set(val, value.get_number());
} else if (value.type == T_STRING) {
LS.set(val, value.get_string());
} else if (value.type == T_XYZ) {
LS.newtable(val);
LS.rawset(val, 1, value.get_xyz().x);
LS.rawset(val, 2, value.get_xyz().y);
LS.rawset(val, 3, value.get_xyz().z);
}
LS.rawset(tab, name, val);
}
}
// The syntax used by this parser is not general enough to represent all
// possible strings. That's OK, though, since it's just for unit testing.
void AnimState::parse(std::string_view config) {
while (true) {
config = sv::ltrim(config);
if (config.empty()) break;
eng::string name(sv::read_ascii_identifier(config));
assert(!name.empty());
AnimValue &value = map_[name];
bool has_equal = sv::has_prefix(config, "=");
bool has_colon = sv::has_prefix(config, ":");
assert(has_equal || has_colon);
config.remove_prefix(1);
value.persistent = has_equal;
eng::string vstr(sv::read_to_space(config));
parse_value(vstr, &value);
}
}
void AnimState::clear_and_parse(std::string_view config) {
map_.clear();
parse(config);
}
void AnimCoreState::decode(std::string_view s) {
plane.clear();
xyz = 0.0;
StreamBuffer sb(s);
AnimValue value;
while (!sb.empty()) {
eng::string name = sb.read_string();
bool persistent = sb.read_bool();
decode_value(&sb, &value);
if (persistent) {
if ((name == "xyz") && (value.type == T_XYZ)) xyz = value.xyz;
if ((name == "plane") && (value.type == T_STRING)) plane = value.str;
}
}
}
AnimQueue::AnimQueue() {
size_limit_ = 10; // Default size limit.
clear();
}
void AnimQueue::clear() {
AnimState state;
clear(state);
}
2021-09-10 17:06:07 -04:00
void AnimQueue::clear(const AnimState &state) {
2021-09-10 17:06:07 -04:00
steps_.clear();
eng::string encoding = state.encode();
uint64_t hash = hash_encoding(0, encoding);
steps_.emplace_back(encoding, hash);
2021-09-10 17:06:07 -04:00
}
void AnimQueue::set_limit(int n) {
assert((n >= 2) && (n <= 250));
size_limit_ = n;
2021-07-21 16:10:29 -04:00
if (int(steps_.size()) > n) {
while (int(steps_.size()) > n) {
steps_.pop_front();
}
2021-07-18 17:48:39 -04:00
}
}
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_);
}
2021-08-03 11:25:12 -04:00
void AnimQueue::serialize(StreamBuffer *sb) const {
2021-07-18 17:48:39 -04:00
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);
}
}
2021-08-03 11:25:12 -04:00
void AnimQueue::deserialize(StreamBuffer *sb) {
2021-07-18 17:48:39 -04:00
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);
}
}
2021-11-21 13:35:39 -05:00
}
bool AnimQueue::diff(const AnimQueue &auth, StreamBuffer *sb) const {
// Fast check for exactly equivalent. If equivalent, skip all the work.
if (exactly_equal_fast(auth)) {
assert(exactly_equal(auth));
2021-11-21 13:35:39 -05:00
sb->write_uint8(255);
2021-07-18 17:48:39 -04:00
return false;
}
2021-11-21 13:35:39 -05:00
assert(auth.steps_.size() < 255);
2021-07-13 16:34:24 -04:00
sb->write_uint8(auth.steps_.size());
2021-07-18 17:48:39 -04:00
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);
}
}
2021-11-21 13:35:39 -05:00
return true;
}
2021-11-21 13:35:39 -05:00
void AnimQueue::patch(StreamBuffer *sb, DebugCollector *dbc) {
int nsteps = sb->read_uint8();
if (nsteps == 255) {
2021-11-21 13:35:39 -05:00
return;
}
DebugLine(dbc) << "AnimQueue modified";
2021-07-18 17:48:39 -04:00
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;
2021-07-13 16:34:24 -04:00
} else {
step.hash = hash_encoding(steps_[i-1].hash, step.encoding);
}
}
2021-07-18 17:48:39 -04:00
}
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;
}
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;
return true;
2021-01-16 01:24:33 -05:00
}
eng::string AnimQueue::steps_debug_string() const {
eng::ostringstream oss;
bool first = true;
for (const AnimQueue::Step &step : steps_) {
if (!first) oss << "; ";
first = false;
AnimState state(step.encoding);
state.print_debug_string(oss);
}
return oss.str();
}
eng::string AnimQueue::full_debug_string() const {
eng::ostringstream oss;
oss << "limit=" << size_limit();
for (const AnimQueue::Step &step : steps_) {
oss << "; ";
AnimState state(step.encoding);
state.print_debug_string(oss);
}
return oss.str();
}
// Get the final entry, xyz and plane only.
//
AnimCoreState AnimQueue::get_final_core_state() const {
AnimCoreState result;
result.decode(steps_.back().encoding);
return result;
}
// Get the final entry, all persistent variables.
//
AnimState AnimQueue::get_final_persistent() const {
AnimState result;
result.decode_persistent(steps_.back().encoding);
return result;
2021-07-21 16:10:29 -04:00
}
// Get the final entry, everything persistent and non-persistent.
//
AnimState AnimQueue::get_final_everything() const {
AnimState result;
result.decode(steps_.back().encoding);
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();
}
}
2021-12-15 23:03:43 -05:00
LuaDefine(unittests_animqueue, "", "some unit tests") {
2021-07-21 16:10:29 -04:00
// Useful objects.
AnimQueue aq, aqs;
2021-07-21 16:10:29 -04:00
StreamBuffer sb;
AnimState astate;
eng::string enc;
AnimCoreState core;
2021-07-21 16:10:29 -04:00
// Debug string of a newly initialized queue
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; [empty]");
// Test AnimState simple setters.
astate.set_string("color", "blue");
astate.set_xyz("xyz", util::DXYZ(1,2,3));
astate.set_number("half", 0.5);
astate.set_boolean("nice", true);
LuaAssertStrEq(L, astate.debug_string(), "color:blue half:0.5 nice:true xyz:1,2,3");
// Test AnimState simple getters.
LuaAssert(L, astate.get_string("color") == "blue");
LuaAssert(L, astate.get_xyz("xyz") == util::DXYZ(1,2,3));
LuaAssert(L, astate.get_number("half") == 0.5);
LuaAssert(L, astate.get_boolean("nice") == true);
// Test AnimState simple getters on nonexistent data.
LuaAssert(L, astate.get_string("q") == "");
LuaAssert(L, astate.get_xyz("q") == util::DXYZ(0,0,0));
LuaAssert(L, astate.get_number("q") == 0.0);
LuaAssert(L, astate.get_boolean("q") == false);
// Test AnimState simple getters on wrong-type data.
LuaAssert(L, astate.get_string("half") == "");
LuaAssert(L, astate.get_xyz("half") == util::DXYZ(0,0,0));
LuaAssert(L, astate.get_number("color") == 0.0);
LuaAssert(L, astate.get_boolean("color") == false);
// Test AnimState persistence manipulation.
astate.set_persistent("color");
astate.set_persistent("nice");
LuaAssertStrEq(L, astate.debug_string(), "color=blue half:0.5 nice=true xyz:1,2,3");
// Test AnimState parser.
astate.clear_and_parse("color:green mean=true pos=3,4,5 ok:false");
LuaAssertStrEq(L, astate.debug_string(), "color:green mean=true ok:false pos=3,4,5");
// Test animstate encoding and decoding.
astate.clear_and_parse("color:green mean=true pos=3,4,5 ok:false");
enc = astate.encode();
astate.clear();
astate.decode(enc);
LuaAssertStrEq(L, astate.debug_string(), "color:green mean=true ok:false pos=3,4,5");
astate.decode_persistent(enc);
LuaAssertStrEq(L, astate.debug_string(), "mean=true pos=3,4,5");
// Test AnimCoreState.decode
//
astate.clear_and_parse("color=blue xyz=1,2,3 plane=banana chicken=3");
core.decode(astate.encode());
LuaAssert(L, core.plane == "banana");
LuaAssert(L, core.xyz == util::DXYZ(1,2,3));
// Verify that a newly-constructed AnimQueue is in a reasonable default state.
//
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; [empty]");
// Clear an AnimQueue to a specified initial state.
//
astate.clear_and_parse("color=blue xyz=1,2,3 plane=somewhere");
aq.clear(astate);
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; color=blue plane=somewhere xyz=1,2,3");
// Add animation steps to animation queue.
// Note: each step is independent of the previous one, no composition is being done.
//
astate.clear_and_parse("xyz=1,2,3 plane=earth");
aq.clear(astate);
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3");
astate.clear_and_parse("xyz=4,5,6 action:jump");
aq.add(astate);
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6");
astate.clear_and_parse("plane=moon airline:southwest");
aq.add(astate);
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon");
astate.clear_and_parse("color=blue");
aq.add(astate);
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue");
// Try reducing the animation queue size limit.
//
aq.set_limit(2);
LuaAssertStrEq(L, aq.full_debug_string(), "limit=2; airline:southwest plane=moon; color=blue");
// Test get_final_persistent, get_final_everything, get_final_core_state
//
astate.clear_and_parse("action:jump plane=earth xyz=1,2,3 bouncy:true");
aq.clear(astate);
astate = aq.get_final_persistent();
LuaAssertStrEq(L, astate.debug_string(), "plane=earth xyz=1,2,3");
astate = aq.get_final_everything();
LuaAssertStrEq(L, astate.debug_string(), "action:jump bouncy:true plane=earth xyz=1,2,3");
core = aq.get_final_core_state();
LuaAssert(L, core.plane == "earth");
LuaAssert(L, core.xyz == util::DXYZ(1,2,3));
// Serialize a queue.
//
aq.set_limit(10);
astate.clear_and_parse("xyz=1,2,3 plane=earth");
aq.clear(astate);
astate.clear_and_parse("xyz=4,5,6 action:jump");
aq.add(astate);
astate.clear_and_parse("plane=moon airline:southwest");
aq.add(astate);
astate.clear_and_parse("color=blue");
aq.add(astate);
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue");
aq.serialize(&sb);
// Deserialize a queue.
//
aqs.set_limit(7);
aqs.clear();
LuaAssert(L, !aqs.exactly_equal(aq));
aqs.deserialize(&sb);
LuaAssert(L, aqs.exactly_equal(aq));
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue");
// Test diff and patch.
//
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue");
aqs.set_limit(7);
aqs.clear();
sb.clear();
aqs.diff(aq, &sb);
int difflen1 = 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");
// Test that diff and patch are more efficient when the two queues contain some shared steps.
//
LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue");
astate.clear_and_parse("xyz=4,5,6 action:jump");
aqs.clear(astate);
astate.clear_and_parse("plane=earth xyz=1,2,3");
aqs.add(astate);
astate.clear_and_parse("plane=moon airline:southwest");
aqs.add(astate);
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();
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));
2021-01-16 01:24:33 -05:00
return 0;
}