EM_Task/CoreUObject/Private/UObject/PackageReload.cpp

685 lines
30 KiB
C++
Raw Normal View History

2026-02-13 16:18:33 +08:00
// 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<UPackage> 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<FExistingPackageReference> 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<FPackageReloadedEvent> 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<FNewPackageReference> 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<UObject*, FObjectAndPackageIndex>& InOldObjectToNewData, const TArray<FExistingPackageReference>& InExistingPackages, const TArray<FNewPackageReference>& 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<FPackageReloadedEvent> PackageEventData;
if (GetNewObjectAndEventData(ObjRef, NewObject, PackageEventData))
{
ObjRef = NewObject;
PackageEventData->AddObjectReferencer(PotentialReferencer);
}
}
return *this;
}
bool GetNewObjectAndEventData(UObject* InOldObject, UObject*& OutNewObject, TSharedPtr<FPackageReloadedEvent>& 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<UObject*, FObjectAndPackageIndex>& OldObjectToNewData;
const TArray<FExistingPackageReference>& ExistingPackages;
const TArray<FNewPackageReference>& 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<FString> 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<UPackage>(InExistingPackage->GetOuter()), UPackage::StaticClass(), *FString::Printf(TEXT("%s_DEADPACKAGE"), *InExistingPackage->GetName())).ToString(), nullptr, PkgRenameFlags);
MarkPackageReplaced(InExistingPackage);
UPackage* NewPackage = LoadPackage(Cast<UPackage>(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<UPackage>(Cast<UPackage>(InExistingPackage->GetOuter()), *ExistingPackageName))
{
FailedPackage->Rename(*MakeUniqueObjectName(Cast<UPackage>(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<FPackageReloadedEvent> GeneratePackageReloadEvent(UPackage* InExistingPackage, UPackage* InNewPackage)
{
TSharedPtr<FPackageReloadedEvent> PackageReloadedEvent;
if (InExistingPackage && InNewPackage)
{
TMap<UObject*, UObject*> 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<FPackageReloadedEvent>(InExistingPackage, InNewPackage, MoveTemp(RedirectedObjectsMap));
}
return PackageReloadedEvent;
}
void SortPackagesForReload(const FName PackageName, TSet<FName>& ProcessedPackages, TArray<UPackage*>& SortedPackagesToReload, const TMap<FName, UPackage*>& AllPackagesToReload, IAssetRegistryInterface& InAssetRegistry)
{
ProcessedPackages.Add(PackageName);
TArray<FName> 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<UPackage*>& 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<FName> ProcessedPackages;
ProcessedPackages.Reserve(PackagesToReload.Num());
TArray<UPackage*> SortedPackagesToReload;
SortedPackagesToReload.Reserve(PackagesToReload.Num());
TMap<FName, UPackage*> 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<UPackage*> ReloadedPackages;
FReloadPackageData ReloadPackageData(InPackageToReload, InLoadFlags);
ReloadPackages(TArrayView<FReloadPackageData>(&ReloadPackageData, 1), ReloadedPackages, 1);
return ReloadedPackages[0];
}
void ReloadPackages(const TArrayView<FReloadPackageData>& InPackagesToReload, TArray<UPackage*>& 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<UObject*, UObject*> 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<FName> DirtyPackages;
ForEachObjectOfClass(UPackage::StaticClass(), [&DirtyPackages](UObject* InPackageObj)
{
UPackage* Package = CastChecked<UPackage>(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<UObject*, UObject*>());
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<UObject*, PackageReloadInternal::FObjectAndPackageIndex> 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<UClass>(PotentialReferencer);
if (!AsClass)
{
AsClass = PotentialReferencer->GetTypedOuter<UClass>();
}
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<UPackage>(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);
}
}
}
}