Files
integration/Source/Integration/AnimQueue.cpp

468 lines
14 KiB
C++
Raw Normal View History

2023-09-08 05:38:09 -04:00
#include "AnimQueue.h"
#include "UtilityLibrary.h"
2023-09-08 05:38:09 -04:00
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");
2023-09-08 05:38:09 -04:00
}
static void ClearProperties(const FString& prefix, UObject* obj, FProperty *except) {
UClass* uclass = obj->GetClass();
FName prefixlo(prefix);
FName prefixhi(prefix + TEXT("\xFFFF"));
for (TFieldIterator<FProperty> It(uclass); It; ++It)
{
FProperty* fprop = *It;
if (fprop == except) continue;
bool match1 = (fprop->GetFName().Compare(prefixlo) > 0);
bool match2 = (fprop->GetFName().Compare(prefixhi) < 0);
if (match1 && match2) {
uint8* pptr = fprop->ContainerPtrToValuePtr<uint8>(obj);
fprop->ClearValue(pptr);
}
}
}
2023-10-12 18:15:56 -04:00
#pragma optimize("", off)
static bool SetProperty(const FString& prefix, UObject* obj, const FlxAnimationField& field) {
UClass* uclass = obj->GetClass();
2023-10-12 18:15:56 -04:00
FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data());
FName nname(prefix + sname);
switch (field.Type) {
case SimpleDynamicTag::STRING: {
FStrProperty* fprop = FindFProperty<FStrProperty>(uclass, nname);
if (fprop == nullptr) return false;
FString* pptr = fprop->ContainerPtrToValuePtr<FString>(obj);
*pptr = FString(field.S.size(), (const UTF8CHAR*)field.S.data());
return true;
}
case SimpleDynamicTag::NUMBER: {
FDoubleProperty* fprop = FindFProperty<FDoubleProperty>(uclass, nname);
if (fprop == nullptr) return false;
double* pptr = fprop->ContainerPtrToValuePtr<double>(obj);
2023-09-25 14:25:24 -04:00
*pptr = field.X;
return true;
}
case SimpleDynamicTag::BOOLEAN: {
FBoolProperty* fprop = FindFProperty<FBoolProperty>(uclass, nname);
if (fprop == nullptr) return false;
uint8* pptr = fprop->ContainerPtrToValuePtr<uint8>(obj);
fprop->SetPropertyValue(pptr, (field.X == 1.0));
return true;
}
case SimpleDynamicTag::VECTOR: {
FStructProperty* fprop = FindFProperty<FStructProperty>(uclass, nname);
if (fprop == nullptr) return false;
if (fprop->Struct != TBaseStructure<FVector>::Get()) return false;
FVector* pptr = fprop->ContainerPtrToValuePtr<FVector>(obj);
*pptr = FVector(field.X, field.Y, field.Z);
return true;
}
}
return false;
}
2023-10-12 18:15:56 -04:00
#pragma optimize("", off)
static FStructProperty *FindAnimationStepProperty(UClass *uclass, const FString &prefix) {
FName nname(prefix + TEXT("Animation Step"));
FStructProperty* fprop = FindFProperty<FStructProperty>(uclass, nname);
if (fprop == nullptr) return nullptr;
if (fprop->Struct != TBaseStructure<FlxAnimationStep>::Get()) return nullptr;
return fprop;
}
#pragma optimize("", off)
void UlxAnimationStepLibrary::UnpackAnimationStep(bool &bChanged, FString &Action, const FlxAnimationStep& step, UObject* target, const FString& prefix) {
bChanged = false;
Action = TEXT("");
if (prefix.IsEmpty())
{
UE_LOG(LogBlueprint, Error, TEXT("UnpackAnimationStep: You may not pass an empty string for prefix"));
return;
}
UClass* uclass = target->GetClass();
std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
FlxAnimationStepDecoder decoder(body);
FStructProperty* stepproperty = FindAnimationStepProperty(uclass, prefix);
if (stepproperty == nullptr) {
UE_LOG(LogBlueprint, Error, TEXT("UnpackAnimationStep: Target object does not have a variable named: '%s Animation Step'"), *prefix);
return;
}
FlxAnimationStep* stepstorage = stepproperty->ContainerPtrToValuePtr<FlxAnimationStep>(target);
uint64 oldhash = stepstorage->Hash;
FlxAnimationField actionfield;
actionfield.Name = "action";
actionfield.Persistent = false;
actionfield.Type = SimpleDynamicTag::STRING;
actionfield.S = "unknown";
// Decode everything. If an action field is found, save it for later.
ClearProperties(prefix, target, stepproperty);
while (!decoder.AtEOF()) {
FlxAnimationField field = decoder.ReadField();
if ((field.Type == SimpleDynamicTag::STRING) && (field.Name == "action")) {
actionfield.S = field.S;
continue;
}
if (step.Finished && !field.Persistent) continue;
SetProperty(prefix, target, field);
}
// Store the action field.
if (step.Finished) actionfield.S = "idle";
SetProperty(prefix, target, actionfield);
// Store the whole step.
*stepstorage = step;
// Return the correct values.
Action = FString(actionfield.S.size(), (const UTF8CHAR*)actionfield.S.data());
bChanged = (step.Hash != oldhash);
}
2023-09-15 00:01:41 -04:00
FString UlxAnimationStepLibrary::AnimationStepDebugString(const FlxAnimationStep& step) {
std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
2023-10-12 18:15:56 -04:00
return FlxAnimationStepDecoder::DebugString(step.Finished, step.Finished, step.Hash, body);
}
2023-10-09 14:59:48 -04:00
static FlxAnimationField FindAnimationFieldLL(const FlxAnimationStep& step, std::string_view name) {
std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
FlxAnimationStepDecoder decoder(body);
FlxAnimationField result;
while (!decoder.AtEOF()) {
result = decoder.ReadField();
2023-10-09 14:59:48 -04:00
if (result.Name == name) {
return result;
}
}
result.Type = SimpleDynamicTag::UNINITIALIZED;
return result;
}
2023-10-09 14:59:48 -04:00
static FlxAnimationField FindAnimationField(const FlxAnimationStep& step, const FString& name) {
FTCHARToUTF8 utf8name(name);
std::string_view uname(utf8name.Get(), utf8name.Length());
return FindAnimationFieldLL(step, uname);
}
void FlxAnimationStep::AutoUpdateXYZ(AActor *actor) const {
FlxAnimationField xyz = FindAnimationFieldLL(*this, "xyz");
if (xyz.Type == SimpleDynamicTag::VECTOR) {
2023-10-09 14:59:48 -04:00
actor->SetActorLocation(FVector(xyz.X, xyz.Y, xyz.Z));
}
}
void FlxAnimationStep::AutoUpdateFacing(AActor *actor) const {
FlxAnimationField facing = FindAnimationFieldLL(*this, "facing");
if (facing.Type == SimpleDynamicTag::NUMBER) {
2023-10-09 14:59:48 -04:00
actor->SetActorRotation(FRotator(0, facing.X, 0));
}
}
void FlxAnimationStep::AutoUpdatePlane(FName *planep) const {
FlxAnimationField plane = FindAnimationFieldLL(*this, "plane");
if (plane.Type == SimpleDynamicTag::STRING) {
2023-10-09 14:59:48 -04:00
FString pname(plane.S.size(), (const UTF8CHAR*)(plane.S.data()));
*planep = FName(pname);
}
}
2023-10-12 18:15:56 -04:00
bool UlxAnimationStepLibrary::AnimationStepEqual(const FlxAnimationStep &stepa, const FlxAnimationStep &stepb) {
return (stepa.Finished == stepb.Finished) && (stepa.Hash == stepb.Hash);
}
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 != SimpleDynamicTag::VECTOR) return FVector(0, 0, 0);
return FVector(field.X, field.Y, field.Z);
}
double UlxAnimationStepLibrary::AnimationStepGetFloat(const FlxAnimationStep& step, const FString& name) {
FlxAnimationField field = FindAnimationField(step, name);
if (field.Type != SimpleDynamicTag::NUMBER) return 0.0;
return field.X;
}
FString UlxAnimationStepLibrary::AnimationStepGetString(const FlxAnimationStep& step, const FString& name) {
2023-10-12 18:15:56 -04:00
if (step.Finished && (name == TEXT("action"))) {
return TEXT("idle");
}
FlxAnimationField field = FindAnimationField(step, name);
if (field.Type != SimpleDynamicTag::STRING) return TEXT("");
return FString(field.S.size(), (const UTF8CHAR*)(field.S.data()));
}
bool UlxAnimationStepLibrary::AnimationStepGetBool(const FlxAnimationStep& step, const FString& name) {
FlxAnimationField field = FindAnimationField(step, name);
if (field.Type != SimpleDynamicTag::BOOLEAN) return false;
return field.X == 1.0;
}
FlxAnimationField FlxAnimationStepDecoder::ReadField() {
FlxAnimationField result;
result.Name = Decoder.read_string_view();
result.Persistent = Decoder.read_bool();
result.Type = (SimpleDynamicTag)Decoder.read_uint8();
switch (result.Type) {
case SimpleDynamicTag::STRING: {
result.S = Decoder.read_string_view();
break;
}
case SimpleDynamicTag::NUMBER: {
result.X = Decoder.read_double();
break;
}
case SimpleDynamicTag::BOOLEAN: {
result.X = Decoder.read_bool() ? 1.0 : 0.0;
break;
}
case SimpleDynamicTag::VECTOR: {
result.X = Decoder.read_double();
result.Y = Decoder.read_double();
result.Z = Decoder.read_double();
break;
}
default: {
Decoder.read_bytes(Decoder.fill());
result.Type = SimpleDynamicTag::UNINITIALIZED;
break;
}
}
return result;
}
2023-10-12 18:15:56 -04:00
FString FlxAnimationStepDecoder::DebugString(bool injectidle, bool persistentonly, uint64 hash, std::string_view body) {
FString result;
FlxAnimationStepDecoder decoder(body);
result.Appendf(TEXT("Hash=%016llx"), hash);
2023-10-12 18:15:56 -04:00
if (injectidle) {
result.Appendf(TEXT(" action=idle"));
}
while (!decoder.AtEOF()) {
FlxAnimationField field = decoder.ReadField();
2023-10-12 18:15:56 -04:00
if (persistentonly && !field.Persistent) continue;
result.Append(TEXT(" "));
result.Append(FString(field.Name.size(), (const UTF8CHAR*)field.Name.data()));
result.Append(field.Persistent ? TEXT("=") : TEXT(":"));
switch (field.Type) {
case SimpleDynamicTag::STRING:
result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data()));
break;
case SimpleDynamicTag::NUMBER:
result.Appendf(TEXT("%lf"), field.X);
break;
case SimpleDynamicTag::BOOLEAN:
result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false"));
break;
case SimpleDynamicTag::VECTOR:
result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z);
break;
}
}
return result;
}
FlxAnimationStepView FlxAnimQueueDecoder::ReadStep() {
FlxAnimationStepView result;
result.Hash = Decoder.read_uint64();
result.Body = Decoder.read_string_view();
return result;
}
uint64 FlxAnimQueueDecoder::PeekHash() {
int64_t read_count = Decoder.total_reads();
uint64 result = Decoder.read_uint64();
Decoder.unread_to(read_count);
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);
while (!decoder.AtEOF()) {
FlxAnimationStepView step = decoder.ReadStep();
2023-10-12 18:15:56 -04:00
FString stepdebug = FlxAnimationStepDecoder::DebugString(false, false, step.Hash, step.Body);
result.Appendf(TEXT("%s\n"), *stepdebug);
}
return result;
}
2023-09-15 13:28:18 -04:00
FlxAnimTracker::FlxAnimTracker() {
Clear();
}
void FlxAnimTracker::Clear() {
2023-09-15 00:01:41 -04:00
AQ.Empty();
Changed = true;
2024-02-16 15:48:22 -05:00
AutoFinish = false;
AutoFinishAction.Empty();
AutoFinishXYZ.Set(0,0,0);
}
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;
}
}
2023-09-15 00:01:41 -04:00
}
2023-09-15 13:28:18 -04:00
void FlxAnimTracker::Update(std::string_view encqueue) {
check(!encqueue.empty());
// If the first hash matches, we don't bother updating at all.
2023-09-15 00:01:41 -04:00
//
FlxAnimQueueDecoder decoder(encqueue);
if (!AQ.IsEmpty()) {
if (decoder.PeekHash() == AQ.Last().Hash) {
2023-09-15 00:01:41 -04:00
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);
2023-09-15 00:01:41 -04:00
}
// 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.
//
TArray<FlxAnimationStepView> newsteps;
int32 matchingindex = -1;
2023-09-15 00:01:41 -04:00
while (!decoder.AtEOF()) {
FlxAnimationStepView step = decoder.ReadStep();
int32* indexp = HashToIndex.Find(step.Hash);
if (indexp == nullptr) {
2023-09-15 00:01:41 -04:00
newsteps.Emplace(step);
} else {
matchingindex = *indexp;
2023-09-15 00:01:41 -04:00
break;
}
}
// Remove all animations after the most recent matching
// record.
//
int32 nremove = (AQ.Num() - (matchingindex + 1));
check((nremove >= 0) && (nremove <= AQ.Num()));
for (int32 i = 0; i < nremove; i++) {
2023-09-15 00:01:41 -04:00
AQ.PopLast();
}
// Transfer the new animations onto the queue.
//
while (!newsteps.IsEmpty()) {
FlxAnimationStepView step = newsteps.Pop();
2023-09-15 00:01:41 -04:00
AQ.EmplaceLast(step.Hash, step.Body);
}
// If there are too many animations in AQ, discard
// any very old ones.
//
// TODO: this is hardwired to keep 10. Instead, we
// should keep the number specified in the queue.
//
int limit = 10;
int ndiscard = AQ.Num() - limit;
if (ndiscard > 0) {
2023-09-15 00:01:41 -04:00
for (int i = 0; i < ndiscard; i++) {
AQ.PopFirst();
}
}
2024-02-16 15:48:22 -05:00
// Autofinish up to one animation.
//
if (AutoFinish) {
for (int i = 0; i < AQ.Num(); i++) {
if (!AQ[i].Finished) {
if (MatchesAutoFinish(AQ[i])) {
AQ[i].Finished = true;
}
break;
}
}
AutoFinish = false;
AutoFinishAction.Empty();
AutoFinishXYZ.Set(0,0,0);
}
}
void FlxAnimTracker::SetAutoFinish(const FString &action, const FVector &xyz) {
AutoFinish = true;
AutoFinishAction = action;
AutoFinishXYZ = xyz;
}
bool FlxAnimTracker::MatchesAutoFinish(const FlxAnimationStep &step) {
FVector xyz = UlxAnimationStepLibrary::AnimationStepGetVector(step, TEXT("xyz"));
if (xyz != AutoFinishXYZ) return false;
FString action = UlxAnimationStepLibrary::AnimationStepGetString(step, TEXT("action"));
if (action != AutoFinishAction) return false;
return true;
2023-09-15 00:01:41 -04:00
}
FString FlxAnimTracker::GetCurrentBlueprintName() {
for (int i = 0; i < AQ.Num(); i++) {
if (!AQ[i].Finished) {
return AQ[i].Blueprint;
}
}
return AQ.Last().Blueprint;
}
FlxAnimationStep FlxAnimTracker::GetCurrentAnimation() {
2023-10-12 18:15:56 -04:00
FlxAnimationStep result;
for (int i = 0; i < AQ.Num(); i++) {
if (!AQ[i].Finished) {
return AQ[i];
}
}
2023-10-12 18:15:56 -04:00
result = AQ.Last();
2024-02-16 15:48:22 -05:00
// This next line is a hack. We need the idle step to have a unique
// hash. This is a passable way to get a unique hash value.
result.Hash += 1;
2023-10-12 18:15:56 -04:00
return result;
2023-09-15 00:01:41 -04:00
}