diff --git a/luprex/cpp/core/animqueue.cpp b/luprex/cpp/core/animqueue.cpp index 719bfb9a..27701058 100644 --- a/luprex/cpp/core/animqueue.cpp +++ b/luprex/cpp/core/animqueue.cpp @@ -419,78 +419,110 @@ void AnimCoreState::decode(std::string_view s) { } } -// Just return the hash of the very last step. (Steps are stored last to first). -static uint64_t encqueue_final_hash(std::string_view encqueue) { - StreamBuffer sb(encqueue); - uint64_t hash = sb.read_uint64(); - return hash; +int AnimQueue::get_size_limit() const { + StreamBuffer sb(*encqueue_); + return sb.read_uint8(); } -// Return the encstep for the final step of the animation queue. -static std::string_view encqueue_final_encstep(std::string_view encqueue) { - StreamBuffer sb(encqueue); - sb.read_uint64(); - std::string_view result = sb.read_string_view(); - return result; +int AnimQueue::get_actual_size() const { + StreamBuffer sb(*encqueue_); + sb.read_bytes(1); + return sb.read_uint8(); } -// Return the encqueue for the first N steps. -// If there aren't that many steps, then just return them all. -std::string_view encqueue_finaln(std::string_view encqueue, int n) { - StreamBuffer sb(encqueue); - while ((n > 0) && (!sb.empty())) { - sb.read_uint64(); - uint64_t slen = sb.read_length(); - sb.read_bytes(slen); - n -= 1; +uint64_t AnimQueue::get_final_hash() const { + StreamBuffer sb(*encqueue_); + sb.read_bytes(2); + return sb.read_uint64(); +} + +std::string_view AnimQueue::get_final_encstep() const { + StreamBuffer sb(*encqueue_); + sb.read_bytes(10); + return sb.read_string_view(); +} + +void AnimQueue::update_encqueue(int limit, bool add, std::string_view add_enc, bool keepold) { + // Make sure the size limit is reasonable. + assert((limit >= 2) && (limit <= 250)); + + // You must either add a new step or retain an old step. The queue can't be empty. + assert(keepold || add); + + // Find out how many old steps we'll be retaining, ignoring the size limit. + int nretain = 0; + if (keepold) nretain = get_actual_size(); + + // If retaining all steps would overflow the size limit, retain fewer. + int retain_limit = limit; + if (add) retain_limit -= 1; + if (nretain > retain_limit) nretain = retain_limit; + + // Calculate the new size of the queue. + int new_size = add ? (nretain + 1) : nretain; + + // If we're retaining steps, extract them from the old queue. + std::string_view retain; + if (nretain > 0) { + std::string_view oldqueue(*encqueue_); + StreamBuffer sb(oldqueue); + sb.read_bytes(2); // Skip over the header. + int pos1 = sb.total_reads(); + for (int i = 0; i < nretain; i++) { + sb.read_uint64(); + sb.read_string_view(); + } + int pos2 = sb.total_reads(); + retain = oldqueue.substr(pos1, pos2 - pos1); } - return encqueue.substr(0, sb.total_reads()); -} + // If we're adding a step, calculate its hash. + uint64_t add_hash = 0; + if (add) { + uint64_t prev_hash = 0; + if (nretain > 0) prev_hash = get_final_hash(); + add_hash = hash_encstep(prev_hash, add_enc); + } + + // Finally, encode everything into a binary blob. + StreamBuffer result; + result.write_uint8(limit); + result.write_uint8(new_size); + if (add) { + result.write_uint64(add_hash); + result.write_string(add_enc); + } + result.write_bytes(retain); + + // Replace the shared string. + encqueue_ = std::make_shared(result.view()); +} AnimQueue::AnimQueue() { - size_limit_ = 10; // Default size limit. - clear(); -} - -void AnimQueue::clear() { - AnimState state; - clear(state); + update_encqueue(10, true, AnimState().encode(), false); } void AnimQueue::clear(const AnimState &state) { - StreamBuffer result; - eng::string encstep = state.encode(); - uint64_t hash = hash_encstep(0, encstep); - result.write_uint64(hash); - result.write_string(encstep); - encqueue_ = std::make_shared(result.view()); + update_encqueue(get_size_limit(), true, state.encode(), false); } -void AnimQueue::set_limit(int nkeep) { - assert((nkeep >= 2) && (nkeep <= 250)); - size_limit_ = nkeep; - encqueue_ = std::make_shared(encqueue_finaln(*encqueue_, nkeep)); +void AnimQueue::clear() { + update_encqueue(get_size_limit(), true, AnimState().encode(), false); +} + +void AnimQueue::set_limit(int limit) { + update_encqueue(limit, false, "", true); } void AnimQueue::add(const AnimState &state) { - uint64_t previoushash = encqueue_final_hash(*encqueue_); - eng::string encstep = state.encode(); - uint64_t hash = hash_encstep(previoushash, encstep); - StreamBuffer result; - result.write_uint64(hash); - result.write_string(encstep); - result.write_bytes(encqueue_finaln(*encqueue_, size_limit_ - 1)); - encqueue_ = std::make_shared(result.view()); + update_encqueue(get_size_limit(), true, state.encode(), true); } void AnimQueue::serialize(StreamBuffer *sb) const { - sb->write_uint8(size_limit_); sb->write_string(*encqueue_); } void AnimQueue::deserialize(StreamBuffer *sb) { - size_limit_ = sb->read_uint8(); encqueue_ = std::make_shared(sb->read_string_view()); } @@ -498,55 +530,52 @@ 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_uint8(255); + sb->write_bool(false); return false; } // TODO: maybe send less data? - sb->write_uint8(0); - sb->write_uint32(auth.size_limit_); + sb->write_bool(true); sb->write_string(*auth.encqueue_); return true; } void AnimQueue::patch(StreamBuffer *sb, DebugCollector *dbc) { - int nsteps = sb->read_uint8(); - if (nsteps == 255) { + bool changed = sb->read_bool(); + if (!changed) { return; } DebugLine(dbc) << "AnimQueue modified"; - - size_limit_ = sb->read_uint32(); - std::string_view steps = sb->read_string_view(); - encqueue_ = std::make_shared(steps); + encqueue_ = std::make_shared(sb->read_string_view()); } bool AnimQueue::exactly_equal(const AnimQueue &other) const { - if (size_limit_ != other.size_limit_) return false; if (*encqueue_ != *other.encqueue_) return false; return true; } bool AnimQueue::exactly_equal_fast(const AnimQueue &other) const { - if (size_limit_ != other.size_limit_) return false; if (encqueue_->size() != other.encqueue_->size()) return false; - if (encqueue_->compare(0, 8, *other.encqueue_) != 0) 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; - if (full) { - oss << "limit=" << size_limit(); - first = false; - } // Break out the steps. eng::vector encsteps; - StreamBuffer sb(*encqueue_); - while (!sb.empty()) { + 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]); @@ -568,21 +597,21 @@ eng::string AnimQueue::full_debug_string() const { } AnimCoreState AnimQueue::get_final_core_state() const { - std::string_view encstep = encqueue_final_encstep(*encqueue_); + std::string_view encstep = get_final_encstep(); AnimCoreState result; result.decode(encstep); return result; } AnimState AnimQueue::get_final_persistent() const { - std::string_view encstep = encqueue_final_encstep(*encqueue_); + 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 = encqueue_final_encstep(*encqueue_); + std::string_view encstep = get_final_encstep(); AnimState result; result.decode(encstep); return result; diff --git a/luprex/cpp/core/animqueue.hpp b/luprex/cpp/core/animqueue.hpp index fb952ba5..9d4add91 100644 --- a/luprex/cpp/core/animqueue.hpp +++ b/luprex/cpp/core/animqueue.hpp @@ -64,16 +64,19 @@ // 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. // -// An animation queue consists of a list of steps. Each step has a hash -// and an encstep. An animation queue is serialized as follows: +// A serialized animation queue consists of the following information: // -// for all animation steps, starting with the most recent, do: -// write_uint64(hash) -// write_string(encstep) +// 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 (except for the -// size limit, which is separate). +// 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 @@ -249,18 +252,14 @@ public: // AnimQueue(); - // Size limit. - // - int32_t size_limit() const { return size_limit_; } - - // Clear and set the initial state. + // Clear the steps to an initial state. // void clear(); void clear(const AnimState &initial); - - // Set the size limit. Must be 2-250 + + // Change the size limit. // - void set_limit(int n); + void set_limit(int limit); // Add an animation step. // @@ -317,8 +316,22 @@ public: util::SharedStdString get_encoded_queue() const { return encqueue_; } private: - int size_limit_; + // Update the encoded queue. + // + // You must specify the new size limit. + // You may optionally specify an encstep to add. + // If keepold, then old steps will be retained up to the size limit. + // + void update_encqueue(int limit, bool add, std::string_view add_enc, bool keepold); + // Read values from the header of the encqueue. + // + int get_size_limit() const; + int get_actual_size() const; + uint64_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.