Weird crash in Game Instance function

I’m currently writing my own ‘lean’ version of the Lyra loading screen, where I’ve encountered and reproduced the same issue multiple times. When I add the loading screen widget to the viewport, the game crashes with a mysterious memory access violation.
The function in question is ‘ULoadingScreenSubsystem::ShowLoadingScreenWidget’.

LoadingScreenSubsystem.h

UCLASS(NotBlueprintable)
class SUPERLOADINGSCREEN_API ULoadingScreenSubsystem : public UGameInstanceSubsystem, public FTickableGameObject
{
	GENERATED_BODY()

public:

	// GAME INSTANCE SUBSYSTEM ////////////////////////////////////////////////

	virtual void Initialize(FSubsystemCollectionBase& InCollection) override;
	/* Only clients need a loading screen. This subsystem will never be created on dedicated servers. */
	virtual bool ShouldCreateSubsystem(UObject* InOuter) const override
	{
		return not (InOuter->GetWorld()->GetNetMode() == ENetMode::NM_DedicatedServer);
	}
	virtual void Deinitialize() override;
	// ~ GAME INSTANCE SUBSYSTEM //////////////////////////////////////////////

	// TICKABLE GAME OBJECT ///////////////////////////////////////////////////

	virtual void Tick(float InDeltaTime) override;
	virtual ETickableTickType GetTickableTickType() const override { return ETickableTickType::Conditional; }
	virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(ULoadingScreenSubsystem, STATGROUP_Tickables); }
	virtual bool IsTickableWhenPaused() const override { return true; }
	virtual bool IsTickableInEditor() const override { return false; }

	// ~ TICKABLE GAME OBJECT /////////////////////////////////////////////////

	/* Is the loading screen currently visible to the player? */
	UFUNCTION(BlueprintCallable, Category = "Loading Screen")
	bool IsLoadingScreenVisible() const { return bIsLoadingScreenVisible; }

protected:

	UPROPERTY()
	TObjectPtr<UGameInstance> OwningGameInstance = nullptr;

	TSharedPtr<SWidget> LoadingScreenWidget = nullptr;

	void ShowLoadingScreen();
	void HideLoadingScreen();

	virtual void ShowLoadingScreenWidget();
	virtual void HideLoadingScreenWidget();
	/* Edit the loading screen widget before it is displayed. */
	virtual void EditLoadingScreenWidgetPreDisplay(class ULoadingScreenWidgetBase* InWidget) { /* No-op */ }

private:

	bool bIsLoadingScreenVisible = false;

	/* Do any console variables require a loading screen? */
	bool DoesConsoleNeedLoadingScreen();
	
};

LoadingScreenSubsystem.cpp

void ULoadingScreenSubsystem::Initialize(FSubsystemCollectionBase& InCollection)
{
	Super::Initialize(InCollection);

	OwningGameInstance = GetGameInstance();
}

void ULoadingScreenSubsystem::Deinitialize()
{
	Super::Deinitialize();
}

void ULoadingScreenSubsystem::Tick(float InDeltaTime)
{
	if (DoesConsoleNeedLoadingScreen()) { ShowLoadingScreen(); }
	else { HideLoadingScreen(); }
}

void ULoadingScreenSubsystem::ShowLoadingScreen()
{
	if (bIsLoadingScreenVisible) { return; }

	ShowLoadingScreenWidget();

	bIsLoadingScreenVisible = true;
}

void ULoadingScreenSubsystem::HideLoadingScreen()
{
	if (!bIsLoadingScreenVisible) { return; }

	HideLoadingScreenWidget();

	bIsLoadingScreenVisible = false;
}

void ULoadingScreenSubsystem::ShowLoadingScreenWidget()
{
	TObjectPtr<const ULoadingScreenSettings> tDevSettings = GetDefault<ULoadingScreenSettings>();
	TSubclassOf<ULoadingScreenWidgetBase> tWidgetClass = tDevSettings->LoadingScreenWidget.LoadSynchronous();

	if (UUserWidget* tWidget = UUserWidget::CreateWidgetInstance(*OwningGameInstance, tWidgetClass, NAME_None))
	{
		EditLoadingScreenWidgetPreDisplay(Cast<ULoadingScreenWidgetBase>(tWidget));
		LoadingScreenWidget = tWidget->TakeWidget();
	}
	else
	{
		UE_LOG(LogSLoadingScreen, Warning, TEXT("Failed to create loading screen widget. Using fallback instead."));
		LoadingScreenWidget = SNew(SThrobber);
	}

	OwningGameInstance->GetGameViewportClient()->AddViewportWidgetContent(LoadingScreenWidget.ToSharedRef(), tDevSettings->ZOrder);
}

