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

733 lines
24 KiB
C++
Raw Normal View History

2024-08-19 13:26:58 -04:00
#define _USE_MATH_DEFINES
#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>
2021-01-12 14:14:38 -05:00
util::SharedStdString AnimQueue::blankqueue_;
void AnimQueue::initialize_module() {
AnimQueue queue;
blankqueue_ = queue.get_encoded_queue();
}
static int64_t hash_encstep(int64_t prev, std::string_view s) {
// We drop the most significant bit to ensure that the value
// can be represented as a nonnegative int64.
return int64_t(0x7FFFFFFF & util::hash_string(util::HashValue(123, prev), s).first);
}
// 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_dxyz(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) {
AnimValue result;
int type = LS.type(val);
switch (type) {
case LUA_TBOOLEAN: {
auto tbool = LS.tryboolean(val);
if (tbool) result.set_boolean(*tbool);
}
case LUA_TNUMBER: {
auto tnum = LS.trynumber(val);
if (tnum) result.set_number(*tnum);
}
case LUA_TSTRING: {
auto tstr = LS.trystringview(val);
if (tstr) result.set_string(*tstr);
}
case LUA_TTABLE: {
auto txyz = LS.tryxyz(val);
if (txyz) result.set_dxyz(*txyz);
}
case LUA_TLIGHTUSERDATA: {
auto ttoken = LS.trytoken(val);
if (ttoken) result.set_token(*ttoken);
}
}
return result;
}
void AnimState::set_persistent(const eng::string &name) {
map_[name].persistent = true;
}
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 SimpleDynamicTag::UNINITIALIZED: oss << "UNINITIALIZED"; break;
case SimpleDynamicTag::STRING: oss << value.s; break;
case SimpleDynamicTag::TOKEN: oss << "[" << value.s << "]"; break;
case SimpleDynamicTag::NUMBER: oss << value.x; break;
case SimpleDynamicTag::BOOLEAN: oss << ((value.x == 1.0) ? "true":"false"); break;
case SimpleDynamicTag::VECTOR: oss << value.x << "," << value.y << "," << value.z; 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);
sb.write_simple_dynamic(value);
}
return eng::string(sb.view());
}
void AnimState::decode(std::string_view s) {
map_.clear();
StreamBuffer sb(s);
while (!sb.empty()) {
eng::string name = sb.read_string();
AnimValue &value = map_[name];
value.persistent = sb.read_bool();
sb.read_simple_dynamic(&value);
}
}
void AnimState::decode_persistent(std::string_view s) {
map_.clear();
StreamBuffer sb(s);
2023-10-17 19:55:34 -04:00
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;
sb.read_simple_dynamic(&value);
} else {
sb.read_simple_dynamic(&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 == SimpleDynamicTag::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 ", def.type_name());
}
return "";
2021-07-18 17:48:39 -04:00
}
eng::string AnimState::add_defaults(const AnimState *other) {
eng::string err;
AnimValue defval;
defval.set_dxyz(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("unknown");
err = add_default("bp", defval, other);
if (!err.empty()) return err;
return "";
}
eng::string AnimState::from_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent, bool allowauto) {
LuaVar key, val;
LuaExtStack LS(LS0.state(), key, val);
util::DXYZ xyz;
clear();
if (!LS.istable(tab)) {
return "A lua 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.";
}
eng::string name = LS.ckstring(key);
if (!sv::is_lua_id(name)) {
return "in animation key-value pairs, key must be a valid lua identifier.";
}
AnimValue parsedvalue = parse_anim_value(LS, val);
if (parsedvalue.type == SimpleDynamicTag::UNINITIALIZED) {
return "in animation key-value pairs, value must be string, token, number, boolean, or xyz";
}
if (parsedvalue.is_token("auto") && !allowauto) {
return "in animation key-value pairs, value must not be [auto] here.";
}
AnimValue &mapentry = map_[name];
mapentry.copy_value(parsedvalue);
mapentry.persistent = persistent;
}
return "";
}
eng::string AnimState::merge(const AnimState &previous, const AnimState &update) {
// Copy everything over from the previous entry.
map_ = previous.map_;
for (const auto &pair : update.map_) {
const eng::string &name = pair.first;
AnimValue &dst = map_[name];
const AnimValue &src = pair.second;
// Handle autocalculation rules.
if (src.is_token("auto")) {
if (name == "facing") {
if (!dst.persistent || dst.type != SimpleDynamicTag::NUMBER) {
return "Cannot auto-calculate facing because facing has not been specified as a persistent number";
}
const auto xyz_previous = previous.map_.find("xyz");
const auto xyz_update = update.map_.find("xyz");
if ((xyz_previous == previous.map_.end()) ||
(xyz_update == update.map_.end()) ||
(xyz_previous->second.type != SimpleDynamicTag::VECTOR) ||
(xyz_update->second.type != SimpleDynamicTag::VECTOR)) {
return "Cannot auto-calculate facing because before/after xyz coordinates are not present";
}
double dx = xyz_update->second.x - xyz_previous->second.x;
double dy = xyz_update->second.y - xyz_previous->second.y;
// If dx and dy are both zero, leave the facing unmodified.
if ((dx != 0.0) || (dy != 0.0)) {
double facing = atan2(dy, dx) * 180.0 / M_PI;
dst.set_number(facing);
}
} else {
return util::ss("No rule to automatically calculate ", name);
}
continue;
}
if (dst.persistent && (src.type != dst.type)) {
return util::ss("Wrong data type for ", name, ", should be ", dst.type_name());
}
dst.copy_value(src);
}
return "";
}
void AnimState::to_lua(LuaCoreStack &LS0, LuaSlot tab, bool transient, bool persistent) {
LuaVar name, val;
LuaExtStack LS(LS0.state(), name, val);
LS.newtable(tab);
for (const auto &pair : map_) {
if (pair.second.persistent) {
if (!persistent) continue;
} else {
if (!transient) continue;
}
LS.set(name, pair.first);
const AnimValue &value = pair.second;
if (value.type == SimpleDynamicTag::BOOLEAN) {
LS.set(val, (value.x == 1.0));
} else if (value.type == SimpleDynamicTag::NUMBER) {
LS.set(val, value.x);
} else if (value.type == SimpleDynamicTag::STRING) {
LS.set(val, std::string_view(value.s));
} else if (value.type == SimpleDynamicTag::TOKEN) {
LS.set(val, LuaToken(value.s));
} else if (value.type == SimpleDynamicTag::VECTOR) {
LS.newtable(val);
LS.rawset(val, 1, value.x);
LS.rawset(val, 2, value.y);
LS.rawset(val, 3, value.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_simple_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);
2023-10-17 19:55:34 -04:00
AnimValue value;
while (!sb.empty()) {
eng::string name = sb.read_string();
bool persistent = sb.read_bool();
sb.read_simple_dynamic(&value);
if (persistent) {
if ((name == "xyz") && (value.type == SimpleDynamicTag::VECTOR)) xyz = util::DXYZ(value.x, value.y, value.z);
if ((name == "plane") && (value.type == SimpleDynamicTag::STRING)) plane = value.s;
}
}
}
int AnimQueue::get_size_limit() const {
2023-10-03 18:17:24 -04:00
if (encqueue_ == nullptr) return 0;
StreamBuffer sb(*encqueue_);
return sb.read_uint8();
}
int AnimQueue::get_actual_size() const {
2023-10-03 18:17:24 -04:00
if (encqueue_ == nullptr) return 0;
StreamBuffer sb(*encqueue_);
sb.read_bytes(1);
return sb.read_uint8();
}
int64_t AnimQueue::get_final_hash() const {
2023-10-03 18:17:24 -04:00
if (encqueue_ == nullptr) return 0;
StreamBuffer sb(*encqueue_);
sb.read_bytes(2);
return sb.read_uint64();
}
std::string_view AnimQueue::get_final_encstep() const {
2023-10-03 18:17:24 -04:00
if (encqueue_ == nullptr) return std::string_view();
StreamBuffer sb(*encqueue_);
sb.read_bytes(10);
return sb.read_string_view();
}
2021-09-10 17:06:07 -04:00
2023-10-03 18:17:24 -04:00
AnimQueue::QueueRange AnimQueue::get_range(int lo, int hi) {
// Clamp lo and hi to the valid range (0 to actual_size).
//
int actual_size = get_actual_size();
if (lo < 0) lo = 0;
if (hi > actual_size) hi = actual_size;
// Abort early if the range is empty. This avoids several edge cases.
//
if (lo >= hi) return QueueRange(0, std::string_view());
// Get the entries.
//
std::string_view queueview(*encqueue_);
StreamBuffer sb(queueview);
sb.read_bytes(2); // Skip over the header.
for (int i = 0; i < lo; i++) {
sb.read_uint64();
sb.read_string_view();
}
int pos1 = sb.total_reads();
for (int i = lo; i < hi; i++) {
sb.read_uint64();
sb.read_string_view();
}
2023-10-03 18:17:24 -04:00
int pos2 = sb.total_reads();
return QueueRange(hi-lo, queueview.substr(pos1, pos2 - pos1));
}
int64_t AnimQueue::hash_encstep(const QueueRange &prev, std::string_view s) {
int64_t prev_hash = 0;
2023-10-03 18:17:24 -04:00
if (prev.size > 0) {
StreamBuffer retsb(prev.entries);
prev_hash = retsb.read_int64();
}
2023-10-03 18:17:24 -04:00
return ::hash_encstep(prev_hash, s);
}
2023-10-03 18:17:24 -04:00
void AnimQueue::update_encqueue(int limit, bool add, std::string_view add_enc, int keeplo, int keephi) {
// Get the retained entries.
QueueRange keeprange = get_range(keeplo, keephi);
// Encode everything into a binary blob.
StreamBuffer result;
result.write_uint8(limit);
2023-10-03 18:17:24 -04:00
result.write_uint8(keeprange.size + (add ? 1:0));
if (add) {
int64_t add_hash = hash_encstep(keeprange, add_enc);
result.write_uint64(add_hash);
result.write_string(add_enc);
}
2023-10-03 18:17:24 -04:00
result.write_bytes(keeprange.entries);
// Replace the shared string.
2023-08-16 15:40:30 -04:00
encqueue_ = std::make_shared<std::string>(result.view());
}
AnimQueue::AnimQueue() {
2023-10-03 18:17:24 -04:00
update_encqueue(10, true, AnimState().encode(), 0, 0);
}
void AnimQueue::clear(const AnimState &state) {
2023-10-03 18:17:24 -04:00
update_encqueue(get_size_limit(), true, state.encode(), 0, 0);
}
void AnimQueue::clear() {
2023-10-03 18:17:24 -04:00
update_encqueue(get_size_limit(), true, AnimState().encode(), 0, 0);
}
void AnimQueue::set_limit(int limit) {
2023-10-03 18:17:24 -04:00
assert((limit >= 2) && (limit <= 250));
update_encqueue(limit, false, std::string_view(), 0, limit);
}
void AnimQueue::add(const AnimState &state) {
2023-10-03 18:17:24 -04:00
int limit = get_size_limit();
update_encqueue(limit, true, state.encode(), 0, limit - 1);
}
void AnimQueue::replace(const AnimState &state) {
int limit = get_size_limit();
update_encqueue(limit, true, state.encode(), 1, limit);
}
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_dxyz("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;
}