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

606 lines
17 KiB
C++

#include <limits>
#include <map>
#include "luastack.hpp"
#include "animqueue.hpp"
AnimStep::AnimStep() {
clear();
}
AnimStep::~AnimStep() {}
void AnimStep::clear() {
id_ = 0;
bits_ = 0;
action_ = "";
facing_ = 0;
xyz_ = util::XYZ(0,0,0);
graphic_ = "";
plane_ = "";
}
void AnimStep::set_action(const std::string &act) {
action_ = act;
}
void AnimStep::set_facing(float f) {
bits_ |= HAS_FACING;
facing_ = f;
}
void AnimStep::set_x(float f) {
bits_ |= HAS_X;
xyz_.x = f;
}
void AnimStep::set_y(float f) {
bits_ |= HAS_Y;
xyz_.y = f;
}
void AnimStep::set_z(float f) {
bits_ |= HAS_Z;
xyz_.z = f;
}
void AnimStep::set_xyz(const util::XYZ &xyz) {
bits_ |= (HAS_X | HAS_Y | HAS_Z);
xyz_ = xyz;
}
void AnimStep::set_graphic(const std::string &g) {
bits_ |= HAS_GRAPHIC;
graphic_ = g;
}
void AnimStep::set_plane(const std::string &p) {
bits_ |= HAS_PLANE;
plane_ = p;
}
bool AnimStep::exactly_equal(const AnimStep &other) const {
if (id_ != other.id_) return false;
if (bits_ != other.bits_) return false;
if (action_ != other.action_) return false;
if (facing_ != other.facing_) return false;
if (xyz_ != other.xyz_) return false;
if (graphic_ != other.graphic_) return false;
if (plane_ != other.plane_) return false;
return true;
}
bool AnimStep::logically_equal(const AnimStep &other) const {
if (id_ != other.id_) return false;
if (bits_ != other.bits_) return false;
if (action_ != other.action_) return false;
if (has_facing() && (facing_ != other.facing_)) return false;
if (has_x() && (xyz_.x != other.xyz_.x)) return false;
if (has_y() && (xyz_.y != other.xyz_.y)) return false;
if (has_z() && (xyz_.z != other.xyz_.z)) return false;
if (has_graphic() && (graphic_ != other.graphic_)) return false;
if (has_plane() && (plane_ != other.plane_)) return false;
return true;
}
bool AnimStep::state_equal(const AnimStep &other) const {
if (facing_ != other.facing_) return false;
if (xyz_ != other.xyz_) return false;
if (graphic_ != other.graphic_) return false;
if (plane_ != other.plane_) return false;
return true;
}
void AnimStep::write_into(StreamBuffer *sb) const {
sb->write_int64(id_);
sb->write_int16(bits_);
sb->write_string(action_);
if (has_facing()) {
sb->write_float(facing_);
}
if (has_x()) {
sb->write_float(xyz_.x);
}
if (has_y()) {
sb->write_float(xyz_.y);
}
if (has_z()) {
sb->write_float(xyz_.z);
}
if (has_graphic()) {
sb->write_string(graphic_);
}
if (has_plane()) {
sb->write_string(plane_);
}
}
void AnimStep::read_from(StreamBuffer *sb) {
id_ = sb->read_int64();
bits_ = sb->read_int16();
action_ = sb->read_string();
if (has_facing()) {
facing_ = sb->read_float();
}
if (has_x()) {
xyz_.x = sb->read_float();
}
if (has_y()) {
xyz_.y = sb->read_float();
}
if (has_z()) {
xyz_.z = sb->read_float();
}
if (has_graphic()) {
graphic_ = sb->read_string();
}
if (has_plane()) {
plane_ = sb->read_string();
}
}
void AnimStep::from_lua_store_string(lua_State *L, int idx, std::string *target, int16_t bits, const char *name) {
if ((bits_ & bits)||(*target != "")) {
luaL_error(L, "specified %s twice", name);
}
if (lua_type(L, idx) != LUA_TSTRING) {
luaL_error(L, "Expected %s to be a string", name);
}
*target = lua_tostring(L, idx);
bits_ |= bits;
}
void AnimStep::from_lua_store_number(lua_State *L, int idx, float *target, float offset, int16_t bits, const char *name) {
if (bits_ & bits) {
luaL_error(L, "specified %s twice", name);
}
if (lua_type(L, idx) != LUA_TNUMBER) {
luaL_error(L, "Expected %s to be a number", name);
}
*target = lua_tonumber(L, idx) + offset;
bits_ |= bits;
}
void AnimStep::from_lua(lua_State *L, int idx, const AnimStep &qback) {
LuaSpecial tab(idx);
LuaVar key, value;
LuaStack LS(L, key, value);
if (!LS.istable(tab)) {
luaL_error(L, "animation spec must be a table");
}
LS.set(key, LuaNil);
while (LS.next(tab, key, value)) {
if (!LS.isstring(key)) {
luaL_error(L, "animation specs must be key/value where key is a string");
}
std::string skey = LS.ckstring(key);
if (skey == "action") {
from_lua_store_string(L, value.index(), &action_, 0, "action");
} else if (skey == "graphic") {
from_lua_store_string(L, value.index(), &graphic_, HAS_GRAPHIC, "graphic");
} else if (skey == "plane") {
from_lua_store_string(L, value.index(), &plane_, HAS_PLANE, "plane");
} else if (skey == "x") {
from_lua_store_number(L, value.index(), &xyz_.x, 0.0, HAS_X, "X coordinate");
} else if (skey == "y") {
from_lua_store_number(L, value.index(), &xyz_.y, 0.0, HAS_Y, "Z coordinate");
} else if (skey == "z") {
from_lua_store_number(L, value.index(), &xyz_.z, 0.0, HAS_Z, "Z coordinate");
} else if (skey == "dx") {
from_lua_store_number(L, value.index(), &xyz_.x, qback.xyz().x, HAS_X, "X coordinate");
} else if (skey == "dy") {
from_lua_store_number(L, value.index(), &xyz_.y, qback.xyz().y, HAS_Y, "Y coordinate");
} else if (skey == "dz") {
from_lua_store_number(L, value.index(), &xyz_.z, qback.xyz().z, HAS_Z, "Z coordinate");
} else if (skey == "facing") {
from_lua_store_number(L, value.index(), &facing_, 0.0, HAS_FACING, "facing");
} else {
luaL_error(L, "Unrecognized animation spec: %s", skey.c_str());
}
}
}
void AnimStep::keep_state_only() {
bits_ = HAS_EVERYTHING;
id_ = 0;
action_ = "";
}
void AnimStep::echo(const AnimStep &prev) {
if (!has_facing()) {
facing_ = prev.facing_;
}
if (!has_x()) {
xyz_.x = prev.xyz_.x;
}
if (!has_y()) {
xyz_.y = prev.xyz_.y;
}
if (!has_z()) {
xyz_.z = prev.xyz_.z;
}
if (!has_graphic()) {
graphic_ = prev.graphic_;
}
if (!has_plane()) {
plane_ = prev.plane_;
}
}
bool AnimStep::echoes(const AnimStep &prev) const {
if (!has_facing() && (facing_ != prev.facing_)) {
return false;
}
if (!has_x() && (xyz_.x != prev.xyz_.x)) {
return false;
}
if (!has_y() && (xyz_.y != prev.xyz_.y)) {
return false;
}
if (!has_z() && (xyz_.z != prev.xyz_.z)) {
return false;
}
if (!has_graphic() && (graphic_ != prev.graphic_)) {
return false;
}
if (!has_plane() && (plane_ != prev.plane_)) {
return false;
}
return true;
}
AnimQueue::AnimQueue() {
size_limit_ = 10; // Default size limit.
clear_steps();
}
bool AnimQueue::exactly_equal(const AnimQueue &other) const {
if (steps_.size() != other.steps_.size()) {
return false;
}
for (int i = 0; i < int(steps_.size()); i++) {
if (!steps_[i].exactly_equal(other.steps_[i])) {
return false;
}
}
return true;
}
void AnimQueue::clear_steps() {
steps_.clear();
steps_.emplace_back();
AnimStep &init = steps_.back();
init.bits_ = AnimStep::HAS_EVERYTHING;
init.action_ = "";
init.graphic_ = "";
init.plane_ = "";
}
void AnimQueue::set_size_limit(int n) {
assert(n >= 2);
size_limit_ = n;
}
void AnimQueue::keep_only(int n) {
if (n < 1) n = 1;
while (int(steps_.size()) > n) {
steps_.pop_front();
}
steps_.front().keep_state_only();
}
void AnimQueue::add(int64_t id, const AnimStep &step) {
AnimStep copy = step;
copy.echo(steps_.back());
copy.id_ = id;
steps_.push_back(copy);
keep_only(size_limit_);
}
bool AnimQueue::valid() const {
// Animqueue must have at least one step, and no more than 255.
if (steps_.empty()) {
return false;
}
if (steps_.size() > 255) {
return false;
}
// First action should be blank.
if (steps_[0].action_ != "") {
return false;
}
// First step should have all bits.
if (steps_[0].bits_ != AnimStep::HAS_EVERYTHING) {
return false;
}
// Any unset bit should correspond to a value copied from the previous step.
for (size_t i = 1; i < steps_.size(); i++) {
const AnimStep &prev = steps_[i - 1];
const AnimStep &curr = steps_[i];
if (!curr.echoes(prev)) return false;
}
return true;
}
void AnimQueue::serialize(StreamBuffer *sb) {
assert(valid()); // can't serialize an invalid animqueue.
sb->write_int32(size_limit_);
sb->write_size(steps_.size());
for (const AnimStep &step : steps_) {
step.write_into(sb);
}
}
void AnimQueue::deserialize(StreamBuffer *sb) {
size_limit_ = sb->read_int32();
size_t nsteps = sb->read_size();
steps_.resize(nsteps);
for (size_t i = 0; i < nsteps; i++) {
AnimStep &step = steps_[i];
step.read_from(sb);
if (i > 0) step.echo(steps_[i - 1]);
}
}
bool AnimQueue::make_patch(const AnimQueue &auth, StreamBuffer *sb) const {
// Sanity check.
assert(valid());
assert(auth.valid());
// Special case: if we're already a perfect match, output 0 bytes.
if (exactly_equal(auth)) {
sb->write_uint8(0);
return false;
}
// Write the first element.
sb->write_uint8(auth.steps_.size());
const AnimStep &first = auth.steps_[0];
int match = 0;
while ((match < int(steps_.size())) && (!steps_[match].state_equal(first))) {
match++;
}
if (match == int(steps_.size())) {
sb->write_uint8(255);
first.write_into(sb);
} else {
sb->write_uint8(match);
}
// Index the remaining elements by id.
std::map<uint64_t, int> index;
for (int i = 1; i < int(steps_.size()); i++) {
index[steps_[i].id_] = i;
}
// Write the remaining elements.
for (int i = 1; i < int(auth.steps_.size()); i++) {
const AnimStep &step = auth.steps_[i];
auto iter = index.find(step.id());
if (iter == index.end()) {
sb->write_uint8(255);
step.write_into(sb);
} else {
const AnimStep &local = steps_[iter->second];
if (local.exactly_equal(step)) {
sb->write_uint8(iter->second);
} else {
sb->write_uint8(255);
step.write_into(sb);
}
}
}
return true;
}
void AnimQueue::apply_patch(StreamBuffer *sb) {
int len = sb->read_uint8();
if (len == 0) {
return;
}
// Decode the diff, stop at eof.
std::deque<AnimStep> old = std::move(steps_);
steps_.clear();
for (int i = 0; i < len; i++) {
uint8_t index = sb->read_uint8();
if (index < 255) {
assert(index < old.size());
steps_.push_back(old[index]);
} else {
AnimStep step;
step.read_from(sb);
steps_.push_back(step);
}
int size = steps_.size();
if (size > 1) {
steps_[size-1].echo(steps_[size-2]);
} else {
steps_[0].keep_state_only();
}
}
}
const AnimStep &AnimQueue::back() const {
return steps_.back();
}
LuaDefine(unittests_animqueue, "c") {
// Check initial state.
AnimStep stp;
AnimQueue aq;
StreamBuffer sb;
AnimQueue aqds;
aq.set_size_limit(3);
LuaAssert(L, aq.valid());
LuaAssert(L, aq.size() == 1);
const AnimStep *st = &aq.nth(0);
LuaAssert(L, st->id() == 0);
LuaAssert(L, st->action() == "");
LuaAssert(L, st->bits() == AnimStep::HAS_EVERYTHING);
LuaAssert(L, st->facing() == 0.0);
LuaAssert(L, st->xyz() == util::XYZ(0,0,0));
LuaAssert(L, st->graphic() == "");
LuaAssert(L, st->plane() == "");
// Test the step setters.
stp.clear();
stp.set_facing(180);
LuaAssert(L, stp.facing() == 180);
LuaAssert(L, stp.bits() == AnimStep::HAS_FACING);
stp.set_x(3);
LuaAssert(L, stp.xyz() == util::XYZ(3, 0, 0));
LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_X));
stp.set_y(4);
LuaAssert(L, stp.xyz() == util::XYZ(3, 4, 0));
LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_X | AnimStep::HAS_Y));
stp.set_z(5);
LuaAssert(L, stp.xyz() == util::XYZ(3, 4, 5));
LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_X | AnimStep::HAS_Y | AnimStep::HAS_Z));
stp.set_plane("somewhere");
LuaAssert(L, stp.plane() == "somewhere");
LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ | AnimStep::HAS_PLANE));
stp.set_graphic("something");
LuaAssert(L, stp.graphic() == "something");
LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ | AnimStep::HAS_PLANE | AnimStep::HAS_GRAPHIC));
// Add a step.
stp.clear();
stp.set_action("walk");
aq.add(12345, stp);
LuaAssert(L, aq.valid());
LuaAssert(L, aq.size() == 2);
st = &aq.nth(1);
LuaAssert(L, st->id() == 12345);
LuaAssert(L, st->action() == "walk");
LuaAssert(L, st->bits() == 0);
LuaAssert(L, st->facing() == 0.0);
LuaAssert(L, st->xyz() == util::XYZ(0,0,0));
LuaAssert(L, st->graphic() == "");
LuaAssert(L, st->plane() == "");
// Exceed the length limit, dropping first element.
stp.clear();
stp.set_action("walk");
aq.add(12346, stp);
aq.add(12347, stp);
LuaAssert(L, aq.size() == 3);
LuaAssert(L, aq.nth(0).id() == 0);
LuaAssert(L, aq.nth(0).action() == "");
LuaAssert(L, aq.nth(0).bits() == AnimStep::HAS_EVERYTHING);
LuaAssert(L, aq.nth(1).id() == 12346);
LuaAssert(L, aq.nth(2).id() == 12347);
LuaAssert(L, aq.valid());
// Test serialization and deserialization.
aq.set_size_limit(5);
aq.clear_steps();
stp.clear();
stp.set_action("walk");
stp.set_xyz(util::XYZ(3,4,5));
aq.add(12345, stp);
stp.clear();
stp.set_action("setgraphic");
stp.set_graphic("banana");
aq.add(12346, stp);
stp.clear();
stp.set_action("setfacing");
stp.set_facing(301.0);
aq.add(12347, stp);
aq.serialize(&sb);
aqds.deserialize(&sb);
LuaAssert(L, aqds.size_limit() == 5);
LuaAssert(L, aqds.size() == 4);
LuaAssert(L, aqds.nth(0).id() == 0);
LuaAssert(L, aqds.nth(0).bits() == AnimStep::HAS_EVERYTHING);
LuaAssert(L, aqds.nth(0).action() == "");
LuaAssert(L, aqds.nth(0).facing() == 0.0);
LuaAssert(L, aqds.nth(0).xyz() == util::XYZ(0,0,0));
LuaAssert(L, aqds.nth(0).graphic() == "");
LuaAssert(L, aqds.nth(0).plane() == "");
LuaAssert(L, aqds.nth(1).id() == 12345);
LuaAssert(L, aqds.nth(1).bits() == AnimStep::HAS_XYZ);
LuaAssert(L, aqds.nth(1).action() == "walk");
LuaAssert(L, aqds.nth(1).facing() == 0.0);
LuaAssert(L, aqds.nth(1).xyz() == util::XYZ(3,4,5));
LuaAssert(L, aqds.nth(1).graphic() == "");
LuaAssert(L, aqds.nth(1).plane() == "");
LuaAssert(L, aqds.nth(2).id() == 12346);
LuaAssert(L, aqds.nth(2).bits() == AnimStep::HAS_GRAPHIC);
LuaAssert(L, aqds.nth(2).action() == "setgraphic");
LuaAssert(L, aqds.nth(2).facing() == 0.0);
LuaAssert(L, aqds.nth(2).xyz() == util::XYZ(3,4,5));
LuaAssert(L, aqds.nth(2).graphic() == "banana");
LuaAssert(L, aqds.nth(2).plane() == "");
LuaAssert(L, aqds.nth(3).id() == 12347);
LuaAssert(L, aqds.nth(3).bits() == AnimStep::HAS_FACING);
LuaAssert(L, aqds.nth(3).action() == "setfacing");
LuaAssert(L, aqds.nth(3).facing() == 301.0);
LuaAssert(L, aqds.nth(3).xyz() == util::XYZ(3,4,5));
LuaAssert(L, aqds.nth(3).graphic() == "banana");
LuaAssert(L, aqds.nth(3).plane() == "");
// Test difference transmission
// Start with an anim queue with an initial state and a single action.
aq.set_size_limit(10);
aqds.set_size_limit(10);
aq.clear_steps();
aqds.clear_steps();
stp.clear();
stp.set_action("walk");
stp.set_xyz(util::XYZ(3,4,5));
aq.add(12345, stp);
sb.clear();
LuaAssert(L, aqds.make_patch(aq, &sb));
aqds.apply_patch(&sb);
LuaAssert(L, aqds.exactly_equal(aq));
// Add another action.
stp.clear();
stp.set_action("fnord");
stp.set_facing(123);
stp.set_plane("where");
aq.add(232, stp);
// Generate diffs, but add 4 extra bytes.
sb.clear();
LuaAssert(L, aqds.make_patch(aq, &sb));
sb.write_uint32(0);
// Apply the diffs, 4 extra bytes should remain.
aqds.apply_patch(&sb);
LuaAssert(L, sb.write_count() - sb.read_count() == 4);
LuaAssert(L, aqds.exactly_equal(aq));
// compare again, should be no differences.
sb.clear();
LuaAssert(L, !aqds.make_patch(aq, &sb));
aqds.apply_patch(&sb);
LuaAssert(L, aqds.exactly_equal(aq));
// Discard all but the last action.
aq.keep_only(1);
sb.clear();
LuaAssert(L, aqds.make_patch(aq, &sb));
aqds.apply_patch(&sb);
LuaAssert(L, aqds.exactly_equal(aq));
return 0;
}