#include "AnimQueue.h" FlxAnimStep FlxAnimQueueDecoder::ReadStep() { FlxAnimStep result; result.Hash = Decoder.read_uint64(); result.Body = Decoder.read_string_view(); return result; } FlxAnimField FlxAnimStepDecoder::ReadField() { FlxAnimField result; result.Name = Decoder.read_string_view(); result.Persistent = Decoder.read_bool(); result.Type = (ElxAnimValueType)Decoder.read_uint8(); switch (result.Type) { case T_STRING: { result.S = Decoder.read_string_view(); break; } case T_NUMBER: { result.X = Decoder.read_double(); break; } case T_BOOLEAN: { result.X = Decoder.read_bool() ? 1.0 : 0.0; break; } case T_XYZ: { result.X = Decoder.read_double(); result.Y = Decoder.read_double(); result.Z = Decoder.read_double(); break; } default: { Decoder.set_at_eof(); result.Type = T_BOOLEAN; result.X = 0; break; } } return result; } FString FlxAnimQueueDecoder::DebugString(std::string_view queue) { FString result; FlxAnimQueueDecoder decoder(queue); while (!decoder.AtEOF()) { FlxAnimStep step = decoder.ReadStep(); FString stepdebug = FlxAnimStepDecoder::DebugString(step); result.Appendf(TEXT("%s\n"), *stepdebug); } return result; } FString FlxAnimStepDecoder::DebugString(const FlxAnimStep& step) { FString result; FlxAnimStepDecoder decoder(step); bool first = true; while (!decoder.AtEOF()) { FlxAnimField field = decoder.ReadField(); if (!first) { result.Append(TEXT(" ")); } result.Append(FString(field.Name.size(), (const UTF8CHAR*)field.Name.data())); result.Append(field.Persistent ? TEXT("=") : TEXT(":")); switch (field.Type) { case ElxAnimValueType::T_STRING: result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data())); break; case ElxAnimValueType::T_NUMBER: result.Appendf(TEXT("%lf"), field.X); break; case ElxAnimValueType::T_BOOLEAN: result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false")); break; case ElxAnimValueType::T_XYZ: result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z); break; } first = false; } return result; } //// Our own copy of the animation queue. We only //// store the hashes, not the steps. The First element //// of the queue is the oldest item. //// //TDeque AQ; //// The sequence number of the first item in AQ. //// //int32 FirstSeqno; //// Map from hash to sequence number. //// //TMap HashToSeqno; //// The sequence number of the first unstarted animation. //// //int32 UnstartedSeqno; //// Array of recently-aborted hash values. //TArray AbortedHashes; FlxAnimTracker::FlxAnimTracker() { AQ.Empty(); FirstSeqno = 0; HashToSeqno.Empty(); UnstartedSeqno = 0; AbortedHashes.Empty(); } void FlxAnimTracker::Update(std::string_view encqueue) { // Check for an exact match. If the most recent entry // in encqueue has the same hash as the most recent // entry in AQ, then that's an exact match. Or, // if both are empty, that's also an exact match. // In case of exact match, abort early, there's no // further work needed. // if (encqueue.empty()) { if (AQ.IsEmpty()) { return; } } else { if (!AQ.IsEmpty()) { FlxStringDecoder qdecoder(encqueue); uint64 hash = qdecoder.read_uint64(); if (hash == AQ.Last().Hash) { return; } } } // Search for a Luprex animation record that matches // something that we already have. Yields the sequence // number of the most recent matching record. // // At the same time, push all non-matching records // after the matching record onto a stack of new records. // FlxAnimQueueDecoder decoder(encqueue); TArray newsteps; int32 matchingseqno = -1; while (!decoder.AtEOF()) { FlxAnimStep step = decoder.ReadStep(); int32* stepseq = HashToSeqno.Find(step.Hash); if (stepseq == nullptr) { newsteps.Emplace(step); } else { matchingseqno = *stepseq; break; } } // Abort all animations after the most recent matching // record. Animations are aborted in most-recent-first order. // int32 nabort = (FirstSeqno + AQ.Num()) - (matchingseqno + 1); check((nabort >= 0) && (nabort <= AQ.Num())); for (int32 i = 0; i < nabort; i++) { uint64 lasthash = AQ.Last().Hash; HashToSeqno.Remove(lasthash); AbortedHashes.Emplace(lasthash); AQ.PopLast(); } // Transfer the new animations onto the queue. // while (!newsteps.IsEmpty()) { FlxAnimStep step = newsteps.Pop(); int32 seqno = FirstSeqno + AQ.Num(); AQ.EmplaceLast(step.Hash, step.Body); HashToSeqno.Emplace(step.Hash, seqno); } // Move back the Unstarted pointer, so that the // unstarted range includes all the new animations. // if (UnstartedSeqno > matchingseqno + 1) { UnstartedSeqno = matchingseqno + 1; } // If there are too many animations in AQ, discard // any very old ones. // if (AQ.Num() > 10) { int ndiscard = AQ.Num() - 10; for (int i = 0; i < ndiscard; i++) { uint64 hash = AQ.First().Hash; HashToSeqno.Remove(hash); AQ.PopFirst(); } FirstSeqno += ndiscard; if (UnstartedSeqno < FirstSeqno) { UnstartedSeqno = FirstSeqno; } } } TArray FlxAnimTracker::GetAborted() { TArray result; result = std::move(AbortedHashes); AbortedHashes.Empty(); return result; } bool FlxAnimTracker::AnyUnstarted() { int offset = UnstartedSeqno - FirstSeqno; return (offset < AQ.Num()); } FlxAnimStoredStep FlxAnimTracker::GetUnstarted() { int offset = UnstartedSeqno - FirstSeqno; check(offset < AQ.Num()); return AQ[offset]; } void FlxAnimTracker::Started(uint64 hash) { int offset = UnstartedSeqno - FirstSeqno; check(offset < AQ.Num()); check(AQ[offset].Hash == hash); UnstartedSeqno += 1; }