Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Public/Kismet2/KismetReinstanceUtilities.h
2025-05-18 13:04:45 +08:00

404 lines
20 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Stats/Stats.h"
#include "Templates/SubclassOf.h"
#include "Components/ActorComponent.h"
#include "UObject/GCObject.h"
class FReinstanceFinalizer;
class UBlueprint;
class FEditorCacheArchetypeManager;
struct FObjectInstancingGraph;
DECLARE_STATS_GROUP(TEXT("Kismet Reinstancer"), STATGROUP_KismetReinstancer, STATCAT_Advanced);
enum class EBlueprintCompileReinstancerFlags
{
None = 0x0,
BytecodeOnly = 0x1,
AutoInferSaveOnCompile = 0x2,
AvoidCDODuplication = 0x4,
UseDeltaSerialization = 0x8,
};
ENUM_CLASS_FLAGS(EBlueprintCompileReinstancerFlags)
struct FReplaceInstancesOfClassParameters
{
UE_NONCOPYABLE(FReplaceInstancesOfClassParameters)
FReplaceInstancesOfClassParameters() = default;
UE_DEPRECATED(5.2, "Please use the default constructor instead.")
FReplaceInstancesOfClassParameters(UClass* InOldClass, UClass* InNewClass) {}
/** The old class, instances of which will be replaced */
UE_DEPRECATED(5.2, "This member is no longer in use.")
UClass* OldClass = nullptr;
/** The new class, used to create new instances to replace instances of the old class */
UE_DEPRECATED(5.2, "This member is no longer in use.")
UClass* NewClass = nullptr;
/** OriginalCDO, use if OldClass->ClassDefaultObject has been overwritten (non-batch only, legacy) */
UObject* OriginalCDO = nullptr;
/* Mapping of all the replaced CDOs and archetypes*/
TMap<UClass*, TMap<UObject*, UObject*>>* OldToNewTemplates = nullptr;
/** Set of objects that should not have their references updated if they refer to instances that are replaced */
TSet<UObject*>* ObjectsThatShouldUseOldStuff = nullptr;
/** Set of objects for which new objects should not be created, useful if client has created new instances themself */
const TSet<UObject*>* InstancesThatShouldUseOldClass = nullptr;
/** Hashes identifying original class's construction version, if there's a match we can skip reconstruction */
TMap<TObjectPtr<const UClass>, FBlake3Hash>* ConstructionVersioningData = nullptr;
/** Set to true if class object has been replaced (non-batch only, legacy) */
bool bClassObjectReplaced = false;
/** Defaults to true, indicates whether root components should be preserved */
bool bPreserveRootComponent = true;
bool bArchetypesAreUpToDate = false;
/**
* Blueprints reuses its UClass* from compile to compile, but it's more
* intuitive to just replace a UClass* with a new instance (e.g. from a
* package reload). This flag tells the reinstancer to replace references
* to old classes with references to new classes.
*/
bool bReplaceReferencesToOldClasses = false;
/**
* Indicates whether CDOs should be included in reference replacement.
* Disabled by default since this can increase search cost, because this
* effectively means replacement will require us to do a referencer search
* for at least one instance of every remapped class (i.e. - the CDO). In
* most cases (e.g. load time), incurring this cost is unnecessary because
* the CDO is not expected to be referenced outside of its own class type,
* so we treat it as an opt-in behavior rather than enabling it by default.
*
* Note: This flag is implied to be 'true' if 'OriginalCDO' is non-NULL. In
* that case, the value of this flag will not be used in order to maintain
* backwards-compatibility with existing code paths.
*/
bool bReplaceReferencesToOldCDOs = false;
};
struct UNREALED_API UE_DEPRECATED(5.2, "This type is no longer in use.") FBatchReplaceInstancesOfClassParameters
{
bool bArchetypesAreUpToDate = false;
/**
* Blueprints reuses its UClass* from compile to compile, but it's more
* intuitive to just replace a UClass* with a new instance (e.g. from a
* package reload). This flag tells the reinstancer to replace references
* to old classes with references to new classes.
*/
bool bReplaceReferencesToOldClasses = false;
};
class FBlueprintCompileReinstancer : public TSharedFromThis<FBlueprintCompileReinstancer>, public FGCObject
{
protected:
friend struct FBlueprintCompilationManagerImpl;
friend struct FRigVMBlueprintCompilationManagerImpl;
friend struct FReinstancingJob;
static UNREALED_API TSet<TWeakObjectPtr<UBlueprint>> CompiledBlueprintsToSave;
static UNREALED_API UClass* HotReloadedOldClass;
static UNREALED_API UClass* HotReloadedNewClass;
/** Reference to the class we're actively reinstancing */
UClass* ClassToReinstance;
/** Reference to the duplicate of ClassToReinstance, which all previous instances are now instances of */
TObjectPtr<UClass> DuplicatedClass;
/** The original CDO object for the class being actively reinstanced */
TObjectPtr<UObject> OriginalCDO;
/** The original Sparse Class Data object for the class being actively reinstanced */
void* OriginalSCD;
/** The original SDO Struct for the class being actively reinstanced */
TObjectPtr<UScriptStruct> OriginalSCDStruct;
/** A snapshot of the SCD, currently delta serialized from its archetype - taken before ownership of SCDs is taken */
TArray<uint8> SCDSnapshot;
/** Children of this blueprint, which will need to be recompiled and relinked temporarily to maintain the class layout */
TArray<UBlueprint*> Children;
/** Mappings from old fields before recompilation to their new equivalents */
TMap<FName, FProperty*> PropertyMap;
TMap<FName, TObjectPtr<UFunction>> FunctionMap;
/** Whether or not this resinstancer has already reinstanced */
bool bHasReinstanced;
/** Cached value, mostly used to determine if we're explicitly targeting the skeleton class or not */
enum EReinstClassType
{
RCT_Unknown,
RCT_BpSkeleton,
RCT_BpGenerated,
RCT_Native,
};
EReinstClassType ReinstClassType;
uint32 ClassToReinstanceDefaultValuesCRC;
/** Objects that should keep reference to old class */
TSet<UObject*> ObjectsThatShouldUseOldStuff;
/** TRUE if this is the root reinstancer that all other active reinstancing is spawned from */
bool bIsRootReinstancer;
/** TRUE if this reinstancer should resave compiled Blueprints if the user has requested it */
bool bAllowResaveAtTheEndIfRequested;
/** TRUE if delta serialization should be forced during FBlueprintCompileReinstancer::CopyPropertiesForUnrelatedObjects */
bool bUseDeltaSerializationToCopyProperties;
public:
// FSerializableObject interface
UNREALED_API virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
virtual FString GetReferencerName() const override
{
return TEXT("FBlueprintCompileReinstancer");
}
// End of FSerializableObject interface
static UNREALED_API void OptionallyRefreshNodes(UBlueprint* BP);
UNREALED_API virtual void BlueprintWasRecompiled(UBlueprint* BP, bool bBytecodeOnly);
static TSharedPtr<FBlueprintCompileReinstancer> Create(UClass* InClassToReinstance, EBlueprintCompileReinstancerFlags Flags = EBlueprintCompileReinstancerFlags::AutoInferSaveOnCompile)
{
return MakeShareable(new FBlueprintCompileReinstancer(InClassToReinstance, Flags));
}
UNREALED_API virtual ~FBlueprintCompileReinstancer();
/** Saves a mapping of field names to their UField equivalents, so we can remap any bytecode that references them later */
UNREALED_API void SaveClassFieldMapping(UClass* InClassToReinstance);
/** Helper to gather mappings from the old class's fields to the new class's version */
UNREALED_API void GenerateFieldMappings(TMap<FFieldVariant, FFieldVariant>& FieldMapping);
/** Reinstances all objects in the ObjectReinstancingMap */
UNREALED_API void ReinstanceObjects(bool bForceAlwaysReinstance = false);
/** Updates references to properties and functions of the class that has in the bytecode of dependent blueprints */
UNREALED_API void UpdateBytecodeReferences( TSet<UBlueprint*>& OutDependentBlueprints, TMap<FFieldVariant, FFieldVariant>& OutFieldMapping);
/** Populates SCDSnapshot, for use via PropagateSparseClassDataToNewClass */
UNREALED_API void SaveSparseClassData(const UClass* ForClass);
/** Instructs the reinstancer to take ownership of the sparse class data - doing so will make it difficult to identify archetype data */
UNREALED_API void TakeOwnershipOfSparseClassData(UClass* ForClass);
/** Copies an owned sparse class data instance to a new class and frees any owned SCD */
UNREALED_API void PropagateSparseClassDataToNewClass(UClass* NewClass);
/** Consumes the set and map populated by calls to UpdateBytecodeReferences */
static UNREALED_API void FinishUpdateBytecodeReferences( const TSet<UBlueprint*>& DependentBPs, const TMap<FFieldVariant, FFieldVariant>& FieldMappings);
// @todo_deprecated - To be removed in a future release.
UE_DEPRECATED(5.2, "Please use the version that takes an FReplaceInstancesOfClassParameters input instead.")
static UNREALED_API void ReplaceInstancesOfClass(UClass* OldClass, UClass* NewClass, UObject* OriginalCDO = nullptr, TSet<UObject*>* ObjectsThatShouldUseOldStuff = nullptr, bool bClassObjectReplaced = false, bool bPreserveRootComponent = true);
UE_DEPRECATED(5.2, "Please use ReplaceInstancesOfClass() instead.")
static UNREALED_API void ReplaceInstancesOfClassEx(const FReplaceInstancesOfClassParameters& Parameters );
UE_DEPRECATED(5.2, "Please use the version that takes an FReplaceInstancesOfClassParameters input instead.")
static UNREALED_API void BatchReplaceInstancesOfClass(TMap<UClass*, UClass*>& InOldToNewClassMap,
PRAGMA_DISABLE_DEPRECATION_WARNINGS
const FBatchReplaceInstancesOfClassParameters& BatchParams = FBatchReplaceInstancesOfClassParameters()
PRAGMA_ENABLE_DEPRECATION_WARNINGS
);
/** Replace all instances of OldClass with a new instance of NewClass */
static UNREALED_API void ReplaceInstancesOfClass(UClass* OldClass, UClass* NewClass, const FReplaceInstancesOfClassParameters& Params);
/** Batch replaces a mapping of one or more classes to their new class */
static UNREALED_API void BatchReplaceInstancesOfClass(const TMap<UClass*, UClass*>& InOldToNewClassMap, const FReplaceInstancesOfClassParameters& Params);
/** Function used to safely discard a CDO, so that the class can have its layout changed, callers must move parent CDOs aside before moving child CDOs aside: */
static UNREALED_API UClass* MoveCDOToNewClass(UClass* OwnerClass, const TMap<UClass*, UClass*>& OldToNewMap, bool bAvoidCDODuplication);
/**
* Moves CDOs aside to immutable versions of classes(`REINST`) so that the CDO's can safely be GC'd.
* These `REINST` classes will be re-parented to a native parent that we know will not be churning
* through this function again later, so we avoid O(N^2) processing of REINST classes.
* Maps each given `SKEL` class to its appropriate `REINST` version of itself
*/
static UNREALED_API void MoveDependentSkelToReinst(UClass* OwnerClass, TMap<UClass*, UClass*>& OldToNewMap);
/** Gathers the full class Hierarchy of the ClassToSearch, sorted top down (0 index being UObject, n being the subclasses) */
static UNREALED_API void GetSortedClassHierarchy(UClass* ClassToSearch, TArray<UClass*>& OutHierarchy, UClass** OutNativeParent);
/** Returns true if the given class is a REINST class (starts with the 'REINST_' prefix) */
static UNREALED_API bool IsReinstClass(const UClass* Class);
/**
* When re-instancing a component, we have to make sure all instance owners'
* construction scripts are re-ran (in-case modifying the component alters
* the construction of the actor).
*
* @param ComponentClass Identifies the component that was altered (used to find all its instances, and thusly all instance owners).
*/
static UNREALED_API void ReconstructOwnerInstances(TSubclassOf<UActorComponent> ComponentClass);
/** Verify that all instances of the duplicated class have been replaced and collected */
UNREALED_API void VerifyReplacement();
virtual bool IsClassObjectReplaced() const { return false; }
UNREALED_API void FinalizeFastReinstancing(TArray<UObject*>& ObjectsToReplace);
protected:
UNREALED_API TSharedPtr<FReinstanceFinalizer> ReinstanceInner(bool bForceAlwaysReinstance);
UNREALED_API TSharedPtr<FReinstanceFinalizer> ReinstanceFast();
UNREALED_API void CompileChildren();
bool IsReinstancingSkeleton() const { return (ReinstClassType == RCT_BpSkeleton); }
/** Default constructor, can only be used by derived classes */
FBlueprintCompileReinstancer()
: ClassToReinstance(NULL)
, DuplicatedClass(nullptr)
, OriginalCDO(nullptr)
, bHasReinstanced(false)
, ReinstClassType(RCT_Unknown)
, ClassToReinstanceDefaultValuesCRC(0)
, bIsRootReinstancer(false)
{}
/**
* Sets the reinstancer up to work on every object of the specified class
*
* @param InClassToReinstance Class being reinstanced
* @param bIsBytecodeOnly TRUE if only the bytecode is being recompiled
* @param bSkipGC TRUE if garbage collection should be skipped
* @param bAutoInferSaveOnCompile TRUE if the reinstancer should infer whether or not save on compile should occur, FALSE if it should never save on compile
*/
UNREALED_API FBlueprintCompileReinstancer(UClass* InClassToReinstance, EBlueprintCompileReinstancerFlags Flags = EBlueprintCompileReinstancerFlags::AutoInferSaveOnCompile);
/** Reparents the specified blueprint or class to be the duplicated class in order to allow properties to be copied from the previous CDO to the new one */
UNREALED_API void ReparentChild(UBlueprint* ChildBP);
UNREALED_API void ReparentChild(UClass* ChildClass);
/** Determine whether reinstancing actors should preserve the root component of the new actor */
virtual bool ShouldPreserveRootComponentOfReinstancedActor() const { return true; }
public:
/**
* Attempts to copy as many properties as possible from the old object to the new.
* Use during BP compilation to copy properties from the old CDO to the new one.
*
* @param OldObject The old object to copy properties from
* @param NewObject The new Object to copy properties to
* @param bClearExternalReferences If true then attempt to replace references to old classes and instances on this object with the corresponding new ones
* @param bForceDeltaSerialization If true the delta serialization will be used when copying
* @param bOnlyHandleDirectSubObjects If true will only copy/handle immediate subobjects
* @param OldToNewInstanceMap If != null it will be used to replace any references found in this object
* @param OldToNewClassMap if != null it will be used to replace any class references found in this object
*/
static UNREALED_API void CopyPropertiesForUnrelatedObjects(UObject* OldObject, UObject* NewObject, bool bClearExternalReferences, bool bForceDeltaSerialization = false, bool bOnlyHandleDirectSubObjects = false, TMap<UObject*, UObject*>* OldToNewInstanceMap =nullptr, const TMap<UClass*,UClass*>* OldToNewClassMap =nullptr);
/**
* This method will pre-create all non-default sub object needed for a re-instantiation, what is left is to CopyPropertiesForUnrelatedObjects on the created instances map to finish the re-instancing
* If the re-instancing is done in a big batch and part to the sub object might already be re-instantiated, you will need to provide those via the OldToNewInstanceMap
*
* @param OldToNewClassMap in case there are subobject that will need new class type
* @param OldObject to pre-create its non-default sub objects
* @param NewUObject where to store those pre-created sub objects
* @param CreatedInstanceMap in/out of the result of all of the pre-created objects
* @param OldToNewInstanceMap optional parameter of the possible re-instanced sub objects if any
* @param OrderedListOfObjectToCopy List of object to copy in order
*/
static UNREALED_API void PreCreateSubObjectsForReinstantiation(const TMap<UClass*, UClass*>& OldToNewClassMap, UObject* OldObject, UObject* NewUObject, TMap<UObject*, UObject*>& CreatedInstanceMap, const TMap<UObject*, UObject*>* OldToNewInstanceMap = nullptr, TArray< TTuple<UObject*, UObject*>>* OrderedListOfObjectToCopy = nullptr);
private:
/** Helper to replace an instance of an object*/
static UNREALED_API void ReplaceObjectHelper(UObject*& OldObject, UClass* OldClass, UObject*& NewUObject, UClass* NewClass, TMap<UObject*, UObject*>& OldToNewInstanceMap, const TMap<UClass*, UClass*>& OldToNewClassMap, TMap<UObject*, FName>& OldToNewNameMap, int32 OldObjIndex, TArray<UObject*>& ObjectsToReplace, TArray<UObject*>& PotentialEditorsForRefreshing, TSet<AActor*>& OwnersToRerunConstructionScript, TFunctionRef<TArray<TObjectPtr<USceneComponent>>&(USceneComponent*)> GetAttachChildrenArray, bool bIsComponent, bool bArchetypesAreUpToDate);
/**
* Pre-populate instancing graph for a specified instance if needed
*
* @param InstancingGraph The instancing graph to populate
* @param OldInstance Object that is about to be reallocated
* @param InArchetype Archetype of ObjectInstance
* @param OldToNewInstanceMap Possible instance mapping of instances
* @return false if the instancing graph is not needed
*/
static UNREALED_API bool PrePopulateInstancingGraphForArchetype(FObjectInstancingGraph& InstancingGraph, TNotNull<UObject*> OldInstance, UObject* InArchetype, TMap<UObject*, UObject*>* OldToNewInstanceMap = nullptr);
/** Handles the sub object pre-creation recursively */
static UNREALED_API void PreCreateSubObjectsForReinstantiation_Inner(const TSet<UObject*>* OldInstancedSubObjects, const TMap<UClass*, UClass*>& OldToNewClassMap, UObject* OldObject, UObject* NewUObject, TMap<UObject*, UObject*>& CreatedInstanceMap, const TMap<UObject*, UObject*>* OldToNewInstanceMap, TArray< TTuple<UObject*, UObject*>>* OrderedListOfObjectToCopy);
/** Handles the work of ReplaceInstancesOfClass, handling both normal replacement of instances and batch */
static UNREALED_API void ReplaceInstancesOfClass_Inner(const TMap<UClass*, UClass*>& InOldToNewClassMap, const FReplaceInstancesOfClassParameters& Params);
/** Returns true if A is higher up the class hierarchy */
static UNREALED_API bool ReinstancerOrderingFunction(UClass* A, UClass* B);
/** Helper for caching archetypes before objects are reconstructed */
static UNREALED_API void CacheArchetypes(
const TArray<UObject*>& ObjectsToReplace,
FEditorCacheArchetypeManager& CacheManager,
TSet<UObject*>& CachedArchetypeObjects );
/** Returns a hash identifying the result of constructing an instance of the provided Class */
static UNREALED_API FBlake3Hash CalculateConstructionHash(const UClass* ForClass);
/** Returns the above, but only if there are instances of the class that would require reinstancing */
static UNREALED_API FBlake3Hash CalculateConstructionHashIfNecessary(const UClass* ForClass);
struct FReinstancingState
{
TArray<UObject*>& ObjectsToReplaceScratch;
TSet<UObject*>& CachedArchetypeObjects;
TMap<UObject*, UObject*>& OldToNewInstanceMap;
const TMap<UClass*, UClass*>& InOldToNewClassMap;
TMap<UObject*, FName>& OldToNewNameMap;
TMap<UClass*, TMap<UObject*, UObject*>>* OldToNewTemplates = nullptr;
TArray<UObject*>& PotentialEditorsForRefreshing;
TSet<AActor*>& OwnersToRerunConstructionScript;
TArray<struct FActorReplacementHelper>& ReplacementActors;
TMap<FSoftObjectPath, UObject*>& ReinstancedObjectsWeakReferenceMap;
TMap<TObjectPtr<const UClass>, FBlake3Hash>& ConstructionVersioningData;
TArray<UObject*>& ObjectsReplaced;
TSet<UPackage*>& CleanPackageList;
bool& bSelectionChanged;
bool& bFixupSCS;
};
static void ReinstanceWithVersioningData(const FReinstancingState& ReinstancingState);
static void CategorizeClassesAndInstances(
const TMap<UClass*, UClass*>& InOldToNewClassMap
, TMap<UClass*, UClass*>& UnchangedClasses
, TMap<UClass*, UClass*>& NormalReinstancingClasses
, TMap<UClass*, UClass*>& ActiveActorReinstancingClasses
, const FReinstancingState& ReinstancingState);
static void RestoreClass(TMap<UClass*, UClass*>& OldToNewClassMap, const FReinstancingState& ReinstancingState);
static void ReinstanceNormalObjects(TMap<UClass*, UClass*>& OldToNewClassMap, const FReinstancingState& ReinstancingState);
static void ReinstanceActors(TMap<UClass*, UClass*>& OldToNewClassMap, const FReinstancingState& ReinstancingState);
static void GetObjectsToReinstance(const UClass* OldClass, TArray<UObject*>& ObjectsToReplace);
static void UpdateObjectBeingDebugged(UObject* InOldObject, UObject* InNewObject);
static void CheckAndSaveOuterPackageToCleanList(TSet<UPackage*>& CleanPackageList, const UObject* InObject);
static void CheckAndSaveAttachedActorsOuterPackageToCleanList(TSet<UPackage*>& CleanPackageList, const AActor* Actor);
static bool ShouldReinstanceObject(
const UObject* OldObject, const TMap<UClass*, UClass*>& InOldToNewClassMap, const bool bIsComponent);
static bool ShouldReinstanceActor(const AActor* OldActor);
};