From 6814732c4b6b7012e85ed1d8b550c4db96112b60 Mon Sep 17 00:00:00 2001 From: LongLy Date: Mon, 6 Jan 2025 14:27:00 -0700 Subject: [PATCH] Added CommonLoadingScreen --- .../CommonLoadingScreen.uplugin | 29 + .../CommonLoadingScreen/Resources/Icon128.png | Bin 0 -> 12699 bytes .../CommonLoadingScreen.Build.cs | 57 ++ .../Private/CommonLoadingScreenModule.cpp | 5 + .../Private/CommonLoadingScreenSettings.cpp | 12 + .../Private/CommonLoadingScreenSettings.h | 67 ++ .../Private/LoadingScreenManager.cpp | 647 ++++++++++++++++++ .../Public/LoadingProcessInterface.h | 30 + .../Public/LoadingProcessTask.cpp | 47 ++ .../Public/LoadingProcessTask.h | 33 + .../Public/LoadingScreenManager.h | 127 ++++ .../CommonStartupLoadingScreen.Build.cs | 55 ++ .../Private/CommonPreLoadScreen.cpp | 18 + .../Private/CommonPreLoadScreen.h | 20 + .../Private/CommonStartupLoadingScreen.cpp | 62 ++ .../Private/SCommonPreLoadingScreenWidget.cpp | 32 + .../Private/SCommonPreLoadingScreenWidget.h | 26 + 17 files changed, 1267 insertions(+) create mode 100644 Plugins/CommonLoadingScreen/CommonLoadingScreen.uplugin create mode 100644 Plugins/CommonLoadingScreen/Resources/Icon128.png create mode 100644 Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/CommonLoadingScreen.Build.cs create mode 100644 Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenModule.cpp create mode 100644 Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.cpp create mode 100644 Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.h create mode 100644 Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/LoadingScreenManager.cpp create mode 100644 Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessInterface.h create mode 100644 Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessTask.cpp create mode 100644 Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessTask.h create mode 100644 Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingScreenManager.h create mode 100644 Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/CommonStartupLoadingScreen.Build.cs create mode 100644 Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.cpp create mode 100644 Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.h create mode 100644 Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonStartupLoadingScreen.cpp create mode 100644 Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.cpp create mode 100644 Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.h diff --git a/Plugins/CommonLoadingScreen/CommonLoadingScreen.uplugin b/Plugins/CommonLoadingScreen/CommonLoadingScreen.uplugin new file mode 100644 index 0000000..69f4f10 --- /dev/null +++ b/Plugins/CommonLoadingScreen/CommonLoadingScreen.uplugin @@ -0,0 +1,29 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "CommonLoadingScreen", + "Description": "Loading screen manager handling creation and display of a project-specified loading screen widget", + "Category": "Gameplay", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "https://www.epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": false, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "CommonLoadingScreen", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "CommonStartupLoadingScreen", + "Type": "ClientOnly", + "LoadingPhase": "PreLoadingScreen" + } + ] +} \ No newline at end of file diff --git a/Plugins/CommonLoadingScreen/Resources/Icon128.png b/Plugins/CommonLoadingScreen/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..1231d4aad4d0d462fb7b178eb5b30aa61a10df0b GIT binary patch literal 12699 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 a;7#~yJh_ha>A&A^fRdb=Y>l)<=>Gxy=2LS3 literal 0 HcmV?d00001 diff --git a/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/CommonLoadingScreen.Build.cs b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/CommonLoadingScreen.Build.cs new file mode 100644 index 0000000..5c9fc07 --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/CommonLoadingScreen.Build.cs @@ -0,0 +1,57 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class CommonLoadingScreen : ModuleRules +{ + public CommonLoadingScreen(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "InputCore", + "PreLoadScreen", + "RenderCore", + "DeveloperSettings", + "UMG" + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenModule.cpp b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenModule.cpp new file mode 100644 index 0000000..7a55970 --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenModule.cpp @@ -0,0 +1,5 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Modules/ModuleManager.h" + +IMPLEMENT_MODULE(FDefaultModuleImpl, CommonLoadingScreen) \ No newline at end of file diff --git a/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.cpp b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.cpp new file mode 100644 index 0000000..bfa8735 --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.cpp @@ -0,0 +1,12 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "CommonLoadingScreenSettings.h" + + +#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonLoadingScreenSettings) + +UCommonLoadingScreenSettings::UCommonLoadingScreenSettings() +{ + CategoryName = TEXT("Game"); +} + diff --git a/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.h b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.h new file mode 100644 index 0000000..33746aa --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.h @@ -0,0 +1,67 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Engine/DeveloperSettingsBackedByCVars.h" +#include "UObject/SoftObjectPath.h" + +#include "CommonLoadingScreenSettings.generated.h" + +class UObject; + +/** + * Settings for a loading screen system. + */ +UCLASS(config=Game, defaultconfig, meta=(DisplayName="Common Loading Screen")) +class UCommonLoadingScreenSettings : public UDeveloperSettingsBackedByCVars +{ + GENERATED_BODY() + +public: + UCommonLoadingScreenSettings(); + +public: + + // The widget to load for the loading screen. + UPROPERTY(config, EditAnywhere, Category=Display, meta=(MetaClass="/Script/UMG.UserWidget")) + FSoftClassPath LoadingScreenWidget; + + // The z-order of the loading screen widget in the viewport stack + UPROPERTY(config, EditAnywhere, Category=Display) + int32 LoadingScreenZOrder = 10000; + + // How long to hold the loading screen up after other loading finishes (in seconds) to + // try to give texture streaming a chance to avoid blurriness + // + // Note: This is not normally applied in the editor for iteration time, but can be + // enabled via HoldLoadingScreenAdditionalSecsEvenInEditor + UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s, ConsoleVariable="CommonLoadingScreen.HoldLoadingScreenAdditionalSecs")) + float HoldLoadingScreenAdditionalSecs = 2.0f; + + // The interval in seconds beyond which the loading screen is considered permanently hung (if non-zero). + UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s)) + float LoadingScreenHeartbeatHangDuration = 0.0f; + + // The interval in seconds between each log of what is keeping a loading screen up (if non-zero). + UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s)) + float LogLoadingScreenHeartbeatInterval = 5.0f; + + // When true, the reason the loading screen is shown or hidden will be printed to the log every frame. + UPROPERTY(Transient, EditAnywhere, Category=Debugging, meta=(ConsoleVariable="CommonLoadingScreen.LogLoadingScreenReasonEveryFrame")) + bool LogLoadingScreenReasonEveryFrame = 0; + + // Force the loading screen to be displayed (useful for debugging) + UPROPERTY(Transient, EditAnywhere, Category=Debugging, meta=(ConsoleVariable="CommonLoadingScreen.AlwaysShow")) + bool ForceLoadingScreenVisible = false; + + // Should we apply the additional HoldLoadingScreenAdditionalSecs delay even in the editor + // (useful when iterating on loading screens) + UPROPERTY(Transient, EditAnywhere, Category=Debugging) + bool HoldLoadingScreenAdditionalSecsEvenInEditor = false; + + // Should we apply the additional HoldLoadingScreenAdditionalSecs delay even in the editor + // (useful when iterating on loading screens) + UPROPERTY(config, EditAnywhere, Category=Configuration) + bool ForceTickLoadingScreenEvenInEditor = true; +}; + diff --git a/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/LoadingScreenManager.cpp b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/LoadingScreenManager.cpp new file mode 100644 index 0000000..c3384c6 --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Private/LoadingScreenManager.cpp @@ -0,0 +1,647 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "LoadingScreenManager.h" + +#include "HAL/ThreadHeartBeat.h" + +#include "Engine/GameInstance.h" +#include "Engine/GameViewportClient.h" +#include "Engine/Engine.h" +#include "GameFramework/GameStateBase.h" +#include "GameFramework/WorldSettings.h" +#include "Misc/CommandLine.h" +#include "Misc/ConfigCacheIni.h" + +#include "LoadingProcessInterface.h" + +#include "Framework/Application/IInputProcessor.h" +#include "Framework/Application/SlateApplication.h" + +#include "PreLoadScreen.h" +#include "PreLoadScreenManager.h" + +#include "ShaderPipelineCache.h" +#include "CommonLoadingScreenSettings.h" + +//@TODO: Used as the placeholder widget in error cases, should probably create a wrapper that at least centers it/etc... +#include "Widgets/Images/SThrobber.h" +#include "Blueprint/UserWidget.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(LoadingScreenManager) + +DECLARE_LOG_CATEGORY_EXTERN(LogLoadingScreen, Log, All); +DEFINE_LOG_CATEGORY(LogLoadingScreen); + +//@TODO: Why can GetLocalPlayers() have nullptr entries? Can it really? +//@TODO: Test with PIE mode set to simulate and decide how much (if any) loading screen action should occur +//@TODO: Allow other things implementing ILoadingProcessInterface besides GameState/PlayerController (and owned components) to register as interested parties +//@TODO: ChangeMusicSettings (either here or using the LoadingScreenVisibilityChanged delegate) +//@TODO: Studio analytics (FireEvent_PIEFinishedLoading / tracking PIE startup time for regressions, either here or using the LoadingScreenVisibilityChanged delegate) + +// Profiling category for loading screens +CSV_DEFINE_CATEGORY(LoadingScreen, true); + +////////////////////////////////////////////////////////////////////// + +bool ILoadingProcessInterface::ShouldShowLoadingScreen(UObject* TestObject, FString& OutReason) +{ + if (TestObject != nullptr) + { + if (ILoadingProcessInterface* LoadObserver = Cast(TestObject)) + { + FString ObserverReason; + if (LoadObserver->ShouldShowLoadingScreen(/*out*/ ObserverReason)) + { + if (ensureMsgf(!ObserverReason.IsEmpty(), TEXT("%s failed to set a reason why it wants to show the loading screen"), *GetPathNameSafe(TestObject))) + { + OutReason = ObserverReason; + } + return true; + } + } + } + + return false; +} + +////////////////////////////////////////////////////////////////////// + +namespace LoadingScreenCVars +{ + // CVars + static float HoldLoadingScreenAdditionalSecs = 2.0f; + static FAutoConsoleVariableRef CVarHoldLoadingScreenUpAtLeastThisLongInSecs( + TEXT("CommonLoadingScreen.HoldLoadingScreenAdditionalSecs"), + HoldLoadingScreenAdditionalSecs, + TEXT("How long to hold the loading screen up after other loading finishes (in seconds) to try to give texture streaming a chance to avoid blurriness"), + ECVF_Default | ECVF_Preview); + + static bool LogLoadingScreenReasonEveryFrame = false; + static FAutoConsoleVariableRef CVarLogLoadingScreenReasonEveryFrame( + TEXT("CommonLoadingScreen.LogLoadingScreenReasonEveryFrame"), + LogLoadingScreenReasonEveryFrame, + TEXT("When true, the reason the loading screen is shown or hidden will be printed to the log every frame."), + ECVF_Default); + + static bool ForceLoadingScreenVisible = false; + static FAutoConsoleVariableRef CVarForceLoadingScreenVisible( + TEXT("CommonLoadingScreen.AlwaysShow"), + ForceLoadingScreenVisible, + TEXT("Force the loading screen to show."), + ECVF_Default); +} + +////////////////////////////////////////////////////////////////////// +// FLoadingScreenInputPreProcessor + +// Input processor to throw in when loading screen is shown +// This will capture any inputs, so active menus under the loading screen will not interact +class FLoadingScreenInputPreProcessor : public IInputProcessor +{ +public: + FLoadingScreenInputPreProcessor() { } + virtual ~FLoadingScreenInputPreProcessor() { } + + bool CanEatInput() const + { + return !GIsEditor; + } + + //~IInputProcess interface + virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef Cursor) override { } + + virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { return CanEatInput(); } + virtual bool HandleKeyUpEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { return CanEatInput(); } + virtual bool HandleAnalogInputEvent(FSlateApplication& SlateApp, const FAnalogInputEvent& InAnalogInputEvent) override { return CanEatInput(); } + virtual bool HandleMouseMoveEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); } + virtual bool HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); } + virtual bool HandleMouseButtonUpEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); } + virtual bool HandleMouseButtonDoubleClickEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); } + virtual bool HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent) override { return CanEatInput(); } + virtual bool HandleMotionDetectedEvent(FSlateApplication& SlateApp, const FMotionEvent& MotionEvent) override { return CanEatInput(); } + //~End of IInputProcess interface +}; + +////////////////////////////////////////////////////////////////////// +// ULoadingScreenManager + +void ULoadingScreenManager::Initialize(FSubsystemCollectionBase& Collection) +{ + FCoreUObjectDelegates::PreLoadMapWithContext.AddUObject(this, &ThisClass::HandlePreLoadMap); + FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &ThisClass::HandlePostLoadMap); + + const UGameInstance* LocalGameInstance = GetGameInstance(); + check(LocalGameInstance); +} + +void ULoadingScreenManager::Deinitialize() +{ + StopBlockingInput(); + + RemoveWidgetFromViewport(); + + FCoreUObjectDelegates::PreLoadMap.RemoveAll(this); + FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this); + + // We are done, so do not attempt to tick us again + SetTickableTickType(ETickableTickType::Never); +} + +bool ULoadingScreenManager::ShouldCreateSubsystem(UObject* Outer) const +{ + // Only clients have loading screens + const UGameInstance* GameInstance = CastChecked(Outer); + const bool bIsServerWorld = GameInstance->IsDedicatedServerInstance(); + return !bIsServerWorld; +} + +void ULoadingScreenManager::Tick(float DeltaTime) +{ + UpdateLoadingScreen(); + + TimeUntilNextLogHeartbeatSeconds = FMath::Max(TimeUntilNextLogHeartbeatSeconds - DeltaTime, 0.0); +} + +ETickableTickType ULoadingScreenManager::GetTickableTickType() const +{ + if (IsTemplate()) + { + return ETickableTickType::Never; + } + return ETickableTickType::Conditional; +} + +bool ULoadingScreenManager::IsTickable() const +{ + // Don't tick if we don't have a game viewport client, this catches cases that ShouldCreateSubsystem does not + UGameInstance* GameInstance = GetGameInstance(); + return (GameInstance && GameInstance->GetGameViewportClient()); +} + +TStatId ULoadingScreenManager::GetStatId() const +{ + RETURN_QUICK_DECLARE_CYCLE_STAT(ULoadingScreenManager, STATGROUP_Tickables); +} + +UWorld* ULoadingScreenManager::GetTickableGameObjectWorld() const +{ + return GetGameInstance()->GetWorld(); +} + +void ULoadingScreenManager::RegisterLoadingProcessor(TScriptInterface Interface) +{ + ExternalLoadingProcessors.Add(Interface.GetObject()); +} + +void ULoadingScreenManager::UnregisterLoadingProcessor(TScriptInterface Interface) +{ + ExternalLoadingProcessors.Remove(Interface.GetObject()); +} + +void ULoadingScreenManager::HandlePreLoadMap(const FWorldContext& WorldContext, const FString& MapName) +{ + if (WorldContext.OwningGameInstance == GetGameInstance()) + { + bCurrentlyInLoadMap = true; + + // Update the loading screen immediately if the engine is initialized + if (GEngine->IsInitialized()) + { + UpdateLoadingScreen(); + } + } +} + +void ULoadingScreenManager::HandlePostLoadMap(UWorld* World) +{ + if ((World != nullptr) && (World->GetGameInstance() == GetGameInstance())) + { + bCurrentlyInLoadMap = false; + } +} + +void ULoadingScreenManager::UpdateLoadingScreen() +{ + bool bLogLoadingScreenStatus = LoadingScreenCVars::LogLoadingScreenReasonEveryFrame; + + if (ShouldShowLoadingScreen()) + { + const UCommonLoadingScreenSettings* Settings = GetDefault(); + + // If we don't make it to the specified checkpoint in the given time will trigger the hang detector so we can better determine where progress stalled. + FThreadHeartBeat::Get().MonitorCheckpointStart(GetFName(), Settings->LoadingScreenHeartbeatHangDuration); + + ShowLoadingScreen(); + + if ((Settings->LogLoadingScreenHeartbeatInterval > 0.0f) && (TimeUntilNextLogHeartbeatSeconds <= 0.0)) + { + bLogLoadingScreenStatus = true; + TimeUntilNextLogHeartbeatSeconds = Settings->LogLoadingScreenHeartbeatInterval; + } + } + else + { + HideLoadingScreen(); + + FThreadHeartBeat::Get().MonitorCheckpointEnd(GetFName()); + } + + if (bLogLoadingScreenStatus) + { + UE_LOG(LogLoadingScreen, Log, TEXT("Loading screen showing: %d. Reason: %s"), bCurrentlyShowingLoadingScreen ? 1 : 0, *DebugReasonForShowingOrHidingLoadingScreen); + } +} + +bool ULoadingScreenManager::CheckForAnyNeedToShowLoadingScreen() +{ + // Start out with 'unknown' reason in case someone forgets to put a reason when changing this in the future. + DebugReasonForShowingOrHidingLoadingScreen = TEXT("Reason for Showing/Hiding LoadingScreen is unknown!"); + + const UGameInstance* LocalGameInstance = GetGameInstance(); + + if (LoadingScreenCVars::ForceLoadingScreenVisible) + { + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("CommonLoadingScreen.AlwaysShow is true")); + return true; + } + + const FWorldContext* Context = LocalGameInstance->GetWorldContext(); + if (Context == nullptr) + { + // We don't have a world context right now... better show a loading screen + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("The game instance has a null WorldContext")); + return true; + } + + UWorld* World = Context->World(); + if (World == nullptr) + { + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We have no world (FWorldContext's World() is null)")); + return true; + } + + AGameStateBase* GameState = World->GetGameState(); + if (GameState == nullptr) + { + // The game state has not yet replicated. + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("GameState hasn't yet replicated (it's null)")); + return true; + } + + if (bCurrentlyInLoadMap) + { + // Show a loading screen if we are in LoadMap + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("bCurrentlyInLoadMap is true")); + return true; + } + + if (!Context->TravelURL.IsEmpty()) + { + // Show a loading screen when pending travel + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We have pending travel (the TravelURL is not empty)")); + return true; + } + + if (Context->PendingNetGame != nullptr) + { + // Connecting to another server + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We are connecting to another server (PendingNetGame != nullptr)")); + return true; + } + + if (!World->HasBegunPlay()) + { + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("World hasn't begun play")); + return true; + } + + if (World->IsInSeamlessTravel()) + { + // Show a loading screen during seamless travel + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We are in seamless travel")); + return true; + } + + // Ask the game state if it needs a loading screen + if (ILoadingProcessInterface::ShouldShowLoadingScreen(GameState, /*out*/ DebugReasonForShowingOrHidingLoadingScreen)) + { + return true; + } + + // Ask any game state components if they need a loading screen + for (UActorComponent* TestComponent : GameState->GetComponents()) + { + if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen)) + { + return true; + } + } + + // Ask any of the external loading processors that may have been registered. These might be actors or components + // that were registered by game code to tell us to keep the loading screen up while perhaps something finishes + // streaming in. + for (const TWeakInterfacePtr& Processor : ExternalLoadingProcessors) + { + if (ILoadingProcessInterface::ShouldShowLoadingScreen(Processor.GetObject(), /*out*/ DebugReasonForShowingOrHidingLoadingScreen)) + { + return true; + } + } + + // Check each local player + bool bFoundAnyLocalPC = false; + bool bMissingAnyLocalPC = false; + + for (ULocalPlayer* LP : LocalGameInstance->GetLocalPlayers()) + { + if (LP != nullptr) + { + if (APlayerController* PC = LP->PlayerController) + { + bFoundAnyLocalPC = true; + + // Ask the PC itself if it needs a loading screen + if (ILoadingProcessInterface::ShouldShowLoadingScreen(PC, /*out*/ DebugReasonForShowingOrHidingLoadingScreen)) + { + return true; + } + + // Ask any PC components if they need a loading screen + for (UActorComponent* TestComponent : PC->GetComponents()) + { + if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen)) + { + return true; + } + } + } + else + { + bMissingAnyLocalPC = true; + } + } + } + + UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient(); + const bool bIsInSplitscreen = GameViewportClient->GetCurrentSplitscreenConfiguration() != ESplitScreenType::None; + + // In splitscreen we need all player controllers to be present + if (bIsInSplitscreen && bMissingAnyLocalPC) + { + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("At least one missing local player controller in splitscreen")); + return true; + } + + // And in non-splitscreen we need at least one player controller to be present + if (!bIsInSplitscreen && !bFoundAnyLocalPC) + { + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("Need at least one local player controller")); + return true; + } + + // Victory! The loading screen can go away now + DebugReasonForShowingOrHidingLoadingScreen = TEXT("(nothing wants to show it anymore)"); + return false; +} + +bool ULoadingScreenManager::ShouldShowLoadingScreen() +{ + const UCommonLoadingScreenSettings* Settings = GetDefault(); + + // Check debugging commands that force the state one way or another +#if !UE_BUILD_SHIPPING + static bool bCmdLineNoLoadingScreen = FParse::Param(FCommandLine::Get(), TEXT("NoLoadingScreen")); + if (bCmdLineNoLoadingScreen) + { + DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("CommandLine has 'NoLoadingScreen'")); + return false; + } +#endif + + // Can't show a loading screen if there's no game viewport + UGameInstance* LocalGameInstance = GetGameInstance(); + if (LocalGameInstance->GetGameViewportClient() == nullptr) + { + return false; + } + + // Check for a need to show the loading screen + const bool bNeedToShowLoadingScreen = CheckForAnyNeedToShowLoadingScreen(); + + // Keep the loading screen up a bit longer if desired + bool bWantToForceShowLoadingScreen = false; + if (bNeedToShowLoadingScreen) + { + // Still need to show it + TimeLoadingScreenLastDismissed = -1.0; + } + else + { + // Don't *need* to show the screen anymore, but might still want to for a bit + const double CurrentTime = FPlatformTime::Seconds(); + const bool bCanHoldLoadingScreen = (!GIsEditor || Settings->HoldLoadingScreenAdditionalSecsEvenInEditor); + const double HoldLoadingScreenAdditionalSecs = bCanHoldLoadingScreen ? LoadingScreenCVars::HoldLoadingScreenAdditionalSecs : 0.0; + + if (TimeLoadingScreenLastDismissed < 0.0) + { + TimeLoadingScreenLastDismissed = CurrentTime; + } + const double TimeSinceScreenDismissed = CurrentTime - TimeLoadingScreenLastDismissed; + + // hold for an extra X seconds, to cover up streaming + if ((HoldLoadingScreenAdditionalSecs > 0.0) && (TimeSinceScreenDismissed < HoldLoadingScreenAdditionalSecs)) + { + // Make sure we're rendering the world at this point, so that textures will actually stream in + //@TODO: If bNeedToShowLoadingScreen bounces back true during this window, we won't turn this off again... + UGameViewportClient* GameViewportClient = GetGameInstance()->GetGameViewportClient(); + GameViewportClient->bDisableWorldRendering = false; + + DebugReasonForShowingOrHidingLoadingScreen = FString::Printf(TEXT("Keeping loading screen up for an additional %.2f seconds to allow texture streaming"), HoldLoadingScreenAdditionalSecs); + bWantToForceShowLoadingScreen = true; + } + } + + return bNeedToShowLoadingScreen || bWantToForceShowLoadingScreen; +} + +bool ULoadingScreenManager::IsShowingInitialLoadingScreen() const +{ + FPreLoadScreenManager* PreLoadScreenManager = FPreLoadScreenManager::Get(); + return (PreLoadScreenManager != nullptr) && PreLoadScreenManager->HasValidActivePreLoadScreen(); +} + +void ULoadingScreenManager::ShowLoadingScreen() +{ + if (bCurrentlyShowingLoadingScreen) + { + return; + } + + // Unable to show loading screen if the engine is still loading with its loading screen. + if (FPreLoadScreenManager::Get() && FPreLoadScreenManager::Get()->HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen)) + { + return; + } + + TimeLoadingScreenShown = FPlatformTime::Seconds(); + + bCurrentlyShowingLoadingScreen = true; + + CSV_EVENT(LoadingScreen, TEXT("Show")); + + const UCommonLoadingScreenSettings* Settings = GetDefault(); + + if (IsShowingInitialLoadingScreen()) + { + UE_LOG(LogLoadingScreen, Log, TEXT("Showing loading screen when 'IsShowingInitialLoadingScreen()' is true.")); + UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen); + } + else + { + UE_LOG(LogLoadingScreen, Log, TEXT("Showing loading screen when 'IsShowingInitialLoadingScreen()' is false.")); + UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen); + + UGameInstance* LocalGameInstance = GetGameInstance(); + + // Eat input while the loading screen is displayed + StartBlockingInput(); + + LoadingScreenVisibilityChanged.Broadcast(/*bIsVisible=*/ true); + + // Create the loading screen widget + TSubclassOf LoadingScreenWidgetClass = Settings->LoadingScreenWidget.TryLoadClass(); + 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); + + ChangePerformanceSettings(/*bEnableLoadingScreen=*/ true); + + if (!GIsEditor || Settings->ForceTickLoadingScreenEvenInEditor) + { + // Tick Slate to make sure the loading screen is displayed immediately + FSlateApplication::Get().Tick(); + } + } +} + +void ULoadingScreenManager::HideLoadingScreen() +{ + if (!bCurrentlyShowingLoadingScreen) + { + return; + } + + StopBlockingInput(); + + if (IsShowingInitialLoadingScreen()) + { + UE_LOG(LogLoadingScreen, Log, TEXT("Hiding loading screen when 'IsShowingInitialLoadingScreen()' is true.")); + UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen); + } + else + { + UE_LOG(LogLoadingScreen, Log, TEXT("Hiding loading screen when 'IsShowingInitialLoadingScreen()' is false.")); + UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen); + + UE_LOG(LogLoadingScreen, Log, TEXT("Garbage Collecting before dropping load screen")); + GEngine->ForceGarbageCollection(true); + + RemoveWidgetFromViewport(); + + ChangePerformanceSettings(/*bEnableLoadingScreen=*/ false); + + // Let observers know that the loading screen is done + LoadingScreenVisibilityChanged.Broadcast(/*bIsVisible=*/ false); + } + + CSV_EVENT(LoadingScreen, TEXT("Hide")); + + const double LoadingScreenDuration = FPlatformTime::Seconds() - TimeLoadingScreenShown; + UE_LOG(LogLoadingScreen, Log, TEXT("LoadingScreen was visible for %.2fs"), LoadingScreenDuration); + + bCurrentlyShowingLoadingScreen = false; +} + +void ULoadingScreenManager::RemoveWidgetFromViewport() +{ + UGameInstance* LocalGameInstance = GetGameInstance(); + if (LoadingScreenWidget.IsValid()) + { + if (UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient()) + { + GameViewportClient->RemoveViewportWidgetContent(LoadingScreenWidget.ToSharedRef()); + } + LoadingScreenWidget.Reset(); + } +} + +void ULoadingScreenManager::StartBlockingInput() +{ + if (!InputPreProcessor.IsValid()) + { + InputPreProcessor = MakeShareable(new FLoadingScreenInputPreProcessor()); + FSlateApplication::Get().RegisterInputPreProcessor(InputPreProcessor, 0); + } +} + +void ULoadingScreenManager::StopBlockingInput() +{ + if (InputPreProcessor.IsValid()) + { + FSlateApplication::Get().UnregisterInputPreProcessor(InputPreProcessor); + InputPreProcessor.Reset(); + } +} + +void ULoadingScreenManager::ChangePerformanceSettings(bool bEnabingLoadingScreen) +{ + UGameInstance* LocalGameInstance = GetGameInstance(); + UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient(); + + FShaderPipelineCache::SetBatchMode(bEnabingLoadingScreen ? FShaderPipelineCache::BatchMode::Fast : FShaderPipelineCache::BatchMode::Background); + + // Don't bother drawing the 3D world while we're loading + GameViewportClient->bDisableWorldRendering = bEnabingLoadingScreen; + + // Make sure to prioritize streaming in levels if the loading screen is up + if (UWorld* ViewportWorld = GameViewportClient->GetWorld()) + { + if (AWorldSettings* WorldSettings = ViewportWorld->GetWorldSettings(false, false)) + { + WorldSettings->bHighPriorityLoadingLocal = bEnabingLoadingScreen; + } + } + + if (bEnabingLoadingScreen) + { + // Set a new hang detector timeout multiplier when the loading screen is visible. + double HangDurationMultiplier; + if (!GConfig || !GConfig->GetDouble(TEXT("Core.System"), TEXT("LoadingScreenHangDurationMultiplier"), /*out*/ HangDurationMultiplier, GEngineIni)) + { + HangDurationMultiplier = 1.0; + } + FThreadHeartBeat::Get().SetDurationMultiplier(HangDurationMultiplier); + + // Do not report hitches while the loading screen is up + FGameThreadHitchHeartBeat::Get().SuspendHeartBeat(); + } + else + { + // Restore the hang detector timeout when we hide the loading screen + FThreadHeartBeat::Get().SetDurationMultiplier(1.0); + + // Resume reporting hitches now that the loading screen is down + FGameThreadHitchHeartBeat::Get().ResumeHeartBeat(); + } +} + diff --git a/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessInterface.h b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessInterface.h new file mode 100644 index 0000000..ed6cc4e --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessInterface.h @@ -0,0 +1,30 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "LoadingProcessInterface.generated.h" + +/** Interface for things that might cause loading to happen which requires a loading screen to be displayed */ +UINTERFACE(BlueprintType) +class COMMONLOADINGSCREEN_API ULoadingProcessInterface : public UInterface +{ + GENERATED_BODY() +}; + +class COMMONLOADINGSCREEN_API ILoadingProcessInterface +{ + GENERATED_BODY() + +public: + // Checks to see if this object implements the interface, and if so asks whether or not we should + // be currently showing a loading screen + static bool ShouldShowLoadingScreen(UObject* TestObject, FString& OutReason); + + virtual bool ShouldShowLoadingScreen(FString& OutReason) const + { + return false; + } +}; diff --git a/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessTask.cpp b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessTask.cpp new file mode 100644 index 0000000..5d36a96 --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessTask.cpp @@ -0,0 +1,47 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "LoadingProcessTask.h" + +#include "Engine/Engine.h" +#include "Engine/GameInstance.h" +#include "Engine/World.h" +#include "LoadingScreenManager.h" +#include "UObject/ScriptInterface.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(LoadingProcessTask) + +/*static*/ ULoadingProcessTask* ULoadingProcessTask::CreateLoadingScreenProcessTask(UObject* WorldContextObject, const FString& ShowLoadingScreenReason) +{ + UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + UGameInstance* GameInstance = World ? World->GetGameInstance() : nullptr; + ULoadingScreenManager* LoadingScreenManager = GameInstance ? GameInstance->GetSubsystem() : nullptr; + + if (LoadingScreenManager) + { + ULoadingProcessTask* NewLoadingTask = NewObject(LoadingScreenManager); + NewLoadingTask->SetShowLoadingScreenReason(ShowLoadingScreenReason); + + LoadingScreenManager->RegisterLoadingProcessor(NewLoadingTask); + + return NewLoadingTask; + } + + return nullptr; +} + +void ULoadingProcessTask::Unregister() +{ + ULoadingScreenManager* LoadingScreenManager = Cast(GetOuter()); + LoadingScreenManager->UnregisterLoadingProcessor(this); +} + +void ULoadingProcessTask::SetShowLoadingScreenReason(const FString& InReason) +{ + Reason = InReason; +} + +bool ULoadingProcessTask::ShouldShowLoadingScreen(FString& OutReason) const +{ + OutReason = Reason; + return true; +} diff --git a/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessTask.h b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessTask.h new file mode 100644 index 0000000..d4ae027 --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingProcessTask.h @@ -0,0 +1,33 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "LoadingProcessInterface.h" +#include "UObject/Object.h" + +#include "LoadingProcessTask.generated.h" + +struct FFrame; + +UCLASS(BlueprintType) +class COMMONLOADINGSCREEN_API ULoadingProcessTask : public UObject, public ILoadingProcessInterface +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, meta=(WorldContext = "WorldContextObject")) + static ULoadingProcessTask* CreateLoadingScreenProcessTask(UObject* WorldContextObject, const FString& ShowLoadingScreenReason); + +public: + ULoadingProcessTask() { } + + UFUNCTION(BlueprintCallable) + void Unregister(); + + UFUNCTION(BlueprintCallable) + void SetShowLoadingScreenReason(const FString& InReason); + + virtual bool ShouldShowLoadingScreen(FString& OutReason) const override; + + FString Reason; +}; diff --git a/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingScreenManager.h b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingScreenManager.h new file mode 100644 index 0000000..c037cde --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonLoadingScreen/Public/LoadingScreenManager.h @@ -0,0 +1,127 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Subsystems/GameInstanceSubsystem.h" +#include "Tickable.h" +#include "UObject/WeakInterfacePtr.h" + +#include "LoadingScreenManager.generated.h" + +template class TScriptInterface; + +class FSubsystemCollectionBase; +class IInputProcessor; +class ILoadingProcessInterface; +class SWidget; +class UObject; +class UWorld; +struct FFrame; +struct FWorldContext; + +/** + * Handles showing/hiding the loading screen + */ +UCLASS() +class COMMONLOADINGSCREEN_API ULoadingScreenManager : public UGameInstanceSubsystem, public FTickableGameObject +{ + GENERATED_BODY() + +public: + //~USubsystem interface + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + virtual bool ShouldCreateSubsystem(UObject* Outer) const override; + //~End of USubsystem interface + + //~FTickableObjectBase interface + virtual void Tick(float DeltaTime) override; + virtual ETickableTickType GetTickableTickType() const override; + virtual bool IsTickable() const override; + virtual TStatId GetStatId() const override; + virtual UWorld* GetTickableGameObjectWorld() const override; + //~End of FTickableObjectBase interface + + UFUNCTION(BlueprintCallable, Category=LoadingScreen) + FString GetDebugReasonForShowingOrHidingLoadingScreen() const + { + return DebugReasonForShowingOrHidingLoadingScreen; + } + + /** Returns True when the loading screen is currently being shown */ + bool GetLoadingScreenDisplayStatus() const + { + return bCurrentlyShowingLoadingScreen; + } + + /** Called when the loading screen visibility changes */ + DECLARE_MULTICAST_DELEGATE_OneParam(FOnLoadingScreenVisibilityChangedDelegate, bool); + FORCEINLINE FOnLoadingScreenVisibilityChangedDelegate& OnLoadingScreenVisibilityChangedDelegate() { return LoadingScreenVisibilityChanged; } + + void RegisterLoadingProcessor(TScriptInterface Interface); + void UnregisterLoadingProcessor(TScriptInterface Interface); + +private: + void HandlePreLoadMap(const FWorldContext& WorldContext, const FString& MapName); + void HandlePostLoadMap(UWorld* World); + + /** Determines if we should show or hide the loading screen. Called every frame. */ + void UpdateLoadingScreen(); + + /** Returns true if we need to be showing the loading screen. */ + bool CheckForAnyNeedToShowLoadingScreen(); + + /** Returns true if we want to be showing the loading screen (if we need to or are artificially forcing it on for other reasons). */ + bool ShouldShowLoadingScreen(); + + /** Returns true if we are in the initial loading flow before this screen should be used */ + bool IsShowingInitialLoadingScreen() const; + + /** Shows the loading screen. Sets up the loading screen widget on the viewport */ + void ShowLoadingScreen(); + + /** Hides the loading screen. The loading screen widget will be destroyed */ + void HideLoadingScreen(); + + /** Removes the widget from the viewport */ + void RemoveWidgetFromViewport(); + + /** Prevents input from being used in-game while the loading screen is visible */ + void StartBlockingInput(); + + /** Resumes in-game input, if blocking */ + void StopBlockingInput(); + + void ChangePerformanceSettings(bool bEnabingLoadingScreen); + +private: + /** Delegate broadcast when the loading screen visibility changes */ + FOnLoadingScreenVisibilityChangedDelegate LoadingScreenVisibilityChanged; + + /** A reference to the loading screen widget we are displaying (if any) */ + TSharedPtr LoadingScreenWidget; + + /** Input processor to eat all input while the loading screen is shown */ + TSharedPtr InputPreProcessor; + + /** External loading processors, components maybe actors that delay the loading. */ + TArray> ExternalLoadingProcessors; + + /** The reason why the loading screen is up (or not) */ + FString DebugReasonForShowingOrHidingLoadingScreen; + + /** The time when we started showing the loading screen */ + double TimeLoadingScreenShown = 0.0; + + /** The time the loading screen most recently wanted to be dismissed (might still be up due to a min display duration requirement) **/ + double TimeLoadingScreenLastDismissed = -1.0; + + /** The time until the next log for why the loading screen is still up */ + double TimeUntilNextLogHeartbeatSeconds = 0.0; + + /** True when we are between PreLoadMap and PostLoadMap */ + bool bCurrentlyInLoadMap = false; + + /** True when the loading screen is currently being shown */ + bool bCurrentlyShowingLoadingScreen = false; +}; diff --git a/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/CommonStartupLoadingScreen.Build.cs b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/CommonStartupLoadingScreen.Build.cs new file mode 100644 index 0000000..72eb553 --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/CommonStartupLoadingScreen.Build.cs @@ -0,0 +1,55 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class CommonStartupLoadingScreen : ModuleRules +{ + public CommonStartupLoadingScreen(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "MoviePlayer", + "PreLoadScreen", + "DeveloperSettings" + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.cpp b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.cpp new file mode 100644 index 0000000..32a2b6b --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.cpp @@ -0,0 +1,18 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "CommonPreLoadScreen.h" + +#include "Misc/App.h" +#include "SCommonPreLoadingScreenWidget.h" + +#define LOCTEXT_NAMESPACE "CommonPreLoadingScreen" + +void FCommonPreLoadScreen::Init() +{ + if (!GIsEditor && FApp::CanEverRender()) + { + EngineLoadingWidget = SNew(SCommonPreLoadingScreenWidget); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.h b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.h new file mode 100644 index 0000000..286f991 --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.h @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "PreLoadScreenBase.h" + +class SWidget; + +class FCommonPreLoadScreen : public FPreLoadScreenBase +{ +public: + + /*** IPreLoadScreen Implementation ***/ + virtual void Init() override; + virtual EPreLoadScreenTypes GetPreLoadScreenType() const override { return EPreLoadScreenTypes::EngineLoadingScreen; } + virtual TSharedPtr GetWidget() override { return EngineLoadingWidget; } +private: + + TSharedPtr EngineLoadingWidget; +}; diff --git a/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonStartupLoadingScreen.cpp b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonStartupLoadingScreen.cpp new file mode 100644 index 0000000..93cb7bd --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/CommonStartupLoadingScreen.cpp @@ -0,0 +1,62 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "CommonPreLoadScreen.h" +#include "Misc/App.h" +#include "Modules/ModuleManager.h" +#include "PreLoadScreenManager.h" + +#define LOCTEXT_NAMESPACE "FCommonLoadingScreenModule" + +class FCommonStartupLoadingScreenModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + bool IsGameModule() const override; + +private: + void OnPreLoadScreenManagerCleanUp(); + + TSharedPtr PreLoadingScreen; +}; + + +void FCommonStartupLoadingScreenModule::StartupModule() +{ + // No need to load these assets on dedicated servers. + // Still want to load them in commandlets so cook catches them + if (!IsRunningDedicatedServer()) + { + PreLoadingScreen = MakeShared(); + PreLoadingScreen->Init(); + + if (!GIsEditor && FApp::CanEverRender() && FPreLoadScreenManager::Get()) + { + FPreLoadScreenManager::Get()->RegisterPreLoadScreen(PreLoadingScreen); + FPreLoadScreenManager::Get()->OnPreLoadScreenManagerCleanUp.AddRaw(this, &FCommonStartupLoadingScreenModule::OnPreLoadScreenManagerCleanUp); + } + } +} + +void FCommonStartupLoadingScreenModule::OnPreLoadScreenManagerCleanUp() +{ + //Once the PreLoadScreenManager is cleaning up, we can get rid of all our resources too + PreLoadingScreen.Reset(); + ShutdownModule(); +} + +void FCommonStartupLoadingScreenModule::ShutdownModule() +{ + +} + +bool FCommonStartupLoadingScreenModule::IsGameModule() const +{ + return true; +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FCommonStartupLoadingScreenModule, CommonStartupLoadingScreen) diff --git a/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.cpp b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.cpp new file mode 100644 index 0000000..b5cf8e6 --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.cpp @@ -0,0 +1,32 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SCommonPreLoadingScreenWidget.h" + +#include "Widgets/Layout/SBorder.h" + +class FReferenceCollector; + +#define LOCTEXT_NAMESPACE "SCommonPreLoadingScreenWidget" + +void SCommonPreLoadingScreenWidget::Construct(const FArguments& InArgs) +{ + ChildSlot + [ + SNew(SBorder) + .BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush")) + .BorderBackgroundColor(FLinearColor::Black) + .Padding(0) + ]; +} + +void SCommonPreLoadingScreenWidget::AddReferencedObjects(FReferenceCollector& Collector) +{ + //WidgetAssets.AddReferencedObjects(Collector); +} + +FString SCommonPreLoadingScreenWidget::GetReferencerName() const +{ + return TEXT("SCommonPreLoadingScreenWidget"); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.h b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.h new file mode 100644 index 0000000..ba1bd56 --- /dev/null +++ b/Plugins/CommonLoadingScreen/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.h @@ -0,0 +1,26 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "UObject/GCObject.h" +#include "Widgets/Accessibility/SlateWidgetAccessibleTypes.h" +#include "Widgets/SCompoundWidget.h" + +class FReferenceCollector; + +class SCommonPreLoadingScreenWidget : public SCompoundWidget, public FGCObject +{ +public: + SLATE_BEGIN_ARGS(SCommonPreLoadingScreenWidget) {} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs); + + //~ Begin FGCObject interface + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; + //~ End FGCObject interface + +private: + +};