From afa0cfbe6d5c85a6a5cdd55e206241c8a855a8f4 Mon Sep 17 00:00:00 2001 From: teppy999 Date: Fri, 15 Sep 2023 00:01:41 -0400 Subject: [PATCH] Progress on Animation Queue Pipeline --- Content/IntegrationGameModeBaseBP.uasset | 4 +- Content/TanActor.uasset | 4 +- .../E/4Q/3EJ0O2NJBUVLG13CK094EE.uasset | 3 - Source/Integration/AnimQueue.cpp | 144 ++++++++++++++++++ Source/Integration/AnimQueue.h | 88 +++++++++++ .../Integration/IntegrationGameModeBase.cpp | 11 +- Source/Integration/SampleEmptyClass.cpp | 12 ++ Source/Integration/SampleEmptyClass.h | 15 ++ Source/Integration/SampleUObject.cpp | 5 + Source/Integration/SampleUObject.h | 17 +++ Source/Integration/Tangible.cpp | 5 + Source/Integration/Tangible.h | 29 ++++ Source/Integration/TangibleInterface.h | 2 +- Source/Integration/TangibleManager.cpp | 23 +-- Source/Integration/TangibleManager.h | 10 +- 15 files changed, 342 insertions(+), 30 deletions(-) delete mode 100644 Content/__ExternalActors__/LpxLevel/E/4Q/3EJ0O2NJBUVLG13CK094EE.uasset create mode 100644 Source/Integration/SampleEmptyClass.cpp create mode 100644 Source/Integration/SampleEmptyClass.h create mode 100644 Source/Integration/SampleUObject.cpp create mode 100644 Source/Integration/SampleUObject.h create mode 100644 Source/Integration/Tangible.cpp create mode 100644 Source/Integration/Tangible.h diff --git a/Content/IntegrationGameModeBaseBP.uasset b/Content/IntegrationGameModeBaseBP.uasset index 1263239d..b4f15e8f 100644 --- a/Content/IntegrationGameModeBaseBP.uasset +++ b/Content/IntegrationGameModeBaseBP.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35aecf64788f3be542623a2483fae2cec2d5cf6f9687570274e67a43d50a7db7 -size 53913 +oid sha256:3fa8c409d86707040e55f343d2828833e8033b4358a0124e88bf62070f5956b9 +size 53909 diff --git a/Content/TanActor.uasset b/Content/TanActor.uasset index e535ab0e..15e395f4 100644 --- a/Content/TanActor.uasset +++ b/Content/TanActor.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16facac2515f1247a8d763794c7b7db3f3d855c1a6a772ae429cdb67e60e941c -size 36344 +oid sha256:76f070f6bd960f1d6f4e73f7b6a60fed5a50aac764cfc6b6e456325d4323651a +size 36294 diff --git a/Content/__ExternalActors__/LpxLevel/E/4Q/3EJ0O2NJBUVLG13CK094EE.uasset b/Content/__ExternalActors__/LpxLevel/E/4Q/3EJ0O2NJBUVLG13CK094EE.uasset deleted file mode 100644 index aa6d1f99..00000000 --- a/Content/__ExternalActors__/LpxLevel/E/4Q/3EJ0O2NJBUVLG13CK094EE.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:63f6763e6264a9a92887af4285996d80fb3312caeb5e3debf5a2d2bf3d31b568 -size 4139 diff --git a/Source/Integration/AnimQueue.cpp b/Source/Integration/AnimQueue.cpp index 336bdb30..17f03378 100644 --- a/Source/Integration/AnimQueue.cpp +++ b/Source/Integration/AnimQueue.cpp @@ -83,3 +83,147 @@ FString FAnimStepDecoder::DebugString(const FAnimStep& step) { 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; + + +FAnimTracker::FAnimTracker() { + AQ.Empty(); + FirstSeqno = 0; + HashToSeqno.Empty(); + UnstartedSeqno = 0; + AbortedHashes.Empty(); +} + +void FAnimTracker::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()) { + FStringDecoder 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. + // + FAnimQueueDecoder decoder(encqueue); + TArray newsteps; + int32 matchingseqno = -1; + while (!decoder.AtEOF()) { + FAnimStep 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()) { + FAnimStep 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 FAnimTracker::GetAborted() { + TArray result; + result = std::move(AbortedHashes); + AbortedHashes.Empty(); + return result; +} + +bool FAnimTracker::AnyUnstarted() { + int offset = UnstartedSeqno - FirstSeqno; + return (offset < AQ.Num()); +} + +FAnimStoredStep FAnimTracker::GetUnstarted() { + int offset = UnstartedSeqno - FirstSeqno; + check(offset < AQ.Num()); + return AQ[offset]; +} + +void FAnimTracker::Started(uint64 hash) { + int offset = UnstartedSeqno - FirstSeqno; + check(offset < AQ.Num()); + check(AQ[offset].Hash == hash); + UnstartedSeqno += 1; +} + diff --git a/Source/Integration/AnimQueue.h b/Source/Integration/AnimQueue.h index 76b1b933..305e849e 100644 --- a/Source/Integration/AnimQueue.h +++ b/Source/Integration/AnimQueue.h @@ -2,6 +2,7 @@ #include "CoreMinimal.h" #include "StringDecoder.h" +#include "Containers/Deque.h" //////////////////////////////////////////////// // @@ -24,12 +25,25 @@ enum EAnimValueType { // The body consists of a sequence of FAnimField // records. The body is encoded, to read the // FAnimField records you need an FAnimStepDecoder. +// This comes in two versions: the string version, +// and the string_view version. // //////////////////////////////////////////////// struct FAnimStep { uint32 Hash; std::string_view Body; + + FAnimStep() : Hash(0), Body("") {} + FAnimStep(uint32 h, std::string_view b) : Hash(h), Body(b) {} +}; + +struct FAnimStoredStep { + uint64 Hash; + std::string Body; + + FAnimStoredStep() : Hash(0), Body("") {} + FAnimStoredStep(uint32 h, std::string_view b) : Hash(h), Body(b) {} }; //////////////////////////////////////////////// @@ -106,6 +120,7 @@ public: // Initialize the FAnimStepDecoder from the FAnimStep. // FAnimStepDecoder(const FAnimStep &step) : Decoder(step.Body) {} + FAnimStepDecoder(const FAnimStoredStep& step) : Decoder(step.Body) {} // Return true if the parser has reached the end of the string. // @@ -119,3 +134,76 @@ public: // static FString DebugString(const FAnimStep &step); }; + +//////////////////////////////////////////////// +// +// FAnimTracker +// +// This class monitors the animation queue for a single +// tangible. It can identify when a new animation has +// appeared on the animation queue, and when animations have +// been removed from the animation queue. It also +// keeps track of which animations have been started. +// +//////////////////////////////////////////////// + +class FAnimTracker { +public: + // 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; + +public: + // Construct a tracker. + // + // Initially, the tracker is in the empty (Clear) state. + // + FAnimTracker(); + + // Update from the specified animation queue. + // + // After the update is done, AQ will be a copy + // of the animation queue that is passed in. + // + void Update(std::string_view encqueue); + + // Fetch the aborted hash values. + // + // This gets the array of aborted hashes and clears + // the stored array. + // + TArray GetAborted(); + + // Return true if there are any unstarted animation steps. + // + bool AnyUnstarted(); + + // Get the next unstarted animation step. + // + // You may only call this if AnyUnstarted returns true. + // + FAnimStoredStep GetUnstarted(); + + // Declare that an animation has been started. + // + // After starting an animation, you should call this. + // + void Started(uint64 Hash); +}; \ No newline at end of file diff --git a/Source/Integration/IntegrationGameModeBase.cpp b/Source/Integration/IntegrationGameModeBase.cpp index b90e0905..eed35467 100644 --- a/Source/Integration/IntegrationGameModeBase.cpp +++ b/Source/Integration/IntegrationGameModeBase.cpp @@ -3,6 +3,7 @@ #include "IntegrationGameModeBase.h" #include "lpx-drvutil.hpp" #include "DebugPrint.h" +#include "Tangible.h" #include "TangibleManager.h" #include "TangibleInterface.h" #include "CommonTypes.h" @@ -120,13 +121,9 @@ void AIntegrationGameModeBase::UpdateTangibles() { // Tick all the tangibles. if (EngineSeconds > NextRotateCube) { for (int i = 0; i < live.Num(); i++) { - AActor* a = TangibleManager.GetTangible(live[i]); - check(a != nullptr); - bool hasInterface = a->GetClass()->ImplementsInterface(UTangibleInterface::StaticClass()); - if (hasInterface) { - ITangibleInterface* iface = Cast(a); - iface->Execute_TurnFromCXX(a); - } + UTangible *t = TangibleManager.GetTangible(live[i]); + check(t != nullptr); + ITangibleInterface::Execute_TurnFromCXX(t->Actor); } NextRotateCube += 0.5; } diff --git a/Source/Integration/SampleEmptyClass.cpp b/Source/Integration/SampleEmptyClass.cpp new file mode 100644 index 00000000..dfa0276e --- /dev/null +++ b/Source/Integration/SampleEmptyClass.cpp @@ -0,0 +1,12 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "SampleEmptyClass.h" + +SampleEmptyClass::SampleEmptyClass() +{ +} + +SampleEmptyClass::~SampleEmptyClass() +{ +} diff --git a/Source/Integration/SampleEmptyClass.h b/Source/Integration/SampleEmptyClass.h new file mode 100644 index 00000000..f61168a6 --- /dev/null +++ b/Source/Integration/SampleEmptyClass.h @@ -0,0 +1,15 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + +/** + * + */ +class INTEGRATION_API SampleEmptyClass +{ +public: + SampleEmptyClass(); + ~SampleEmptyClass(); +}; diff --git a/Source/Integration/SampleUObject.cpp b/Source/Integration/SampleUObject.cpp new file mode 100644 index 00000000..02ec96b9 --- /dev/null +++ b/Source/Integration/SampleUObject.cpp @@ -0,0 +1,5 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "SampleUObject.h" + diff --git a/Source/Integration/SampleUObject.h b/Source/Integration/SampleUObject.h new file mode 100644 index 00000000..abd28fba --- /dev/null +++ b/Source/Integration/SampleUObject.h @@ -0,0 +1,17 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "SampleUObject.generated.h" + +/** + * + */ +UCLASS() +class INTEGRATION_API USampleUObject : public UObject +{ + GENERATED_BODY() + +}; diff --git a/Source/Integration/Tangible.cpp b/Source/Integration/Tangible.cpp new file mode 100644 index 00000000..094c20c7 --- /dev/null +++ b/Source/Integration/Tangible.cpp @@ -0,0 +1,5 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Tangible.h" + diff --git a/Source/Integration/Tangible.h b/Source/Integration/Tangible.h new file mode 100644 index 00000000..c45f5452 --- /dev/null +++ b/Source/Integration/Tangible.h @@ -0,0 +1,29 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "AnimQueue.h" +#include "TangibleInterface.h" +#include "Tangible.generated.h" + +/** + * + */ +UCLASS() +class INTEGRATION_API UTangible : public UObject +{ + GENERATED_BODY() + +public: + UPROPERTY() + AActor* Actor; + + FAnimTracker AnimTracker; + + void Init(AActor* a) { + Actor = a; + } +}; + diff --git a/Source/Integration/TangibleInterface.h b/Source/Integration/TangibleInterface.h index 4ba55ff7..bdcbdbad 100644 --- a/Source/Integration/TangibleInterface.h +++ b/Source/Integration/TangibleInterface.h @@ -7,7 +7,7 @@ #include "TangibleInterface.generated.h" // This class does not need to be modified. -UINTERFACE(MinimalAPI) +UINTERFACE(Blueprintable) class UTangibleInterface : public UInterface { GENERATED_BODY() diff --git a/Source/Integration/TangibleManager.cpp b/Source/Integration/TangibleManager.cpp index e041c935..6c0fd9f8 100644 --- a/Source/Integration/TangibleManager.cpp +++ b/Source/Integration/TangibleManager.cpp @@ -2,8 +2,10 @@ #include "TangibleManager.h" +#include "TangibleInterface.h" +#include "DebugPrint.h" -using AActorPtr = AActor*; +using namespace DebugPrint; FTangibleManager::FTangibleManager() { World = nullptr; @@ -19,8 +21,8 @@ void FTangibleManager::Init(UWorld *world, UClass* tanact) { Near = IdView(); } -AActorPtr FTangibleManager::GetTangible(int64 id) { - AActorPtr *p = IdToActor.Find(id); +UTangible *FTangibleManager::GetTangible(int64 id) { + UTangible **p = IdToTangible.Find(id); if (p == nullptr) { return nullptr; } else { @@ -28,14 +30,17 @@ AActorPtr FTangibleManager::GetTangible(int64 id) { } } -AActorPtr FTangibleManager::MakeTangible(int64 id) { - AActorPtr& p = IdToActor.FindOrAdd(id); +UTangible *FTangibleManager::MakeTangible(int64 id) { + UTangible *& p = IdToTangible.FindOrAdd(id); if (p == nullptr) { FVector location(0,0,0); FRotator rotation(0, 0, 0); FActorSpawnParameters params; - p = World->SpawnActor(ClassTangibleActor, &location, &rotation, params); - check(p != nullptr); + AActor* a = World->SpawnActor(ClassTangibleActor, &location, &rotation, params); + check(a != nullptr); + check(a->GetClass()->ImplementsInterface(UTangibleInterface::StaticClass())); + p = NewObject(); + p->Init(a); } return p; } @@ -46,9 +51,9 @@ void FTangibleManager::DeleteTangible(int64 id) { FTangibleManager::IdArray FTangibleManager::GetLive() { IdArray result; - result.SetNum(IdToActor.Num()); + result.SetNum(IdToTangible.Num()); int next = 0; - for (auto &pair : IdToActor) { + for (auto &pair : IdToTangible) { result[next++] = pair.Key; } return result; diff --git a/Source/Integration/TangibleManager.h b/Source/Integration/TangibleManager.h index 876204c0..cc9c2a93 100644 --- a/Source/Integration/TangibleManager.h +++ b/Source/Integration/TangibleManager.h @@ -5,11 +5,9 @@ #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "CommonTypes.h" +#include "Tangible.h" #include "TangibleManager.generated.h" -/** - * - */ USTRUCT() struct INTEGRATION_API FTangibleManager { @@ -28,7 +26,7 @@ public: // Given a tangible ID, look up actor pointer (or NULL if actor was deleted) UPROPERTY() - TMap IdToActor; + TMap IdToTangible; // Actor tangible Id. int64 Actor; @@ -44,10 +42,10 @@ public: void Init(UWorld *world, UClass* tanact); // Get the tangible if it exists, otherwise return NULL - AActor* GetTangible(int64 id); + UTangible* GetTangible(int64 id); // Get the tangible if it exists, otherwise create it. - AActor* MakeTangible(int64 id); + UTangible* MakeTangible(int64 id); // Delete the tangible. void DeleteTangible(int64 id);