EM_Task/UnrealEd/Public/Kismet2/KismetReinstanceUtilities.h
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

259 lines
12 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;
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)
class FReinstanceFinalizer;
struct UNREALED_API FRecreateUberGraphFrameScope
{
private:
TArray<UObject*> Objects;
UClass* RecompiledClass;
public:
FRecreateUberGraphFrameScope(UClass* InClass, bool bRecreate);
~FRecreateUberGraphFrameScope();
};
struct UNREALED_API FReplaceInstancesOfClassParameters
{
FReplaceInstancesOfClassParameters(UClass* InOldClass, UClass* InNewClass);
/** The old class, instances of which will be replaced */
UClass* OldClass;
/** The new class, used to create new instances to replace instances of the old class */
UClass* NewClass;
/** OriginalCDO, use if OldClass->ClassDefaultObject has been overwritten */
UObject* OriginalCDO;
/** Set of objects that should not have their references updated if they refer to instances that are replaced */
TSet<UObject*>* ObjectsThatShouldUseOldStuff;
/** Set of objects for which new objects should not be created, useful if client has created new instances themself */
const TSet<UObject*>* InstancesThatShouldUseOldClass;
/** Set to true if class object has been replaced*/
bool bClassObjectReplaced;
/** Defaults to true, indicates whether root component should be preserved */
bool bPreserveRootComponent;
};
struct UNREALED_API FBatchReplaceInstancesOfClassParameters
{
TSet<UObject*>* ObjectsThatShouldUseOldStuff = nullptr;
const TSet<UObject*>* InstancesThatShouldUseOldClass = nullptr;
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 UNREALED_API FBlueprintCompileReinstancer: public TSharedFromThis<FBlueprintCompileReinstancer>
, public FGCObject
{
protected:
friend struct FBlueprintCompilationManagerImpl;
friend struct FReinstancingJob;
static TSet<TWeakObjectPtr<UBlueprint>> DependentBlueprintsToRefresh;
static TSet<TWeakObjectPtr<UBlueprint>> CompiledBlueprintsToSave;
static UClass* HotReloadedOldClass;
static 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 */
UClass* DuplicatedClass;
/** The original CDO object for the class being actively reinstanced */
UObject* OriginalCDO;
/** 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, 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
virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
// End of FSerializableObject interface
static void OptionallyRefreshNodes(UBlueprint* BP);
void ListDependentBlueprintsToRefresh(const TArray<UBlueprint*>& DependentBPs);
virtual void EnlistDependentBlueprintToRecompile(UBlueprint* BP, bool bBytecodeOnly);
virtual void BlueprintWasRecompiled(UBlueprint* BP, bool bBytecodeOnly);
static TSharedPtr<FBlueprintCompileReinstancer> Create(UClass* InClassToReinstance, EBlueprintCompileReinstancerFlags Flags = EBlueprintCompileReinstancerFlags::AutoInferSaveOnCompile)
{
return MakeShareable(new FBlueprintCompileReinstancer(InClassToReinstance, Flags));
}
virtual ~FBlueprintCompileReinstancer();
/** Saves a mapping of field names to their UField equivalents, so we can remap any bytecode that references them later */
void SaveClassFieldMapping(UClass* InClassToReinstance);
/** Helper to gather mappings from the old class's fields to the new class's version */
void GenerateFieldMappings(TMap<FFieldVariant, FFieldVariant>& FieldMapping);
/** Reinstances all objects in the ObjectReinstancingMap */
void ReinstanceObjects(bool bForceAlwaysReinstance = false);
/** Updates references to properties and functions of the class that has in the bytecode of dependent blueprints */
void UpdateBytecodeReferences();
/** Worker function to replace all instances of OldClass with a new instance of NewClass */
static void ReplaceInstancesOfClass(UClass* OldClass, UClass* NewClass, UObject* OriginalCDO = nullptr, TSet<UObject*>* ObjectsThatShouldUseOldStuff = nullptr, bool bClassObjectReplaced = false, bool bPreserveRootComponent = true);
static void ReplaceInstancesOfClassEx(const FReplaceInstancesOfClassParameters& Parameters);
/** Batch replaces a mapping of one or more classes to their new class by leveraging ReplaceInstancesOfClass */
static void BatchReplaceInstancesOfClass(TMap<UClass*, UClass*>& InOldToNewClassMap, const FBatchReplaceInstancesOfClassParameters& Options = FBatchReplaceInstancesOfClassParameters());
/** 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 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 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 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 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 void ReconstructOwnerInstances(TSubclassOf<UActorComponent> ComponentClass);
/** Verify that all instances of the duplicated class have been replaced and collected */
void VerifyReplacement();
virtual bool IsClassObjectReplaced() const { return false; }
void FinalizeFastReinstancing(TArray<UObject*>& ObjectsToReplace);
protected:
TSharedPtr<FReinstanceFinalizer> ReinstanceInner(bool bForceAlwaysReinstance);
TSharedPtr<FReinstanceFinalizer> ReinstanceFast();
void CompileChildren();
bool IsReinstancingSkeleton() const { return (ReinstClassType == RCT_BpSkeleton); }
/** Default constructor, can only be used by derived classes */
FBlueprintCompileReinstancer()
: ClassToReinstance(NULL), DuplicatedClass(NULL), OriginalCDO(NULL), 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
*/
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 */
void ReparentChild(UBlueprint* ChildBP);
void ReparentChild(UClass* ChildClass);
/** Determine whether reinstancing actors should preserve the root component of the new actor */
virtual bool ShouldPreserveRootComponentOfReinstancedActor() const { return true; }
/**
* 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
*/
static void CopyPropertiesForUnrelatedObjects(UObject* OldObject, UObject* NewObject, bool bClearExternalReferences, bool bForceDeltaSerialization = false);
private:
/** Handles the work of ReplaceInstancesOfClass, handling both normal replacement of instances and batch */
static void ReplaceInstancesOfClass_Inner(TMap<UClass*, UClass*>& InOldToNewClassMap, UObject* InOriginalCDO, TSet<UObject*>* ObjectsThatShouldUseOldStuff = NULL, bool bClassObjectReplaced = false, bool bPreserveRootComponent = true, bool bArchetypesAreUpToDate = false, const TSet<UObject*>* InstancesThatShouldUseOldClass = nullptr, bool bReplaceReferencesToOldClasses = false);
/** Returns true if A is higher up the class hierarchy */
static bool ReinstancerOrderingFunction(UClass* A, UClass* B);
};