From c2a69b5b235ab45ef83717e60cb0298658396172 Mon Sep 17 00:00:00 2001 From: Josh Yelon Date: Wed, 21 Jul 2021 00:50:06 -0400 Subject: [PATCH] Support for debug_string method for testing --- luprex/core/cpp/animqueue.cpp | 261 ++++++++++++++++++---------------- luprex/core/cpp/animqueue.hpp | 17 ++- luprex/core/cpp/luastack.hpp | 2 +- luprex/core/cpp/textgame.cpp | 2 +- luprex/core/cpp/util.cpp | 55 +++---- luprex/core/cpp/util.hpp | 10 +- 6 files changed, 197 insertions(+), 150 deletions(-) diff --git a/luprex/core/cpp/animqueue.cpp b/luprex/core/cpp/animqueue.cpp index dac97958..9e5bd1d3 100644 --- a/luprex/core/cpp/animqueue.cpp +++ b/luprex/core/cpp/animqueue.cpp @@ -3,6 +3,8 @@ #include "luastack.hpp" #include "animqueue.hpp" #include "streambuffer.hpp" +#include +#include AnimStep::AnimStep() { clear(); @@ -252,6 +254,75 @@ bool AnimStep::echoes(const AnimStep &prev) const { return true; } +std::string AnimStep::debug_string() const { + std::ostringstream oss; + oss << "id=" << id(); + oss << " action=" << action(); + if (has_plane()) { + oss << " plane=" << plane(); + } + if (has_x()) { + oss << " x=" << xyz().x; + } + if (has_y()) { + oss << " y=" << xyz().y; + } + if (has_z()) { + oss << " z=" << xyz().z; + } + if (has_facing()) { + oss << " facing=" << facing(); + } + if (has_graphic()) { + oss << " graphic=" << graphic(); + } + return oss.str(); +} + + +bool AnimStep::from_string(const std::string &config) { + clear(); + util::stringvec parts = util::split(config, ' '); + for (int i = 0; i < int(parts.size()); i++) { + const std::string &part = parts[i]; + if (part == "") continue; + util::stringvec lr = util::split(part, '='); + if (lr.size() != 2) return false; + const std::string &key = lr[0]; + const std::string &val = lr[1]; + if (key == "action") { + action_ = val; + } else if (key == "id") { + int64_t id = util::strtoint(val, -1); + if (id < 0) return false; + id_ = id; + } else if (key == "plane") { + set_plane(val); + } else if (key == "x") { + double v = util::strtodouble(val); + if (std::isnan(v)) return false; + set_x(v); + } else if (key == "y") { + double v = util::strtodouble(val); + if (std::isnan(v)) return false; + set_y(v); + } else if (key == "z") { + double v = util::strtodouble(val); + if (std::isnan(v)) return false; + set_z(v); + } else if (key == "facing") { + double v = util::strtodouble(val); + if (std::isnan(v)) return false; + set_facing(v); + } else if (key == "graphic") { + set_graphic(val); + } else { + return false; + } + } + return true; +} + AnimQueue::AnimQueue(util::WorldType wt) { world_type_ = wt; size_limit_ = 10; // Default size limit. @@ -293,11 +364,16 @@ void AnimQueue::clear_steps() { mutated(); } -void AnimQueue::set_size_limit(int n) { +void AnimQueue::full_clear_and_set_limit(int n) { assert(n >= 1); - if (size_limit_ == n) return; + clear_steps(); size_limit_ = n; - while (int(steps_.size()) > size_limit_) { + version_number_ = 1; +} +void AnimQueue::set_limit(int n) { + assert(n >= 1); + size_limit_ = n; + while (int(steps_.size()) > n) { steps_.pop_front(); } steps_.front().keep_state_only(); @@ -345,6 +421,16 @@ bool AnimQueue::valid() const { return true; } +std::string AnimQueue::debug_string() const { + std::ostringstream oss; + oss << "version=" << version_number(); + oss << "; limit=" << size_limit(); + for (int i = 0; i < int(size()); i++) { + oss << "; " << nth(i).debug_string(); + } + return oss.str(); +} + void AnimQueue::serialize(StreamBuffer *sb) { assert(valid()); // can't serialize an invalid animqueue. sb->write_int64(version_number_); @@ -469,163 +555,98 @@ LuaDefine(unittests_animqueue, "c") { AnimQueue aq(util::WORLD_TYPE_MASTER); StreamBuffer sb; AnimQueue aqds(util::WORLD_TYPE_S_SYNC); - 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(); + LuaAssertStrEq(L, stp.debug_string(), "id=0 action="); stp.set_facing(180); - LuaAssert(L, stp.facing() == 180); - LuaAssert(L, stp.bits() == AnimStep::HAS_FACING); + LuaAssertStrEq(L, stp.debug_string(), "id=0 action= facing=180"); stp.set_x(3); - LuaAssert(L, stp.xyz() == util::XYZ(3, 0, 0)); - LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_X)); + LuaAssertStrEq(L, stp.debug_string(), "id=0 action= x=3 facing=180"); 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)); + LuaAssertStrEq(L, stp.debug_string(), "id=0 action= x=3 y=4 facing=180"); 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)); + LuaAssertStrEq(L, stp.debug_string(), "id=0 action= x=3 y=4 z=5 facing=180"); stp.set_plane("somewhere"); - LuaAssert(L, stp.plane() == "somewhere"); - LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ | AnimStep::HAS_PLANE)); + LuaAssertStrEq(L, stp.debug_string(), "id=0 action= plane=somewhere x=3 y=4 z=5 facing=180"); 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)); + LuaAssertStrEq(L, stp.debug_string(), "id=0 action= plane=somewhere x=3 y=4 z=5 facing=180 graphic=something"); - // Add a step. - stp.clear(); - stp.set_action("walk"); - aq.add(12345, stp); + // Test the step parser. + LuaAssert(L, stp.from_string("id=123 action=walk x=1 y=2 z=3 facing=4 plane=p graphic=g")); + LuaAssertStrEq(L, stp.debug_string(), "id=123 action=walk plane=p x=1 y=2 z=3 facing=4 graphic=g"); + + // Test a blank queue. + aq.full_clear_and_set_limit(3); 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() == ""); + LuaAssertStrEq(L, aq.debug_string(), "version=1; limit=3; id=0 action= plane= x=0 y=0 z=0 facing=0 graphic="); + + // Add a step to a queue. + aq.full_clear_and_set_limit(3); + LuaAssert(L, stp.from_string("action=walk")); + aq.add(12345, stp); + LuaAssertStrEq(L, aq.debug_string(), + "version=2; limit=3; " + "id=0 action= plane= x=0 y=0 z=0 facing=0 graphic=; " + "id=12345 action=walk"); // Exceed the length limit, dropping first element. - stp.clear(); - stp.set_action("walk"); + aq.full_clear_and_set_limit(3); + LuaAssert(L, stp.from_string("action=walk plane=foo")); + aq.add(12345, stp); 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()); + LuaAssertStrEq(L, aq.debug_string(), + "version=4; limit=3; " + "id=0 action= plane=foo x=0 y=0 z=0 facing=0 graphic=; " + "id=12346 action=walk plane=foo; " + "id=12347 action=walk plane=foo" + ); // 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.full_clear_and_set_limit(5); + LuaAssert(L, stp.from_string("action=walk x=3 y=4 z=5")); aq.add(12345, stp); - - stp.clear(); - stp.set_action("setgraphic"); - stp.set_graphic("banana"); + LuaAssert(L, stp.from_string("action=setgraphic graphic=banana")); aq.add(12346, stp); - - stp.clear(); - stp.set_action("setfacing"); - stp.set_facing(301.0); + LuaAssert(L, stp.from_string("action=setfacing facing=301")); 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() == ""); + LuaAssertStrEq(L, aqds.debug_string(), + "version=4; limit=5; " + "id=0 action= plane= x=0 y=0 z=0 facing=0 graphic=; " + "id=12345 action=walk x=3 y=4 z=5; " + "id=12346 action=setgraphic graphic=banana; " + "id=12347 action=setfacing facing=301" + ); - 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.full_clear_and_set_limit(10); + aqds.full_clear_and_set_limit(10); + + // Add a single action to the queue and DT + LuaAssert(L, stp.from_string("action=walk x=3 y=4 z=5")); aq.add(12345, stp); - sb.clear(); LuaAssert(L, aqds.make_patch(aq, &sb)); aqds.apply_patch(&sb); LuaAssert(L, aqds.size_and_steps_equal(aq)); - // Add another action. - stp.clear(); - stp.set_action("fnord"); - stp.set_facing(123); - stp.set_plane("where"); + // Add another action and DT + LuaAssert(L, stp.from_string("action=fnord plane=where facing=123")); 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.total_writes() - sb.total_reads() == 4); LuaAssert(L, aqds.size_and_steps_equal(aq)); // Change the queue size limit. - aq.set_size_limit(13); - - // Generate diffs, but add 4 extra bytes. + aq.set_limit(13); sb.clear(); LuaAssert(L, !aqds.size_and_steps_equal(aq)); LuaAssert(L, aqds.make_patch(aq, &sb)); @@ -639,7 +660,7 @@ LuaDefine(unittests_animqueue, "c") { LuaAssert(L, aqds.size_and_steps_equal(aq)); // Discard all but the last action. - aq.set_size_limit(1); + aq.set_limit(1); sb.clear(); LuaAssert(L, aqds.make_patch(aq, &sb)); diff --git a/luprex/core/cpp/animqueue.hpp b/luprex/core/cpp/animqueue.hpp index 5db7e7dd..6076898c 100644 --- a/luprex/core/cpp/animqueue.hpp +++ b/luprex/core/cpp/animqueue.hpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include "streambuffer.hpp" #include "util.hpp" @@ -155,8 +156,15 @@ public: // Verify that this step echoes the previous step. bool echoes(const AnimStep &prev) const; + + // Convert to a string for debugging purposes. + std::string debug_string() const; + + // Convert a string to an animstep (for testing only). + bool from_string(const std::string &s); }; + class AnimQueue { private: util::WorldType world_type_; @@ -174,6 +182,7 @@ public: const AnimStep &nth(int n) const { return steps_[n]; } size_t size() const { return steps_.size(); } int32_t size_limit() const { return size_limit_; } + int64_t version_number() const { return version_number_; } // Return true if the size limit and steps are identical. bool size_and_steps_equal(const AnimQueue &aq) const; @@ -199,10 +208,14 @@ public: const AnimStep &back() const; // (For testing): change the size limit. - void set_size_limit(int32_t n); - + void full_clear_and_set_limit(int szl); + void set_limit(int szl); + // (For testing): make sure the invariants are preserved. bool valid() const; + + // Convert to a string for debugging purposes. + std::string debug_string() const; }; #endif // ANIMQUEUE_HPP diff --git a/luprex/core/cpp/luastack.hpp b/luprex/core/cpp/luastack.hpp index f1d0530b..a48124b3 100644 --- a/luprex/core/cpp/luastack.hpp +++ b/luprex/core/cpp/luastack.hpp @@ -514,5 +514,5 @@ public: #define LuaStringify(x) #x #define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); } - +#define LuaAssertStrEq(L, x, y) { std::string _s1_ = (x); std::string _s2_ = (y); if (_s1_ != _s2_) luaL_error((L), "Assert failed: value=%s (file %s line %d)", _s1_.c_str(), __FILE__, __LINE__); } #endif // LUASTACK_HPP diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index 5e3cbb61..6f16f9b4 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -85,7 +85,7 @@ void TextGame::do_view_command(const StringVec &cmd) { for (int64_t id : world_->get_near(actor_id_, 100, true)) { const Tangible *tan = world_->tangible_get(id); const AnimStep &aqback = tan->anim_queue_.back(); - std::cerr << id << ": " << aqback.graphic() << " " << aqback.plane() << " " << aqback.xyz() << std::endl; + std::cerr << id << ": " << aqback.graphic() << " " << aqback.plane() << " " << aqback.xyz().debug_string() << std::endl; } } diff --git a/luprex/core/cpp/util.cpp b/luprex/core/cpp/util.cpp index eef94132..a4054bf6 100644 --- a/luprex/core/cpp/util.cpp +++ b/luprex/core/cpp/util.cpp @@ -5,6 +5,7 @@ #include "util.hpp" #include #include +#include #ifndef WIN32 #include #define stat _stat @@ -12,6 +13,21 @@ namespace util { +stringvec split(const std::string &s, char sep) { + stringvec result; + int start = 0; + for (int i = 0; i < int(s.size()); i++) { + if (s[i] == sep) { + result.push_back(s.substr(start, i-start)); + start = i+1; + } + } + if (start < int(s.size())) { + result.push_back(s.substr(start)); + } + return result; +} + std::string tolower(std::string input) { for (int i = 0; i < int(input.size()); i++) { input[i] = std::tolower(input[i]); @@ -42,6 +58,16 @@ int64_t strtoint(const std::string &value, int64_t errval) { } } +double strtodouble(const std::string &value) { + char *endptr; + double result = strtod(value.c_str(), &endptr); + if (endptr == value.c_str() + value.size()) { + return result; + } else { + return std::nan(""); + } +} + std::string ltrim(std::string s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); @@ -97,31 +123,10 @@ double distance_squared(double x1, double y1, double x2, double y2) { return dx*dx + dy*dy; } -// These hash functions are part of Bob Jenkins' Lookup3. -#define hash_rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) - -#define hash_mix(a,b,c) { \ - a -= c; a ^= hash_rot(c, 4); c += b; \ - b -= a; b ^= hash_rot(a, 6); a += c; \ - c -= b; c ^= hash_rot(b, 8); b += a; \ - a -= c; a ^= hash_rot(c,16); c += b; \ - b -= a; b ^= hash_rot(a,19); a += c; \ - c -= b; c ^= hash_rot(b, 4); b += a; \ -} - -#define hash_final(a,b,c) { \ - c ^= b; c -= hash_rot(b,14); \ - a ^= c; a -= hash_rot(c,11); \ - b ^= a; b -= hash_rot(a,25); \ - c ^= b; c -= hash_rot(b,16); \ - a ^= c; a -= hash_rot(c,4); \ - b ^= a; b -= hash_rot(a,14); \ - c ^= b; c -= hash_rot(b,24); \ -} - -std::ostream & operator << (std::ostream &out, const XYZ &xyz) { - out << "(" << xyz.x << "," << xyz.y << "," << xyz.z << ")"; - return out; +std::string XYZ::debug_string() const { + std::ostringstream oss; + oss << "(" << x << "," << y << "," << z << ")"; + return oss.str(); } } // namespace util diff --git a/luprex/core/cpp/util.hpp b/luprex/core/cpp/util.hpp index 9d2e8b58..ab512277 100644 --- a/luprex/core/cpp/util.hpp +++ b/luprex/core/cpp/util.hpp @@ -22,6 +22,9 @@ using stringvec = std::vector; using stringset = std::set; using HashValue = std::pair; +// Split a string into multiple strings +stringvec split(const std::string &s, char sep); + // String to lowercase/uppercase std::string tolower(std::string input); std::string toupper(std::string input); @@ -32,6 +35,9 @@ bool validinteger(const std::string &value); // String to integer. Returns errval if the number is not parseable. int64_t strtoint(const std::string &value, int64_t errval); +// String to double. Returns NAN if the number is not parseable. +double strtodouble(const std::string &value); + // Trim strings: left end, right end, both ends. std::string ltrim(std::string s); std::string rtrim(std::string s); @@ -49,6 +55,7 @@ std::string get_file_fingerprint(const std::string &path); // Calculate distance between two points double distance_squared(double x1, double y1, double x2, double y2); + // An XYZ coordinate, general purpose. struct XYZ { float x, y, z; @@ -56,8 +63,9 @@ struct XYZ { XYZ(float ix, float iy, float iz) { x=ix; y=iy; z=iz; } bool operator ==(const XYZ &o) const { return x==o.x && y == o.y && z==o.z; } bool operator !=(const XYZ &o) const { return x!=o.x || y != o.y || z!=o.z; } + std::string debug_string() const; }; -std::ostream & operator << (std::ostream &out, const XYZ &xyz); } // namespace util + #endif // UTIL_HPP