Now passing FlxAnimationStep into the blueprint

This commit is contained in:
2023-09-19 22:08:15 -04:00
parent b74f9495fc
commit 1d8bdfc7bf
5 changed files with 222 additions and 191 deletions

Binary file not shown.

View File

@@ -1,89 +1,13 @@
#include "AnimQueue.h" #include "AnimQueue.h"
FlxAnimStep FlxAnimQueueDecoder::ReadStep() { FlxAnimationStep::FlxAnimationStep(uint64 hash, std::string_view body) {
FlxAnimStep result; Hash = hash;
result.Hash = Decoder.read_uint64(); Body.SetNum(body.size());
result.Body = Decoder.read_string_view(); memcpy(Body.GetData(), body.data(), body.size());
return result;
} }
FlxAnimField FlxAnimStepDecoder::ReadField() { static bool ClearProperties(const FString& prefix, UObject* obj) {
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) {
UClass* uclass = obj->GetClass(); UClass* uclass = obj->GetClass();
if (prefix.IsEmpty()) { if (prefix.IsEmpty()) {
return false; return false;
@@ -103,7 +27,7 @@ bool FlxAnimStepDecoder::ClearProperties(const FString& prefix, UObject* obj) {
return true; 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(); UClass* uclass = obj->GetClass();
FName nname(name); FName nname(name);
switch (field.Type) { switch (field.Type) {
@@ -140,28 +64,118 @@ bool FlxAnimStepDecoder::SetProperty(const FString& name, UObject* obj, const Fl
return false; return false;
} }
#pragma optimize("", off) bool FlxAnimationStep::Unpack(const FString& prefix, UObject* into, bool preclear) const {
bool FlxAnimStepDecoder::UnpackInto(std::string_view body, const FString& prefix, bool preclear, UObject* obj) { UClass* uclass = into->GetClass();
UClass* uclass = obj->GetClass(); std::string_view body((const char*)(Body.GetData()), Body.Num());
FlxAnimStepDecoder decoder(body); FlxAnimationStepDecoder decoder(body);
bool ok = true; bool ok = true;
if (preclear) { if (preclear) {
ok &= ClearProperties(prefix, obj); ok &= ClearProperties(prefix, into);
} }
while (!decoder.AtEOF()) { while (!decoder.AtEOF()) {
FlxAnimField field = decoder.ReadField(); FlxAnimationField field = decoder.ReadField();
FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data()); FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data());
ok &= SetProperty(prefix + sname, obj, field); ok &= SetProperty(prefix + sname, into, field);
} }
return ok; 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() { FlxAnimTracker::FlxAnimTracker() {
AQ.Empty(); AQ.Empty();
FirstSeqno = 0; FirstSeqno = 0;
HashToSeqno.Empty(); HashToSeqno.Empty();
UnstartedSeqno = 0; UnstartedSeqno = 0;
PlaybackMode = ElxAnimPlaybackMode::INVALID; PlaybackMode = ElxAnimationMode::INVALID;
AbortedHashes.Empty(); AbortedHashes.Empty();
} }
@@ -195,10 +209,10 @@ void FlxAnimTracker::Update(std::string_view encqueue) {
// after the matching record onto a stack of new records. // after the matching record onto a stack of new records.
// //
FlxAnimQueueDecoder decoder(encqueue); FlxAnimQueueDecoder decoder(encqueue);
TArray<FlxAnimStep> newsteps; TArray<FlxAnimationStepView> newsteps;
int32 matchingseqno = -1; int32 matchingseqno = -1;
while (!decoder.AtEOF()) { while (!decoder.AtEOF()) {
FlxAnimStep step = decoder.ReadStep(); FlxAnimationStepView step = decoder.ReadStep();
int32* stepseq = HashToSeqno.Find(step.Hash); int32* stepseq = HashToSeqno.Find(step.Hash);
if (stepseq == nullptr) { if (stepseq == nullptr) {
newsteps.Emplace(step); newsteps.Emplace(step);
@@ -233,13 +247,13 @@ void FlxAnimTracker::Update(std::string_view encqueue) {
// //
if (UnstartedSeqno > (FirstSeqno + AQ.Num())) { if (UnstartedSeqno > (FirstSeqno + AQ.Num())) {
UnstartedSeqno = matchingseqno; UnstartedSeqno = matchingseqno;
PlaybackMode = ElxAnimPlaybackMode::BLEND_TO_FINAL; PlaybackMode = ElxAnimationMode::BlendToFinal;
} }
// Transfer the new animations onto the queue. // Transfer the new animations onto the queue.
// //
while (!newsteps.IsEmpty()) { while (!newsteps.IsEmpty()) {
FlxAnimStep step = newsteps.Pop(); FlxAnimationStepView step = newsteps.Pop();
int32 seqno = FirstSeqno + AQ.Num(); int32 seqno = FirstSeqno + AQ.Num();
AQ.EmplaceLast(step.Hash, step.Body); AQ.EmplaceLast(step.Hash, step.Body);
HashToSeqno.Emplace(step.Hash, seqno); HashToSeqno.Emplace(step.Hash, seqno);
@@ -268,7 +282,7 @@ void FlxAnimTracker::Update(std::string_view encqueue) {
} else { } else {
UnstartedSeqno = FirstSeqno + AQ.Num() - 1; UnstartedSeqno = FirstSeqno + AQ.Num() - 1;
} }
PlaybackMode = ElxAnimPlaybackMode::WARP_TO_FINAL; PlaybackMode = ElxAnimationMode::WarpToFinal;
} }
} }
@@ -279,15 +293,15 @@ TArray<uint64> FlxAnimTracker::GetAborted() {
return result; return result;
} }
ElxAnimPlaybackMode FlxAnimTracker::GetNextStep(FlxAnimStoredStep &step) { ElxAnimationMode FlxAnimTracker::GetNextStep(FlxAnimationStep &step) {
int offset = UnstartedSeqno - FirstSeqno; int offset = UnstartedSeqno - FirstSeqno;
if (offset < AQ.Num()) { if (offset < AQ.Num()) {
step = AQ[offset]; step = AQ[offset];
return PlaybackMode; return PlaybackMode;
} else { } else {
step.Hash = 0; step.Hash = 0;
step.Body = ""; step.Body.Empty();
return ElxAnimPlaybackMode::INVALID; return ElxAnimationMode::INVALID;
} }
} }
@@ -296,6 +310,6 @@ void FlxAnimTracker::StartedStep(uint64 hash) {
check(offset < AQ.Num()); check(offset < AQ.Num());
check(AQ[offset].Hash == hash); check(AQ[offset].Hash == hash);
UnstartedSeqno += 1; UnstartedSeqno += 1;
PlaybackMode = ElxAnimPlaybackMode::START_ANIMATION; PlaybackMode = ElxAnimationMode::StartAnimation;
} }

View File

@@ -3,6 +3,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "StringDecoder.h" #include "StringDecoder.h"
#include "Containers/Deque.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 // 3. Blend to Final. Skip the actual animation, and blend
// smoothly to the final state of the animation. If the current // smoothly to the final state of the animation. If the current
// state is very far from the final state, then maybe jump instantaneously // state is very far from the final state, then maybe
// instead. // jump instantaneously instead.
// //
//////////////////////////////////////////////// ////////////////////////////////////////////////
enum class ElxAnimPlaybackMode { UENUM(BlueprintType)
START_ANIMATION, enum class ElxAnimationMode : uint8 {
WARP_TO_FINAL, StartAnimation,
BLEND_TO_FINAL, WarpToFinal,
BlendToFinal,
INVALID, INVALID,
}; };
@@ -47,28 +49,91 @@ enum class ElxAnimPlaybackMode {
// //
// An single animation step. // 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 // records. The body is encoded, to read the
// FlxAnimField records you need an FlxAnimStepDecoder. // FlxAnimationField records you need an
// This comes in two versions: the string version, // FlxAnimationStepDecoder.
// and the string_view version.
// //
//////////////////////////////////////////////// ////////////////////////////////////////////////
struct FlxAnimStep { USTRUCT(Blueprintable)
struct INTEGRATION_API FlxAnimationStep {
GENERATED_BODY()
public:
UPROPERTY()
uint64 Hash;
UPROPERTY()
TArray<uint8> 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; uint64 Hash;
std::string_view Body; std::string_view Body;
FlxAnimStep() : Hash(0), Body("") {} FlxAnimationStepView() : Hash(0), Body("") {}
FlxAnimStep(uint64 h, std::string_view b) : Hash(h), Body(b) {} FlxAnimationStepView(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) {}
}; };
//////////////////////////////////////////////// ////////////////////////////////////////////////
@@ -85,7 +150,7 @@ struct FlxAnimStoredStep {
// //
//////////////////////////////////////////////// ////////////////////////////////////////////////
struct FlxAnimField { struct FlxAnimationField {
std::string_view Name; std::string_view Name;
bool Persistent; bool Persistent;
ElxAnimValueType Type; ElxAnimValueType Type;
@@ -119,7 +184,7 @@ public:
// Read one animation step. // Read one animation step.
// //
FlxAnimStep ReadStep(); FlxAnimationStepView ReadStep();
// Convert an AnimQueue to an FString. // Convert an AnimQueue to an FString.
// //
@@ -131,22 +196,20 @@ public:
// An Animation Step Decoder. // An Animation Step Decoder.
// //
// This acts a lot like a stream reader, // 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 // the animation queue until you reach
// 'end-of-file'. // 'end-of-file'.
// //
//////////////////////////////////////////////// ////////////////////////////////////////////////
class FlxAnimStepDecoder { class FlxAnimationStepDecoder {
private: private:
FlxStringDecoder Decoder; FlxStringDecoder Decoder;
static bool ClearProperties(const FString& prefix, UObject* obj);
static bool SetProperty(const FString& name, UObject *obj, const FlxAnimField& value);
public: 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. // Return true if the parser has reached the end of the string.
// //
@@ -154,37 +217,11 @@ public:
// Read one field. // Read one field.
// //
FlxAnimField ReadField(); FlxAnimationField ReadField();
// Convert an AnimStep to an FString. // Convert an AnimStep to an FString.
// //
static FString DebugString(std::string_view body); static FString DebugString(uint64 hash, 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);
}; };
//////////////////////////////////////////////// ////////////////////////////////////////////////
@@ -205,7 +242,7 @@ public:
// store the hashes, not the steps. The First element // store the hashes, not the steps. The First element
// of the queue is the oldest item. // of the queue is the oldest item.
// //
TDeque<FlxAnimStoredStep> AQ; TDeque<FlxAnimationStep> AQ;
// The sequence number of the first item in 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. // Indicates whether the unstarted animation should be played or otherwise.
// //
ElxAnimPlaybackMode PlaybackMode; ElxAnimationMode PlaybackMode;
// Array of recently-aborted hash values. // Array of recently-aborted hash values.
// //
@@ -254,7 +291,7 @@ public:
// playback mode. If the playback mode is INVALID then there is // playback mode. If the playback mode is INVALID then there is
// no next step to play // no next step to play
// //
ElxAnimPlaybackMode GetNextStep(FlxAnimStoredStep& step); ElxAnimationMode GetNextStep(FlxAnimationStep& step);
// Declare that an animation has been started. // Declare that an animation has been started.
// //

View File

@@ -136,28 +136,13 @@ void AIntegrationGameModeBase::UpdateTangibles() {
for (uint64 hash : aborted) { for (uint64 hash : aborted) {
IlxTangibleInterface::Execute_AbortAnimation(t->Actor, hash); IlxTangibleInterface::Execute_AbortAnimation(t->Actor, hash);
} }
FlxAnimStoredStep step; FlxAnimationStep step;
ElxAnimPlaybackMode mode = t->AnimTracker.GetNextStep(step); ElxAnimationMode mode = t->AnimTracker.GetNextStep(step);
bool started = false; if (mode != ElxAnimationMode::INVALID) {
if (mode != ElxAnimPlaybackMode::INVALID) { bool started = IlxTangibleInterface::Execute_StartAnimation(t->Actor, mode, step);
FlxAnimStepDecoder::UnpackInto(step.Body, TEXT("aq"), true, t->Actor); if (started) {
} t->AnimTracker.StartedStep(step.Hash);
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);
} }
} }
} }

View File

@@ -4,6 +4,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "UObject/Interface.h" #include "UObject/Interface.h"
#include "AnimQueue.h"
#include "TangibleInterface.generated.h" #include "TangibleInterface.generated.h"
// This class does not need to be modified. // This class does not need to be modified.
@@ -23,13 +24,7 @@ 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(int64 hash, int StrLen); bool StartAnimation(ElxAnimationMode mode, const FlxAnimationStep& step);
UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality")
bool WarpToFinal(int64 hash, int StrLen);
UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality")
bool BlendToFinal(int64 hash, int StrLen);
UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality")
bool AbortAnimation(int64 hash); bool AbortAnimation(int64 hash);