/////////////////////////////////////////////////////////////////// // // ANIMATION QUEUES AND ANIMATION STEPS // // See "Animation Queues and Tangible Actors.md" for an overview. // // An animation queue is a fifo queue of animation steps. New animations are // pushed on the back, and old ones are popped from the front. // // An animation step is a set of key-value pairs, where each key is an // identifier, and each value is either a number, boolean, vector, // token, or string. A key-value pair can be either persistent or nonpersistent. // So a typical animation step might be: // // action=walk [nonpersistent] // xyz=3,4,5 [persistent] // facing=320 [persistent] // plane=earth [persistent] // // Persistent values are retained from one animation step to the next, // nonpersistent values exist for one animation step only. // // Each animation step has a hash value. The hash value is generated // by mixing the hash value of the previous step with the hash value // of the encoded string of key-value pairs. // // /////////////////////////////////////////////////////////////////// // // Class AnimStepEditor is used to read+write animation steps. // // Note: this class is not used for storage of animation steps. // Animation steps are stored in class AnimQueue. AnimStepEditor // is only used when you want to extract animation steps from // an AnimQueue, or insert new animation steps into an AnimQueue. // // Class AnimStepEditor is quite simple: it's a map from Key to // AnimValue. AnimValue is a container that can hold a number, // string, vector, token, or boolean. Class AnimStepEditor provides // a variety of accessors to set key-value pairs. // // For example, you can populate an animation step by setting // key-value pairs directly. You can import key-value pairs // from a lua table. You can also merge key-value pairs from // a different AnimStepEditor. // // When importing from a lua_table or a from another AnimStepEditor, // there are rules for resolving conflicts between any key-value // pairs that are already in the builder with those being imported. // See the documentation for those functions for more information. // /////////////////////////////////////////////////////////////////// // // Class AnimQueue stores animation queues. // // The entired animation queue is stored in a serialized format, // as a shared string. This means that the animation queue can be // passed to the graphics engine as a single string. This can be // accomplished by the function EngineWrapper.get_animation_queues. // // The fact that the queue is stored in a serialized format means // that when manipulating the animation queue, we have to decode it, // manipulate it, and then reencode it. // // From an efficiency perspective, this means that manipulation is // slower, but passing the strings to the graphics engine is faster // and simpler. This is a good tradeoff: we manipulate animation // queues rarely, compared to how often we pass them to the graphics // engine. // /////////////////////////////////////////////////////////////////// // // THE SERIALIZED REPRESENTATION // // So first, you need to know how to serialize a single animation // step. Remember, an animation step consists of a list of key-value // pairs (see above). The key-value pairs are serialized as follows: // // for all key-value pairs do: // write_string(key) // write_bool(persistent) // write_lua_value(value) // // The encoded string produced by the loop above is called an "encstep". // That's short for "encoded animation step". The encstep has a hash // value, which is a function that accepts the encstep and also the hash // of the previous encstep. Note that the hash is not part of the encstep. // // A serialized animation queue consists of the following information: // // write_uint8(size_limit); // write_uint8(actual_size); // for all animation steps, starting with the most recent, do: // write_uint64(hash) // write_string(encstep) // // The encoded string produced by the loop above is called an "encqueue", // because it encodes everything in the animation queue. // // Note that the 'serialize' routine for animation queues just returns // the encqueue string, which is the whole thing. // // Since the steps in an encqueue are stored most-recent first, if you // want some information about the most recent animation entry, you // don't need to decode the entire encqueue. You only need to // decode the first step. // /////////////////////////////////////////////////////////////////// #pragma once #include "wrap-set.hpp" #include "wrap-string.hpp" #include "wrap-deque.hpp" #include "wrap-unordered-map.hpp" #include "base-buffer.hpp" #include "luastack.hpp" #include "streambuffer.hpp" #include "debugcollector.hpp" #include "util.hpp" #include #include struct AnimValue : public LuaValue { bool persistent; AnimValue() { persistent = false; } void set_token(LuaToken token) { type = LuaValueType::TOKEN; s=token.str(); x=y=z=0; } void set_dxyz(const util::DXYZ &xyz) { type = LuaValueType::VECTOR; s.clear(); x = xyz.x; y = xyz.y; z = xyz.z; } bool is_token(const char *t) const { return (type == LuaValueType::TOKEN) && (s == t); } }; class AnimStepEditor { private: using Map = eng::map; Map map_; // Set the default value, internal // eng::string add_default(const eng::string &name, const AnimValue &v, const AnimStepEditor *other); public: // Clear everything // void clear() { map_.clear(); } // Set the persistent flag on a single variable. // If the variable isn't present, add it. // void set_persistent(const eng::string &name); // Return if it contains a value for the specified name. // bool contains(const eng::string &name) { return map_.find(name) != map_.end(); } // Set a single variable to a value of a specified type. // If the variable isn't present, add it. // void set_string(const eng::string &name, std::string_view v) { map_[name].set_string(v); } void set_token(const eng::string &name, LuaToken v) { map_[name].set_token(v); } void set_number(const eng::string &name, double v) { map_[name].set_number(v); } void set_boolean(const eng::string &name, bool v) { map_[name].set_boolean(v); } void set_dxyz(const eng::string &name, const util::DXYZ &v) { map_[name].set_dxyz(v); } // Print a debug string into the stringstream. // void print_debug_string(eng::ostringstream &oss); // Return the debug string. eng::string debug_string(); // Constructs an empty state. // AnimStepEditor() {} // Convert to an encoded string. // eng::string encode() const; // Decode an encoded string. // void decode(std::string_view enc); // Decode an encoded string, discarding non-persistent data. // void decode_persistent(std::string_view enc); // Add default values for all builtin persistent variables. // // For each builtin default (plane, xyz, facing, bp) // // - Will generate an error if a value is already present, // but the present value is of the wrong type. // // - If 'other' is not nullptr, then we look in 'other' for a default // value. If a valid value of the correct type is present, it is // used as the default value. // // - If no default value can be found in 'other', then a hardwired // default value is provided. // eng::string add_defaults(const AnimStepEditor *other); // Parse an animstate from a Lua Table. // // Table keys must be valid lua identifiers. Table values may be string, // floats, bool, or coordinates. Persistent flags are all set the same, // from the persistent parameter. Returns empty string on success, or an // error message on failure. // // If 'allowauto' is true, then the lua table may contain a key-value // pair of the form (key, math.auto). These keys will be stored in the // AnimStepEditor map with mapentry.type == LuaValueType::AUTO. // This is done to express an intent that the value should be // automatically computer later. // eng::string from_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent, bool allowauto); // Generate a merged animstate using a previous state and an update. // // Keys from both previous and update are combined to create this AnimStepEditor. // Values from 'update' override values from 'previous'. Persistent flags // are taken from 'previous'. If a key exists in both previous and update, // and the key is persistent in 'previous', then the types must match, // otherwise an error is generated. Returns empty string on success or // an error message on failure. // // If a key in the 'update' map has type AUTO, then we will attempt to find // a rule to compute that value automatically. Failure to find a rule // results in an error. // eng::string merge(const AnimStepEditor &previous, const AnimStepEditor &update); // Convert an animstate to a lua table. // // You can specify whether you want to include the transient values, the // persistent values, or both. // void to_lua(LuaCoreStack &LS, LuaSlot tab, bool transient, bool persistent); // Parse a string, for unit testing. // // This parses a simple notation designed to facilitate writing unit tests. // The notation looks like this: // // parse("plane=earth xyz=1,2,3 action:jump"); // // Determining the type of the value is done as follows: First, try // interpreting it as boolean true or false. If that fails, // try interpreting it as a number. If that fails, try interpreting it as // a coordinate. If all else fails, it's a string. Obviously, this is a // limited approach: for example, there's no way to express the string "123" // because "123" will get interpreted as a number. But that's ok, since this // is purely intended for unit testing. // // Key-value pairs can have either an equal sign or a colon. If it's an equal // sign, the persistent bit is set, colon means not persistent. // void parse(std::string_view s); void clear_and_parse(std::string_view s); // Constructor from an encoded string. // AnimStepEditor(std::string_view s) { decode(s); } }; struct AnimCoreState { util::DXYZ xyz; eng::string plane; void decode(std::string_view enc); }; class AnimQueue : public eng::nevernew { public: // Construct an empty animation queue. // clears the state to a valid state. // AnimQueue(); // Clear the steps to an initial state. // void clear(); void clear(const AnimStepEditor &initial); // Change the size limit. // void set_limit(int limit); // Add an animation step. // // Note: add does not automatically compose the step with the previous // step, you have to do that yourself. // void add(const AnimStepEditor &state); // Replace the most recent animation step. // // Note: replace does not automatically compose the step with the previous // step, you have to do that yourself. // void replace(const AnimStepEditor &state); // Serialize or deserialize to a StreamBuffer // void serialize(StreamBuffer *sb) const; void deserialize(StreamBuffer *sb); // Difference transmission // bool diff(const AnimQueue &auth, StreamBuffer *sb) const; void patch(StreamBuffer *sb, DebugCollector *dbc); // Check for exactly equal. This does the full check of all // fields, it is used exclusively for debugging. // bool exactly_equal(const AnimQueue &aq) const; // Check for exactly equal (fast). Compares the size, limit, // and hash of the last entry. If these are equal, then the whole // thing should be equal. // bool exactly_equal_fast(const AnimQueue &aq) const; // Debug strings. // void print_debug_string(eng::ostringstream &oss, bool full) const; eng::string steps_debug_string() const; eng::string full_debug_string() const; // Get the final entry, xyz and plane only. // AnimCoreState get_final_core_state() const; // Get the final entry, all persistent variables. // AnimStepEditor get_final_persistent() const; // Get the final entry, everything persistent and non-persistent. // AnimStepEditor get_final_everything() const; // Get a serialized representation of the animation queue. // // Get the entire animation queue in a serialized format (encqueue). // The string returned is a shared string. No string copy // is made during this process. // util::SharedStdString get_encoded_queue() const { return encqueue_; } private: struct QueueRange { int size; std::string_view entries; QueueRange(int sz, std::string_view ent) : size(sz), entries(ent) {} }; // Get a range of entries from the queue. // // You must specify a range (lo-hi) of steps. In this numbering, 0 is the // most recent entry in the queue. The indices lo and hi are automatically // clamped to the valid range (0 to actual_size). If lo >= hi, then an // empty range is returned. // QueueRange get_range(int lo, int hi); // Hash a new step given the range of steps that precede it. // static int64_t hash_encstep(const QueueRange &prev, std::string_view step); // Update the animation queue. // // The range (keeplo to keephi) specifies which old steps should be // retained. The numbers keephi and keeplo are automatically clamped // so that they lie inside the actual size of the queue. // // If add is true, then an additional step is added to the queue. // The hash of the new step is calculated automatically. // // There is no enforcement that you respected the size limit that you // specified. For example, you could say "keep 0-5, and add 1." That // would make 6 entries in the queue. It is up to the caller to respect // the size limit. The value passed in is just for reporting. // void update_encqueue(int limit, bool add, std::string_view add_enc, int keeplo, int keephi); // Read values from the header of the encqueue. // int get_size_limit() const; int get_actual_size() const; int64_t get_final_hash() const; std::string_view get_final_encstep() const; private: // Note: this is stored as a std::string, not an eng::string, because the // ownership ends up being shared between us and the graphics engine. We // can't have the graphics engine affecting the behavior of the engine heap. // util::SharedStdString encqueue_; };