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…