You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- CDO property values that have changed after hot-reload will now be propagated to the existing instances - Adding code to a non-code project will no longer require restarting the editor to be able to work with new code (the new module will automatically be compiled and loaded) [CL 2385307 by Robert Manuszewski in Main branch]
342 lines
12 KiB
C++
342 lines
12 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#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;
|
|
NewClass = InNewClass;
|
|
|
|
// Collect the original CDO property values
|
|
SerializeCDOProperties(InOldClass->GetDefaultObject(), OriginalCDOProperties);
|
|
// Collect the property values of the new CDO
|
|
SerializeCDOProperties(InNewClass->GetDefaultObject(), ReconstructedCDOProperties);
|
|
|
|
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 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);
|
|
}
|
|
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;
|
|
}
|
|
|
|
void FHotReloadClassReinstancer::SerializeCDOProperties(UObject* InObject, FHotReloadClassReinstancer::FCDOPropertyData& OutData)
|
|
{
|
|
// Creates a mem-comparable CDO data
|
|
class FCDOWriter : public FMemoryWriter
|
|
{
|
|
/** Objects already visited by this archive */
|
|
TSet<UObject*>& VisitedObjects;
|
|
FCDOPropertyData& PropertyData;
|
|
|
|
public:
|
|
/** Serializes all script properties of the provided DefaultObject */
|
|
FCDOWriter(FCDOPropertyData& InOutData, UObject* DefaultObject, TSet<UObject*>& InVisitedObjects)
|
|
: FMemoryWriter(InOutData.Bytes, /* bIsPersistent = */ false, /* bSetOffset = */ true)
|
|
, VisitedObjects(InVisitedObjects)
|
|
, PropertyData(InOutData)
|
|
{
|
|
// Disable delta serialization, we want to serialize everything
|
|
ArNoDelta = true;
|
|
DefaultObject->SerializeScriptProperties(*this);
|
|
}
|
|
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;
|
|
PropertyInfo.SerializedValueOffset = Tell();
|
|
PropertyInfo.SerializedValueSize = Num;
|
|
PropertyData.Properties.Add(SerializedProperty->GetFName(), PropertyInfo);
|
|
}
|
|
else
|
|
{
|
|
PropertyInfo.SerializedValueSize += Num;
|
|
}
|
|
}
|
|
FMemoryWriter::Serialize(Data, Num);
|
|
}
|
|
/** 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;
|
|
if (!VisitedObjects.Contains(InObj))
|
|
{
|
|
VisitedObjects.Add(InObj);
|
|
if (Ar.GetSerializedProperty() && Ar.GetSerializedProperty()->ContainsInstancedObjectProperty())
|
|
{
|
|
// Serialize all DSO properties too
|
|
FCDOWriter DefaultSubobjectWriter(PropertyData, InObj, VisitedObjects);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FName UnusedName = NAME_None;
|
|
Ar << UnusedName;
|
|
Ar << UnusedName;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
/** Serializes an FName as its index and number */
|
|
virtual FArchive& operator<<(FName& InName) override
|
|
{
|
|
FArchive& Ar = *this;
|
|
NAME_INDEX ComparisonIndex = InName.GetComparisonIndex();
|
|
NAME_INDEX DisplayIndex = InName.GetDisplayIndex();
|
|
int32 Number = InName.GetNumber();
|
|
Ar << ComparisonIndex;
|
|
Ar << DisplayIndex;
|
|
Ar << Number;
|
|
return Ar;
|
|
}
|
|
virtual FArchive& operator<<(FLazyObjectPtr& LazyObjectPtr) override
|
|
{
|
|
FArchive& Ar = *this;
|
|
auto UniqueID = LazyObjectPtr.GetUniqueID();
|
|
Ar << UniqueID;
|
|
return *this;
|
|
}
|
|
virtual FArchive& operator<<(FAssetPtr& AssetPtr) override
|
|
{
|
|
FArchive& Ar = *this;
|
|
auto UniqueID = AssetPtr.GetUniqueID();
|
|
Ar << UniqueID;
|
|
return Ar;
|
|
}
|
|
virtual FArchive& operator<<(FStringAssetReference& Value) override
|
|
{
|
|
FArchive& Ar = *this;
|
|
Ar << Value.AssetLongPathname;
|
|
return Ar;
|
|
}
|
|
/** Archive name, for debugging */
|
|
virtual FString GetArchiveName() const override { return TEXT("FCDOWriter"); }
|
|
};
|
|
TSet<UObject*> VisitedObjects;
|
|
VisitedObjects.Add(InObject);
|
|
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;
|
|
(*InOldClass->ClassConstructor)(FObjectInitializer(OldCDO, ParentDefaultObject, bCopyTransientsFromClassDefaults, bShouldInitilizeProperties));
|
|
}
|
|
|
|
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;
|
|
NewClass = InOldClass; // The class doesn't change in this case
|
|
|
|
// 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
|
|
if (DefaultPropertiesHaveChanged())
|
|
{
|
|
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)
|
|
: NewClass(nullptr)
|
|
, bNeedsReinstancing(false)
|
|
{
|
|
// 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.
|
|
DuplicatedClass = NULL;
|
|
}
|
|
|
|
void FHotReloadClassReinstancer::UpdateDefaultProperties()
|
|
{
|
|
struct FPropertyToUpdate
|
|
{
|
|
UProperty* Property;
|
|
uint8* OldSerializedValuePtr;
|
|
uint8* NewValuePtr;
|
|
int64 OldSerializedSize;
|
|
};
|
|
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;
|
|
if (NewPropertyInfo.Property->GetOuter() == NewClass)
|
|
{
|
|
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)
|
|
{
|
|
// Property value has changed so add it to the list of properties that need updating on instances
|
|
FPropertyToUpdate PropertyToUpdate;
|
|
PropertyToUpdate.Property = NewPropertyInfo.Property;
|
|
PropertyToUpdate.OldSerializedValuePtr = OldSerializedValuePtr;
|
|
PropertyToUpdate.NewValuePtr = PropertyToUpdate.Property->ContainerPtrToValuePtr<uint8>(NewClass->GetDefaultObject());
|
|
PropertyToUpdate.OldSerializedSize = OldPropertyInfo->SerializedValueSize;
|
|
PropertiesToUpdate.Add(PropertyToUpdate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (PropertiesToUpdate.Num())
|
|
{
|
|
TArray<uint8> CurrentValueSerializedData;
|
|
|
|
// Update properties on all existing instances of the class
|
|
for (FObjectIterator It(NewClass); It; ++It)
|
|
{
|
|
UObject* ObjectPtr = *It;
|
|
for (auto& PropertyToUpdate : PropertiesToUpdate)
|
|
{
|
|
uint8* InstanceValuePtr = PropertyToUpdate.Property->ContainerPtrToValuePtr<uint8>(ObjectPtr);
|
|
|
|
// 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());
|
|
FMemoryWriter CurrentValueWriter(CurrentValueSerializedData);
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FHotReloadClassReinstancer::ReinstanceObjectsAndUpdateDefaults()
|
|
{
|
|
ReinstanceObjects();
|
|
UpdateDefaultProperties();
|
|
}
|
|
|
|
#endif |