diff --git a/Content/TangibleActor.uasset b/Content/TangibleActor.uasset index a13f4cdd..3a1da417 100644 --- a/Content/TangibleActor.uasset +++ b/Content/TangibleActor.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05b62c5757100adf5d29b718f75d9f540e11267ca0aa8e03cd33d2676fea9b91 -size 142754 +oid sha256:e99102328387901e912868879c8f206986da80d8e38ba0352da7b12df46400b0 +size 160277 diff --git a/Source/Integration/AnimQueue.cpp b/Source/Integration/AnimQueue.cpp index 050747f4..7301d4ab 100644 --- a/Source/Integration/AnimQueue.cpp +++ b/Source/Integration/AnimQueue.cpp @@ -2,9 +2,11 @@ #include "AnimQueue.h" FlxAnimationStep::FlxAnimationStep(uint64 hash, std::string_view body) { + Finished = false; Hash = hash; Body.SetNum(body.size()); memcpy(Body.GetData(), body.data(), body.size()); + Blueprint = UlxAnimationStepLibrary::AnimationStepGetString(*this, "bp"); } static bool ClearProperties(const FString& prefix, UObject* obj) { @@ -85,10 +87,9 @@ FString UlxAnimationStepLibrary::AnimationStepDebugString(const FlxAnimationStep return FlxAnimationStepDecoder::DebugString(step.Hash, body); } -void UlxAnimationStepLibrary::UnpackAnimationStep(const FlxAnimationStep& step, - const FString& prefix, UObject* into) { +void UlxAnimationStepLibrary::UnpackAnimationStep(UObject* into, const FlxAnimationStep& step, const FString& prefix) { step.Unpack(prefix, into, true); -}; +} static FlxAnimationField FindAnimationField(const FlxAnimationStep& step, const FString& name) { std::string_view body((const char*)(step.Body.GetData()), step.Body.Num()); @@ -106,6 +107,10 @@ static FlxAnimationField FindAnimationField(const FlxAnimationStep& step, const return result; } +bool UlxAnimationStepLibrary::AnimationStepIsIdle(const FlxAnimationStep &step) { + return step.Finished; +} + FVector UlxAnimationStepLibrary::AnimationStepGetVector(const FlxAnimationStep& step, const FString& name) { FlxAnimationField field = FindAnimationField(step, name); if (field.Type != ElxAnimValueType::XYZ) return FVector(0, 0, 0); @@ -138,6 +143,13 @@ FlxAnimationStepView FlxAnimQueueDecoder::ReadStep() { return result; } +uint64 FlxAnimQueueDecoder::PeekHash() { + std::string_view rest = Decoder.GetRest(); + uint64 result = Decoder.read_uint64(); + Decoder.Reset(rest, false); + return result; +} + FlxAnimationField FlxAnimationStepDecoder::ReadField() { FlxAnimationField result; result.Name = Decoder.read_string_view(); @@ -199,6 +211,11 @@ FString FlxAnimationStepDecoder::DebugString(uint64 hash, std::string_view body) return result; } +FlxAnimQueueDecoder::FlxAnimQueueDecoder(std::string_view queue) : Decoder(queue) { + SizeLimit = Decoder.read_uint8(); + ActualSize = Decoder.read_uint8(); +} + FString FlxAnimQueueDecoder::DebugString(std::string_view queue) { FString result; FlxAnimQueueDecoder decoder(queue); @@ -216,33 +233,50 @@ FlxAnimTracker::FlxAnimTracker() { void FlxAnimTracker::Clear() { AQ.Empty(); - FirstSeqno = 0; - HashToSeqno.Empty(); - UnstartedSeqno = 0; - PlaybackMode = ElxAnimationMode::INVALID; - AbortedHashes.Empty(); + Changed = true; +} + +void FlxAnimTracker::FinishedAnimation(uint64 hash) { + for (int i = 0; i < AQ.Num(); i++) { + if (AQ[i].Hash == hash) { + AQ[i].Finished = true; + Changed = true; + } + } +} + +void FlxAnimTracker::SkipToEnd() { + for (int i = 0; i < AQ.Num(); i++) { + if (!AQ[i].Finished) { + AQ[i].Finished = true; + Changed = true; + } + } } 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. + check(!encqueue.empty()); + + // If the first hash matches, we don't bother updating at all. // - if (encqueue.empty()) { - if (AQ.IsEmpty()) { + FlxAnimQueueDecoder decoder(encqueue); + if (!AQ.IsEmpty()) { + if (decoder.PeekHash() == AQ.Last().Hash) { return; } - } else { - if (!AQ.IsEmpty()) { - FlxStringDecoder qdecoder(encqueue); - uint64 hash = qdecoder.read_uint64(); - if (hash == AQ.Last().Hash) { - return; - } - } + } + + + // The Changed flag is set whenever there is any change to + // the animation queue of any kind. + // + Changed = true; + + // Build a map indexing the steps in AQ. + // + TMap HashToIndex; + for (int i = 0; i < AQ.Num(); i++) { + HashToIndex.Emplace(AQ[i].Hash, i); } // Search for a Luprex animation record that matches @@ -252,108 +286,55 @@ void FlxAnimTracker::Update(std::string_view encqueue) { // 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; + int32 matchingindex = -1; while (!decoder.AtEOF()) { FlxAnimationStepView step = decoder.ReadStep(); - int32* stepseq = HashToSeqno.Find(step.Hash); - if (stepseq == nullptr) { + int32* indexp = HashToIndex.Find(step.Hash); + if (indexp == nullptr) { newsteps.Emplace(step); } else { - matchingseqno = *stepseq; + matchingindex = *indexp; break; } } // Remove all animations after the most recent matching - // record. If we remove a 'started' animation, add that - // animation to the list of aborted animations. + // record. // - int32 nremove = (FirstSeqno + AQ.Num()) - (matchingseqno + 1); + int32 nremove = (AQ.Num() - (matchingindex + 1)); check((nremove >= 0) && (nremove <= AQ.Num())); for (int32 i = 0; i < nremove; i++) { - uint64 lasthash = AQ.Last().Hash; - int32 seqno = FirstSeqno + AQ.Num() - 1; - HashToSeqno.Remove(lasthash); - if (seqno < UnstartedSeqno) { - AbortedHashes.Emplace(lasthash); - } AQ.PopLast(); } - // If we aborted a 'started' animation, we have to fix - // up the Unstarted animation pointer. - // - // Note: this could leave the Unstarted pointer at - // seqno less than FirstSeqno. We will fix that state - // of affairs up later. - // - if (UnstartedSeqno > (FirstSeqno + AQ.Num())) { - UnstartedSeqno = matchingseqno; - PlaybackMode = ElxAnimationMode::BlendToFinal; - } - // Transfer the new animations onto the queue. // while (!newsteps.IsEmpty()) { FlxAnimationStepView step = newsteps.Pop(); - int32 seqno = FirstSeqno + AQ.Num(); AQ.EmplaceLast(step.Hash, step.Body); - HashToSeqno.Emplace(step.Hash, seqno); } // 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 += 1; - } - } - - // If UnstartedSeqno is before the live range, - // then the whole queue has to be replayed. Don't - // do that: just fast skip to the end. + // TODO: this is hardwired to keep 10. Instead, we + // should keep the number specified in the queue. // - if (UnstartedSeqno <= FirstSeqno) { - if (AQ.Num() == 0) { - UnstartedSeqno = FirstSeqno; - } else { - UnstartedSeqno = FirstSeqno + AQ.Num() - 1; + int limit = 10; + int ndiscard = AQ.Num() - limit; + if (ndiscard > 0) { + for (int i = 0; i < ndiscard; i++) { + AQ.PopFirst(); } - PlaybackMode = ElxAnimationMode::WarpToFinal; } } -TArray FlxAnimTracker::GetAborted() { - TArray result; - result = std::move(AbortedHashes); - AbortedHashes.Empty(); - return result; -} - -ElxAnimationMode FlxAnimTracker::GetNextStep(FlxAnimationStep &step) { - int offset = UnstartedSeqno - FirstSeqno; - if (offset < AQ.Num()) { - step = AQ[offset]; - return PlaybackMode; - } else { - step.Hash = 0; - step.Body.Empty(); - return ElxAnimationMode::INVALID; +FlxAnimationStep FlxAnimTracker::GetCurrentAnimation() { + for (int i = 0; i < AQ.Num(); i++) { + if (!AQ[i].Finished) { + return AQ[i]; + } } + return AQ.Last(); } - -void FlxAnimTracker::StartedStep(uint64 hash) { - int offset = UnstartedSeqno - FirstSeqno; - check(offset < AQ.Num()); - check(AQ[offset].Hash == hash); - UnstartedSeqno += 1; - PlaybackMode = ElxAnimationMode::StartAnimation; -} - diff --git a/Source/Integration/AnimQueue.h b/Source/Integration/AnimQueue.h index 5df05d1f..1f5d8188 100644 --- a/Source/Integration/AnimQueue.h +++ b/Source/Integration/AnimQueue.h @@ -19,32 +19,6 @@ enum class ElxAnimValueType { INVALID }; -//////////////////////////////////////////////// -// -// Playback Modes -// -// There are three different ways to play an animation: -// -// 1. Start Animation. This is the normal way to play an animation. -// -// 2. Warp to Final. Skip the actual animation, and jump -// instantaneously to the final state of the animation. -// -// 3. Blend to Final. Skip the actual animation, and blend -// smoothly to the final state of the animation. If the current -// state is very far from the final state, then maybe -// jump instantaneously instead. -// -//////////////////////////////////////////////// - -UENUM(BlueprintType) -enum class ElxAnimationMode : uint8 { - StartAnimation, - WarpToFinal, - BlendToFinal, - INVALID, -}; - //////////////////////////////////////////////// // // An single animation step. @@ -61,13 +35,19 @@ struct INTEGRATION_API FlxAnimationStep { GENERATED_BODY() public: + UPROPERTY() + bool Finished; + UPROPERTY() uint64 Hash; UPROPERTY() TArray Body; - FlxAnimationStep() : Hash(0), Body() {} + UPROPERTY() + FString Blueprint; + + FlxAnimationStep() : Finished(false), Hash(0), Body(), Blueprint() {} FlxAnimationStep(uint64 h, std::string_view b); // Unpack an AnimStep into a UObject @@ -114,10 +94,12 @@ public: UFUNCTION(BlueprintCallable, BlueprintPure, Category = Luprex) static FString AnimationStepDebugString(const FlxAnimationStep& step); - UFUNCTION(BlueprintCallable, Category = Luprex) - static void UnpackAnimationStep(const FlxAnimationStep& step, - const FString& VariableNamePrefix, UObject* into); + UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "into"), Category = Luprex) + static void UnpackAnimationStep(UObject* into, const FlxAnimationStep& step, const FString& VariableNamePrefix); + UFUNCTION(BlueprintCallable, BlueprintPure, Category = Luprex) + static bool AnimationStepIsIdle(const FlxAnimationStep &step); + UFUNCTION(BlueprintCallable, BlueprintPure, Category = Luprex) static FVector AnimationStepGetVector(const FlxAnimationStep& step, const FString& name); @@ -180,11 +162,24 @@ struct FlxAnimationField { class FlxAnimQueueDecoder { private: FlxStringDecoder Decoder; + + // These values are immediately read from the header. + // + int SizeLimit; + int ActualSize; public: // Initialize the FlxAnimQueueDecoder with the encoded animation queue. // - FlxAnimQueueDecoder(std::string_view s) : Decoder(s) {} + FlxAnimQueueDecoder(std::string_view s); + + // Get the size limit of the animation queue. + // + int GetSizeLimit() const { return SizeLimit; } + + // Get the Actual Size of the animation queue. + // + int GetActualSize() const { return ActualSize; } // Return true if the parser has reached the end of the string. // @@ -194,6 +189,10 @@ public: // FlxAnimationStepView ReadStep(); + // Peek at the hash of the next animation step. + // + uint64 PeekHash(); + // Convert an AnimQueue to an FString. // static FString DebugString(std::string_view s); @@ -252,26 +251,11 @@ public: // TDeque AQ; - // The sequence number of the first item in AQ. + // True if something has recently changed. // - int32 FirstSeqno; - - // Map from hash to sequence number. - // - TMap HashToSeqno; - - // The sequence number of the first unstarted animation. - // - int32 UnstartedSeqno; - - // Indicates whether the unstarted animation should be played or otherwise. - // - ElxAnimationMode PlaybackMode; - - // Array of recently-aborted hash values. - // - TArray AbortedHashes; + bool Changed; +private: public: // Construct a tracker. // @@ -290,24 +274,39 @@ public: // void Update(std::string_view encqueue); - // Fetch the aborted hash values. + // Get the current animation step. // - // This gets the array of aborted hashes and clears - // the stored array. - // - TArray GetAborted(); + // Get the current animation step. This is the step that the + // blueprint should currently be playing. + // + FlxAnimationStep GetCurrentAnimation(); - // Get the next unstarted animation step. + // Declare that an animation is finished. // - // Get the next animation step. Returns the next step and the - // playback mode. If the playback mode is INVALID then there is - // no next step to play - // - ElxAnimationMode GetNextStep(FlxAnimationStep& step); + // The blueprint uses this function call to indicate that it + // is done playing the specified animation. This will cause the + // animation to be marked as finished, which in turn causes + // 'GetCurrentStep' to advance to the next animation. + // + void FinishedAnimation(uint64 Hash); - // Declare that an animation has been started. + // Skip to the end of the animation queue. + // + // This is equivalent to calling 'FinishedHash' on every + // animation in the entire queue. // - // After starting an animation, you should call this. + void SkipToEnd(); + + // Clear the 'Changed' flag. // - void StartedStep(uint64 Hash); + void ClearChanged() { Changed = false; } + + // Get the 'Changed' flag. + // + // The changed flag is set to true whenever the Luprex animation + // queue changes from its previous state. The changed flag is also + // set to true whenever 'SetFinished' marks an animation as finished. + // The changed flag can only be set to false by 'ClearChanged,' above. + // + bool IsChanged() const { return Changed; } }; \ No newline at end of file diff --git a/Source/Integration/StringDecoder.cpp b/Source/Integration/StringDecoder.cpp index 1182914d..a5b5a75f 100644 --- a/Source/Integration/StringDecoder.cpp +++ b/Source/Integration/StringDecoder.cpp @@ -7,6 +7,19 @@ FlxStringDecoder::FlxStringDecoder(std::string_view s) { ErrStringTooLong = false; } +void FlxStringDecoder::Reset(std::string_view s, bool clear) { + Text = s.data(); + Size = s.size(); + if (clear) { + ErrBeyondEOF = false; + ErrStringTooLong = false; + } +} + +std::string_view FlxStringDecoder::GetRest() { + return std::string_view(Text, Size); +} + std::string_view FlxStringDecoder::read_string_view() { size_t length = read_length(); if (length > Size) { @@ -22,5 +35,4 @@ std::string_view FlxStringDecoder::read_string_view() { void FlxStringDecoder::set_at_eof() { Text += Size; Size = 0; - -} \ No newline at end of file +} diff --git a/Source/Integration/StringDecoder.h b/Source/Integration/StringDecoder.h index e585e741..b5d2abc8 100644 --- a/Source/Integration/StringDecoder.h +++ b/Source/Integration/StringDecoder.h @@ -70,6 +70,16 @@ public: // FlxStringDecoder(std::string_view s); + // Get the remaining text as a string_view. + // + std::string_view GetRest(); + + // Reinitialize with a new string_view. + // + // If clear is true, clears the error flags. + // + void Reset(std::string_view s, bool clear); + // Get the size of the remaining text. // size_t get_size() { return Size; } @@ -82,6 +92,7 @@ public: // void set_at_eof(); + // Read a string as a string_view // // This reads a string from the source, returning diff --git a/Source/Integration/Tangible.cpp b/Source/Integration/Tangible.cpp index 68a885f7..4ec72d9f 100644 --- a/Source/Integration/Tangible.cpp +++ b/Source/Integration/Tangible.cpp @@ -75,17 +75,12 @@ void UlxTangible::SetActorBlueprintClass(TSubclassOf bp) { void UlxTangible::UpdateAnimationQueue(std::string_view aq) { AnimTracker.Update(aq); - TArray aborted = AnimTracker.GetAborted(); - for (uint64 hash : aborted) { - IlxTangibleInterface::Execute_AbortAnimation(GetActor(), hash); - } - FlxAnimationStep step; - ElxAnimationMode mode = AnimTracker.GetNextStep(step); - if (mode != ElxAnimationMode::INVALID) { - bool started = IlxTangibleInterface::Execute_StartAnimation(GetActor(), mode, step); - if (started) { - AnimTracker.StartedStep(step.Hash); - } + int limit = 3; + while (AnimTracker.IsChanged()) { + if (limit == 0) break; + limit -= 1; + AnimTracker.ClearChanged(); + IlxTangibleInterface::Execute_AnimationStateChanged(GetActor()); } } @@ -108,15 +103,27 @@ void UlxTangible::Destroy() { NearAccordingToUnreal = false; } -FString UlxTangible::GetTangiblePlane(AActor* actor) { +static UlxTangible *GetActorTangible(AActor *actor) { UlxTangibleComponent* comp = actor->GetComponentByClass(); check(comp != nullptr); - return comp->Tangible->Plane.ToString(); + UlxTangible *result = comp->Tangible.Get(); + check(result != nullptr); + return result; } -void UlxTangible::SetTangiblePlane(AActor* actor, const FString& plane) { - UlxTangibleComponent* comp = actor->GetComponentByClass(); - check(comp != nullptr); - comp->Tangible->Plane = FName(plane); +void UlxTangible::GetCurrentAnimation(AActor *target, FlxAnimationStep &step) { + step = GetActorTangible(target)->AnimTracker.GetCurrentAnimation(); +} + +void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step) { + GetActorTangible(target)->AnimTracker.FinishedAnimation(step.Hash); +} + +FString UlxTangible::GetTangiblePlane(AActor* target) { + return GetActorTangible(target)->Plane.ToString(); +} + +void UlxTangible::SetTangiblePlane(AActor* target, const FString& plane) { + GetActorTangible(target)->Plane = FName(plane); } diff --git a/Source/Integration/Tangible.h b/Source/Integration/Tangible.h index d3323a22..fc7e9e79 100644 --- a/Source/Integration/Tangible.h +++ b/Source/Integration/Tangible.h @@ -33,15 +33,10 @@ class INTEGRATION_API IlxTangibleInterface // Add interface functions to this class. This is the class that will be inherited to implement this interface. public: UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") - bool StartAnimation(ElxAnimationMode mode, const FlxAnimationStep& step); - - UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") - bool AbortAnimation(int64 hash); + bool AnimationStateChanged(); }; - - /** * * UlxTangible @@ -168,6 +163,12 @@ public: void UpdateAnimationQueue(std::string_view aq); public: + UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex) + static void GetCurrentAnimation(AActor *target, FlxAnimationStep &step); + + UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex) + static void FinishedAnimation(AActor *target, const FlxAnimationStep &step); + UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex) static FString GetTangiblePlane(AActor* target);