[Best Practices] Keeping UObject containers clean (TArray, TMap etc.)

Example situation:
A header file contains the following property:

UPROPERTY()
  TArray<UWidget*> LotsOfWidgets;

For reasons, it is required that this class manages an array of widgets. These widgets are created and removed (RemoveFromParent) all the time while the application runs.

Tell me if I’m wrong:

  1. Any pointers to these widgets within the UPROPERTY prevents the garbage collector from collecting the removed widgets.
  2. These pointers will remain in the TArray and have to be removed manually in response to the destruction of the widget, in order to clean up the array?

Your are right…I usually have widgets that I create and Store in variables so I can add to viewport / remove to parent and I know they stay there and don’t need to create again…just add to viewport again :slight_smile:

2 Likes

Ah , that is very useful to know that they are still valid after removing them from the viewport.

About these TArrays, somehow I assumed that there could be some magic in place to automatically remove invalid pointers, since the GC tracks it and because responding to destruction events over and over seems ridiculous. Do you know of such a system?

fun fact:

when you remove from viewport, and add again stored in a variable, widgets react just like level streams: keep all values as they were before removing AND runs ‘event construct’ (equals to beginplay) AGAIN!

also stop ticking just like a hidden stream level.

I think those widgets will stay out of the garbage system as long as someone references…if you remove from parent AND remove from the array (so there is no way you can actually reference again) then garbage collector will crush it!

I’m not entirely convinced how this works for UObjects in general.
Taking the following documentation:
> unreal-object-handling

In short:

**Garbage Collection**

Unreal implements a garbage collection scheme whereby UObjects that are no longer referenced or have been explicitly flagged for destruction will be cleaned up at regular intervals. The engine builds a reference graph to determine which UObjects are still in use and which ones are orphaned. At the root of this graph is a set of UObjects designated as the "root set". Any UObject can be added to the root set. When garbage collection occurs, the engine can track all referenced UObjects by searching the tree of known UObject references, starting from the root set. Any unreferenced UObjects, meaning those which are not found in the tree search, will be assumed to be unneeded, and will be removed.

One practical implication here is that you typically need to maintain a UPROPERTY reference to any Object you wish to keep alive, or store a pointer to it in a TArray or other Unreal Engine container class. Actors and their Components are frequently an exception to this, since the Actors are usually referenced by an Object that links back to the root set, such as the Level to which they belong, and the Actor's Components are referenced by the Actor itself. Actors can be explicitly marked for destruction by calling their Destroy function, which is the standard way to remove an Actor from an in-progress game. Components can be destroyed explicitly with the DestroyComponent function, but they are usually destroyed when their owning Actor is removed from the game. 

**Automatic Updating of References**

When an AActor or UActorComponent is destroyed or otherwise removed from play, all references to it that are visible to the reflection system (UProperty pointers and pointers stored in Unreal Engine container classes such as TArray) are automatically nulled. 
~~~

It is important to realize that this feature applies only to UActorComponent or AActor references marked with UPROPERTY or stored in an Unreal Engine container class.
~~~

Sadly it mixes “Objects, UObjects, Actors” on the same lines but I assume that everything applies to UObject with the exception of that one rule that Actors automatically reference their ActorComponents.

Thing is, when it “nulls a pointer” automatically on destruction of a UObject / Actor, this would mean these pointers no longer reference anything and that GC would automatically collect anything previously referenced in the UPROPERTY TArray , right? And you’d assume such an array would not keep a nullptr?

So this still raises the question, do I have to clean up my containers manually when or before UObjects are destroyed? Or does the GC do this?

1 Like

I think some objects like actors, components, etc, once you destroy it, if it were stored in an array, the index of that array will be null…the array wont clean itself…will stay with that cell but with a null pointer so you must clean it up. But the object that was there is not reachable anymore because you destroyed. thing is that you cannot destroy a widget…there is not such a thing like destroy…just remove from parent. So the only way to actually ‘destroy it’ is dereferencing from everywhere (like setting a variable with nothing) and then when nobody references it then will we wiped, but if you have the widget in an array, then you must remove the index to kill and have it removed from parent to actually destroy it.

1 Like

Thank you, this makes sense.

One note about widgets: It seems possible to destroy them immediately:

void UUserWidget::BeginDestroy()
{
	Super::BeginDestroy();

	TearDownAnimations();

	if (AnimationTickManager)
	{
		AnimationTickManager->RemoveWidget(this);
		AnimationTickManager = nullptr;
	}

	//TODO: Investigate why this would ever be called directly, RemoveFromParent isn't safe to call during GC,
	// as the widget structure may be in a partially destroyed state.

	// If anyone ever calls BeginDestroy explicitly on a widget we need to immediately remove it from
	// the the parent as it may be owned currently by a slate widget.  As long as it's the viewport we're
	// fine.
	RemoveFromParent();

	// If it's not owned by the viewport we need to take more extensive measures.  If the GC widget still
	// exists after this point we should just reset the widget, which will forcefully cause the SObjectWidget
	// to lose access to this UObject.
	TSharedPtr<SObjectWidget> SafeGCWidget = MyGCWidget.Pin();
	if ( SafeGCWidget.IsValid() )
	{
		SafeGCWidget->ResetWidget();
	}
}

But what you are saying (remove from parent) is definitely the recommended approach. That TODO comment in the code doesn’t look too promising right? haha.

For now this is solved.

1 Like