UWebBrowser Causes problems with GC

anonymous_user_d84280ef
Tue, 09 Mar 2021 02:18:46 +0000
UI

Situation

I am using UE 4.26 and have issue with UWebBrowser not being properly deleted.
We ale implementing all of this stuff in C++. Here is the thing:

We are trying to implement this:

I am spawning the actor in my Player controller:

...
FTransform T{};

FActorSpawnParameters P{};

TestScreenActor =
    Cast<ACSScreenActor>(GetWorld()->SpawnActor(ACSScreenActor::StaticClass(), &T, P));
...

Actors Constructor:

    ACSScreenActor::ACSScreenActor()
    {
        PrimaryActorTick.bCanEverTick = true;

        ...

        // Create the widget component
        WebBrowserWidgetComponent = CreateDefaultSubobject<UWidgetComponent>("WebWidget");
        WebBrowserWidgetComponent->SetWidgetClass(UCSScreenWidget::StaticClass());
        WebBrowserWidgetComponent->SetDrawSize(Resolution);
        WebBrowserWidgetComponent->SetWidgetSpace(EWidgetSpace::World);
        WebBrowserWidgetComponent->SetGeometryMode(EWidgetGeometryMode::Plane);

        ...

        WebBrowserWidgetComponent->SetMaterial(0, MaterialInstanceObject);

        SetRootComponent(WebBrowserWidgetComponent);
    }

Then comes the construction of the widget. I Inherit from UUserWidget and implement its rebuild widget and native construct as follows:

TSharedRef<SWidget> UCSScreenWidget::RebuildWidget()
{
    auto* RootWidget = Cast<UCanvasPanel>(GetRootWidget());

    if (nullptr == RootWidget)
    {
        RootWidget =
            WidgetTree->ConstructWidget<UCanvasPanel>(
                UCanvasPanel::StaticClass(), FName("Canvas"));
        UCanvasPanelSlot* RootSlot = Cast<UCanvasPanelSlot>(RootWidget->Slot);
        if (nullptr != RootSlot)
        {
            RootSlot->SetAnchors({ 0.0, 0.0, 1.0, 1.0 });
            RootSlot->SetOffsets({ 0.0, 0.0 });
        }
        WidgetTree->RootWidget = RootWidget;
    }

    if (nullptr == RootWidget || nullptr == WidgetTree)
    {
        return Super::RebuildWidget();
    }

    RootWidget->SetClipping(EWidgetClipping::Inherit);
    RootWidget->SetIsEnabled(true);
    RootWidget->SetVisibility(ESlateVisibility::Visible);
    RootWidget->SetRenderTransformPivot({ 0.5, 0.5 });

    WebBrowser =
        WidgetTree->ConstructWidget<UWebBrowser>(
            UWebBrowser::StaticClass(), FName("WebBrowser"));
    UCanvasPanelSlot* CanvasPanelSlot =
            Cast<UCanvasPanelSlot>(RootWidget->AddChild(WebBrowser));

    if (nullptr == CanvasPanelSlot)
    {
        UE_LOG(LogTemp, Error, TEXT("Resize failed"));
        return Super::RebuildWidget();
    }

    CanvasPanelSlot->SetAnchors({ 0.0f, 0.0f, 0.0f, 0.0f });
    CanvasPanelSlot->SetPosition(FVector2D(0.f, 0.f));
    CanvasPanelSlot->SetAlignment({ 0.0, 0.0 });
    CanvasPanelSlot->SetSize(Resolution);

    return Super::RebuildWidget();
}

void UCSScreenWidget::NativeConstruct()
{
    Super::NativeConstruct();

    if (nullptr != WebBrowser)
    {
        WebBrowser->LoadURL(
            "https://duckduckgo.com/");
    }
}

The issue

The issue is that when i load any URL ( the component has to not be hidden and has to be visible ) it does not get properly cleaned up. What i mean by that is i get an exception from here:

SObjectWidget::~SObjectWidget(void)
{
#if SLATE_VERBOSE_NAMED_EVENTS
// This can happen during blueprint compiling, so just ignore it if it happens then, this is really only a concern
// in a running game.
if (!GCompilingBlueprint && !GIsGCingAfterBlueprintCompile)
{
	// This is only a concern during a running game - design-time instances can be destroyed from GC quite often when recompiling
	if (!WidgetObject || !WidgetObject->IsDesignTime())
	{
		ensureMsgf(!IsGarbageCollecting(), TEXT("SObjectWidget for '%s' destroyed while collecting garbage.  This can lead to multiple GCs being required to cleanup the object.  Possible causes might be,\n1) ReleaseSlateResources not being implemented for the owner of this pointer.\n2) You may just be holding onto some slate pointers on an actor that don't get reset until the actor is Garbage Collected.  You should avoid doing this, and instead reset those references when the actor is Destroyed."), *DebugName);
	}
}

In fact this object is the “SObjectWidget” is the “CurrentSlateWidget” Created inside the “void UWidgetComponent::UpdateWidget()” function.

Looks to me that WidgetObject is being removed by GC twice for some reason.

I got stuck with this already for few days. Would be bo great to have some help on this one.

Thank you so much advance.

2 posts - 1 participant