From 1d8bdfc7bfa53adcee3d871b07ca71c32f025ad9 Mon Sep 17 00:00:00 2001 From: jyelon Date: Tue, 19 Sep 2023 22:08:15 -0400 Subject: [PATCH] Now passing FlxAnimationStep into the blueprint --- Content/TangibleActor.uasset | 4 +- Source/Integration/AnimQueue.cpp | 212 ++++++++++-------- Source/Integration/AnimQueue.h | 157 ++++++++----- .../Integration/IntegrationGameModeBase.cpp | 29 +-- Source/Integration/TangibleInterface.h | 11 +- 5 files changed, 222 insertions(+), 191 deletions(-) diff --git a/Content/TangibleActor.uasset b/Content/TangibleActor.uasset index b1af93f6..42e011e4 100644 --- a/Content/TangibleActor.uasset +++ b/Content/TangibleActor.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b16c6946e9c9604d021490b1cf4a60a216b9c22d17c5885ce242866e3949ad02 -size 95418 +oid sha256:595ddbe1ad470359c21f25fc41afb5692cae59238999273975cc805d83665524 +size 68824 diff --git a/Source/Integration/AnimQueue.cpp b/Source/Integration/AnimQueue.cpp index eaa1cb62..d6d6f0d4 100644 --- a/Source/Integration/AnimQueue.cpp +++ b/Source/Integration/AnimQueue.cpp @@ -1,89 +1,13 @@ #include "AnimQueue.h" -FlxAnimStep FlxAnimQueueDecoder::ReadStep() { - FlxAnimStep result; - result.Hash = Decoder.read_uint64(); - result.Body = Decoder.read_string_view(); - return result; +FlxAnimationStep::FlxAnimationStep(uint64 hash, std::string_view body) { + Hash = hash; + Body.SetNum(body.size()); + memcpy(Body.GetData(), body.data(), body.size()); } -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 ElxAnimValueType::STRING: { - result.S = Decoder.read_string_view(); - break; - } - case ElxAnimValueType::NUMBER: { - result.X = Decoder.read_double(); - break; - } - case ElxAnimValueType::BOOLEAN: { - result.X = Decoder.read_bool() ? 1.0 : 0.0; - break; - } - case ElxAnimValueType::XYZ: { - result.X = Decoder.read_double(); - result.Y = Decoder.read_double(); - result.Z = Decoder.read_double(); - break; - } - default: { - Decoder.set_at_eof(); - result.Type = ElxAnimValueType::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.Body); - result.Appendf(TEXT("%s\n"), *stepdebug); - } - return result; -} - -FString FlxAnimStepDecoder::DebugString(std::string_view body) { - FString result; - FlxAnimStepDecoder decoder(body); - 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::STRING: - result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data())); - break; - case ElxAnimValueType::NUMBER: - result.Appendf(TEXT("%lf"), field.X); - break; - case ElxAnimValueType::BOOLEAN: - result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false")); - break; - case ElxAnimValueType::XYZ: - result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z); - break; - } - first = false; - } - return result; -} - -bool FlxAnimStepDecoder::ClearProperties(const FString& prefix, UObject* obj) { +static bool ClearProperties(const FString& prefix, UObject* obj) { UClass* uclass = obj->GetClass(); if (prefix.IsEmpty()) { return false; @@ -103,7 +27,7 @@ bool FlxAnimStepDecoder::ClearProperties(const FString& prefix, UObject* obj) { return true; } -bool FlxAnimStepDecoder::SetProperty(const FString& name, UObject* obj, const FlxAnimField& field) { +static bool SetProperty(const FString& name, UObject* obj, const FlxAnimationField& field) { UClass* uclass = obj->GetClass(); FName nname(name); switch (field.Type) { @@ -140,28 +64,118 @@ bool FlxAnimStepDecoder::SetProperty(const FString& name, UObject* obj, const Fl return false; } -#pragma optimize("", off) -bool FlxAnimStepDecoder::UnpackInto(std::string_view body, const FString& prefix, bool preclear, UObject* obj) { - UClass* uclass = obj->GetClass(); - FlxAnimStepDecoder decoder(body); +bool FlxAnimationStep::Unpack(const FString& prefix, UObject* into, bool preclear) const { + UClass* uclass = into->GetClass(); + std::string_view body((const char*)(Body.GetData()), Body.Num()); + FlxAnimationStepDecoder decoder(body); bool ok = true; if (preclear) { - ok &= ClearProperties(prefix, obj); + ok &= ClearProperties(prefix, into); } while (!decoder.AtEOF()) { - FlxAnimField field = decoder.ReadField(); + FlxAnimationField field = decoder.ReadField(); FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data()); - ok &= SetProperty(prefix + sname, obj, field); + ok &= SetProperty(prefix + sname, into, field); } return ok; } +FString UlxAnimationStepLibrary::AnimationStepDebugString(const FlxAnimationStep& step) { + std::string_view body((const char*)(step.Body.GetData()), step.Body.Num()); + return FlxAnimationStepDecoder::DebugString(step.Hash, body); +} + +void UlxAnimationStepLibrary::UnpackAnimationStep(const FlxAnimationStep& step, + const FString& prefix, UObject* into, bool preclear) { + step.Unpack(prefix, into, preclear); +}; + +FlxAnimationStepView FlxAnimQueueDecoder::ReadStep() { + FlxAnimationStepView result; + result.Hash = Decoder.read_uint64(); + result.Body = Decoder.read_string_view(); + return result; +} + +FlxAnimationField FlxAnimationStepDecoder::ReadField() { + FlxAnimationField result; + result.Name = Decoder.read_string_view(); + result.Persistent = Decoder.read_bool(); + result.Type = (ElxAnimValueType)Decoder.read_uint8(); + switch (result.Type) { + case ElxAnimValueType::STRING: { + result.S = Decoder.read_string_view(); + break; + } + case ElxAnimValueType::NUMBER: { + result.X = Decoder.read_double(); + break; + } + case ElxAnimValueType::BOOLEAN: { + result.X = Decoder.read_bool() ? 1.0 : 0.0; + break; + } + case ElxAnimValueType::XYZ: { + result.X = Decoder.read_double(); + result.Y = Decoder.read_double(); + result.Z = Decoder.read_double(); + break; + } + default: { + Decoder.set_at_eof(); + result.Type = ElxAnimValueType::BOOLEAN; + result.X = 0; + break; + } + } + return result; +} + +FString FlxAnimationStepDecoder::DebugString(uint64 hash, std::string_view body) { + FString result; + FlxAnimationStepDecoder decoder(body); + result.Appendf(TEXT("Hash=%016llx"), hash); + while (!decoder.AtEOF()) { + FlxAnimationField field = decoder.ReadField(); + 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::STRING: + result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data())); + break; + case ElxAnimValueType::NUMBER: + result.Appendf(TEXT("%lf"), field.X); + break; + case ElxAnimValueType::BOOLEAN: + result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false")); + break; + case ElxAnimValueType::XYZ: + result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z); + break; + } + } + return result; +} + +FString FlxAnimQueueDecoder::DebugString(std::string_view queue) { + FString result; + FlxAnimQueueDecoder decoder(queue); + while (!decoder.AtEOF()) { + FlxAnimationStepView step = decoder.ReadStep(); + FString stepdebug = FlxAnimationStepDecoder::DebugString(step.Hash, step.Body); + result.Appendf(TEXT("%s\n"), *stepdebug); + } + return result; +} + + FlxAnimTracker::FlxAnimTracker() { AQ.Empty(); FirstSeqno = 0; HashToSeqno.Empty(); UnstartedSeqno = 0; - PlaybackMode = ElxAnimPlaybackMode::INVALID; + PlaybackMode = ElxAnimationMode::INVALID; AbortedHashes.Empty(); } @@ -195,10 +209,10 @@ void FlxAnimTracker::Update(std::string_view encqueue) { // after the matching record onto a stack of new records. // FlxAnimQueueDecoder decoder(encqueue); - TArray newsteps; + TArray newsteps; int32 matchingseqno = -1; while (!decoder.AtEOF()) { - FlxAnimStep step = decoder.ReadStep(); + FlxAnimationStepView step = decoder.ReadStep(); int32* stepseq = HashToSeqno.Find(step.Hash); if (stepseq == nullptr) { newsteps.Emplace(step); @@ -233,13 +247,13 @@ void FlxAnimTracker::Update(std::string_view encqueue) { // if (UnstartedSeqno > (FirstSeqno + AQ.Num())) { UnstartedSeqno = matchingseqno; - PlaybackMode = ElxAnimPlaybackMode::BLEND_TO_FINAL; + PlaybackMode = ElxAnimationMode::BlendToFinal; } // Transfer the new animations onto the queue. // while (!newsteps.IsEmpty()) { - FlxAnimStep step = newsteps.Pop(); + FlxAnimationStepView step = newsteps.Pop(); int32 seqno = FirstSeqno + AQ.Num(); AQ.EmplaceLast(step.Hash, step.Body); HashToSeqno.Emplace(step.Hash, seqno); @@ -268,7 +282,7 @@ void FlxAnimTracker::Update(std::string_view encqueue) { } else { UnstartedSeqno = FirstSeqno + AQ.Num() - 1; } - PlaybackMode = ElxAnimPlaybackMode::WARP_TO_FINAL; + PlaybackMode = ElxAnimationMode::WarpToFinal; } } @@ -279,15 +293,15 @@ TArray FlxAnimTracker::GetAborted() { return result; } -ElxAnimPlaybackMode FlxAnimTracker::GetNextStep(FlxAnimStoredStep &step) { +ElxAnimationMode FlxAnimTracker::GetNextStep(FlxAnimationStep &step) { int offset = UnstartedSeqno - FirstSeqno; if (offset < AQ.Num()) { step = AQ[offset]; return PlaybackMode; } else { step.Hash = 0; - step.Body = ""; - return ElxAnimPlaybackMode::INVALID; + step.Body.Empty(); + return ElxAnimationMode::INVALID; } } @@ -296,6 +310,6 @@ void FlxAnimTracker::StartedStep(uint64 hash) { check(offset < AQ.Num()); check(AQ[offset].Hash == hash); UnstartedSeqno += 1; - PlaybackMode = ElxAnimPlaybackMode::START_ANIMATION; + PlaybackMode = ElxAnimationMode::StartAnimation; } diff --git a/Source/Integration/AnimQueue.h b/Source/Integration/AnimQueue.h index 338dad14..c773bc32 100644 --- a/Source/Integration/AnimQueue.h +++ b/Source/Integration/AnimQueue.h @@ -3,6 +3,7 @@ #include "CoreMinimal.h" #include "StringDecoder.h" #include "Containers/Deque.h" +#include "AnimQueue.generated.h" //////////////////////////////////////////////// // @@ -31,15 +32,16 @@ enum class ElxAnimValueType { // // 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. +// state is very far from the final state, then maybe +// jump instantaneously instead. // //////////////////////////////////////////////// -enum class ElxAnimPlaybackMode { - START_ANIMATION, - WARP_TO_FINAL, - BLEND_TO_FINAL, +UENUM(BlueprintType) +enum class ElxAnimationMode : uint8 { + StartAnimation, + WarpToFinal, + BlendToFinal, INVALID, }; @@ -47,28 +49,91 @@ enum class ElxAnimPlaybackMode { // // An single animation step. // -// The body consists of a sequence of FlxAnimField +// The body consists of a sequence of FlxAnimationField // records. The body is encoded, to read the -// FlxAnimField records you need an FlxAnimStepDecoder. -// This comes in two versions: the string version, -// and the string_view version. +// FlxAnimationField records you need an +// FlxAnimationStepDecoder. // //////////////////////////////////////////////// -struct FlxAnimStep { +USTRUCT(Blueprintable) +struct INTEGRATION_API FlxAnimationStep { + GENERATED_BODY() + +public: + UPROPERTY() + uint64 Hash; + + UPROPERTY() + TArray Body; + + FlxAnimationStep() : Hash(0), Body() {} + FlxAnimationStep(uint64 h, std::string_view b); + + // Unpack an AnimStep into a UObject + // + // Stores the key-value pairs of the animation step into + // properties of a UObject. For example, if the key-value + // pair "color=blue" is present in the AnimStep, then this + // routine tries to find a string property "color" in the + // UObject, and then it sets that property to "blue." + // + // Returns true if all of the key-value pairs in the + // animation step could be unpacked into fields of the UObject. + // This could fail, for instance, if the UObject just doesn't + // contain one of the necessary properties. It can also + // fail if there's a type mismatch. For example, "color=blue" + // cannot be stored in a property "color" of type int. + // + // The prefix is prepended to the key names. For example, + // if one of the key-value pairs is "color=blue", and the + // prefix is "aq", then the property "aqColor=blue" will be + // stored in the UObject. + // + // If pre-clear is true, then all properties of the UObject + // starting with the specified prefix are cleared before + // unpacking the animation step. + // + bool Unpack(const FString& prefix, UObject* into, bool preclear = true) const; +}; + +//////////////////////////////////////////////// +// +// This UClass is never instantiated. It exists to +// expose certain static functions to the blueprint +// library. +// +//////////////////////////////////////////////// + +UCLASS() +class INTEGRATION_API UlxAnimationStepLibrary : public UObject +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, BlueprintPure, Category = Tangibles) + static FString AnimationStepDebugString(const FlxAnimationStep& step); + + UFUNCTION(BlueprintCallable, Category = Tangibles) + static void UnpackAnimationStep(const FlxAnimationStep& step, + const FString& prefix, UObject* into, bool preclear = true); +}; + +//////////////////////////////////////////////// +// +// Exposing functions to blueprints. +// +//////////////////////////////////////////////// + +//UFUNCTION(BlueprintCallable) +//void Unpack(const FString& prefix, UObject* into, bool preclear = true); + +struct FlxAnimationStepView { uint64 Hash; std::string_view Body; - FlxAnimStep() : Hash(0), Body("") {} - FlxAnimStep(uint64 h, std::string_view b) : Hash(h), Body(b) {} -}; - -struct FlxAnimStoredStep { - uint64 Hash; - std::string Body; - - FlxAnimStoredStep() : Hash(0), Body("") {} - FlxAnimStoredStep(uint64 h, std::string_view b) : Hash(h), Body(b) {} + FlxAnimationStepView() : Hash(0), Body("") {} + FlxAnimationStepView(uint64 h, std::string_view b) : Hash(h), Body(b) {} }; //////////////////////////////////////////////// @@ -85,7 +150,7 @@ struct FlxAnimStoredStep { // //////////////////////////////////////////////// -struct FlxAnimField { +struct FlxAnimationField { std::string_view Name; bool Persistent; ElxAnimValueType Type; @@ -119,7 +184,7 @@ public: // Read one animation step. // - FlxAnimStep ReadStep(); + FlxAnimationStepView ReadStep(); // Convert an AnimQueue to an FString. // @@ -131,22 +196,20 @@ public: // An Animation Step Decoder. // // This acts a lot like a stream reader, -// it reads one FlxAnimField at a time from +// it reads one FlxAnimationField at a time from // the animation queue until you reach // 'end-of-file'. // //////////////////////////////////////////////// -class FlxAnimStepDecoder { +class FlxAnimationStepDecoder { private: FlxStringDecoder Decoder; - static bool ClearProperties(const FString& prefix, UObject* obj); - static bool SetProperty(const FString& name, UObject *obj, const FlxAnimField& value); public: - // Initialize the FlxAnimStepDecoder from the FlxAnimStep. + // Initialize the FlxAnimationStepDecoder from the FlxAnimationStepView. // - FlxAnimStepDecoder(std::string_view body) : Decoder(body) {} + FlxAnimationStepDecoder(std::string_view body) : Decoder(body) {} // Return true if the parser has reached the end of the string. // @@ -154,37 +217,11 @@ public: // Read one field. // - FlxAnimField ReadField(); + FlxAnimationField ReadField(); // Convert an AnimStep to an FString. // - static FString DebugString(std::string_view body); - - // Unpack an AnimStep into a UObject - // - // Stores the key-value pairs of the animation step into - // properties of a UObject. For example, if the key-value - // pair "color=blue" is present in the AnimStep, then this - // routine tries to find a string property "color" in the - // UObject, and then it sets that property to "blue." - // - // Returns true if all of the key-value pairs in the - // animation step could be unpacked into fields of the UObject. - // This could fail, for instance, if the UObject just doesn't - // contain one of the necessary properties. It can also - // fail if there's a type mismatch. For example, "color=blue" - // cannot be stored in a property "color" of type int. - // - // The prefix is prepended to the key names. For example, - // if one of the key-value pairs is "color=blue", and the - // prefix is "aq", then the property "aqColor=blue" will be - // stored in the UObject. - // - // If pre-clear is true, then all properties of the UObject - // starting with the specified prefix are cleared before - // unpacking the animation step. - // - static bool UnpackInto(std::string_view body, const FString &prefix, bool preclear, UObject* obj); + static FString DebugString(uint64 hash, std::string_view body); }; //////////////////////////////////////////////// @@ -205,7 +242,7 @@ public: // store the hashes, not the steps. The First element // of the queue is the oldest item. // - TDeque AQ; + TDeque AQ; // The sequence number of the first item in AQ. // @@ -221,7 +258,7 @@ public: // Indicates whether the unstarted animation should be played or otherwise. // - ElxAnimPlaybackMode PlaybackMode; + ElxAnimationMode PlaybackMode; // Array of recently-aborted hash values. // @@ -254,7 +291,7 @@ public: // playback mode. If the playback mode is INVALID then there is // no next step to play // - ElxAnimPlaybackMode GetNextStep(FlxAnimStoredStep& step); + ElxAnimationMode GetNextStep(FlxAnimationStep& step); // Declare that an animation has been started. // diff --git a/Source/Integration/IntegrationGameModeBase.cpp b/Source/Integration/IntegrationGameModeBase.cpp index d338527c..2f6dfb69 100644 --- a/Source/Integration/IntegrationGameModeBase.cpp +++ b/Source/Integration/IntegrationGameModeBase.cpp @@ -136,28 +136,13 @@ void AIntegrationGameModeBase::UpdateTangibles() { for (uint64 hash : aborted) { IlxTangibleInterface::Execute_AbortAnimation(t->Actor, hash); } - FlxAnimStoredStep step; - ElxAnimPlaybackMode mode = t->AnimTracker.GetNextStep(step); - bool started = false; - if (mode != ElxAnimPlaybackMode::INVALID) { - FlxAnimStepDecoder::UnpackInto(step.Body, TEXT("aq"), true, t->Actor); - } - switch (mode) { - case ElxAnimPlaybackMode::INVALID: - started = false; // Nothing to do. - break; - case ElxAnimPlaybackMode::WARP_TO_FINAL: - started = IlxTangibleInterface::Execute_WarpToFinal(t->Actor, step.Hash, step.Body.size()); - break; - case ElxAnimPlaybackMode::BLEND_TO_FINAL: - started = IlxTangibleInterface::Execute_BlendToFinal(t->Actor, step.Hash, step.Body.size()); - break; - case ElxAnimPlaybackMode::START_ANIMATION: - started = IlxTangibleInterface::Execute_StartAnimation(t->Actor, step.Hash, step.Body.size()); - break; - } - if (started) { - t->AnimTracker.StartedStep(step.Hash); + FlxAnimationStep step; + ElxAnimationMode mode = t->AnimTracker.GetNextStep(step); + if (mode != ElxAnimationMode::INVALID) { + bool started = IlxTangibleInterface::Execute_StartAnimation(t->Actor, mode, step); + if (started) { + t->AnimTracker.StartedStep(step.Hash); + } } } } diff --git a/Source/Integration/TangibleInterface.h b/Source/Integration/TangibleInterface.h index 269fcd39..89f9c4e7 100644 --- a/Source/Integration/TangibleInterface.h +++ b/Source/Integration/TangibleInterface.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "UObject/Interface.h" +#include "AnimQueue.h" #include "TangibleInterface.generated.h" // This class does not need to be modified. @@ -23,14 +24,8 @@ 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(int64 hash, int StrLen); - - UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") - bool WarpToFinal(int64 hash, int StrLen); - - UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") - bool BlendToFinal(int64 hash, int StrLen); - + bool StartAnimation(ElxAnimationMode mode, const FlxAnimationStep& step); + UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") bool AbortAnimation(int64 hash); };