685 lines
22 KiB
C++
685 lines
22 KiB
C++
#include "wrap-sstream.hpp"
|
|
#include "wrap-map.hpp"
|
|
#include "util.hpp"
|
|
#include "animqueue.hpp"
|
|
#include "luastack.hpp"
|
|
#include "streambuffer.hpp"
|
|
|
|
#include <limits>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
|
|
|
|
|
|
static uint64_t hash_encstep(uint64_t prev, std::string_view s) {
|
|
return 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, 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_dxyz(xyz);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void AnimState::set_persistent(const eng::string &name) {
|
|
map_[name].persistent = true;
|
|
}
|
|
|
|
void AnimState::set_boolean(const eng::string &name, bool v) {
|
|
AnimValue &value = map_[name];
|
|
value.set_boolean(v);
|
|
}
|
|
|
|
void AnimState::set_number(const eng::string &name, double v) {
|
|
AnimValue &value = map_[name];
|
|
value.set_number(v);
|
|
}
|
|
|
|
void AnimState::set_dxyz(const eng::string &name, const util::DXYZ &v) {
|
|
AnimValue &value = map_[name];
|
|
value.set_dxyz(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 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;
|
|
case SimpleDynamicTag::STRING: oss << value.s; 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);
|
|
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 "";
|
|
}
|
|
if (value.type != def.type) {
|
|
return util::ss("Animation key ", name, " must be a ", def.type_name());
|
|
}
|
|
return "";
|
|
}
|
|
|
|
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("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 == SimpleDynamicTag::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 != SimpleDynamicTag::UNINITIALIZED) && (mapentry.type != parsedvalue.type)) {
|
|
return util::ss("animation '", name, "' must be a ", mapentry.type_name());
|
|
}
|
|
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 == 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::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_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();
|
|
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 {
|
|
if (encqueue_ == nullptr) return 0;
|
|
StreamBuffer sb(*encqueue_);
|
|
return sb.read_uint8();
|
|
}
|
|
|
|
int AnimQueue::get_actual_size() const {
|
|
if (encqueue_ == nullptr) return 0;
|
|
StreamBuffer sb(*encqueue_);
|
|
sb.read_bytes(1);
|
|
return sb.read_uint8();
|
|
}
|
|
|
|
uint64_t AnimQueue::get_final_hash() const {
|
|
if (encqueue_ == nullptr) return 0;
|
|
StreamBuffer sb(*encqueue_);
|
|
sb.read_bytes(2);
|
|
return sb.read_uint64();
|
|
}
|
|
|
|
std::string_view AnimQueue::get_final_encstep() const {
|
|
if (encqueue_ == nullptr) return std::string_view();
|
|
StreamBuffer sb(*encqueue_);
|
|
sb.read_bytes(10);
|
|
return sb.read_string_view();
|
|
}
|
|
|
|
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();
|
|
}
|
|
int pos2 = sb.total_reads();
|
|
return QueueRange(hi-lo, queueview.substr(pos1, pos2 - pos1));
|
|
}
|
|
|
|
uint64_t AnimQueue::hash_encstep(const QueueRange &prev, std::string_view s) {
|
|
uint64_t prev_hash = 0;
|
|
if (prev.size > 0) {
|
|
StreamBuffer retsb(prev.entries);
|
|
prev_hash = retsb.read_uint64();
|
|
}
|
|
return ::hash_encstep(prev_hash, s);
|
|
}
|
|
|
|
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);
|
|
result.write_uint8(keeprange.size + (add ? 1:0));
|
|
if (add) {
|
|
uint64_t add_hash = hash_encstep(keeprange, add_enc);
|
|
result.write_uint64(add_hash);
|
|
result.write_string(add_enc);
|
|
}
|
|
result.write_bytes(keeprange.entries);
|
|
|
|
// Replace the shared string.
|
|
encqueue_ = std::make_shared<std::string>(result.view());
|
|
}
|
|
|
|
AnimQueue::AnimQueue() {
|
|
update_encqueue(10, true, AnimState().encode(), 0, 0);
|
|
}
|
|
|
|
void AnimQueue::clear(const AnimState &state) {
|
|
update_encqueue(get_size_limit(), true, state.encode(), 0, 0);
|
|
}
|
|
|
|
void AnimQueue::clear() {
|
|
update_encqueue(get_size_limit(), true, AnimState().encode(), 0, 0);
|
|
}
|
|
|
|
void AnimQueue::set_limit(int limit) {
|
|
assert((limit >= 2) && (limit <= 250));
|
|
update_encqueue(limit, false, std::string_view(), 0, limit);
|
|
}
|
|
|
|
void AnimQueue::add(const AnimState &state) {
|
|
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);
|
|
}
|
|
|
|
void AnimQueue::serialize(StreamBuffer *sb) const {
|
|
sb->write_string(*encqueue_);
|
|
}
|
|
|
|
void AnimQueue::deserialize(StreamBuffer *sb) {
|
|
encqueue_ = std::make_shared<std::string>(sb->read_string_view());
|
|
}
|
|
|
|
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);
|
|
return false;
|
|
}
|
|
|
|
// TODO: maybe send less data?
|
|
sb->write_bool(true);
|
|
sb->write_string(*auth.encqueue_);
|
|
return true;
|
|
}
|
|
|
|
void AnimQueue::patch(StreamBuffer *sb, DebugCollector *dbc) {
|
|
bool changed = sb->read_bool();
|
|
if (!changed) {
|
|
return;
|
|
}
|
|
DebugLine(dbc) << "AnimQueue modified";
|
|
encqueue_ = std::make_shared<std::string>(sb->read_string_view());
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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();
|
|
if (full) {
|
|
oss << "limit=" << size_limit;
|
|
first = false;
|
|
}
|
|
for (int i = 0; i < actual_size; i++) {
|
|
sb.read_uint64();
|
|
encsteps.push_back(sb.read_string_view());
|
|
}
|
|
assert(sb.empty());
|
|
for (int i = encsteps.size() - 1; i >= 0; i --) {
|
|
if (!first) oss << "; ";
|
|
AnimState state(encsteps[i]);
|
|
state.print_debug_string(oss);
|
|
first = false;
|
|
}
|
|
}
|
|
|
|
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;
|
|
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;
|
|
}
|
|
|
|
AnimState AnimQueue::get_final_everything() const {
|
|
std::string_view encstep = get_final_encstep();
|
|
AnimState result;
|
|
result.decode(encstep);
|
|
return result;
|
|
}
|
|
|
|
LuaDefine(unittests_animqueue, "", "some unit tests") {
|
|
// Useful objects.
|
|
AnimQueue aq, aqs;
|
|
StreamBuffer sb;
|
|
AnimState astate;
|
|
eng::string enc;
|
|
AnimCoreState core;
|
|
|
|
// 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));
|
|
|
|
return 0;
|
|
}
|