Fix bugs in animation queue handling, and add animation timeouts

This commit is contained in:
2025-07-02 16:01:18 -04:00
parent c0307b970b
commit fd453e6b30
9 changed files with 251 additions and 106 deletions

View File

@@ -23,7 +23,32 @@ void UlxTangible::Init(UlxTangibleManager* tm, int64 id)
TangibleId = id;
}
#pragma optimize("", off)
void UlxTangible::DeleteCurrentActor()
{
if (CurrentActor == nullptr) return;
// Remove the tangible component. This is probably
// unnecessary, but it makes it more likely that we'll
// catch bugs early.
UlxTangibleComponent* comp = CurrentActor->GetComponentByClass<UlxTangibleComponent>();
if (comp != nullptr) {
comp->DestroyComponent();
}
// Now destroy the actor itself. According to various
// documents I've read online, it may be necessary to take
// further steps to delete the object. Not clear.
CurrentActor->Destroy();
// If this actor previously was posessed by a player controller,
// then it's not posessed anymore, because there is no actor any more.
if (Manager->PossessedTangible == this) {
Manager->PossessedTangible = nullptr;
};
CurrentActor = nullptr;
}
void UlxTangible::SetActorBlueprint(const FString &XName) {
FString Name = XName.ToLower();
@@ -32,7 +57,6 @@ void UlxTangible::SetActorBlueprint(const FString &XName) {
return;
}
// Get the blueprint.
UClass *blueprint = UlxAssetLookup::LoadTangibleBlueprintAsset(this, Name);
if (blueprint == nullptr)
{
@@ -40,71 +64,50 @@ void UlxTangible::SetActorBlueprint(const FString &XName) {
check(blueprint != nullptr);
}
// If there's already an actor, delete it.
if (CurrentActor != nullptr) {
// Remove the tangible component. This is probably
// unnecessary, but it makes it more likely that we'll
// catch bugs early.
UlxTangibleComponent* comp = CurrentActor->GetComponentByClass<UlxTangibleComponent>();
if (comp != nullptr) {
comp->DestroyComponent();
}
// Now destroy the actor itself. According to various
// documents I've read online, it may be necessary to take
// further steps to delete the object. Not clear.
CurrentActor->Destroy();
// If this actor previously was posessed by a player controller,
// then it's not posessed anymore, because there is no actor any more.
if (Manager->PossessedTangible == this) {
Manager->PossessedTangible = nullptr;
};
}
// Update the blueprint name
ActorBlueprintName = Name;
// Now create a new actor, unless the BP is nullptr.
if (blueprint != nullptr) {
UWorld* w = Manager->GetWorld();
FActorSpawnParameters params;
// If there's already an actor, delete it.
DeleteCurrentActor();
// Give the new actor a reasonable name.
params.Name = FName(*FString::Printf(TEXT("%s_%ld"), *Name, TangibleId));
// Now create a new actor.
UWorld* w = Manager->GetWorld();
FActorSpawnParameters params;
// Currently, the actor is spawned at (0,0,0), which is not good.
// We should spawn at the actor's current location. I'll get to it
// eventually.
FTransform transform;
transform.SetLocation(FVector(0,0,0));
transform.SetRotation(FQuat(FRotator(0,0,0)));
// Give the new actor a reasonable name.
params.Name = FName(*FString::Printf(TEXT("%s_%ld"), *Name, TangibleId));
// Create the actor at the specified location even if there's something in the way.
params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
// Normally, SpawnActor runs the BeginPlay entry point. We
// want to delay that until after we've had a chance to set
// up the TangibleComponent.
params.bDeferConstruction = true;
AActor* a = w->SpawnActor(blueprint, &transform, params);
check(a != nullptr);
// Currently, the actor is spawned at (0,0,0), which is not good.
// We should spawn at the actor's current location. I'll get to it
// eventually.
FTransform transform;
transform.SetLocation(FVector(0,0,0));
transform.SetRotation(FQuat(FRotator(0,0,0)));
// Make sure the label and the name are the same.
a->SetActorLabel(params.Name.ToString());
// Create the actor at the specified location even if there's something in the way.
params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
// Normally, SpawnActor runs the BeginPlay entry point. We
// want to delay that until after we've had a chance to set
// up the TangibleComponent.
params.bDeferConstruction = true;
AActor* a = w->SpawnActor(blueprint, &transform, params);
check(a != nullptr);
// Insert a TangibleComponent into the actor.
// Link the actor to its tangible, and the tangible to its actor.
UActorComponent* ac = a->AddComponentByClass(UlxTangibleComponent::StaticClass(), false, FTransform::Identity, false);
UlxTangibleComponent* tc = Cast<UlxTangibleComponent>(ac);
check(tc != nullptr);
tc->Tangible = this;
CurrentActor = a;
// Make sure the label and the name are the same.
a->SetActorLabel(params.Name.ToString());
// This executes the BeginPlay entry point. We have to do this here
// because we deferred it in SpawnActor.
a->FinishSpawning(transform, true);
}
// Insert a TangibleComponent into the actor.
// Link the actor to its tangible, and the tangible to its actor.
UActorComponent* ac = a->AddComponentByClass(UlxTangibleComponent::StaticClass(), false, FTransform::Identity, false);
UlxTangibleComponent* tc = Cast<UlxTangibleComponent>(ac);
check(tc != nullptr);
tc->Tangible = this;
CurrentActor = a;
// This executes the BeginPlay entry point. We have to do this here
// because we deferred it in SpawnActor.
a->FinishSpawning(transform, true);
}
void UlxTangible::UpdateAnimationQueue(std::string_view aq) {
@@ -113,6 +116,9 @@ void UlxTangible::UpdateAnimationQueue(std::string_view aq) {
void UlxTangible::MaybeExecuteAnimStateChanged() {
int limit = 3;
bool AnyChange = AnimTracker.IsChanged();
while (AnimTracker.IsChanged()) {
if (limit == 0) break;
limit -= 1;
@@ -122,13 +128,47 @@ void UlxTangible::MaybeExecuteAnimStateChanged() {
SetActorBlueprint(blueprint);
AActor *actor = GetActor();
UFunction *aqchanged = actor->GetClass()->FindFunctionByName(FName(TEXT("Animation Queue Changed")));
if (aqchanged != nullptr) {
if (aqchanged != nullptr)
{
actor->ProcessEvent(aqchanged, nullptr);
}
}
// Refresh the pending animation state.
double Now = GetWorld()->GetTimeSeconds();
if (AnyChange)
{
const FlxAnimationStep *Unfinished = AnimTracker.FirstUnfinished();
if (Unfinished == nullptr)
{
PendingAnimationHash = 0;
PendingAnimationTimeout = 0.0;
}
else if (Unfinished->Hash != PendingAnimationHash)
{
PendingAnimationHash = Unfinished->Hash;
PendingAnimationTimeout = Now + 10.0;
}
}
// Implement the animation timeout.
if ((PendingAnimationHash != 0) && (Now > PendingAnimationTimeout))
{
const FlxAnimationStep *Step = AnimTracker.FindAnimation(PendingAnimationHash);
if (Step != nullptr)
{
FString DS = UlxAnimationStepLibrary::AnimationStepDebugString(*Step);
UE_LOG(LogLuprex, Warning, TEXT("Timeout - blueprint didn't finish animation: %s"), *DS);
AnimTracker.FinishedAnimation(PendingAnimationHash);
}
AutoUpdatePosition();
PendingAnimationHash = 0;
PendingAnimationTimeout = 0.0;
}
}
FVector UlxTangible::GetLocation() const {
FVector UlxTangible::GetLocation() const
{
if (CurrentActor == nullptr) {
return FVector(0,0,0);
} else {
@@ -136,8 +176,9 @@ FVector UlxTangible::GetLocation() const {
}
}
void UlxTangible::Destroy() {
SetActorBlueprint("");
void UlxTangible::Destroy()
{
DeleteCurrentActor();
Manager = nullptr;
TangibleId = -1;
CurrentActor = nullptr;
@@ -147,14 +188,16 @@ void UlxTangible::Destroy() {
NearAccordingToUnreal = false;
}
UlxTangible *UlxTangible::GetActorTangibleQuiet(AActor *actor) {
UlxTangible *UlxTangible::GetActorTangibleQuiet(AActor *actor)
{
if (actor == nullptr) return nullptr;
UlxTangibleComponent* comp = actor->GetComponentByClass<UlxTangibleComponent>();
if (comp == nullptr) return nullptr;
return comp->Tangible.Get();
}
UlxTangible *UlxTangible::GetActorTangibleOrLog(AActor *actor) {
UlxTangible *UlxTangible::GetActorTangibleOrLog(AActor *actor)
{
UlxTangible *tan = GetActorTangibleQuiet(actor);
if (tan == nullptr) {
if (actor == nullptr) {
@@ -175,18 +218,24 @@ void UlxTangible::GetCurrentAnimation(AActor *target, FlxAnimationStep &step) {
step = tan->AnimTracker.GetCurrentAnimation();
}
void UlxTangible::AutoUpdatePosition()
{
const FlxAnimationStep *Step = AnimTracker.LastFinished();
if (Step != nullptr)
{
Step->AutoUpdateXYZ(GetActor());
Step->AutoUpdateFacing(GetActor());
Step->AutoUpdatePlane(&(this->Plane));
}
}
void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step, bool AutoUpdate) {
UlxTangible *tan = GetActorTangibleOrLog(target);
if (tan == nullptr) return;
if (AutoUpdate)
{
step.AutoUpdateXYZ(target);
step.AutoUpdateFacing(target);
step.AutoUpdatePlane(&(tan->Plane));
}
tan->AnimTracker.FinishedAnimation(step.Hash);
if (AutoUpdate) tan->AutoUpdatePosition();
FString DebugString = UlxAnimationStepLibrary::AnimationStepDebugString(step);
UE_LOG(LogBlueprint, Display, TEXT("Animation Finished: %s"), *DebugString);
UE_LOG(LogLuprex, Display, TEXT("Animation Finished: %s"), *DebugString);
}
FString UlxTangible::GetTangiblePlane(AActor* target) {