2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-09-17 04:34:40 -04:00
# include "HotReloadPrivatePCH.h"
# include "HotReloadClassReinstancer.h"
# if WITH_ENGINE
void FHotReloadClassReinstancer : : SetupNewClassReinstancing ( UClass * InNewClass , UClass * InOldClass )
{
// Set base class members to valid values
ClassToReinstance = InNewClass ;
DuplicatedClass = InOldClass ;
OriginalCDO = InOldClass - > GetDefaultObject ( ) ;
bHasReinstanced = false ;
bSkipGarbageCollection = false ;
bNeedsReinstancing = true ;
2014-12-11 06:03:58 -05:00
NewClass = InNewClass ;
// Collect the original CDO property values
SerializeCDOProperties ( InOldClass - > GetDefaultObject ( ) , OriginalCDOProperties ) ;
// Collect the property values of the new CDO
SerializeCDOProperties ( InNewClass - > GetDefaultObject ( ) , ReconstructedCDOProperties ) ;
2014-09-17 04:34:40 -04:00
SaveClassFieldMapping ( InOldClass ) ;
2015-02-04 10:13:13 -05:00
ObjectsThatShouldUseOldStuff . Add ( InOldClass ) ; //CDO of REINST_ class can be used as archetype
2014-09-17 04:34:40 -04:00
TArray < UClass * > ChildrenOfClass ;
GetDerivedClasses ( InOldClass , ChildrenOfClass ) ;
for ( auto ClassIt = ChildrenOfClass . CreateConstIterator ( ) ; ClassIt ; + + ClassIt )
{
UClass * ChildClass = * ClassIt ;
UBlueprint * ChildBP = Cast < UBlueprint > ( ChildClass - > ClassGeneratedBy ) ;
if ( ChildBP & & ! ChildBP - > HasAnyFlags ( RF_BeingRegenerated ) )
{
// If this is a direct child, change the parent and relink so the property chain is valid for reinstancing
if ( ! ChildBP - > HasAnyFlags ( RF_NeedLoad ) )
{
if ( ChildClass - > GetSuperClass ( ) = = InOldClass )
{
ReparentChild ( ChildBP ) ;
}
Children . AddUnique ( ChildBP ) ;
2015-02-04 10:54:51 -05:00
if ( ChildBP - > ParentClass = = InOldClass )
{
ChildBP - > ParentClass = NewClass ;
}
2014-09-17 04:34:40 -04:00
}
else
{
// If this is a child that caused the load of their parent, relink to the REINST class so that we can still serialize in the CDO, but do not add to later processing
ReparentChild ( ChildClass ) ;
}
}
}
// Finally, remove the old class from Root so that it can get GC'd and mark it as CLASS_NewerVersionExists
InOldClass - > RemoveFromRoot ( ) ;
InOldClass - > ClassFlags | = CLASS_NewerVersionExists ;
}
2014-12-11 06:03:58 -05:00
void FHotReloadClassReinstancer : : SerializeCDOProperties ( UObject * InObject , FHotReloadClassReinstancer : : FCDOPropertyData & OutData )
2014-09-17 04:34:40 -04:00
{
// Creates a mem-comparable CDO data
class FCDOWriter : public FMemoryWriter
{
/** Objects already visited by this archive */
TSet < UObject * > & VisitedObjects ;
2015-01-23 08:34:49 -05:00
/** Output property data */
2014-12-11 06:03:58 -05:00
FCDOPropertyData & PropertyData ;
2015-01-23 08:34:49 -05:00
/** Current subobject being serialized */
FName SubobjectName ;
2014-09-17 04:34:40 -04:00
public :
/** Serializes all script properties of the provided DefaultObject */
2015-01-23 08:34:49 -05:00
FCDOWriter ( FCDOPropertyData & InOutData , UObject * DefaultObject , TSet < UObject * > & InVisitedObjects , FName InSubobjectName = NAME_None )
2014-12-11 06:03:58 -05:00
: FMemoryWriter ( InOutData . Bytes , /* bIsPersistent = */ false , /* bSetOffset = */ true )
2014-09-17 04:34:40 -04:00
, VisitedObjects ( InVisitedObjects )
2014-12-11 06:03:58 -05:00
, PropertyData ( InOutData )
2015-01-23 08:34:49 -05:00
, SubobjectName ( InSubobjectName )
2014-09-17 04:34:40 -04:00
{
2014-12-11 06:03:58 -05:00
// Disable delta serialization, we want to serialize everything
ArNoDelta = true ;
2014-09-17 04:34:40 -04:00
DefaultObject - > SerializeScriptProperties ( * this ) ;
}
2014-12-11 06:03:58 -05:00
virtual void Serialize ( void * Data , int64 Num ) override
{
// Collect serialized properties so we can later update their values on instances if they change
auto SerializedProperty = GetSerializedProperty ( ) ;
if ( SerializedProperty ! = nullptr )
{
FCDOProperty & PropertyInfo = PropertyData . Properties . FindOrAdd ( SerializedProperty - > GetFName ( ) ) ;
if ( PropertyInfo . Property = = nullptr )
{
PropertyInfo . Property = SerializedProperty ;
2015-01-23 08:34:49 -05:00
PropertyInfo . SubobjectName = SubobjectName ;
2014-12-11 06:03:58 -05:00
PropertyInfo . SerializedValueOffset = Tell ( ) ;
PropertyInfo . SerializedValueSize = Num ;
PropertyData . Properties . Add ( SerializedProperty - > GetFName ( ) , PropertyInfo ) ;
}
else
{
PropertyInfo . SerializedValueSize + = Num ;
}
}
FMemoryWriter : : Serialize ( Data , Num ) ;
}
2014-09-17 04:34:40 -04:00
/** Serializes an object. Only name and class for normal references, deep serialization for DSOs */
virtual FArchive & operator < < ( class UObject * & InObj ) override
{
FArchive & Ar = * this ;
if ( InObj )
{
FName ClassName = InObj - > GetClass ( ) - > GetFName ( ) ;
FName ObjectName = InObj - > GetFName ( ) ;
Ar < < ClassName ;
Ar < < ObjectName ;
2014-10-24 08:09:33 -04:00
if ( ! VisitedObjects . Contains ( InObj ) )
2014-09-17 04:34:40 -04:00
{
VisitedObjects . Add ( InObj ) ;
2014-10-30 09:52:57 -04:00
if ( Ar . GetSerializedProperty ( ) & & Ar . GetSerializedProperty ( ) - > ContainsInstancedObjectProperty ( ) )
2014-10-24 08:09:33 -04:00
{
// Serialize all DSO properties too
2015-01-23 08:34:49 -05:00
FCDOWriter DefaultSubobjectWriter ( PropertyData , InObj , VisitedObjects , InObj - > GetFName ( ) ) ;
2015-02-04 06:43:42 -05:00
Seek ( PropertyData . Bytes . Num ( ) ) ;
2014-10-24 08:09:33 -04:00
}
2014-09-17 04:34:40 -04:00
}
}
else
{
FName UnusedName = NAME_None ;
Ar < < UnusedName ;
Ar < < UnusedName ;
}
return * this ;
}
2014-09-17 06:20:17 -04:00
/** Serializes an FName as its index and number */
2014-09-17 04:34:40 -04:00
virtual FArchive & operator < < ( FName & InName ) override
{
FArchive & Ar = * this ;
2014-09-17 06:20:17 -04:00
NAME_INDEX ComparisonIndex = InName . GetComparisonIndex ( ) ;
NAME_INDEX DisplayIndex = InName . GetDisplayIndex ( ) ;
2014-09-17 04:34:40 -04:00
int32 Number = InName . GetNumber ( ) ;
2014-09-17 06:20:17 -04:00
Ar < < ComparisonIndex ;
Ar < < DisplayIndex ;
2014-09-17 04:34:40 -04:00
Ar < < Number ;
return Ar ;
}
virtual FArchive & operator < < ( FLazyObjectPtr & LazyObjectPtr ) override
{
FArchive & Ar = * this ;
2014-10-30 09:53:23 -04:00
auto UniqueID = LazyObjectPtr . GetUniqueID ( ) ;
Ar < < UniqueID ;
2014-09-17 04:34:40 -04:00
return * this ;
}
virtual FArchive & operator < < ( FAssetPtr & AssetPtr ) override
{
FArchive & Ar = * this ;
2014-10-30 09:53:23 -04:00
auto UniqueID = AssetPtr . GetUniqueID ( ) ;
Ar < < UniqueID ;
2014-09-17 04:34:40 -04:00
return Ar ;
}
virtual FArchive & operator < < ( FStringAssetReference & Value ) override
{
FArchive & Ar = * this ;
2014-10-30 09:53:23 -04:00
Ar < < Value . AssetLongPathname ;
2014-09-17 04:34:40 -04:00
return Ar ;
}
/** Archive name, for debugging */
virtual FString GetArchiveName ( ) const override { return TEXT ( " FCDOWriter " ) ; }
} ;
TSet < UObject * > VisitedObjects ;
2014-10-24 08:09:33 -04:00
VisitedObjects . Add ( InObject ) ;
2014-09-17 04:34:40 -04:00
FCDOWriter Ar ( OutData , InObject , VisitedObjects ) ;
}
void FHotReloadClassReinstancer : : ReconstructClassDefaultObject ( UClass * InOldClass )
{
// Remember all the basic info about the object before we destroy it
UObject * OldCDO = InOldClass - > GetDefaultObject ( ) ;
EObjectFlags CDOFlags = OldCDO - > GetFlags ( ) ;
UObject * CDOOuter = OldCDO - > GetOuter ( ) ;
FName CDOName = OldCDO - > GetFName ( ) ;
// Get the parent CDO
UClass * ParentClass = InOldClass - > GetSuperClass ( ) ;
UObject * ParentDefaultObject = NULL ;
if ( ParentClass ! = NULL )
{
ParentDefaultObject = ParentClass - > GetDefaultObject ( ) ; // Force the default object to be constructed if it isn't already
}
if ( ! OldCDO - > HasAnyFlags ( RF_FinishDestroyed ) )
{
// Begin the asynchronous object cleanup.
OldCDO - > ConditionalBeginDestroy ( ) ;
// Wait for the object's asynchronous cleanup to finish.
while ( ! OldCDO - > IsReadyForFinishDestroy ( ) )
{
FPlatformProcess : : Sleep ( 0 ) ;
}
// Finish destroying the object.
OldCDO - > ConditionalFinishDestroy ( ) ;
}
OldCDO - > ~ UObject ( ) ;
// Re-create
FMemory : : Memzero ( ( void * ) OldCDO , InOldClass - > GetPropertiesSize ( ) ) ;
new ( ( void * ) OldCDO ) UObjectBase ( InOldClass , CDOFlags , CDOOuter , CDOName ) ;
const bool bShouldInitilizeProperties = false ;
const bool bCopyTransientsFromClassDefaults = false ;
2014-10-14 10:29:11 -04:00
( * InOldClass - > ClassConstructor ) ( FObjectInitializer ( OldCDO , ParentDefaultObject , bCopyTransientsFromClassDefaults , bShouldInitilizeProperties ) ) ;
2014-09-17 04:34:40 -04:00
}
void FHotReloadClassReinstancer : : RecreateCDOAndSetupOldClassReinstancing ( UClass * InOldClass )
{
// Set base class members to valid values
ClassToReinstance = InOldClass ;
DuplicatedClass = InOldClass ;
OriginalCDO = InOldClass - > GetDefaultObject ( ) ;
bHasReinstanced = false ;
bSkipGarbageCollection = false ;
bNeedsReinstancing = false ;
2014-12-11 06:03:58 -05:00
NewClass = InOldClass ; // The class doesn't change in this case
2014-09-17 04:34:40 -04:00
// Collect the original property values
SerializeCDOProperties ( InOldClass - > GetDefaultObject ( ) , OriginalCDOProperties ) ;
// Destroy and re-create the CDO, re-running its constructor
ReconstructClassDefaultObject ( InOldClass ) ;
// Collect the property values after re-constructing the CDO
SerializeCDOProperties ( InOldClass - > GetDefaultObject ( ) , ReconstructedCDOProperties ) ;
// We only want to re-instance the old class if its CDO's values have changed or any of its DSOs' property values have changed
2014-12-11 06:03:58 -05:00
if ( DefaultPropertiesHaveChanged ( ) )
2014-09-17 04:34:40 -04:00
{
bNeedsReinstancing = true ;
SaveClassFieldMapping ( InOldClass ) ;
TArray < UClass * > ChildrenOfClass ;
GetDerivedClasses ( InOldClass , ChildrenOfClass ) ;
for ( auto ClassIt = ChildrenOfClass . CreateConstIterator ( ) ; ClassIt ; + + ClassIt )
{
UClass * ChildClass = * ClassIt ;
UBlueprint * ChildBP = Cast < UBlueprint > ( ChildClass - > ClassGeneratedBy ) ;
if ( ChildBP & & ! ChildBP - > HasAnyFlags ( RF_BeingRegenerated ) )
{
if ( ! ChildBP - > HasAnyFlags ( RF_NeedLoad ) )
{
Children . AddUnique ( ChildBP ) ;
}
}
}
}
}
FHotReloadClassReinstancer : : FHotReloadClassReinstancer ( UClass * InNewClass , UClass * InOldClass )
2014-12-11 06:03:58 -05:00
: NewClass ( nullptr )
, bNeedsReinstancing ( false )
2014-09-17 04:34:40 -04:00
{
// If InNewClass is NULL, then the old class has not changed after hot-reload.
// However, we still need to check for changes to its constructor code (CDO values).
if ( InNewClass )
{
SetupNewClassReinstancing ( InNewClass , InOldClass ) ;
}
else
{
RecreateCDOAndSetupOldClassReinstancing ( InOldClass ) ;
}
}
FHotReloadClassReinstancer : : ~ FHotReloadClassReinstancer ( )
{
// Make sure the base class does not remove the DuplicatedClass from root, we not always want it.
// For example when we're just reconstructing CDOs. Other cases are handled by HotReloadClassReinstancer.
2015-01-23 08:34:49 -05:00
DuplicatedClass = nullptr ;
}
/** Helper for finding subobject in an array. Usually there's not that many subobjects on a class to justify a TMap */
FORCEINLINE static UObject * FindDefaultSubobject ( TArray < UObject * > & InDefaultSubobjects , FName SubobjectName )
{
for ( auto Subobject : InDefaultSubobjects )
{
if ( Subobject - > GetFName ( ) = = SubobjectName )
{
return Subobject ;
}
}
return nullptr ;
2014-09-17 04:34:40 -04:00
}
2014-12-11 06:03:58 -05:00
void FHotReloadClassReinstancer : : UpdateDefaultProperties ( )
{
struct FPropertyToUpdate
{
UProperty * Property ;
2015-01-23 08:34:49 -05:00
FName SubobjectName ;
2014-12-11 06:03:58 -05:00
uint8 * OldSerializedValuePtr ;
uint8 * NewValuePtr ;
int64 OldSerializedSize ;
} ;
2015-02-06 02:46:08 -05:00
/** Memory writer archive that supports UObject values the same way as FCDOWriter. */
class FPropertyValueMemoryWriter : public FMemoryWriter
{
public :
FPropertyValueMemoryWriter ( TArray < uint8 > & OutData )
: FMemoryWriter ( OutData )
{ }
virtual FArchive & operator < < ( class UObject * & InObj ) override
{
FArchive & Ar = * this ;
if ( InObj )
{
FName ClassName = InObj - > GetClass ( ) - > GetFName ( ) ;
FName ObjectName = InObj - > GetFName ( ) ;
Ar < < ClassName ;
Ar < < ObjectName ;
}
else
{
FName UnusedName = NAME_None ;
Ar < < UnusedName ;
Ar < < UnusedName ;
}
return * this ;
}
} ;
2015-01-23 08:34:49 -05:00
// Collect default subobjects to update their properties too
const int32 DefaultSubobjectArrayCapacity = 16 ;
TArray < UObject * > DefaultSubobjectArray ;
DefaultSubobjectArray . Empty ( DefaultSubobjectArrayCapacity ) ;
NewClass - > GetDefaultObject ( ) - > CollectDefaultSubobjects ( DefaultSubobjectArray ) ;
2014-12-11 06:03:58 -05:00
TArray < FPropertyToUpdate > PropertiesToUpdate ;
// Collect all properties that have actually changed
for ( auto & Pair : ReconstructedCDOProperties . Properties )
{
auto OldPropertyInfo = OriginalCDOProperties . Properties . Find ( Pair . Key ) ;
if ( OldPropertyInfo )
{
auto & NewPropertyInfo = Pair . Value ;
2015-01-23 08:34:49 -05:00
uint8 * OldSerializedValuePtr = OriginalCDOProperties . Bytes . GetData ( ) + OldPropertyInfo - > SerializedValueOffset ;
uint8 * NewSerializedValuePtr = ReconstructedCDOProperties . Bytes . GetData ( ) + NewPropertyInfo . SerializedValueOffset ;
if ( OldPropertyInfo - > SerializedValueSize ! = NewPropertyInfo . SerializedValueSize | |
FMemory : : Memcmp ( OldSerializedValuePtr , NewSerializedValuePtr , OldPropertyInfo - > SerializedValueSize ) ! = 0 )
2014-12-11 06:03:58 -05:00
{
2015-01-23 08:34:49 -05:00
// Property value has changed so add it to the list of properties that need updating on instances
FPropertyToUpdate PropertyToUpdate ;
PropertyToUpdate . Property = NewPropertyInfo . Property ;
PropertyToUpdate . NewValuePtr = nullptr ;
PropertyToUpdate . SubobjectName = NewPropertyInfo . SubobjectName ;
if ( NewPropertyInfo . Property - > GetOuter ( ) = = NewClass )
2014-12-11 06:03:58 -05:00
{
PropertyToUpdate . NewValuePtr = PropertyToUpdate . Property - > ContainerPtrToValuePtr < uint8 > ( NewClass - > GetDefaultObject ( ) ) ;
}
2015-01-23 08:34:49 -05:00
else if ( NewPropertyInfo . SubobjectName ! = NAME_None )
{
UObject * DefaultSubobjectPtr = FindDefaultSubobject ( DefaultSubobjectArray , NewPropertyInfo . SubobjectName ) ;
if ( DefaultSubobjectPtr & & NewPropertyInfo . Property - > GetOuter ( ) = = DefaultSubobjectPtr - > GetClass ( ) )
{
PropertyToUpdate . NewValuePtr = PropertyToUpdate . Property - > ContainerPtrToValuePtr < uint8 > ( DefaultSubobjectPtr ) ;
}
}
if ( PropertyToUpdate . NewValuePtr )
{
PropertyToUpdate . OldSerializedValuePtr = OldSerializedValuePtr ;
PropertyToUpdate . OldSerializedSize = OldPropertyInfo - > SerializedValueSize ;
PropertiesToUpdate . Add ( PropertyToUpdate ) ;
}
2014-12-11 06:03:58 -05:00
}
}
}
if ( PropertiesToUpdate . Num ( ) )
{
TArray < uint8 > CurrentValueSerializedData ;
// Update properties on all existing instances of the class
for ( FObjectIterator It ( NewClass ) ; It ; + + It )
{
UObject * ObjectPtr = * It ;
2015-01-23 08:34:49 -05:00
DefaultSubobjectArray . Empty ( DefaultSubobjectArrayCapacity ) ;
ObjectPtr - > CollectDefaultSubobjects ( DefaultSubobjectArray ) ;
2014-12-11 06:03:58 -05:00
for ( auto & PropertyToUpdate : PropertiesToUpdate )
{
2015-01-23 08:34:49 -05:00
uint8 * InstanceValuePtr = nullptr ;
if ( PropertyToUpdate . SubobjectName = = NAME_None )
2014-12-11 06:03:58 -05:00
{
2015-01-23 08:34:49 -05:00
InstanceValuePtr = PropertyToUpdate . Property - > ContainerPtrToValuePtr < uint8 > ( ObjectPtr ) ;
}
else
{
UObject * DefaultSubobjectPtr = FindDefaultSubobject ( DefaultSubobjectArray , PropertyToUpdate . SubobjectName ) ;
if ( DefaultSubobjectPtr & & PropertyToUpdate . Property - > GetOuter ( ) = = DefaultSubobjectPtr - > GetClass ( ) )
{
InstanceValuePtr = PropertyToUpdate . Property - > ContainerPtrToValuePtr < uint8 > ( DefaultSubobjectPtr ) ;
}
}
if ( InstanceValuePtr )
{
// Serialize current value to a byte array as we don't have the previous CDO to compare against, we only have its serialized property data
CurrentValueSerializedData . Empty ( CurrentValueSerializedData . Num ( ) + CurrentValueSerializedData . GetSlack ( ) ) ;
2015-02-06 02:46:08 -05:00
FPropertyValueMemoryWriter CurrentValueWriter ( CurrentValueSerializedData ) ;
2015-01-23 08:34:49 -05:00
PropertyToUpdate . Property - > SerializeItem ( CurrentValueWriter , InstanceValuePtr ) ;
// Update only when the current value on the instance is identical to the original CDO
if ( CurrentValueSerializedData . Num ( ) = = PropertyToUpdate . OldSerializedSize & &
FMemory : : Memcmp ( CurrentValueSerializedData . GetData ( ) , PropertyToUpdate . OldSerializedValuePtr , CurrentValueSerializedData . Num ( ) ) = = 0 )
{
// Update with the new value
PropertyToUpdate . Property - > CopyCompleteValue ( InstanceValuePtr , PropertyToUpdate . NewValuePtr ) ;
}
2014-12-11 06:03:58 -05:00
}
}
}
}
}
void FHotReloadClassReinstancer : : ReinstanceObjectsAndUpdateDefaults ( )
{
ReinstanceObjects ( ) ;
UpdateDefaultProperties ( ) ;
}
2014-09-17 04:34:40 -04:00
# endif