Overhaul of tangible/blueprint animation interface

This commit is contained in:
2023-10-02 15:48:42 -04:00
parent 642b444d13
commit a2e49338cf
7 changed files with 197 additions and 186 deletions

Binary file not shown.

View File

@@ -2,9 +2,11 @@
#include "AnimQueue.h" #include "AnimQueue.h"
FlxAnimationStep::FlxAnimationStep(uint64 hash, std::string_view body) { FlxAnimationStep::FlxAnimationStep(uint64 hash, std::string_view body) {
Finished = false;
Hash = hash; Hash = hash;
Body.SetNum(body.size()); Body.SetNum(body.size());
memcpy(Body.GetData(), body.data(), body.size()); memcpy(Body.GetData(), body.data(), body.size());
Blueprint = UlxAnimationStepLibrary::AnimationStepGetString(*this, "bp");
} }
static bool ClearProperties(const FString& prefix, UObject* obj) { static bool ClearProperties(const FString& prefix, UObject* obj) {
@@ -85,10 +87,9 @@ FString UlxAnimationStepLibrary::AnimationStepDebugString(const FlxAnimationStep
return FlxAnimationStepDecoder::DebugString(step.Hash, body); return FlxAnimationStepDecoder::DebugString(step.Hash, body);
} }
void UlxAnimationStepLibrary::UnpackAnimationStep(const FlxAnimationStep& step, void UlxAnimationStepLibrary::UnpackAnimationStep(UObject* into, const FlxAnimationStep& step, const FString& prefix) {
const FString& prefix, UObject* into) {
step.Unpack(prefix, into, true); step.Unpack(prefix, into, true);
}; }
static FlxAnimationField FindAnimationField(const FlxAnimationStep& step, const FString& name) { static FlxAnimationField FindAnimationField(const FlxAnimationStep& step, const FString& name) {
std::string_view body((const char*)(step.Body.GetData()), step.Body.Num()); 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; return result;
} }
bool UlxAnimationStepLibrary::AnimationStepIsIdle(const FlxAnimationStep &step) {
return step.Finished;
}
FVector UlxAnimationStepLibrary::AnimationStepGetVector(const FlxAnimationStep& step, const FString& name) { FVector UlxAnimationStepLibrary::AnimationStepGetVector(const FlxAnimationStep& step, const FString& name) {
FlxAnimationField field = FindAnimationField(step, name); FlxAnimationField field = FindAnimationField(step, name);
if (field.Type != ElxAnimValueType::XYZ) return FVector(0, 0, 0); if (field.Type != ElxAnimValueType::XYZ) return FVector(0, 0, 0);
@@ -138,6 +143,13 @@ FlxAnimationStepView FlxAnimQueueDecoder::ReadStep() {
return result; 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 FlxAnimationStepDecoder::ReadField() {
FlxAnimationField result; FlxAnimationField result;
result.Name = Decoder.read_string_view(); result.Name = Decoder.read_string_view();
@@ -199,6 +211,11 @@ FString FlxAnimationStepDecoder::DebugString(uint64 hash, std::string_view body)
return result; 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 FlxAnimQueueDecoder::DebugString(std::string_view queue) {
FString result; FString result;
FlxAnimQueueDecoder decoder(queue); FlxAnimQueueDecoder decoder(queue);
@@ -216,33 +233,50 @@ FlxAnimTracker::FlxAnimTracker() {
void FlxAnimTracker::Clear() { void FlxAnimTracker::Clear() {
AQ.Empty(); AQ.Empty();
FirstSeqno = 0; Changed = true;
HashToSeqno.Empty(); }
UnstartedSeqno = 0;
PlaybackMode = ElxAnimationMode::INVALID; void FlxAnimTracker::FinishedAnimation(uint64 hash) {
AbortedHashes.Empty(); 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) { void FlxAnimTracker::Update(std::string_view encqueue) {
// Check for an exact match. If the most recent entry check(!encqueue.empty());
// in encqueue has the same hash as the most recent
// entry in AQ, then that's an exact match. Or, // If the first hash matches, we don't bother updating at all.
// 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()) { FlxAnimQueueDecoder decoder(encqueue);
if (AQ.IsEmpty()) {
return;
}
} else {
if (!AQ.IsEmpty()) { if (!AQ.IsEmpty()) {
FlxStringDecoder qdecoder(encqueue); if (decoder.PeekHash() == AQ.Last().Hash) {
uint64 hash = qdecoder.read_uint64();
if (hash == AQ.Last().Hash) {
return; 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<uint64, int32> HashToIndex;
for (int i = 0; i < AQ.Num(); i++) {
HashToIndex.Emplace(AQ[i].Hash, i);
} }
// Search for a Luprex animation record that matches // 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 // At the same time, push all non-matching records
// after the matching record onto a stack of new records. // after the matching record onto a stack of new records.
// //
FlxAnimQueueDecoder decoder(encqueue);
TArray<FlxAnimationStepView> newsteps; TArray<FlxAnimationStepView> newsteps;
int32 matchingseqno = -1; int32 matchingindex = -1;
while (!decoder.AtEOF()) { while (!decoder.AtEOF()) {
FlxAnimationStepView step = decoder.ReadStep(); FlxAnimationStepView step = decoder.ReadStep();
int32* stepseq = HashToSeqno.Find(step.Hash); int32* indexp = HashToIndex.Find(step.Hash);
if (stepseq == nullptr) { if (indexp == nullptr) {
newsteps.Emplace(step); newsteps.Emplace(step);
} else { } else {
matchingseqno = *stepseq; matchingindex = *indexp;
break; break;
} }
} }
// Remove all animations after the most recent matching // Remove all animations after the most recent matching
// record. If we remove a 'started' animation, add that // record.
// animation to the list of aborted animations.
// //
int32 nremove = (FirstSeqno + AQ.Num()) - (matchingseqno + 1); int32 nremove = (AQ.Num() - (matchingindex + 1));
check((nremove >= 0) && (nremove <= AQ.Num())); check((nremove >= 0) && (nremove <= AQ.Num()));
for (int32 i = 0; i < nremove; i++) { 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(); 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. // Transfer the new animations onto the queue.
// //
while (!newsteps.IsEmpty()) { while (!newsteps.IsEmpty()) {
FlxAnimationStepView step = newsteps.Pop(); FlxAnimationStepView step = newsteps.Pop();
int32 seqno = FirstSeqno + AQ.Num();
AQ.EmplaceLast(step.Hash, step.Body); AQ.EmplaceLast(step.Hash, step.Body);
HashToSeqno.Emplace(step.Hash, seqno);
} }
// If there are too many animations in AQ, discard // If there are too many animations in AQ, discard
// any very old ones. // any very old ones.
// //
if (AQ.Num() > 10) { // TODO: this is hardwired to keep 10. Instead, we
int ndiscard = AQ.Num() - 10; // should keep the number specified in the queue.
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.
// //
if (UnstartedSeqno <= FirstSeqno) { int limit = 10;
if (AQ.Num() == 0) { int ndiscard = AQ.Num() - limit;
UnstartedSeqno = FirstSeqno; if (ndiscard > 0) {
} else { for (int i = 0; i < ndiscard; i++) {
UnstartedSeqno = FirstSeqno + AQ.Num() - 1; AQ.PopFirst();
} }
PlaybackMode = ElxAnimationMode::WarpToFinal;
} }
} }
TArray<uint64> FlxAnimTracker::GetAborted() { FlxAnimationStep FlxAnimTracker::GetCurrentAnimation() {
TArray<uint64> result; for (int i = 0; i < AQ.Num(); i++) {
result = std::move(AbortedHashes); if (!AQ[i].Finished) {
AbortedHashes.Empty(); return AQ[i];
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;
} }
} }
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;
} }

View File

@@ -19,32 +19,6 @@ enum class ElxAnimValueType {
INVALID 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. // An single animation step.
@@ -61,13 +35,19 @@ struct INTEGRATION_API FlxAnimationStep {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY()
bool Finished;
UPROPERTY() UPROPERTY()
uint64 Hash; uint64 Hash;
UPROPERTY() UPROPERTY()
TArray<uint8> Body; TArray<uint8> Body;
FlxAnimationStep() : Hash(0), Body() {} UPROPERTY()
FString Blueprint;
FlxAnimationStep() : Finished(false), Hash(0), Body(), Blueprint() {}
FlxAnimationStep(uint64 h, std::string_view b); FlxAnimationStep(uint64 h, std::string_view b);
// Unpack an AnimStep into a UObject // Unpack an AnimStep into a UObject
@@ -114,9 +94,11 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category = Luprex) UFUNCTION(BlueprintCallable, BlueprintPure, Category = Luprex)
static FString AnimationStepDebugString(const FlxAnimationStep& step); static FString AnimationStepDebugString(const FlxAnimationStep& step);
UFUNCTION(BlueprintCallable, Category = Luprex) UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "into"), Category = Luprex)
static void UnpackAnimationStep(const FlxAnimationStep& step, static void UnpackAnimationStep(UObject* into, const FlxAnimationStep& step, const FString& VariableNamePrefix);
const FString& VariableNamePrefix, UObject* into);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = Luprex)
static bool AnimationStepIsIdle(const FlxAnimationStep &step);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = Luprex) UFUNCTION(BlueprintCallable, BlueprintPure, Category = Luprex)
static FVector AnimationStepGetVector(const FlxAnimationStep& step, const FString& name); static FVector AnimationStepGetVector(const FlxAnimationStep& step, const FString& name);
@@ -181,10 +163,23 @@ class FlxAnimQueueDecoder {
private: private:
FlxStringDecoder Decoder; FlxStringDecoder Decoder;
// These values are immediately read from the header.
//
int SizeLimit;
int ActualSize;
public: public:
// Initialize the FlxAnimQueueDecoder with the encoded animation queue. // 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. // Return true if the parser has reached the end of the string.
// //
@@ -194,6 +189,10 @@ public:
// //
FlxAnimationStepView ReadStep(); FlxAnimationStepView ReadStep();
// Peek at the hash of the next animation step.
//
uint64 PeekHash();
// Convert an AnimQueue to an FString. // Convert an AnimQueue to an FString.
// //
static FString DebugString(std::string_view s); static FString DebugString(std::string_view s);
@@ -252,26 +251,11 @@ public:
// //
TDeque<FlxAnimationStep> AQ; TDeque<FlxAnimationStep> AQ;
// The sequence number of the first item in AQ. // True if something has recently changed.
// //
int32 FirstSeqno; bool Changed;
// Map from hash to sequence number.
//
TMap<uint64, int32> 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<uint64> AbortedHashes;
private:
public: public:
// Construct a tracker. // Construct a tracker.
// //
@@ -290,24 +274,39 @@ public:
// //
void Update(std::string_view encqueue); 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 // Get the current animation step. This is the step that the
// the stored array. // blueprint should currently be playing.
// //
TArray<uint64> GetAborted(); 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 // The blueprint uses this function call to indicate that it
// playback mode. If the playback mode is INVALID then there is // is done playing the specified animation. This will cause the
// no next step to play // animation to be marked as finished, which in turn causes
// 'GetCurrentStep' to advance to the next animation.
// //
ElxAnimationMode GetNextStep(FlxAnimationStep& step); void FinishedAnimation(uint64 Hash);
// Declare that an animation has been started. // Skip to the end of the animation queue.
// //
// After starting an animation, you should call this. // This is equivalent to calling 'FinishedHash' on every
// animation in the entire queue.
// //
void StartedStep(uint64 Hash); void SkipToEnd();
// Clear the 'Changed' flag.
//
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; }
}; };

View File

@@ -7,6 +7,19 @@ FlxStringDecoder::FlxStringDecoder(std::string_view s) {
ErrStringTooLong = false; 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() { std::string_view FlxStringDecoder::read_string_view() {
size_t length = read_length(); size_t length = read_length();
if (length > Size) { if (length > Size) {
@@ -22,5 +35,4 @@ std::string_view FlxStringDecoder::read_string_view() {
void FlxStringDecoder::set_at_eof() { void FlxStringDecoder::set_at_eof() {
Text += Size; Text += Size;
Size = 0; Size = 0;
} }

View File

@@ -70,6 +70,16 @@ public:
// //
FlxStringDecoder(std::string_view s); 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. // Get the size of the remaining text.
// //
size_t get_size() { return Size; } size_t get_size() { return Size; }
@@ -82,6 +92,7 @@ public:
// //
void set_at_eof(); void set_at_eof();
// Read a string as a string_view // Read a string as a string_view
// //
// This reads a string from the source, returning // This reads a string from the source, returning

View File

@@ -75,17 +75,12 @@ void UlxTangible::SetActorBlueprintClass(TSubclassOf<AActor> bp) {
void UlxTangible::UpdateAnimationQueue(std::string_view aq) { void UlxTangible::UpdateAnimationQueue(std::string_view aq) {
AnimTracker.Update(aq); AnimTracker.Update(aq);
TArray<uint64> aborted = AnimTracker.GetAborted(); int limit = 3;
for (uint64 hash : aborted) { while (AnimTracker.IsChanged()) {
IlxTangibleInterface::Execute_AbortAnimation(GetActor(), hash); if (limit == 0) break;
} limit -= 1;
FlxAnimationStep step; AnimTracker.ClearChanged();
ElxAnimationMode mode = AnimTracker.GetNextStep(step); IlxTangibleInterface::Execute_AnimationStateChanged(GetActor());
if (mode != ElxAnimationMode::INVALID) {
bool started = IlxTangibleInterface::Execute_StartAnimation(GetActor(), mode, step);
if (started) {
AnimTracker.StartedStep(step.Hash);
}
} }
} }
@@ -108,15 +103,27 @@ void UlxTangible::Destroy() {
NearAccordingToUnreal = false; NearAccordingToUnreal = false;
} }
FString UlxTangible::GetTangiblePlane(AActor* actor) { static UlxTangible *GetActorTangible(AActor *actor) {
UlxTangibleComponent* comp = actor->GetComponentByClass<UlxTangibleComponent>(); UlxTangibleComponent* comp = actor->GetComponentByClass<UlxTangibleComponent>();
check(comp != nullptr); 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) { void UlxTangible::GetCurrentAnimation(AActor *target, FlxAnimationStep &step) {
UlxTangibleComponent* comp = actor->GetComponentByClass<UlxTangibleComponent>(); step = GetActorTangible(target)->AnimTracker.GetCurrentAnimation();
check(comp != nullptr); }
comp->Tangible->Plane = FName(plane);
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);
} }

View File

@@ -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. // Add interface functions to this class. This is the class that will be inherited to implement this interface.
public: public:
UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality")
bool StartAnimation(ElxAnimationMode mode, const FlxAnimationStep& step); bool AnimationStateChanged();
UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality")
bool AbortAnimation(int64 hash);
}; };
/** /**
* *
* UlxTangible * UlxTangible
@@ -168,6 +163,12 @@ public:
void UpdateAnimationQueue(std::string_view aq); void UpdateAnimationQueue(std::string_view aq);
public: 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) UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex)
static FString GetTangiblePlane(AActor* target); static FString GetTangiblePlane(AActor* target);