Deleting actors in WorldPartitionRuntimeCellTransformer

Hello,

I am having some issues deleting actors in WorldPartitionRuntimeCellTransformer::Transform. FastGeo does this by just removing the actor from the level list. When I try to do this with more complex actors, sometimes ActorTick gets called on these actors, and usually the editor crashes at that point (as these actors will be marked as garbage soon after beeing removed from the list).

I have tried calling Destroy on the actor before removing it from the list, which causes a different weird bug - when starting PIE in an unloaded region, exiting PIE and loading this region, all of the actor will be deleted - in editor. The transformer is not called when loading the cell, but maybe it uses some cached data? I really don’t understand the inner workings of WP, so I am only guessing.

Is there a safe way to delete actors during PIE start/cook? (We are using some actors as data holders, we extract information from them when starting or cooking the game, and delete these actors.)

I have already asked about this system here: [Preprocessing data from a World Partition [Content removed] we found a solution there, but we are now struggling with the actor removing part

[Attachment Removed]

Hello,

Removing the actor pointers from ULevel::Actors should work unless the actors are referenced by other actors or systems which would keep them alive. This should also prevent the actors from ticking if done properly. Can you share a couple of call stacks for review?

Regards,

Martin

[Attachment Removed]

That type of problem can happen when an object is referenced by a raw C++ pointer (ie, not a UPROPERTY). That raw reference would be invisible to the GC and can lead to accessing a UObject tagged as garbage.

[Attachment Removed]

Unfortunately I can’t. I had a 100% repro a month ago, but today I just can’t reproduce it, even on the original changelist.

However, I don’t think it’s leftover references. The actor was marked as garbage, the actual crash happened because it’s InternalIndex was -1 and some code tried to look something up using this index (stat id maybe?). Unless I am mistaken in my understanding of garbage collector, then if something is garbage, it means there were no references to it left.

[Attachment Removed]

I believe I have found the issue. The problem is that when transforming runtime cells, some actors in the level already come with enabled tick function - and just removing them from the list will not disable the tick function.

It happens for actors which are loaded in editor and modifed and UNSAVED. When loading in a WP cell, UWorldPartitionLevelStreamingDynamic::IssueLoadRequests will look into UnsavedActorsContainer and duplicate these unsaved actors instead. Then it calls Rename, which moves them into the WP cell level. However, AActor has overriden Rename which also enables the tick functions.

I have managed to reproduce this issue with following setup:

UCLASS()
class ATransformedActor : public AActor
{
	GENERATED_BODY()
 
public:
	ATransformedActor()
	{
		PrimaryActorTick.bCanEverTick = true;
		PrimaryActorTick.bStartWithTickEnabled = true;
	}
 
	virtual void Tick(float DeltaSeconds) override
	{
		ensure(IsValid(this));
	};
};
 
UCLASS()
class UTestDeleteTransformedActors : public UWorldPartitionRuntimeCellTransformer
{
	GENERATED_BODY()
 
public:
	virtual void Transform(ULevel* InLevel) override
	{
		for (auto ActorIter = InLevel->Actors.CreateIterator(); ActorIter; ++ActorIter)
		{
			if (ActorIter->IsA(ATransformedActor::StaticClass()))
			{
				ActorIter.RemoveCurrent();
			}
		}
	}
};

Create new WP world, add this transformer, add several TransformedActors far away from the starting point (WP cells around player start are, I believe, transformed before BeginPlay, and they don’t have this issue) and DON’t save them. Start PIE and then move towards them. When the cell is loaded, actor is removed, but the tick function remains registered. Then, after then next GC run, the game will crash will following callstack:

UnrealEditor-Engine.dll!FUObjectArray::IndexToObject(int Index) Line 1091	C++
UnrealEditor-Engine.dll!UObjectBaseUtility::GetStatID(bool) Line 826	C++
UnrealEditor-Engine.dll!FScopeCycleCounterUObject::FScopeCycleCounterUObject(const UObjectBaseUtility * Object) Line 1007	C++
UnrealEditor-Engine.dll!FActorTickFunction::ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const TRefCountPtr<FBaseGraphTask> & MyCompletionGraphEvent) Line 379	C++
UnrealEditor-Engine.dll!FTickFunctionTask::DoTask(ENamedThreads::Type CurrentThread, const TRefCountPtr<FBaseGraphTask> &) Line 334	C++
UnrealEditor-Engine.dll!TGraphTask<FTickFunctionTask>::ExecuteTask() Line 710	C++
...

(I don’t know why, but the Tick function needs to be overriden. Is there some optimization that will silently disable tick on actor if it does nothing?)

[Attachment Removed]

Hello!

I tried to reproduce the problem with the instructions you shared but I couldn’t reproduce the problem. There is one thing I change in the provided code which was adding a RootComponent to ATransformedActor as this is a requirement for the Actor to have a transform and be placeable in the WP level. All the instances of Transformed actors will be part of the PersistentLevel otherwise.

Martin

[Attachment Removed]