Download

Why does AddDefaulted cause this bug?

So for a menu I’m loading textures at runtime and putting them in buttons like so:


FReply SStandardSlateWidget::selectPuzzleCategory(int32 catSent) {
    pickPuzzleScrollBox->ClearChildren();
    UMyGameInstance* gameInstance = Cast<UMyGameInstance>(playerControllerReference->GetGameInstance());
    for (int32 counter = 0; counter < gameInstance->puzzlesArray.Num(); counter++) {
        if (gameInstance->puzzlesArray[counter].catNumber == catSent) {
            ItemsToStream.AddUnique(gameInstance->puzzlesArray[counter].iconTextureRef.ToSoftObjectPath());            
        }
    }
    currentCatNum = catSent;
    gameInstance->StreamableManager.RequestAsyncLoad(ItemsToStream, FStreamableDelegate::CreateRaw(this, &SStandardSlateWidget::OnLoadComplete));
    return FReply::Handled();
}
void SStandardSlateWidget::OnLoadComplete() {
    UKismetSystemLibrary::PrintString(GEngine->GetWorld(), "Load Complete ", true, true, FLinearColor(1.000000, 1.000000, 1.000000, 1.000000), 40.0);
    UMyGameInstance* gameInstance = Cast<UMyGameInstance>(playerControllerReference->GetGameInstance());
    for (int32 counter = 0; counter < gameInstance->puzzlesArray.Num(); counter++) {
        if (gameInstance->puzzlesArray[counter].catNumber == currentCatNum) {
            int32 currentTextureNumber = iconTextures.Emplace(Cast<UTexture2D>(gameInstance->puzzlesArray[counter].iconTextureRef.Get()));
            iconTextures[currentTextureNumber]->AddToRoot();
        }
    }
    for (int32 counter = 0; counter < iconTextures.Num(); counter++) {
        if (iconTextures[counter]) {
            //int32 currentBrushNumber = iconBrushes.AddDefaulted(1);
            int32 currentBrushNumber = iconBrushes.Add(new FSlateBrush);
            iconBrushes[currentBrushNumber]->SetResourceObject(iconTextures[counter]);
            iconBrushes[currentBrushNumber]->ImageSize.X = iconTextures[counter]->GetSurfaceWidth();
            iconBrushes[currentBrushNumber]->ImageSize.Y = iconTextures[counter]->GetSurfaceHeight();
            iconBrushes[currentBrushNumber]->DrawAs = ESlateBrushDrawType::Image;

            //UKismetSystemLibrary::PrintString(GEngine->GetWorld(), LexToString(currentBrushNumber)+" iconBrushes.GetRenderingResource().IsValid " + LexToString(iconBrushes[currentBrushNumber].GetRenderingResource().IsValid()), true, true, FLinearColor(1.000000, 1.000000, 1.000000, 1.000000), 40.0);
            UKismetSystemLibrary::PrintString(GEngine->GetWorld(), "GetSurfaceWidth() " + LexToString(iconTextures[counter]->GetSurfaceWidth()) + " GetSurfaceHeight() " + LexToString(iconTextures[counter]->GetSurfaceHeight()), true, true, FLinearColor(1.000000, 1.000000, 1.000000, 1.000000), 40.0);

            pickPuzzleScrollBox->AddSlot()
                SNew(SHorizontalBox)
                    + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left)
                        SNew(SButton)
                            .ButtonStyle(FCoreStyle::Get(), "NoBorder")
                            .HAlign(HAlign_Center)
                            .VAlign(VAlign_Center)
                            .ForegroundColor(FSlateColor::UseForeground())
                            
                                TSharedRef<SImage>(SNew(SImage).Image(iconBrushes[currentBrushNumber]))
                            ]
                        .TextStyle(&buttonFont).OnClicked_Raw(this, &SStandardSlateWidget::selectPuzzleCategory, counter).IsFocusable(false)
                    ]
            ];
            pickPuzzleScrollBox->AddSlot()
                SNew(SHorizontalBox)
                    + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left)[SNew(SSpacer).Size(FVector2D(10, 10))]
            ];
        } else {
            UKismetSystemLibrary::PrintString(GEngine->GetWorld(), "No Item Data ", true, true, FLinearColor(1.000000, 1.000000, 1.000000, 1.000000), 40.0);
        }
        if (counter >= 5) {
            //break;
        }
    }
}

A ran into an issue where with 11 images loaded the first 4 were not displayed. If I tried to load just 4 they all showed up but as soon as I tried loading 5 the first 4 didn’t show (the button was still there with an image of width/height zero.

After looking into possible garbage collection of textures and stuff I finally found out it was the slate brush array.

Changing:


UPROPERTY() TArray<FSlateBrush> iconBrushes;
int32 currentBrushNumber = iconBrushes.AddDefaulted(1);

To:


UPROPERTY() TArray<FSlateBrush*> iconBrushes;
int32 currentBrushNumber = iconBrushes.Add(new FSlateBrush);

Did fix the problem, but I’m not sure why. Doesn’t AddDefaulted create a new one as well? I’m scared that I didn’t fix the problem but just changed the timing of things enough where it’s working…for now.

Just a guess:
Any call to TArray<>.AddDefaulted() can reallocate internal array storage and invalidate any pointers pointed to array item.
Try to reserve enough memory in advance to rule out any potential reallocation of iconBrushes array inside loop.

Ohhh yea, that actually makes sense. Seems the likely reason. I could test by checking the value of pointers.