FSlateBrush object destroyed in SWidget by the garbadge collector

I have a widget that owns a const FSlateBrush* IconBrush;
I load the brush in the widget constructor

void SKInventoryListItem::Construct(const FArguments& InArgs)
{
	UID = InArgs._UID;
...
	IconBrush = CreateSlateBrush(UKObjectManager::GetPickupObjectTexture(UID), InArgs._ItemHeight, InArgs._ItemHeight);
....

When I need to paint it, the object is invalid.

int32 SKInventoryListItem::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
	FSlateRect rect = MyClippingRect;
	FVector2D size = rect.GetSize();

	LayerId = SButton::OnPaint(Args, AllottedGeometry, rect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);

	// Draw icon box
	if (IconBrush)
	{

I try to set the IconBrush a UPROPERTY(), but it is still destroyed.

The ObjectManager class takes the texture from a UDataTable.

UTexture2D* UKObjectManager::GetPickupObjectTexture(FString UID)
{
	if (ObjectLookupTable)
	{
		const FKObjectDefinitionStruct* Data = ObjectLookupTable->FindRow<FKObjectDefinitionStruct>(*UID, TEXT(""));
		if (Data)
		{
			if (Data->Icon.IsPending())
			{
				UObject* Asset = Data->Icon.ToStringReference().TryLoad();
				if (Asset == nullptr)
				{
					UE_LOG(LogKSGM, Log, TEXT("UKObjectManager::GetPickupObjectTexture: Still couldn't load Data->Icon"));
				}
			}
			UTexture2D* obj = Data->Icon.Get();
			return obj;
		}
	}
	// Not found, return the unknown object texture
	return UKObjectManager::GetPickupObjectUnknownTexture();
}

I’ve also tried to make a new object returning from the UKObjectManager::GetPickupObject()

IconBrush = CreateSlateBrush(NewObject<UTexture2D>(UKObjectManager::GetPickupObjectTexture(UID)), InArgs._ItemHeight, InArgs._ItemHeight);

inline FSlateBrush* CreateSlateBrush(UTexture2D* Texture, const float Width, const float Height, ESlateBrushDrawType::Type ImageType = ESlateBrushDrawType::Image)
{
	FSlateBrush* ImageIconBrush = new FSlateBrush();
	ImageIconBrush->SetResourceObject(Texture);
	ImageIconBrush->ImageSize.X = Width;
	ImageIconBrush->ImageSize.Y = Height;
	ImageIconBrush->DrawAs = ImageType;
	return ImageIconBrush;

}

But no way, the object in the IconBrush becomes invalid.

What am I doing wrong ?

Txs for your help.
D.

1 Like

Slate widgets aren’t UObject types, so aren’t part of the GC hierarchy (given this fact, I’m unsure how you managed to make your IconBrush a UPROPERTY without UHT complaining).

The simplest way to resolve this is to derive from FGCObject and override AddReferencedObjects to reference any UObject instances that you need to keep alive. FGCObject is a special class that allows non-UObject types to work with the GC. Note that you’ll need to add a reference to the ResourceObject within the FSlateBrush, as FReferenceCollector doesn’t understand structs - you can use FSlateBrush::GetResourceObject to get at the object.

I also have no idea why the UHT didn’t complain.
The object destroyed is the one coming from the function UTexture2D* UKObjectManager::GetPickupObjectTexture(FString UID). This is a static class helper that lookup at records loaded from a DataTable.

What I did to avoid the problem is to AddToRoot the returned object.

UTexture2D* UKObjectManager::GetPickupObjectTexture(FString UID)
{
	if (ObjectLookupTable)
	{
		const FKObjectDefinitionStruct* Data = ObjectLookupTable->FindRow<FKObjectDefinitionStruct>(*UID, TEXT(""));
		if (Data)
		{
			if (Data->Icon.IsPending())
			{
				UObject* Asset = Data->Icon.ToStringReference().TryLoad();
				if (Asset == nullptr)
				{
					UE_LOG(LogKSGM, Log, TEXT("UKObjectManager::GetPickupObjectTexture: Still couldn't load Data->Icon"));
				}
			}
			// Add the icon to root to avoid the element from being destroyed by the GC
			if (Data->Icon.IsValid() && !Data->Icon->IsRooted())
				Data->Icon->AddToRoot();
			return Data->Icon.Get();
		}
	}
	// Not found, return the unknown object texture
	return UKObjectManager::GetPickupObjectUnknownTexture();
}

But I don’t think it is very clean since the object wont never be destroyed.

I will check how works the FGCObject.

D.