// Copyright Epic Games, Inc. All Rights Reserved. #include "Serialization/AsyncPackageLoader.h" #include "HAL/IConsoleManager.h" #include "Serialization/AsyncLoadingThread.h" #include "Serialization/AsyncLoading2.h" #include "Serialization/EditorPackageLoader.h" #include "UObject/GCObject.h" #include "UObject/LinkerLoad.h" #include "UObject/PackageId.h" #include "Misc/CoreDelegates.h" #include "IO/IoDispatcher.h" #include "HAL/IConsoleManager.h" volatile int32 GIsLoaderCreated; TUniquePtr GPackageLoader; bool GAsyncLoadingAllowed = true; FThreadSafeCounter IAsyncPackageLoader::NextPackageRequestId; int32 IAsyncPackageLoader::GetNextRequestId() { return NextPackageRequestId.Increment(); } #if !UE_BUILD_SHIPPING static void LoadPackageCommand(const TArray& Args) { for (const FString& PackageName: Args) { UE_LOG(LogStreaming, Display, TEXT("LoadPackageCommand: %s - Requested"), *PackageName); UPackage* Package = LoadPackage(nullptr, *PackageName, LOAD_None); UE_LOG(LogStreaming, Display, TEXT("LoadPackageCommand: %s - %s"), *PackageName, (Package != nullptr) ? TEXT("Loaded") : TEXT("Failed")); } } static void LoadPackageAsyncCommand(const TArray& Args) { for (const FString& PackageName: Args) { UE_LOG(LogStreaming, Display, TEXT("LoadPackageAsyncCommand: %s - Requested"), *PackageName); LoadPackageAsync(PackageName, FLoadPackageAsyncDelegate::CreateLambda( [](const FName& PackageName, UPackage* Package, EAsyncLoadingResult::Type Result) { UE_LOG(LogStreaming, Display, TEXT("LoadPackageAsyncCommand: %s - %s"), *PackageName.ToString(), (Package != nullptr) ? TEXT("Loaded") : TEXT("Failed")); })); } } static FAutoConsoleCommand CVar_LoadPackageCommand( TEXT("LoadPackage"), TEXT("Loads packages by names. Usage: LoadPackage [ ...]"), FConsoleCommandWithArgsDelegate::CreateStatic(LoadPackageCommand)); static FAutoConsoleCommand CVar_LoadPackageAsyncCommand( TEXT("LoadPackageAsync"), TEXT("Loads packages async by names. Usage: LoadPackageAsync [ ...]"), FConsoleCommandWithArgsDelegate::CreateStatic(LoadPackageAsyncCommand)); #endif const FName PrestreamPackageClassNameLoad = FName("PrestreamPackage"); struct FEDLBootObjectState { ENotifyRegistrationType NotifyRegistrationType; ENotifyRegistrationPhase LastNotifyRegistrationPhase; UObject* (*Register)(); bool bDynamic; }; struct FEDLBootWaitingPackage { void* Package; FPackageIndex Import; }; struct FEDLBootNotificationManager : public IEDLBootNotificationManager { TMap PathToState; TMultiMap PathToWaitingPackageNodes; TArray PathsToFire; TArray CDORecursiveStack; TArray CDORecursives; FCriticalSection EDLBootNotificationManagerLock; bool bEnabled = true; void Disable() { PathToState.Empty(); PathsToFire.Empty(); bEnabled = false; } // return true if you are waiting for this compiled in object bool AddWaitingPackage(void* Pkg, FName PackageName, FName ObjectName, FPackageIndex Import, bool bIgnoreMissingPackage) override { if (PackageName == GLongCoreUObjectPackageName) { return false; // We assume nothing in coreuobject ever loads assets in a constructor } FScopeLock Lock(&EDLBootNotificationManagerLock); check(GIsInitialLoad); check(Import.IsImport()); // compiled in exports make no sense FString ObjectNameString = ObjectName.ToString(); FName LongFName(*(PackageName.ToString() / ObjectNameString)); check(LongFName != NAME_None); FName WaitName = LongFName; FEDLBootObjectState* ExistingState = PathToState.Find(LongFName); if (!ExistingState) { // if (ObjectName.ToString().EndsWith(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX)) // there are also some arg structs and other things that are just part of the package with no registration { ExistingState = PathToState.Find(PackageName); WaitName = PackageName; } if (!ExistingState) { UE_CLOG(!bIgnoreMissingPackage, LogStreaming, Fatal, TEXT("Compiled in export %s not found; it was never registered."), *LongFName.ToString()); return false; } } if (ExistingState->LastNotifyRegistrationPhase == ENotifyRegistrationPhase::NRP_Finished) { return false; } FEDLBootWaitingPackage WaitingPackage; WaitingPackage.Package = Pkg; WaitingPackage.Import = Import; PathToWaitingPackageNodes.Add(WaitName, WaitingPackage); return true; } void NotifyRegistrationEvent(const TCHAR* PackageName, const TCHAR* Name, ENotifyRegistrationType NotifyRegistrationType, ENotifyRegistrationPhase NotifyRegistrationPhase, UObject* (*InRegister)(), bool InbDynamic) { if (!bEnabled || !GIsInitialLoad) { return; } static FName LongCoreUObjectPackageName(TEXT("/Script/CoreUObject")); // can't use the global here because it may not be initialized yet FName PackageFName(PackageName); if (PackageFName == LongCoreUObjectPackageName) { return; // We assume nothing in coreuobject ever loads assets in a constructor } FScopeLock Lock(&EDLBootNotificationManagerLock); FName LongFName(*(FString(PackageName) / Name)); // FPlatformMisc::LowLevelOutputDebugStringf(TEXT("NotifyRegistrationEvent %s %d %d\r\n"), *LongFName.ToString(), int32(NotifyRegistrationType), int32(NotifyRegistrationPhase)); // some things, like delegate signatures, are not registered; rather they are part of the package singleton, so we track the package state as being the max of any member of that package FEDLBootObjectState* ExistingPackageState = PathToState.Find(PackageFName); FEDLBootObjectState* ExistingState = PathToState.Find(LongFName); if (!ExistingState) { if (NotifyRegistrationPhase != ENotifyRegistrationPhase::NRP_Added) { UE_LOG(LogStreaming, Fatal, TEXT("Attempt to process %s before it has been added."), *LongFName.ToString()); } FEDLBootObjectState NewState; NewState.LastNotifyRegistrationPhase = NotifyRegistrationPhase; NewState.NotifyRegistrationType = NotifyRegistrationType; NewState.Register = InRegister; NewState.bDynamic = InbDynamic; PathToState.Add(LongFName, NewState); if (!ExistingPackageState) { NewState.NotifyRegistrationType = ENotifyRegistrationType::NRT_Package; PathToState.Add(PackageFName, NewState); } } else { if (int32(ExistingState->LastNotifyRegistrationPhase) + 1 != int32(NotifyRegistrationPhase)) { UE_CLOG(GEventDrivenLoaderEnabled, LogStreaming, Fatal, TEXT("Invalid state transition %d %d with %s when it has already been processed."), int32(ExistingState->LastNotifyRegistrationPhase), int32(NotifyRegistrationPhase), *LongFName.ToString()); } if (ExistingState->NotifyRegistrationType != (NotifyRegistrationType)) { UE_CLOG(GEventDrivenLoaderEnabled, LogStreaming, Fatal, TEXT("Multiple types %d %d with %s when it has already been processed."), int32(ExistingState->NotifyRegistrationType), int32(NotifyRegistrationType), *LongFName.ToString()); } ExistingState->LastNotifyRegistrationPhase = NotifyRegistrationPhase; if (NotifyRegistrationPhase == ENotifyRegistrationPhase::NRP_Finished) { ExistingState->Register = nullptr; // we don't need to do this in ConstructWaitingBootObjects PathsToFire.Add(LongFName); } check(ExistingPackageState); // if we have an existing state for the thing, we should also have a if (ExistingPackageState && int32(NotifyRegistrationPhase) > int32(ExistingPackageState->LastNotifyRegistrationPhase)) { ExistingPackageState->LastNotifyRegistrationPhase = NotifyRegistrationPhase; if (NotifyRegistrationPhase == ENotifyRegistrationPhase::NRP_Finished) { // FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Fired package %s %d %d\r\n"), *PackageFName.ToString(), int32(NotifyRegistrationType), int32(NotifyRegistrationPhase)); PathsToFire.Add(PackageFName); } } } } void NotifyRegistrationComplete() { if (!bEnabled) { return; } #if USE_EVENT_DRIVEN_ASYNC_LOAD_AT_BOOT_TIME FireCompletedCompiledInImports(true); FlushAsyncLoading(); #endif #if !HACK_HEADER_GENERATOR check(!GIsInitialLoad && IsInGameThread()); FScopeLock Lock(&EDLBootNotificationManagerLock); for (auto& Pair: PathToState) { if (Pair.Value.LastNotifyRegistrationPhase != ENotifyRegistrationPhase::NRP_Finished && !Pair.Value.bDynamic) { #if USE_EVENT_DRIVEN_ASYNC_LOAD_AT_BOOT_TIME UE_CLOG(GEventDrivenLoaderEnabled, LogStreaming, Fatal, TEXT("%s (%d) was not complete (%d) after registration was complete."), *Pair.Key.ToString(), int32(Pair.Value.NotifyRegistrationType), int32(Pair.Value.LastNotifyRegistrationPhase)); #else UE_LOG(LogStreaming, Warning, TEXT("%s was not complete (%d) after registration was complete."), *Pair.Key.ToString(), int32(Pair.Value.LastNotifyRegistrationPhase)); #endif } } if (PathToWaitingPackageNodes.Num()) { UE_LOG(LogStreaming, Fatal, TEXT("Initial load is complete, but we still have %d waiting packages."), PathToWaitingPackageNodes.Num()); } if (GEventDrivenLoaderEnabled && PathsToFire.Num() && USE_EVENT_DRIVEN_ASYNC_LOAD_AT_BOOT_TIME) { for (FName Path: PathsToFire) { UE_LOG(LogStreaming, Error, TEXT("%s was not fired."), *Path.ToString()); } UE_LOG(LogStreaming, Fatal, TEXT("Initial load is complete, but we still have %d imports to fire (listed above)."), PathsToFire.Num()); } #endif Disable(); } bool ConstructWaitingBootObjects() override { static struct FFixedBootOrder { TArray Array; FFixedBootOrder() { // look for any packages that we want to force preload at startup FConfigSection* BootObjects = GConfig->GetSectionPrivate(TEXT("/Script/Engine.StreamingSettings"), false, true, GEngineIni); if (BootObjects) { // go through list and add to the array for (FConfigSectionMap::TIterator It(*BootObjects); It; ++It) { if (It.Key() == TEXT("FixedBootOrder")) { // add this package to the list to be fully loaded later Array.Add(FName(*It.Value().GetValue())); } } } } } FixedBootOrder; check(GIsInitialLoad && IsInGameThread()); UObject* (*BootObjectRegister)() = nullptr; UObject* (*BootPackageObjectRegister)() = nullptr; FName WaitingPackage; bool bIsCDO = false; while (FixedBootOrder.Array.Num()) { FName ThisItem = FixedBootOrder.Array.Pop(); FScopeLock Lock(&EDLBootNotificationManagerLock); FEDLBootObjectState* ExistingState = PathToState.Find(ThisItem); if (!ExistingState) { UE_LOG(LogStreaming, Fatal, TEXT("%s was listed as a fixed load order but was not found,"), *ThisItem.ToString()); } else if (!ExistingState->Register) { UE_LOG(LogStreaming, Log, TEXT("%s was listed as a fixed load order but was already processed"), *ThisItem.ToString()); } else { // FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Booting Fixed %s %d\r\n"), *ThisItem.ToString(), int32(ExistingState->NotifyRegistrationType)); BootObjectRegister = ExistingState->Register; ExistingState->Register = nullptr; // we don't need to do this more than once bIsCDO = ExistingState->NotifyRegistrationType == ENotifyRegistrationType::NRT_ClassCDO; break; } } if (!BootObjectRegister) { FScopeLock Lock(&EDLBootNotificationManagerLock); for (auto& Pair: PathToWaitingPackageNodes) { FEDLBootObjectState* ExistingState = PathToState.Find(Pair.Key); if (ExistingState) { if (ExistingState->Register) { // FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Booting %s %d\r\n"), *Pair.Key.ToString(), int32(ExistingState->NotifyRegistrationType)); BootObjectRegister = ExistingState->Register; ExistingState->Register = nullptr; // we don't need to do this more than once bIsCDO = ExistingState->NotifyRegistrationType == ENotifyRegistrationType::NRT_ClassCDO; break; } } } } if (BootObjectRegister) { UObject* BootObject = BootObjectRegister(); check(BootObject); UObjectForceRegistration(BootObject); if (bIsCDO) { UClass* Class = CastChecked(BootObject); bool bAnyParentOnStack = false; UClass* Super = Class; while (Super) { if (CDORecursiveStack.Contains(Super)) { bAnyParentOnStack = true; break; } Super = Super->GetSuperClass(); } if (!bAnyParentOnStack) { CDORecursiveStack.Push(Class); // FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Create CDO %s\r\n"), *BootObject->GetName()); Class->GetDefaultObject(); verify(CDORecursiveStack.Pop() == Class); } else { // FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Recursive Deferred %s\r\n"), *BootObject->GetName()); CDORecursives.Add(Class); } } return true; } if (CDORecursives.Num()) { UClass* OkToRun = nullptr; for (UClass* Class: CDORecursives) { bool bAnyParentOnStack = false; UClass* Super = Class; while (Super) { if (CDORecursiveStack.Contains(Super)) { bAnyParentOnStack = true; break; } Super = Super->GetSuperClass(); } if (!bAnyParentOnStack) { OkToRun = Class; break; } } if (OkToRun) { // FPlatformMisc::LowLevelOutputDebugStringf(TEXT("CDORecursives %s\r\n"), *OkToRun->GetName()); CDORecursives.Remove(OkToRun); CDORecursiveStack.Push(OkToRun); OkToRun->GetDefaultObject(); verify(CDORecursiveStack.Pop() == OkToRun); } else { FPlatformProcess::Sleep(.001f); } return true; // even if we didn't do anything we need to return true to avoid checking for cycles } return false; } bool IsWaitingForSomething() override { FScopeLock Lock(&EDLBootNotificationManagerLock); return PathToWaitingPackageNodes.Num() > 0; } bool IsObjComplete(UObject* Obj) { static FName LongCoreUObjectPackageName(TEXT("/Script/CoreUObject")); // can't use the global here because it may not be initialized yet FName PackageName = Obj->GetOutermost()->GetFName(); if (PackageName == LongCoreUObjectPackageName) { return true; // We assume nothing in coreuobject ever loads assets in a constructor, therefore it can be considered complete } FScopeLock Lock(&EDLBootNotificationManagerLock); FName LongFName(*(PackageName.ToString() / Obj->GetName())); FEDLBootObjectState* ExistingState = PathToState.Find(LongFName); if (!ExistingState || ExistingState->LastNotifyRegistrationPhase == ENotifyRegistrationPhase::NRP_Finished) { return true; } return false; } bool FireCompletedCompiledInImports(bool bFinalRun) override { #if USE_EVENT_DRIVEN_ASYNC_LOAD_AT_BOOT_TIME FScopeLock Lock(&EDLBootNotificationManagerLock); check(bFinalRun || GIsInitialLoad); bool bResult = !!PathsToFire.Num(); for (FName LongName: PathsToFire) { for (auto It = PathToWaitingPackageNodes.CreateKeyIterator(LongName); It; ++It) { FEDLBootWaitingPackage& WaitingPackage = It.Value(); GPackageLoader->FireCompletedCompiledInImport(WaitingPackage.Package, WaitingPackage.Import); } PathToWaitingPackageNodes.Remove(LongName); } PathsToFire.Empty(); return bResult; #else return false; #endif } }; static FEDLBootNotificationManager& GetGEDLBootNotificationManager() { static FEDLBootNotificationManager Singleton; return Singleton; } FAsyncLoadingThreadSettings::FAsyncLoadingThreadSettings() { #if THREADSAFE_UOBJECTS if (FPlatformProperties::RequiresCookedData()) { check(GConfig); bool bConfigValue = true; GConfig->GetBool(TEXT("/Script/Engine.StreamingSettings"), TEXT("s.AsyncLoadingThreadEnabled"), bConfigValue, GEngineIni); bool bCommandLineDisable = FParse::Param(FCommandLine::Get(), TEXT("NoAsyncLoadingThread")); bool bCommandLineEnable = FParse::Param(FCommandLine::Get(), TEXT("AsyncLoadingThread")); bAsyncLoadingThreadEnabled = bCommandLineEnable || (bConfigValue && FApp::ShouldUseThreadingForPerformance() && !bCommandLineDisable); bConfigValue = true; GConfig->GetBool(TEXT("/Script/Engine.StreamingSettings"), TEXT("s.AsyncPostLoadEnabled"), bConfigValue, GEngineIni); bCommandLineDisable = FParse::Param(FCommandLine::Get(), TEXT("NoAsyncPostLoad")); bCommandLineEnable = FParse::Param(FCommandLine::Get(), TEXT("AsyncPostLoad")); bAsyncPostLoadEnabled = bCommandLineEnable || (bConfigValue && FApp::ShouldUseThreadingForPerformance() && !bCommandLineDisable); } else #endif { bAsyncLoadingThreadEnabled = false; bAsyncPostLoadEnabled = false; } } FAsyncLoadingThreadSettings& FAsyncLoadingThreadSettings::Get() { static FAsyncLoadingThreadSettings Settings; return Settings; } bool IsFullyLoadedObj(UObject* Obj) { if (!Obj) { return false; } if (Obj->HasAllFlags(RF_WasLoaded | RF_LoadCompleted) || Obj->IsA(UPackage::StaticClass())) // packages are never really loaded, so if it exists, it is loaded { return true; } if (Obj->HasAnyFlags(RF_WasLoaded | RF_NeedLoad | RF_WillBeLoaded)) { return false; } if (GIsInitialLoad && Obj->GetOutermost()->HasAnyPackageFlags(PKG_CompiledIn)) { return GetGEDLBootNotificationManager().IsObjComplete(Obj); } // native blueprint UDynamicClass* UD = Cast(Obj); if (!UD) { return true; } if (GEventDrivenLoaderEnabled && EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME) { if (0 != (UD->ClassFlags & CLASS_Constructed)) { return true; } } else { if (UD->GetDefaultObject(false)) { UE_CLOG(!UD->HasAnyClassFlags(CLASS_TokenStreamAssembled), LogStreaming, Fatal, TEXT("Class %s is fully loaded, but does not have its token stream assembled."), *UD->GetFullName()); return true; } } return false; } bool IsNativeCodePackage(UPackage* Package) { if (!Package || !Package->HasAnyPackageFlags(PKG_CompiledIn)) { return false; } // Make sure it isn't a dynamically loaded one, this check is slower return !GetConvertedDynamicPackageNameToTypeName().Contains(Package->GetFName()); } /** Checks if the object can have PostLoad called on the Async Loading Thread */ bool CanPostLoadOnAsyncLoadingThread(UObject* Object) { if (Object->IsPostLoadThreadSafe()) { bool bCanPostLoad = true; // All outers should also be safe to call PostLoad on ALT for (UObject* Outer = Object->GetOuter(); Outer && bCanPostLoad; Outer = Outer->GetOuter()) { bCanPostLoad = !Outer->HasAnyFlags(RF_NeedPostLoad) || Outer->IsPostLoadThreadSafe(); } return bCanPostLoad; } return false; } IAsyncPackageLoader& GetAsyncPackageLoader() { check(GPackageLoader.Get()); return *GPackageLoader; } void SetAsyncLoadingAllowed(bool bAllowAsyncLoading) { GAsyncLoadingAllowed = bAllowAsyncLoading; } void InitAsyncThread() { LLM_SCOPE(ELLMTag::AsyncLoading); #if WITH_ASYNCLOADING2 if (FIoDispatcher::IsInitialized()) { GetGEDLBootNotificationManager().Disable(); #if WITH_IOSTORE_IN_EDITOR GPackageLoader = MakeEditorPackageLoader(FIoDispatcher::Get(), GetGEDLBootNotificationManager()); #else GPackageLoader.Reset(MakeAsyncPackageLoader2(FIoDispatcher::Get())); #endif } else #endif { GPackageLoader = MakeUnique(/** ThreadIndex = */ 0, GetGEDLBootNotificationManager()); } FPlatformAtomics::InterlockedIncrement(&GIsLoaderCreated); FCoreDelegates::OnSyncLoadPackage.AddStatic([](const FString&) { GSyncLoadCount++; }); GPackageLoader->InitializeLoading(); } void ShutdownAsyncThread() { LLM_SCOPE(ELLMTag::AsyncLoading); if (GPackageLoader) { GPackageLoader->ShutdownLoading(); GPackageLoader.Reset(nullptr); } } bool IsInAsyncLoadingThreadCoreUObjectInternal() { if (GPackageLoader) { return GPackageLoader->IsInAsyncLoadThread(); } else { return false; } } void FlushAsyncLoading(int32 PackageID /* = INDEX_NONE */) { #if defined(WITH_CODE_GUARD_HANDLER) && WITH_CODE_GUARD_HANDLER void CheckImageIntegrityAtRuntime(); CheckImageIntegrityAtRuntime(); #endif LLM_SCOPE(ELLMTag::AsyncLoading); checkf(IsInGameThread(), TEXT("Unable to FlushAsyncLoading from any thread other than the game thread.")); if (GPackageLoader) { #if NO_LOGGING == 0 if (IsAsyncLoading()) { // Log the flush, but only display once per frame to avoid log spam. static uint64 LastFrameNumber = -1; if (LastFrameNumber != GFrameNumber) { UE_LOG(LogStreaming, Display, TEXT("FlushAsyncLoading: %d QueuedPackages, %d AsyncPackages"), GPackageLoader->GetNumQueuedPackages(), GPackageLoader->GetNumAsyncPackages()); } else { UE_LOG(LogStreaming, Log, TEXT("FlushAsyncLoading: %d QueuedPackages, %d AsyncPackages"), GPackageLoader->GetNumQueuedPackages(), GPackageLoader->GetNumAsyncPackages()); } LastFrameNumber = GFrameNumber; } #endif GPackageLoader->FlushLoading(PackageID); } } EAsyncPackageState::Type ProcessAsyncLoadingUntilComplete(TFunctionRef CompletionPredicate, float TimeLimit) { LLM_SCOPE(ELLMTag::AsyncLoading); return GetAsyncPackageLoader().ProcessLoadingUntilComplete(CompletionPredicate, TimeLimit); } int32 GetNumAsyncPackages() { return GetAsyncPackageLoader().GetNumAsyncPackages(); } EAsyncPackageState::Type ProcessAsyncLoading(bool bUseTimeLimit, bool bUseFullTimeLimit, float TimeLimit) { LLM_SCOPE(ELLMTag::AsyncLoading); return GetAsyncPackageLoader().ProcessLoading(bUseTimeLimit, bUseFullTimeLimit, TimeLimit); } bool IsAsyncLoadingCoreUObjectInternal() { // GIsInitialLoad guards the async loading thread from being created too early return GetAsyncPackageLoader().IsAsyncLoadingPackages(); } bool IsAsyncLoadingMultithreadedCoreUObjectInternal() { // GIsInitialLoad guards the async loading thread from being created too early return GetAsyncPackageLoader().IsMultithreaded(); } void SuspendAsyncLoadingInternal() { LLM_SCOPE(ELLMTag::AsyncLoading); check(IsInGameThread() && !IsInSlateThread()); GetAsyncPackageLoader().SuspendLoading(); } void ResumeAsyncLoadingInternal() { LLM_SCOPE(ELLMTag::AsyncLoading); check(IsInGameThread() && !IsInSlateThread()); GetAsyncPackageLoader().ResumeLoading(); } bool IsAsyncLoadingSuspendedInternal() { return GetAsyncPackageLoader().IsAsyncLoadingSuspended(); } int32 LoadPackageAsync(const FString& InName, const FGuid* InGuid /*= nullptr*/, const TCHAR* InPackageToLoadFrom /*= nullptr*/, FLoadPackageAsyncDelegate InCompletionDelegate /*= FLoadPackageAsyncDelegate()*/, EPackageFlags InPackageFlags /*= PKG_None*/, int32 InPIEInstanceID /*= INDEX_NONE*/, int32 InPackagePriority /*= 0*/, const FLinkerInstancingContext* InstancingContext /*=nullptr*/) { LLM_SCOPE(ELLMTag::AsyncLoading); UE_CLOG(!GAsyncLoadingAllowed && !IsInAsyncLoadingThread(), LogStreaming, Fatal, TEXT("Requesting async load of \"%s\" when async loading is not allowed (after shutdown). Please fix higher level code."), *InName); return GetAsyncPackageLoader().LoadPackage(InName, InGuid, InPackageToLoadFrom, InCompletionDelegate, InPackageFlags, InPIEInstanceID, InPackagePriority, InstancingContext); } int32 LoadPackageAsync(const FString& PackageName, FLoadPackageAsyncDelegate CompletionDelegate, int32 InPackagePriority /*= 0*/, EPackageFlags InPackageFlags /*= PKG_None*/, int32 InPIEInstanceID /*= INDEX_NONE*/) { const FGuid* Guid = nullptr; const TCHAR* PackageToLoadFrom = nullptr; return LoadPackageAsync(PackageName, Guid, PackageToLoadFrom, CompletionDelegate, InPackageFlags, InPIEInstanceID, InPackagePriority); } void CancelAsyncLoading() { LLM_SCOPE(ELLMTag::AsyncLoading); // Cancelling async loading while loading is suspend will result in infinite stall UE_CLOG(GetAsyncPackageLoader().IsAsyncLoadingSuspended(), LogStreaming, Fatal, TEXT("Cannot Cancel Async Loading while async loading is suspended.")); GetAsyncPackageLoader().CancelLoading(); if (!IsEngineExitRequested()) { CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true); } const EInternalObjectFlags AsyncFlags = EInternalObjectFlags::Async | EInternalObjectFlags::AsyncLoading; for (int32 ObjectIndex = 0; ObjectIndex < GUObjectArray.GetObjectArrayNum(); ++ObjectIndex) { FUObjectItem* ObjectItem = &GUObjectArray.GetObjectItemArrayUnsafe()[ObjectIndex]; if (UObject* Obj = static_cast(ObjectItem->Object)) { check(!Obj->HasAnyInternalFlags(AsyncFlags)); } } } float GetAsyncLoadPercentage(const FName& PackageName) { LLM_SCOPE(ELLMTag::AsyncLoading); return GetAsyncPackageLoader().GetAsyncLoadPercentage(PackageName); } void NotifyRegistrationEvent(const TCHAR* PackageName, const TCHAR* Name, ENotifyRegistrationType NotifyRegistrationType, ENotifyRegistrationPhase NotifyRegistrationPhase, UObject* (*InRegister)(), bool InbDynamic) { LLM_SCOPE(ELLMTag::AsyncLoading); GetGEDLBootNotificationManager().NotifyRegistrationEvent(PackageName, Name, NotifyRegistrationType, NotifyRegistrationPhase, InRegister, InbDynamic); } void NotifyRegistrationComplete() { LLM_SCOPE(ELLMTag::AsyncLoading); GetGEDLBootNotificationManager().NotifyRegistrationComplete(); FlushAsyncLoading(); GPackageLoader->StartThread(); } void NotifyConstructedDuringAsyncLoading(UObject* Object, bool bSubObject) { LLM_SCOPE(ELLMTag::AsyncLoading); GetAsyncPackageLoader().NotifyConstructedDuringAsyncLoading(Object, bSubObject); } void NotifyUnreachableObjects(const TArrayView& UnreachableObjects) { LLM_SCOPE(ELLMTag::AsyncLoading); GetAsyncPackageLoader().NotifyUnreachableObjects(UnreachableObjects); } #if WITH_IOSTORE_IN_EDITOR bool DoesPackageExistInIoStore(FName InPackageName) { if (FIoDispatcher::IsInitialized()) { FIoChunkId PackageChunkId = CreateIoChunkId(FPackageId::FromName(InPackageName).Value(), 0, EIoChunkType::ExportBundleData); return FIoDispatcher::Get().DoesChunkExist(PackageChunkId); } return false; } #endif double GFlushAsyncLoadingTime = 0.0; uint32 GFlushAsyncLoadingCount = 0; uint32 GSyncLoadCount = 0; void ResetAsyncLoadingStats() { check(IsInGameThread()); GFlushAsyncLoadingTime = 0.0; GFlushAsyncLoadingCount = 0; GSyncLoadCount = 0; } int32 GWarnIfTimeLimitExceeded = 0; static FAutoConsoleVariableRef CVarWarnIfTimeLimitExceeded( TEXT("s.WarnIfTimeLimitExceeded"), GWarnIfTimeLimitExceeded, TEXT("Enables log warning if time limit for time-sliced package streaming has been exceeded."), ECVF_Default); float GTimeLimitExceededMultiplier = 1.5f; static FAutoConsoleVariableRef CVarTimeLimitExceededMultiplier( TEXT("s.TimeLimitExceededMultiplier"), GTimeLimitExceededMultiplier, TEXT("Multiplier for time limit exceeded warning time threshold."), ECVF_Default); float GTimeLimitExceededMinTime = 0.005f; static FAutoConsoleVariableRef CVarTimeLimitExceededMinTime( TEXT("s.TimeLimitExceededMinTime"), GTimeLimitExceededMinTime, TEXT("Minimum time the time limit exceeded warning will be triggered by."), ECVF_Default); void IsTimeLimitExceededPrint( double InTickStartTime, double CurrentTime, double LastTestTime, float InTimeLimit, const TCHAR* InLastTypeOfWorkPerformed, UObject* InLastObjectWorkWasPerformedOn) { static double LastPrintStartTime = -1.0; // Log single operations that take longer than time limit (but only in cooked builds) if (LastPrintStartTime != InTickStartTime && (CurrentTime - InTickStartTime) > GTimeLimitExceededMinTime && (CurrentTime - InTickStartTime) > (GTimeLimitExceededMultiplier * InTimeLimit)) { float EstimatedTimeForThisStep = (CurrentTime - InTickStartTime) * 1000; if (LastTestTime > InTickStartTime) { EstimatedTimeForThisStep = (CurrentTime - LastTestTime) * 1000; } LastPrintStartTime = InTickStartTime; UE_LOG(LogStreaming, Warning, TEXT("IsTimeLimitExceeded: %s %s Load Time %5.2fms Last Step Time %5.2fms"), InLastTypeOfWorkPerformed ? InLastTypeOfWorkPerformed : TEXT("unknown"), InLastObjectWorkWasPerformedOn ? *InLastObjectWorkWasPerformedOn->GetFullName() : TEXT("nullptr"), (CurrentTime - InTickStartTime) * 1000, EstimatedTimeForThisStep); } }