// Copyright Epic Games, Inc. All Rights Reserved. #include "UObject/PackageReload.h" #include "UObject/ReferenceChainSearch.h" #include "UObject/UObjectHash.h" #include "UObject/GCObject.h" #include "Misc/AssetRegistryInterface.h" #include "Misc/ScopedSlowTask.h" #include "UObject/Package.h" #include "UObject/Linker.h" #include "Templates/Casts.h" #include "UObject/UObjectIterator.h" namespace PackageReloadInternal { /** * Reference to an existing package that prevents it being GC'd while we're still using it (via FExistingPackageReferences). * Once we're done with it, we clear out the strong reference and use the weak reference to verify that it was purged correctly via GC. */ struct FExistingPackageReference { FExistingPackageReference(UPackage* InPackage) : RawRef(InPackage), StrongRef(InPackage), WeakRef(InPackage) { } UPackage* RawRef; UPackage* StrongRef; TWeakObjectPtr WeakRef; }; /** * Array wrapper that prevents the packages inside the FExistingPackageReference instances being GC'd while we're still using them. */ struct FExistingPackageReferences: public FGCObject { virtual void AddReferencedObjects(FReferenceCollector& Collector) override { for (FExistingPackageReference& Ref: Refs) { // Note: We deliberate don't ARO RawRef here Collector.AddReferencedObject(Ref.StrongRef); } } TArray Refs; }; /** * Reference to an replacement package that prevents it being GC'd while we're still using it (via FNewPackageReferences). * This also includes the event data used when broadcasting package reload events for this package. */ struct FNewPackageReference { FNewPackageReference(UPackage* InPackage) : Package(InPackage), EventData() { } UPackage* Package; TSharedPtr EventData; }; /** * Array wrapper that prevents the packages inside the FNewPackageReferences instances being GC'd while we're still using them. */ struct FNewPackageReferences: public FGCObject { virtual void AddReferencedObjects(FReferenceCollector& Collector) override { for (FNewPackageReference& Ref: Refs) { Collector.AddReferencedObject(Ref.Package); if (Ref.EventData) { Ref.EventData->AddReferencedObjects(Collector); } } } TArray Refs; }; /** * Used to map objects from the old package, to objects in the new package, including the index of the package being reloaded. */ struct FObjectAndPackageIndex { FObjectAndPackageIndex(UObject* InObject, const int32 InPackageIndex) : Object(InObject), PackageIndex(InPackageIndex) { } UObject* Object; int32 PackageIndex; }; /** * Custom archive type used to re-point any in-memory references to objects in the old package to objects in the new package, or null if there is no replacement object. */ class FReplaceObjectReferencesArchive: public FArchiveUObject , public FReferenceCollector { public: FReplaceObjectReferencesArchive(UObject* InPotentialReferencer, const TMap& InOldObjectToNewData, const TArray& InExistingPackages, const TArray& InNewPackages) : PotentialReferencer(InPotentialReferencer), OldObjectToNewData(InOldObjectToNewData), ExistingPackages(InExistingPackages), NewPackages(InNewPackages) { ArIsObjectReferenceCollector = true; ArIsModifyingWeakAndStrongReferences = true; ArIgnoreOuterRef = true; ArNoDelta = true; } virtual ~FReplaceObjectReferencesArchive() { } virtual FString GetArchiveName() const override { return TEXT("FReplaceObjectReferencesArchive"); } virtual void HandleObjectReference(UObject*& Object, const UObject* ReferencingObject, const FProperty* ReferencingProperty) override { (*this) << Object; } virtual void HandleObjectReferences(UObject** InObjects, const int32 ObjectNum, const UObject* InReferencingObject, const FProperty* InReferencingProperty) override { for (int32 ObjectIndex = 0; ObjectIndex < ObjectNum; ++ObjectIndex) { UObject*& Object = InObjects[ObjectIndex]; (*this) << Object; } } virtual bool IsIgnoringArchetypeRef() const override { return false; } virtual bool IsIgnoringTransient() const override { return false; } FArchive& operator<<(UObject*& ObjRef) { if (ObjRef && ObjRef != PotentialReferencer) { UObject* NewObject = nullptr; TSharedPtr PackageEventData; if (GetNewObjectAndEventData(ObjRef, NewObject, PackageEventData)) { ObjRef = NewObject; PackageEventData->AddObjectReferencer(PotentialReferencer); } } return *this; } bool GetNewObjectAndEventData(UObject* InOldObject, UObject*& OutNewObject, TSharedPtr& OutEventData) const { const FObjectAndPackageIndex* ObjectAndPackageIndexPtr = OldObjectToNewData.Find(InOldObject); // Only fix-up references to objects outside of the potential referencer package, as internal object references will be orphaned automatically if (ObjectAndPackageIndexPtr && PotentialReferencer->GetOutermost() != ExistingPackages[ObjectAndPackageIndexPtr->PackageIndex].RawRef) { OutNewObject = ObjectAndPackageIndexPtr->Object; OutEventData = NewPackages[ObjectAndPackageIndexPtr->PackageIndex].EventData; return true; } return false; } UObject* PotentialReferencer; const TMap& OldObjectToNewData; const TArray& ExistingPackages; const TArray& NewPackages; }; /** * Given a package, mark it and all its sub-objects with the RF_NewerVersionExists flag so that other systems can detect that they're being replaced. */ void MarkPackageReplaced(UPackage* InPackage) { InPackage->SetFlags(RF_NewerVersionExists); ForEachObjectWithPackage(InPackage, [](UObject* InSubObject) { InSubObject->SetFlags(RF_NewerVersionExists); return true; // continue }); } /** * Given a package, remove the RF_NewerVersionExists flag from it and all its sub-objects. */ void ClearPackageReplaced(UPackage* InPackage) { InPackage->ClearFlags(RF_NewerVersionExists); ForEachObjectWithPackage(InPackage, [](UObject* InSubObject) { InSubObject->ClearFlags(RF_NewerVersionExists); return true; // continue }); } /** * Given an object, put it into a state where a GC may purge it (assuming there are no external references). */ void MakeObjectPurgeable(UObject* InObject) { if (InObject->IsRooted()) { InObject->RemoveFromRoot(); } InObject->ClearFlags(RF_Public | RF_Standalone); } /** * Given a package, put it into a state where a GC may purge it (assuming there are no external references). */ void MakePackagePurgeable(UPackage* InPackage) { MakeObjectPurgeable(InPackage); ForEachObjectWithPackage(InPackage, [](UObject* InObject) { MakeObjectPurgeable(InObject); return true; // continue }); } /** * Given an object, dump anything that is still externally referencing it to the log. */ void DumpExternalReferences(UObject* InObject, UPackage* InPackage) { TArray ExternalRefDumps; { FReferenceChainSearch ObjectRefChains(InObject, EReferenceChainSearchMode::Default); for (const FReferenceChainSearch::FReferenceChain* ObjectRefChain: ObjectRefChains.GetReferenceChains()) { for (int32 NodeIndex = 0; NodeIndex < ObjectRefChain->Num(); ++NodeIndex) { const FReferenceChainSearch::FGraphNode* ObjectRefChainLink = ObjectRefChain->GetNode(NodeIndex); const bool bIsExternalRef = ObjectRefChainLink->Object->GetOutermost() != InPackage; if (bIsExternalRef) { ExternalRefDumps.Emplace(ObjectRefChainLink->Object->GetFullName()); } } } } if (ExternalRefDumps.Num() > 0) { UE_LOG(LogUObjectGlobals, Display, TEXT("ReloadPackage external references for '%s'."), *InObject->GetPathName()); for (const FString& ExternalRefDump: ExternalRefDumps) { UE_LOG(LogUObjectGlobals, Display, TEXT(" %s"), *ExternalRefDump); } } } /** * Given a package, validate and prepare it for reload. * @return The package to be reloaded, or null if the given package isn't valid to be reloaded. */ UPackage* ValidateAndPreparePackageForReload(UPackage* InExistingPackage) { // We can't reload memory-only packages if (InExistingPackage->HasAnyPackageFlags(PKG_InMemoryOnly)) { UE_LOG(LogUObjectGlobals, Warning, TEXT("ReloadPackage cannot reload '%s' as it is marked PKG_InMemoryOnly."), *InExistingPackage->GetName()); return nullptr; } // Make sure the package has finished loading before we try and unload it again if (!InExistingPackage->IsFullyLoaded()) { FlushAsyncLoading(); InExistingPackage->FullyLoad(); } ResetLoaders(InExistingPackage); return InExistingPackage; } /** * Given a package, reload it from disk. * @return The package that was reloaded, or null if the given package couldn't be reloaded. */ UPackage* LoadReplacementPackage(UPackage* InExistingPackage, const uint32 InLoadFlags) { if (!InExistingPackage) { return nullptr; } const FString ExistingPackageName = InExistingPackage->GetName(); // Rename the old package, and then load the new one in its place const ERenameFlags PkgRenameFlags = REN_ForceNoResetLoaders | REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional | REN_SkipGeneratedClasses; InExistingPackage->Rename(*MakeUniqueObjectName(Cast(InExistingPackage->GetOuter()), UPackage::StaticClass(), *FString::Printf(TEXT("%s_DEADPACKAGE"), *InExistingPackage->GetName())).ToString(), nullptr, PkgRenameFlags); MarkPackageReplaced(InExistingPackage); UPackage* NewPackage = LoadPackage(Cast(InExistingPackage->GetOuter()), *ExistingPackageName, InLoadFlags); if (!NewPackage) { UE_LOG(LogUObjectGlobals, Warning, TEXT("ReloadPackage cannot reload '%s' as the new package failed to load. The old package will be restored."), *ExistingPackageName); // Make sure that the failed load attempt hasn't left any objects behind that would prevent the rename if (UPackage* FailedPackage = FindObject(Cast(InExistingPackage->GetOuter()), *ExistingPackageName)) { FailedPackage->Rename(*MakeUniqueObjectName(Cast(FailedPackage->GetOuter()), UPackage::StaticClass(), *FString::Printf(TEXT("%s_DEADPACKAGE"), *FailedPackage->GetName())).ToString(), nullptr, PkgRenameFlags); MakePackagePurgeable(FailedPackage); } // Failed to load the new package, give the old package its original name and bail! InExistingPackage->Rename(*ExistingPackageName, nullptr, PkgRenameFlags); ClearPackageReplaced(InExistingPackage); return nullptr; } // Make sure the package has finished loading before we try and find things from inside it if (!NewPackage->IsFullyLoaded()) { FlushAsyncLoading(); NewPackage->FullyLoad(); } return NewPackage; } /** * Given an old and new package, generate the event payload data needed to fix-up references to objects from the old package to the corresponding objects in the new package. * @return The event payload data, or null if either given package is invalid. */ TSharedPtr GeneratePackageReloadEvent(UPackage* InExistingPackage, UPackage* InNewPackage) { TSharedPtr PackageReloadedEvent; if (InExistingPackage && InNewPackage) { TMap RedirectedObjectsMap; RedirectedObjectsMap.Emplace(InExistingPackage, InNewPackage); InExistingPackage->BuildSubobjectMapping(InNewPackage, RedirectedObjectsMap); for (const auto& ObjectMappingPair: RedirectedObjectsMap) { UObject* ExistingObject = ObjectMappingPair.Key; UObject* NewObject = ObjectMappingPair.Value; if (NewObject) { // Pass on the root-set state from the old object to the new one if (ExistingObject->IsRooted()) { NewObject->AddToRoot(); } // Pass on some important flags to the new object { const EObjectFlags FlagsToPassToNewObject = ExistingObject->GetMaskedFlags(RF_Public | RF_Standalone | RF_Transactional); NewObject->SetFlags(FlagsToPassToNewObject); } } else if (ExistingObject->HasAnyFlags(RF_Transient)) { UE_LOG(LogUObjectGlobals, Display, TEXT("ReloadPackage failed to find a replacement object for '%s' (transient) in the new package '%s'. Any existing references to this object will be nulled out."), *ExistingObject->GetPathName(InExistingPackage), *InNewPackage->GetName()); } else { UE_LOG(LogUObjectGlobals, Warning, TEXT("ReloadPackage failed to find a replacement object for '%s' in the new package '%s'. Any existing references to this object will be nulled out."), *ExistingObject->GetPathName(InExistingPackage), *InNewPackage->GetName()); } } PackageReloadedEvent = MakeShared(InExistingPackage, InNewPackage, MoveTemp(RedirectedObjectsMap)); } return PackageReloadedEvent; } void SortPackagesForReload(const FName PackageName, TSet& ProcessedPackages, TArray& SortedPackagesToReload, const TMap& AllPackagesToReload, IAssetRegistryInterface& InAssetRegistry) { ProcessedPackages.Add(PackageName); TArray PackageDependencies; InAssetRegistry.GetDependencies(PackageName, PackageDependencies, UE::AssetRegistry::EDependencyCategory::Package, UE::AssetRegistry::EDependencyQuery::Hard); // Recursively go through processing each new dependency until we run out for (const FName& Dependency: PackageDependencies) { if (!ProcessedPackages.Contains(Dependency)) { SortPackagesForReload(Dependency, ProcessedPackages, SortedPackagesToReload, AllPackagesToReload, InAssetRegistry); } } // Add this package to the sorted array now that its dependencies have been processed if (AllPackagesToReload.Contains(PackageName)) { SortedPackagesToReload.Emplace(AllPackagesToReload[PackageName]); } } } // namespace PackageReloadInternal void SortPackagesForReload(TArray& PackagesToReload) { // We need to sort the packages to reload so that dependencies are reloaded before the assets that depend on them if (PackagesToReload.Num() > 1) { IAssetRegistryInterface* AssetRegistry = IAssetRegistryInterface::GetPtr(); checkf(AssetRegistry, TEXT("SortPackagesForReload requires the asset registry to perform dependency analysis, but no asset registry is available.")); TSet ProcessedPackages; ProcessedPackages.Reserve(PackagesToReload.Num()); TArray SortedPackagesToReload; SortedPackagesToReload.Reserve(PackagesToReload.Num()); TMap AllPackagesToReload; AllPackagesToReload.Reserve(PackagesToReload.Num()); for (UPackage* PackageToReload: PackagesToReload) { AllPackagesToReload.Emplace(PackageToReload->GetFName(), PackageToReload); } for (UPackage* PackageToReload: PackagesToReload) { if (!ProcessedPackages.Contains(PackageToReload->GetFName())) { PackageReloadInternal::SortPackagesForReload(PackageToReload->GetFName(), ProcessedPackages, SortedPackagesToReload, AllPackagesToReload, *AssetRegistry); } } PackagesToReload = MoveTemp(SortedPackagesToReload); } } UPackage* ReloadPackage(UPackage* InPackageToReload, const uint32 InLoadFlags) { TArray ReloadedPackages; FReloadPackageData ReloadPackageData(InPackageToReload, InLoadFlags); ReloadPackages(TArrayView(&ReloadPackageData, 1), ReloadedPackages, 1); return ReloadedPackages[0]; } void ReloadPackages(const TArrayView& InPackagesToReload, TArray& OutReloadedPackages, int32 InNumPackagesPerBatch) { // Interdependencies between packages (in particular Blueprints) make it unsafe to run this logic in batches. // There are a number of edge cases that would have to be addressed if the batching logic were to be re-enabled, but // most likely the blueprint reparenting step would have to take in a TMap that it could update // when it decided that it needed to replace an instance due to hierarchy changes (e.g. class layout changing // due to SuperStruct changes). For now, just process assets in a batch size of 1: InNumPackagesPerBatch = 1; FString Msg; Msg.Append(FString::Printf(TEXT("Reloading %d Package(s):"), InPackagesToReload.Num())); const int32 MAX_PACKAGES_TO_LOG = 10; for (int32 I = 0; I < InPackagesToReload.Num() && I < MAX_PACKAGES_TO_LOG; ++I) { const FReloadPackageData& ReloadPackageData = InPackagesToReload[I]; Msg.Append(TEXT("\n")); Msg.Append(FString::Printf(TEXT("\tAsset Name: %s"), *ReloadPackageData.PackageToReload->GetName())); } UE_LOG(LogUObjectGlobals, Log, TEXT("%s"), *Msg); FScopedSlowTask ReloadingPackagesSlowTask(InPackagesToReload.Num(), NSLOCTEXT("CoreUObject", "ReloadingPackages", "Reloading Packages")); ReloadingPackagesSlowTask.MakeDialog(); // Cache the current dirty state of all packages so we can restore it after the reload TSet DirtyPackages; ForEachObjectOfClass(UPackage::StaticClass(), [&DirtyPackages](UObject* InPackageObj) { UPackage* Package = CastChecked(InPackageObj); if (Package->IsDirty()) { DirtyPackages.Add(Package->GetFName()); } }, false); // Gather up the list of all packages to reload (note: this array may include null packages!) PackageReloadInternal::FExistingPackageReferences ExistingPackages; ExistingPackages.Refs.Reserve(InPackagesToReload.Num()); { FScopedSlowTask PreparingPackagesForReloadSlowTask(InPackagesToReload.Num(), NSLOCTEXT("CoreUObject", "PreparingPackagesForReload", "Preparing Packages for Reload")); for (const FReloadPackageData& PackageToReloadData: InPackagesToReload) { PreparingPackagesForReloadSlowTask.EnterProgressFrame(1.0f); ExistingPackages.Refs.Emplace(PackageReloadInternal::ValidateAndPreparePackageForReload(PackageToReloadData.PackageToReload)); } if (ExistingPackages.Refs.Num() > 0) { // Run a GC before we start to clean-up any lingering objects that may reference things we're about to reload CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); } } // Rename the existing packages, load the new packages, then fix-up any references PackageReloadInternal::FNewPackageReferences NewPackages; NewPackages.Refs.Reserve(ExistingPackages.Refs.Num()); { // Process the packages in batches to avoid consuming too much memory due to a lack of GC int32 PackageIndex = 0; while (PackageIndex < ExistingPackages.Refs.Num()) { FCoreUObjectDelegates::OnPackageReloaded.Broadcast(EPackageReloadPhase::PreBatch, nullptr); const int32 BatchStartIndex = PackageIndex; for (; PackageIndex < ExistingPackages.Refs.Num(); ++PackageIndex) { UPackage* ExistingPackage = ExistingPackages.Refs[PackageIndex].RawRef; const FText ProgressText = ExistingPackage ? FText::Format(NSLOCTEXT("CoreUObject", "ReloadingPackagef", "Reloading {0}..."), FText::FromName(ExistingPackage->GetFName())) : NSLOCTEXT("CoreUObject", "ReloadingPackages", "Reloading Packages"); ReloadingPackagesSlowTask.EnterProgressFrame(1, ProgressText); { FPackageReloadedEvent TempReloadEvent(ExistingPackage, nullptr, TMap()); FCoreUObjectDelegates::OnPackageReloaded.Broadcast(EPackageReloadPhase::PrePackageLoad, &TempReloadEvent); } check(NewPackages.Refs.Num() == PackageIndex); NewPackages.Refs.Emplace(PackageReloadInternal::LoadReplacementPackage(ExistingPackage, InPackagesToReload[PackageIndex].LoadFlags)); UPackage* NewPackage = NewPackages.Refs[PackageIndex].Package; NewPackages.Refs[PackageIndex].EventData = PackageReloadInternal::GeneratePackageReloadEvent(ExistingPackage, NewPackage); const bool bEndBatch = (PackageIndex == (BatchStartIndex + InNumPackagesPerBatch)) || (ExistingPackage && ExistingPackage->ContainsMap()); if (bEndBatch) { ++PackageIndex; // We still need to move on-to the next package for the next batch break; } } const int32 NumPackagesInBatch = PackageIndex - BatchStartIndex; FScopedSlowTask FixingUpReferencesSlowTask((NumPackagesInBatch * 4) + GUObjectArray.GetObjectArrayNum(), NSLOCTEXT("CoreUObject", "FixingUpReferences", "Fixing-Up References")); // Pre-pass to notify things that the package old package is about to be fixed-up TMap OldObjectToNewData; for (int32 BatchPackageIndex = BatchStartIndex; BatchPackageIndex < PackageIndex; ++BatchPackageIndex) { FixingUpReferencesSlowTask.EnterProgressFrame(1.0f); PackageReloadInternal::FNewPackageReference& NewPackageData = NewPackages.Refs[BatchPackageIndex]; if (NewPackageData.EventData.IsValid()) { FCoreUObjectDelegates::OnPackageReloaded.Broadcast(EPackageReloadPhase::PrePackageFixup, NewPackageData.EventData.Get()); FCoreUObjectDelegates::OnPackageReloaded.Broadcast(EPackageReloadPhase::OnPackageFixup, NewPackageData.EventData.Get()); // Build up the mapping of old objects to the package index that contains them; this is needed to track per-package references correctly OldObjectToNewData.Reserve(OldObjectToNewData.Num() + NewPackageData.EventData->GetRepointedObjects().Num()); for (const auto& ObjectMappingPair: NewPackageData.EventData->GetRepointedObjects()) { OldObjectToNewData.Add(ObjectMappingPair.Key, PackageReloadInternal::FObjectAndPackageIndex(ObjectMappingPair.Value, BatchPackageIndex)); } } } // Main pass to go through and fix-up any references pointing to data from the old package to point to data from the new package // todo: multi-thread this like FHotReloadModule::ReplaceReferencesToReconstructedCDOs? for (FThreadSafeObjectIterator ObjIter(UObject::StaticClass(), false, RF_NoFlags, EInternalObjectFlags::PendingKill); ObjIter; ++ObjIter) { UObject* PotentialReferencer = *ObjIter; // Mutating the old versions of classes can result in us replacing the SuperStruct pointer, which results // in class layout change and subsequently crashes because instances will not match this new class layout: UClass* AsClass = Cast(PotentialReferencer); if (!AsClass) { AsClass = PotentialReferencer->GetTypedOuter(); } if (AsClass) { if (AsClass->HasAnyClassFlags(CLASS_NewerVersionExists) || AsClass->HasAnyFlags(RF_NewerVersionExists)) { continue; } } FixingUpReferencesSlowTask.EnterProgressFrame(1.0f); PackageReloadInternal::FReplaceObjectReferencesArchive ReplaceRefsArchive(PotentialReferencer, OldObjectToNewData, ExistingPackages.Refs, NewPackages.Refs); PotentialReferencer->Serialize(ReplaceRefsArchive); // Deal with direct references during Serialization PotentialReferencer->GetClass()->CallAddReferencedObjects(PotentialReferencer, ReplaceRefsArchive); // Deal with indirect references via AddReferencedObjects } // The above fix-up also repoints the StrongRef in FExistingPackageReference, so we'll fix that up again now to prevent the old package from being GC'd for (int32 BatchPackageIndex = BatchStartIndex; BatchPackageIndex < PackageIndex; ++BatchPackageIndex) { FixingUpReferencesSlowTask.EnterProgressFrame(1.0f); ExistingPackages.Refs[BatchPackageIndex].StrongRef = ExistingPackages.Refs[BatchPackageIndex].RawRef; } // Final pass to clean-up any remaining references prior to GC // Note: We do this as a separate pass to preparing the objects for GC as this callback may prematurely invoke a GC that invalidates some data we're working with for (int32 BatchPackageIndex = BatchStartIndex; BatchPackageIndex < PackageIndex; ++BatchPackageIndex) { FixingUpReferencesSlowTask.EnterProgressFrame(1.0f); PackageReloadInternal::FNewPackageReference& NewPackageData = NewPackages.Refs[BatchPackageIndex]; if (NewPackageData.EventData.IsValid()) { FCoreUObjectDelegates::OnPackageReloaded.Broadcast(EPackageReloadPhase::PostPackageFixup, NewPackageData.EventData.Get()); } } FCoreUObjectDelegates::OnPackageReloaded.Broadcast(EPackageReloadPhase::PostBatchPreGC, nullptr); // Purge old packages that have had a replacement package loaded for (int32 BatchPackageIndex = BatchStartIndex; BatchPackageIndex < PackageIndex; ++BatchPackageIndex) { FixingUpReferencesSlowTask.EnterProgressFrame(1.0f); UPackage* ExistingPackage = ExistingPackages.Refs[BatchPackageIndex].RawRef; UPackage* NewPackage = NewPackages.Refs[BatchPackageIndex].Package; if (ExistingPackage && NewPackage) { // Allow the old package to be GC'd PackageReloadInternal::MakePackagePurgeable(ExistingPackage); ExistingPackages.Refs[BatchPackageIndex].StrongRef = nullptr; NewPackages.Refs[BatchPackageIndex].EventData.Reset(); } } CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); FCoreUObjectDelegates::OnPackageReloaded.Broadcast(EPackageReloadPhase::PostBatchPostGC, nullptr); } } // Clean any packages that we dirtied as part of the replacement process ForEachObjectOfClass(UPackage::StaticClass(), [&DirtyPackages](UObject* InPackageObj) { UPackage* Package = CastChecked(InPackageObj); if (Package->IsDirty() && !DirtyPackages.Contains(Package->GetFName())) { Package->SetDirtyFlag(false); } }, false); // Finalization and error reporting OutReloadedPackages.Reserve(ExistingPackages.Refs.Num()); for (int32 PackageIndex = 0; PackageIndex < ExistingPackages.Refs.Num(); ++PackageIndex) { UPackage* ExistingPackage = ExistingPackages.Refs[PackageIndex].WeakRef.Get(); UPackage* NewPackage = NewPackages.Refs[PackageIndex].Package; check(OutReloadedPackages.Num() == PackageIndex); OutReloadedPackages.Emplace(NewPackage); // Report any old packages that failed to purge if (ExistingPackage && NewPackage) { UE_LOG(LogUObjectGlobals, Warning, TEXT("ReloadPackage failed to purge the old package '%s'. This is unexpected, and likely means that it was still externally referenced."), *ExistingPackage->GetName()); const bool bDumpExternalReferences = DO_GUARD_SLOW || (WITH_EDITOR && GIsEditor); if (bDumpExternalReferences) { PackageReloadInternal::DumpExternalReferences(ExistingPackage, ExistingPackage); // ForEachObjectWithPackage(ExistingPackage, [](UObject* InExistingObject) //{ // PackageReloadInternal::DumpExternalReferences(InExistingObject, ExistingPackage); // return true; // continue // }, true, RF_NoFlags, EInternalObjectFlags::PendingKill); } } } }