2023-09-08 05:38:09 -04:00
|
|
|
|
|
|
|
|
#include "AnimQueue.h"
|
|
|
|
|
|
2023-09-15 13:28:18 -04:00
|
|
|
FlxAnimStep FlxAnimQueueDecoder::ReadStep() {
|
|
|
|
|
FlxAnimStep result;
|
2023-09-08 05:38:09 -04:00
|
|
|
result.Hash = Decoder.read_uint64();
|
|
|
|
|
result.Body = Decoder.read_string_view();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 13:28:18 -04:00
|
|
|
FlxAnimField FlxAnimStepDecoder::ReadField() {
|
|
|
|
|
FlxAnimField result;
|
2023-09-08 05:38:09 -04:00
|
|
|
result.Name = Decoder.read_string_view();
|
|
|
|
|
result.Persistent = Decoder.read_bool();
|
2023-09-15 13:28:18 -04:00
|
|
|
result.Type = (ElxAnimValueType)Decoder.read_uint8();
|
2023-09-08 05:38:09 -04:00
|
|
|
switch (result.Type) {
|
2023-09-15 15:44:01 -04:00
|
|
|
case ElxAnimValueType::STRING: {
|
2023-09-08 05:38:09 -04:00
|
|
|
result.S = Decoder.read_string_view();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-09-15 15:44:01 -04:00
|
|
|
case ElxAnimValueType::NUMBER: {
|
2023-09-08 05:38:09 -04:00
|
|
|
result.X = Decoder.read_double();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-09-15 15:44:01 -04:00
|
|
|
case ElxAnimValueType::BOOLEAN: {
|
2023-09-08 05:38:09 -04:00
|
|
|
result.X = Decoder.read_bool() ? 1.0 : 0.0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-09-15 15:44:01 -04:00
|
|
|
case ElxAnimValueType::XYZ: {
|
2023-09-08 05:38:09 -04:00
|
|
|
result.X = Decoder.read_double();
|
|
|
|
|
result.Y = Decoder.read_double();
|
|
|
|
|
result.Z = Decoder.read_double();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
Decoder.set_at_eof();
|
2023-09-15 15:44:01 -04:00
|
|
|
result.Type = ElxAnimValueType::BOOLEAN;
|
2023-09-08 05:38:09 -04:00
|
|
|
result.X = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 13:28:18 -04:00
|
|
|
FString FlxAnimQueueDecoder::DebugString(std::string_view queue) {
|
2023-09-11 03:44:57 -04:00
|
|
|
FString result;
|
2023-09-15 13:28:18 -04:00
|
|
|
FlxAnimQueueDecoder decoder(queue);
|
2023-09-11 03:44:57 -04:00
|
|
|
while (!decoder.AtEOF()) {
|
2023-09-15 13:28:18 -04:00
|
|
|
FlxAnimStep step = decoder.ReadStep();
|
2023-09-18 19:24:52 -04:00
|
|
|
FString stepdebug = FlxAnimStepDecoder::DebugString(step.Body);
|
2023-09-11 03:44:57 -04:00
|
|
|
result.Appendf(TEXT("%s\n"), *stepdebug);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
2023-09-08 05:38:09 -04:00
|
|
|
}
|
|
|
|
|
|
2023-09-18 19:24:52 -04:00
|
|
|
FString FlxAnimStepDecoder::DebugString(std::string_view body) {
|
2023-09-11 03:44:57 -04:00
|
|
|
FString result;
|
2023-09-18 19:24:52 -04:00
|
|
|
FlxAnimStepDecoder decoder(body);
|
2023-09-11 03:44:57 -04:00
|
|
|
bool first = true;
|
|
|
|
|
while (!decoder.AtEOF()) {
|
2023-09-15 13:28:18 -04:00
|
|
|
FlxAnimField field = decoder.ReadField();
|
2023-09-11 03:44:57 -04:00
|
|
|
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) {
|
2023-09-15 15:44:01 -04:00
|
|
|
case ElxAnimValueType::STRING:
|
2023-09-11 03:44:57 -04:00
|
|
|
result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data()));
|
|
|
|
|
break;
|
2023-09-15 15:44:01 -04:00
|
|
|
case ElxAnimValueType::NUMBER:
|
2023-09-11 03:44:57 -04:00
|
|
|
result.Appendf(TEXT("%lf"), field.X);
|
|
|
|
|
break;
|
2023-09-15 15:44:01 -04:00
|
|
|
case ElxAnimValueType::BOOLEAN:
|
2023-09-11 03:44:57 -04:00
|
|
|
result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false"));
|
|
|
|
|
break;
|
2023-09-15 15:44:01 -04:00
|
|
|
case ElxAnimValueType::XYZ:
|
2023-09-11 03:44:57 -04:00
|
|
|
result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
first = false;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
2023-09-08 05:38:09 -04:00
|
|
|
}
|
|
|
|
|
|
2023-09-18 19:24:52 -04:00
|
|
|
bool FlxAnimStepDecoder::ClearProperties(const FString& prefix, UObject* obj) {
|
|
|
|
|
UClass* uclass = obj->GetClass();
|
|
|
|
|
if (prefix.IsEmpty()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
FName prefixlo(prefix);
|
|
|
|
|
FName prefixhi(prefix + TEXT("\xFFFF"));
|
|
|
|
|
for (TFieldIterator<FProperty> It(uclass); It; ++It)
|
|
|
|
|
{
|
|
|
|
|
FProperty* fprop = *It;
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FlxAnimStepDecoder::SetProperty(const FString& name, UObject* obj, const FlxAnimField& field) {
|
|
|
|
|
UClass* uclass = obj->GetClass();
|
|
|
|
|
FName nname(name);
|
|
|
|
|
switch (field.Type) {
|
|
|
|
|
case ElxAnimValueType::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 ElxAnimValueType::NUMBER: {
|
|
|
|
|
FDoubleProperty* fprop = FindFProperty<FDoubleProperty>(uclass, nname);
|
|
|
|
|
if (fprop == nullptr) return false;
|
|
|
|
|
double* pptr = fprop->ContainerPtrToValuePtr<double>(obj);
|
|
|
|
|
fprop->SetPropertyValue(pptr, field.X);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
case ElxAnimValueType::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 ElxAnimValueType::XYZ: {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#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 ok = true;
|
|
|
|
|
if (preclear) {
|
|
|
|
|
ok &= ClearProperties(prefix, obj);
|
|
|
|
|
}
|
|
|
|
|
while (!decoder.AtEOF()) {
|
|
|
|
|
FlxAnimField field = decoder.ReadField();
|
|
|
|
|
FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data());
|
|
|
|
|
ok &= SetProperty(prefix + sname, obj, field);
|
|
|
|
|
}
|
|
|
|
|
return ok;
|
|
|
|
|
}
|
2023-09-15 00:01:41 -04:00
|
|
|
|
2023-09-15 13:28:18 -04:00
|
|
|
FlxAnimTracker::FlxAnimTracker() {
|
2023-09-15 00:01:41 -04:00
|
|
|
AQ.Empty();
|
|
|
|
|
FirstSeqno = 0;
|
|
|
|
|
HashToSeqno.Empty();
|
|
|
|
|
UnstartedSeqno = 0;
|
2023-09-15 15:44:01 -04:00
|
|
|
PlaybackMode = ElxAnimPlaybackMode::INVALID;
|
2023-09-15 00:01:41 -04:00
|
|
|
AbortedHashes.Empty();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 13:28:18 -04:00
|
|
|
void FlxAnimTracker::Update(std::string_view encqueue) {
|
2023-09-15 00:01:41 -04:00
|
|
|
// 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()) {
|
2023-09-15 13:28:18 -04:00
|
|
|
FlxStringDecoder qdecoder(encqueue);
|
2023-09-15 00:01:41 -04:00
|
|
|
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.
|
|
|
|
|
//
|
2023-09-15 13:28:18 -04:00
|
|
|
FlxAnimQueueDecoder decoder(encqueue);
|
|
|
|
|
TArray<FlxAnimStep> newsteps;
|
2023-09-15 00:01:41 -04:00
|
|
|
int32 matchingseqno = -1;
|
|
|
|
|
while (!decoder.AtEOF()) {
|
2023-09-15 13:28:18 -04:00
|
|
|
FlxAnimStep step = decoder.ReadStep();
|
2023-09-15 00:01:41 -04:00
|
|
|
int32* stepseq = HashToSeqno.Find(step.Hash);
|
|
|
|
|
if (stepseq == nullptr) {
|
|
|
|
|
newsteps.Emplace(step);
|
|
|
|
|
} else {
|
|
|
|
|
matchingseqno = *stepseq;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 15:44:01 -04:00
|
|
|
// Remove all animations after the most recent matching
|
|
|
|
|
// record. If we remove a 'started' animation, add that
|
|
|
|
|
// animation to the list of aborted animations.
|
|
|
|
|
//
|
|
|
|
|
int32 nremove = (FirstSeqno + AQ.Num()) - (matchingseqno + 1);
|
|
|
|
|
check((nremove >= 0) && (nremove <= AQ.Num()));
|
|
|
|
|
for (int32 i = 0; i < nremove; i++) {
|
2023-09-15 00:01:41 -04:00
|
|
|
uint64 lasthash = AQ.Last().Hash;
|
2023-09-15 15:44:01 -04:00
|
|
|
int32 seqno = FirstSeqno + AQ.Num() - 1;
|
2023-09-15 00:01:41 -04:00
|
|
|
HashToSeqno.Remove(lasthash);
|
2023-09-15 15:44:01 -04:00
|
|
|
if (seqno < UnstartedSeqno) {
|
|
|
|
|
AbortedHashes.Emplace(lasthash);
|
|
|
|
|
}
|
2023-09-15 00:01:41 -04:00
|
|
|
AQ.PopLast();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 15:44:01 -04:00
|
|
|
// 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 = ElxAnimPlaybackMode::BLEND_TO_FINAL;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 00:01:41 -04:00
|
|
|
// Transfer the new animations onto the queue.
|
|
|
|
|
//
|
|
|
|
|
while (!newsteps.IsEmpty()) {
|
2023-09-15 13:28:18 -04:00
|
|
|
FlxAnimStep step = newsteps.Pop();
|
2023-09-15 00:01:41 -04:00
|
|
|
int32 seqno = FirstSeqno + AQ.Num();
|
|
|
|
|
AQ.EmplaceLast(step.Hash, step.Body);
|
|
|
|
|
HashToSeqno.Emplace(step.Hash, seqno);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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();
|
2023-09-15 15:44:01 -04:00
|
|
|
FirstSeqno += 1;
|
2023-09-15 00:01:41 -04:00
|
|
|
}
|
2023-09-15 15:44:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
|
if (AQ.Num() == 0) {
|
2023-09-15 00:01:41 -04:00
|
|
|
UnstartedSeqno = FirstSeqno;
|
2023-09-15 15:44:01 -04:00
|
|
|
} else {
|
|
|
|
|
UnstartedSeqno = FirstSeqno + AQ.Num() - 1;
|
2023-09-15 00:01:41 -04:00
|
|
|
}
|
2023-09-15 15:44:01 -04:00
|
|
|
PlaybackMode = ElxAnimPlaybackMode::WARP_TO_FINAL;
|
2023-09-15 00:01:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 13:28:18 -04:00
|
|
|
TArray<uint64> FlxAnimTracker::GetAborted() {
|
2023-09-15 00:01:41 -04:00
|
|
|
TArray<uint64> result;
|
|
|
|
|
result = std::move(AbortedHashes);
|
|
|
|
|
AbortedHashes.Empty();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 15:44:01 -04:00
|
|
|
ElxAnimPlaybackMode FlxAnimTracker::GetNextStep(FlxAnimStoredStep &step) {
|
2023-09-15 00:01:41 -04:00
|
|
|
int offset = UnstartedSeqno - FirstSeqno;
|
2023-09-15 15:44:01 -04:00
|
|
|
if (offset < AQ.Num()) {
|
|
|
|
|
step = AQ[offset];
|
|
|
|
|
return PlaybackMode;
|
|
|
|
|
} else {
|
|
|
|
|
step.Hash = 0;
|
|
|
|
|
step.Body = "";
|
|
|
|
|
return ElxAnimPlaybackMode::INVALID;
|
|
|
|
|
}
|
2023-09-15 00:01:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-09-15 15:44:01 -04:00
|
|
|
void FlxAnimTracker::StartedStep(uint64 hash) {
|
2023-09-15 00:01:41 -04:00
|
|
|
int offset = UnstartedSeqno - FirstSeqno;
|
|
|
|
|
check(offset < AQ.Num());
|
|
|
|
|
check(AQ[offset].Hash == hash);
|
|
|
|
|
UnstartedSeqno += 1;
|
2023-09-15 15:44:01 -04:00
|
|
|
PlaybackMode = ElxAnimPlaybackMode::START_ANIMATION;
|
2023-09-15 00:01:41 -04:00
|
|
|
}
|
|
|
|
|
|