void ULoadingScreenSubsystem::HideLoadingScreenWidget()
{
	if (!LoadingScreenWidget.IsValid()) { return; }

	OwningGameInstance->GetGameViewportClient()->RemoveViewportWidgetContent(LoadingScreenWidget.ToSharedRef());
	LoadingScreenWidget.Reset();
}

bool ULoadingScreenSubsystem::DoesConsoleNeedLoadingScreen()
{
	if (LoadingScreenConsoleVariables::bForceVisible) { return true; }
	else { return false; }
}

Relevant Crash Report:

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x0000000000000048

UnrealEditor_Engine!UGameInstance::GetFirstGamePlayer() [D:\build++UE5\Sync\Engine\Source\Runtime\Engine\Private\GameInstance.cpp:1211]
UnrealEditor_UMG!UUserWidget::CreateWidgetInstance() [D:\build++UE5\Sync\Engine\Source\Runtime\UMG\Private\UserWidget.cpp:2408]
UnrealEditor_SuperLoadingScreen!ULoadingScreenSubsystem::ShowLoadingScreenWidget() [F:\Gate\Plugins\SuperLoadingScreen\Source\Runtime\Private\LoadingScreenSubsystem.cpp:78]
UnrealEditor_SuperLoadingScreen!ULoadingScreenSubsystem::Tick() [F:\Gate\Plugins\SuperLoadingScreen\Source\Runtime\Private\LoadingScreenSubsystem.cpp:51]

The issue seems to lie in GetFirstGamePlayer (does the engine find no local player?). I’ve checked all objects so I don’t think the memory issue is on my part. Is this a bug in the engine? Maybe I’m missing something.

Any help or comment appreciated…

If I had to guess, you’re just calling this before there are any players. So there can’t be a first one. Or your calling it in-between players being destroyed and players being created as part of the transition. You might have to look more closely at the Lyra version and how it’s interacting with widgets to get around this. (I haven’t looked at this part of Lyra yet so I can’t tell you what you’re doing differently)

1 Like

I’ve tried this out and, sure enough, GetFirstGamePlayer() returns nullptr. The local player array is also empty. This is weird because

  1. When I try to display the loading screen (using console commands), I am in the middle of a map, i.e. in normal gameplay, where a local player should actually exist.
  2. It says in the API documentation for ULocalPlayer:

Each player that is active on the current client has a LocalPlayer. It stays active across maps. There may be several spawned in the case of splitscreen/coop. There may be 0 spawned on servers.

Wouldn’t that mean the local player is created only once per run and not everytime a new map is loaded?

Thanks anyway for putting me on the right track. I’ll keep experimenting…


Also, this is the Lyra code:

// Create the loading screen widget
		TSubclassOf<UUserWidget> LoadingScreenWidgetClass = Settings->LoadingScreenWidget.TryLoadClass<UUserWidget>();
		if (UUserWidget* UserWidget = UUserWidget::CreateWidgetInstance(*LocalGameInstance, LoadingScreenWidgetClass, NAME_None))
		{
			LoadingScreenWidget = UserWidget->TakeWidget();
		}
		else
		{
			UE_LOG(LogLoadingScreen, Error, TEXT("Failed to load the loading screen widget %s, falling back to placeholder."), *Settings->LoadingScreenWidget.ToString());
			LoadingScreenWidget = SNew(SThrobber);
		}

		// Add to the viewport at a high ZOrder to make sure it is on top of most things
		UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient();
		GameViewportClient->AddViewportWidgetContent(LoadingScreenWidget.ToSharedRef(), Settings->LoadingScreenZOrder);

After days of testing, I’ve found the, admittedly very stupid, mistake. Turns out Tick was being called before Init finished (i.e. before the game instance pointer is properly assigned). A simply bool check/guard fixed the issue entirely.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.