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

783 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_encstep(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);
}
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;
}
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 "";
}
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;
}
}
}
int AnimQueue::get_size_limit() const {
StreamBuffer sb(*encqueue_);
return sb.read_uint8();
}
int AnimQueue::get_actual_size() const {
StreamBuffer sb(*encqueue_);
sb.read_bytes(1);
return sb.read_uint8();
}
uint64_t AnimQueue::get_final_hash() const {
StreamBuffer sb(*encqueue_);
sb.read_bytes(2);
return sb.read_uint64();
}
std::string_view AnimQueue::get_final_encstep() const {
StreamBuffer sb(*encqueue_);
sb.read_bytes(10);
return sb.read_string_view();
}
2021-09-10 17:06:07 -04:00
void AnimQueue::update_encqueue(int limit, bool add, std::string_view add_enc, bool keepold) {
// Make sure the size limit is reasonable.
assert((limit >= 2) && (limit <= 250));
// You must either add a new step or retain an old step. The queue can't be empty.
assert(keepold || add);
// Find out how many old steps we'll be retaining, ignoring the size limit.
int nretain = 0;
if (keepold) nretain = get_actual_size();
// If retaining all steps would overflow the size limit, retain fewer.
int retain_limit = limit;
if (add) retain_limit -= 1;
if (nretain > retain_limit) nretain = retain_limit;
// Calculate the new size of the queue.
int new_size = add ? (nretain + 1) : nretain;
// If we're retaining steps, extract them from the old queue.
std::string_view retain;
if (nretain > 0) {
std::string_view oldqueue(*encqueue_);
StreamBuffer sb(oldqueue);
sb.read_bytes(2); // Skip over the header.
int pos1 = sb.total_reads();
for (int i = 0; i < nretain; i++) {
sb.read_uint64();
sb.read_string_view();
}
int pos2 = sb.total_reads();
retain = oldqueue.substr(pos1, pos2 - pos1);
}
// If we're adding a step, calculate its hash.
uint64_t add_hash = 0;
if (add) {
uint64_t prev_hash = 0;
if (nretain > 0) prev_hash = get_final_hash();
add_hash = hash_encstep(prev_hash, add_enc);
}
// Finally, encode everything into a binary blob.
StreamBuffer result;
result.write_uint8(limit);
result.write_uint8(new_size);
if (add) {
result.write_uint64(add_hash);
result.write_string(add_enc);
}
result.write_bytes(retain);
// Replace the shared string.
2023-08-16 15:40:30 -04:00
encqueue_ = std::make_shared<std::string>(result.view());
}
AnimQueue::AnimQueue() {
update_encqueue(10, true, AnimState().encode(), false);
}
void AnimQueue::clear(const AnimState &state) {
update_encqueue(get_size_limit(), true, state.encode(), false);
}
void AnimQueue::clear() {
update_encqueue(get_size_limit(), true, AnimState().encode(), false);
}
void AnimQueue::set_limit(int limit) {
update_encqueue(limit, false, "", true);
}
void AnimQueue::add(const AnimState &state) {
update_encqueue(get_size_limit(), true, state.encode(), true);
}
2021-08-03 11:25:12 -04:00
void AnimQueue::serialize(StreamBuffer *sb) const {
sb->write_string(*encqueue_);
}
2021-08-03 11:25:12 -04:00
void AnimQueue::deserialize(StreamBuffer *sb) {
encqueue_ = std::make_shared<std::string>(sb->read_string_view());
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));
sb->write_bool(false);
2021-07-18 17:48:39 -04:00
return false;
}
// TODO: maybe send less data?
sb->write_bool(true);
sb->write_string(*auth.encqueue_);
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) {
bool changed = sb->read_bool();
if (!changed) {
2021-11-21 13:35:39 -05:00
return;
}
DebugLine(dbc) << "AnimQueue modified";
encqueue_ = std::make_shared<std::string>(sb->read_string_view());
2021-07-18 17:48:39 -04:00
}
bool AnimQueue::exactly_equal(const AnimQueue &other) const {
if (*encqueue_ != *other.encqueue_) return false;
return true;
}
bool AnimQueue::exactly_equal_fast(const AnimQueue &other) const {
if (encqueue_->size() != other.encqueue_->size()) return false;
if (encqueue_->compare(0, 10, *other.encqueue_) != 0) return false;
return true;
2021-01-16 01:24:33 -05:00
}
2023-08-16 15:40:30 -04:00
void AnimQueue::print_debug_string(eng::ostringstream &oss, bool full) const {
bool first = true;
// Break out the steps.
eng::vector<std::string_view> encsteps;
StreamBuffer sb(*encqueue_);
int size_limit = sb.read_uint8();
int actual_size = sb.read_uint8();
2023-08-16 15:40:30 -04:00
if (full) {
oss << "limit=" << size_limit;
first = false;
2023-08-16 15:40:30 -04:00
}
for (int i = 0; i < actual_size; i++) {
2023-08-16 15:40:30 -04:00
sb.read_uint64();
encsteps.push_back(sb.read_string_view());
}
assert(sb.empty());
2023-08-16 15:40:30 -04:00
for (int i = encsteps.size() - 1; i >= 0; i --) {
if (!first) oss << "; ";
AnimState state(encsteps[i]);
state.print_debug_string(oss);
2023-08-16 15:40:30 -04:00
first = false;
}
2023-08-16 15:40:30 -04:00
}
eng::string AnimQueue::steps_debug_string() const {
eng::ostringstream oss;
print_debug_string(oss, false);
return oss.str();
}
eng::string AnimQueue::full_debug_string() const {
eng::ostringstream oss;
2023-08-16 15:40:30 -04:00
print_debug_string(oss, true);
return oss.str();
}
AnimCoreState AnimQueue::get_final_core_state() const {
std::string_view encstep = get_final_encstep();
AnimCoreState result;
result.decode(encstep);
return result;
}
AnimState AnimQueue::get_final_persistent() const {
std::string_view encstep = get_final_encstep();
AnimState result;
result.decode_persistent(encstep);
return result;
2021-07-21 16:10:29 -04:00
}
AnimState AnimQueue::get_final_everything() const {
std::string_view encstep = get_final_encstep();
AnimState result;
result.decode(encstep);
return result;
}
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");
// TODO: if we make the diff routine more efficient, this should be true.
// LuaAssert(L, difflen2 < (difflen1 / 2));
2021-01-16 01:24:33 -05:00
return 0;
}