diff --git a/Source/PrefabricatorRuntime/Private/Prefab/PrefabTools.cpp b/Source/PrefabricatorRuntime/Private/Prefab/PrefabTools.cpp index 2283628d..016f9dba 100644 --- a/Source/PrefabricatorRuntime/Private/Prefab/PrefabTools.cpp +++ b/Source/PrefabricatorRuntime/Private/Prefab/PrefabTools.cpp @@ -358,7 +358,9 @@ namespace { if (!InObjToDeserialize) return; for (UPrefabricatorProperty* PrefabProperty : InProperties) { - if (!PrefabProperty || PrefabProperty->bIsCrossReferencedActor) continue; + // If its a struct property, still let us use the value found in PrefabProperty->ExportedValue + // as a starting point, only the object reference will be fixed-up. + if (!PrefabProperty || (PrefabProperty->bIsCrossReferencedActor && !PrefabProperty->bContainsStructProperty)) continue; FString PropertyName = PrefabProperty->PropertyName; if (PropertyName == "AssetUserData") continue; // Skip this as assignment is very slow and is not needed @@ -385,6 +387,140 @@ namespace { } } + FString GetNestedPropertyPath(const FString& PropertyPath, const FProperty* Property, int32 PropertyElementIndex=-1) + { + FString NestedPropertyName = Property->GetName(); + FString NewPropertyPath(PropertyPath); + NewPropertyPath += PropertyElementIndex < 0 ? + FString::Format(TEXT("|{0}"), { NestedPropertyName }) : + FString::Format(TEXT("|{0}[{1}]"), { NestedPropertyName, PropertyElementIndex }); + return NewPropertyPath; + } + + // SerializeObjectProperty method to reuse the code. + bool SerializePreProcessForReference_ObjectHelper( + UObject* ObjPtr + , const void* PropertyValueAddress + , const FPrefabActorLookup& CrossReferences + , UPrefabricatorProperty* PrefabProperty + , const FObjectPropertyBase* ObjProperty + , const FString& PropertyPath + , int32 PropertyElementIndex=-1 + ) + { + UObject* PropertyObjectValue = ObjProperty->GetObjectPropertyValue(PropertyValueAddress); + if (PropertyObjectValue == nullptr || PropertyObjectValue->HasAnyFlags(RF_DefaultSubObject | RF_ArchetypeObject)) { + return false; + } + + FString ObjectPath = PropertyObjectValue->GetPathName(); + FGuid CrossRefPrefabItem; + if (CrossReferences.GetPrefabItemId(ObjectPath, CrossRefPrefabItem)) { + PrefabProperty->bIsCrossReferencedActor = true; + // Obtain property path relative to object provided + FString NewPropertyPath = GetNestedPropertyPath(PropertyPath, ObjProperty, PropertyElementIndex); + + auto NestedPropertyData = PrefabProperty->NestedPropertyData.Find(NewPropertyPath); + if(!NestedPropertyData) + { + NestedPropertyData = &PrefabProperty->NestedPropertyData.Add(NewPropertyPath, FPrefabricatorNestedPropertyData()); + } + + if (ensure(NestedPropertyData)) + { + NestedPropertyData->CrossReferencePrefabActorId = CrossRefPrefabItem; + return true; + } + } + return false; + } + + void SerializePreProcessForReference_StructHelper( + UObject* ObjPtr + , void* ContainerPtr + , const FPrefabActorLookup& CrossReferences + , UPrefabricatorProperty* PrefabProperty + , const FStructProperty* StructProperty + , const FString& PropertyPath + , int32 PropertyElementIndex=-1); + + void SerializePreProcessForReference_ArrayHelper( + UObject* ObjPtr + , void* ContainerPtr + , const FPrefabActorLookup& CrossReferences + , UPrefabricatorProperty* PrefabProperty + , const FArrayProperty* ArrayProperty + , const FString& PropertyPath + , int32 PropertyElementIndex=-1 + ) + { + const FObjectPropertyBase* ObjProperty = CastField(ArrayProperty->Inner); + const FStructProperty* StructProperty = CastField(ArrayProperty->Inner); + + if (ObjProperty || StructProperty) + { + FScriptArrayHelper_InContainer Helper(ArrayProperty, ContainerPtr); + FString NewPropertyPath = GetNestedPropertyPath(PropertyPath, ArrayProperty, PropertyElementIndex); + + FPrefabricatorNestedPropertyData* NestedPropertyData = PrefabProperty->NestedPropertyData.Find(NewPropertyPath); + if (!NestedPropertyData) + { + NestedPropertyData = &PrefabProperty->NestedPropertyData.Add(NewPropertyPath, FPrefabricatorNestedPropertyData()); + } + + NestedPropertyData->ArrayLength = Helper.Num(); + + // TODO: Array of arrays + if (ObjProperty) + { + for (int32 Index = 0; Index < Helper.Num(); Index++) + { + SerializePreProcessForReference_ObjectHelper(ObjPtr, Helper.GetRawPtr(Index), CrossReferences, PrefabProperty, ObjProperty, NewPropertyPath, Index); + } + } + else if (StructProperty) + { + for (int32 Index = 0; Index < Helper.Num(); Index++) + { + SerializePreProcessForReference_StructHelper(ObjPtr, Helper.GetRawPtr(Index), CrossReferences, PrefabProperty, StructProperty, NewPropertyPath, Index); + } + } + } + } + + void SerializePreProcessForReference_StructHelper( + UObject* ObjPtr + , void* ContainerPtr + , const FPrefabActorLookup& CrossReferences + , UPrefabricatorProperty* PrefabProperty + , const FStructProperty* StructProperty + , const FString& PropertyPath + , int32 PropertyElementIndex + ) + { + PrefabProperty->bContainsStructProperty = true; + FString NewPropertyPath = GetNestedPropertyPath(PropertyPath, StructProperty, PropertyElementIndex); + + for (TFieldIterator It(StructProperty->Struct); It; ++It) { + FProperty* InnerProperty = *It; + + if (const FStructProperty* StructProperty = CastField(InnerProperty)) { + void* PropValuePtr = StructProperty->ContainerPtrToValuePtr(ContainerPtr, 0); + SerializePreProcessForReference_StructHelper(ObjPtr, PropValuePtr, CrossReferences, PrefabProperty, StructProperty, NewPropertyPath); + } + // Support for TArrays (TODO: Adds support for sets and maps). + else if (const FArrayProperty* ArrayProperty = CastField(InnerProperty)) + { + SerializePreProcessForReference_ArrayHelper(ObjPtr, ContainerPtr, CrossReferences, PrefabProperty, ArrayProperty, NewPropertyPath); + } + // FObjectPropertyBase instead of FObjectProperty to also support soft references. + else if (const FObjectPropertyBase* ObjProperty = CastField(InnerProperty)) { + void* PropValuePtr = ObjProperty->ContainerPtrToValuePtr(ContainerPtr, 0); + SerializePreProcessForReference_ObjectHelper(ObjPtr, PropValuePtr, CrossReferences, PrefabProperty, ObjProperty, NewPropertyPath); + } + } + } + void SerializeFields(UObject* ObjToSerialize, UObject* ObjTemplate, APrefabActor* PrefabActor, const FPrefabActorLookup& CrossReferences, TArray& OutProperties) { if (!ObjToSerialize || !PrefabActor) { return; @@ -443,28 +579,27 @@ namespace { PrefabProperty = NewObject(PrefabAsset); PrefabProperty->PropertyName = PropertyName; - // Check for cross actor references - bool bFoundCrossReference = false; + FString PropertyPath = ""; - if (const FObjectProperty* ObjProperty = CastField(Property)) { - UObject* PropertyObjectValue = ObjProperty->GetObjectPropertyValue_InContainer(ObjToSerialize); - if (PropertyObjectValue) { - if (PropertyObjectValue->HasAnyFlags(RF_DefaultSubObject | RF_ArchetypeObject)) { - continue; - } - - FString ObjectPath = PropertyObjectValue->GetPathName(); - FGuid CrossRefPrefabItem; - if (CrossReferences.GetPrefabItemId(ObjectPath, CrossRefPrefabItem)) { - PrefabProperty->bIsCrossReferencedActor = true; - PrefabProperty->CrossReferencePrefabActorId = CrossRefPrefabItem; - bFoundCrossReference = true; - } - } + // Support for USTRUCT + if (const FStructProperty* StructProperty = CastField(Property)) { + void* StructPtr = StructProperty->ContainerPtrToValuePtr(ObjToSerialize, 0); + SerializePreProcessForReference_StructHelper(ObjToSerialize, StructPtr, CrossReferences, PrefabProperty, StructProperty, PropertyPath); + } + // Support for TArrays (TODO: Adds support for sets and maps). + else if (const FArrayProperty* ArrayProperty = CastField(Property)) + { + SerializePreProcessForReference_ArrayHelper(ObjToSerialize, ObjToSerialize, CrossReferences, PrefabProperty, ArrayProperty, PropertyPath); + } + // FObjectPropertyBase instead of FObjectProperty to also support soft references. + else if (const FObjectPropertyBase* ObjProperty = CastField(Property)) { + SerializePreProcessForReference_ObjectHelper(ObjToSerialize, ObjProperty->ContainerPtrToValuePtr(ObjToSerialize), CrossReferences, PrefabProperty, ObjProperty, PropertyPath); } - // Save as usual if no cross reference was found - if (!bFoundCrossReference) { + // Save as usual even if cross reference was found + // This is is to support fields present in a struct which have nothing to do with objects + if (!PrefabProperty->bIsCrossReferencedActor || PrefabProperty->bContainsStructProperty) + { GetPropertyData(Property, ObjToSerialize, ObjTemplate, PrefabProperty->ExportedValue); PrefabProperty->SaveReferencedAssetValues(); } @@ -926,27 +1061,152 @@ void FPrefabTools::LoadStateFromPrefabAsset(APrefabActor* PrefabActor, const FPr } } -void FPrefabTools::FixupCrossReferences(const TArray& PrefabProperties, UObject* ObjToWrite, TMap& PrefabItemToActorMap) +namespace { - for (UPrefabricatorProperty* PrefabProperty : PrefabProperties) { - if (!PrefabProperty || !PrefabProperty->bIsCrossReferencedActor) continue; - - FProperty* Property = ObjToWrite->GetClass()->FindPropertyByName(*PrefabProperty->PropertyName); + void FixupCrossReferences_Helper( + UObject* ObjPtr + , UPrefabricatorProperty* PrefabProperty + , const FObjectPropertyBase* ObjectProperty + , void* PropertyValueAddress + , const FString& PropertyPath + , int32 Index + , TMap& PrefabItemToActorMap + ) + { + FString NewPropertyPath = GetNestedPropertyPath(PropertyPath, ObjectProperty, Index); - const FObjectProperty* ObjectProperty = CastField(Property); - if (!ObjectProperty) continue; + auto NestedPropertyData = PrefabProperty->NestedPropertyData.Find(NewPropertyPath); + if (!NestedPropertyData) return; - AActor** SearchResult = PrefabItemToActorMap.Find(PrefabProperty->CrossReferencePrefabActorId); - if (!SearchResult) continue; + AActor** SearchResult = PrefabItemToActorMap.Find(NestedPropertyData->CrossReferencePrefabActorId); + if (!SearchResult) return; AActor* CrossReference = *SearchResult; - ObjectProperty->SetObjectPropertyValue_InContainer(ObjToWrite, CrossReference); + ObjectProperty->SetObjectPropertyValue(PropertyValueAddress, CrossReference); //////// FString ActorName = CrossReference ? CrossReference->GetName() : "[NONE]"; - UE_LOG(LogPrefabTools, Log, TEXT("Cross Reference: %s -> %s"), *PrefabProperty->CrossReferencePrefabActorId.ToString(), *ActorName); + UE_LOG(LogPrefabTools, Log, TEXT("Cross Reference: %s -> %s"), *NestedPropertyData->CrossReferencePrefabActorId.ToString(), *ActorName); //////// } + + void FixupCrossReferences_StructHelper( + UObject* ObjPtr + , void* ContainerPtr + , UPrefabricatorProperty* PrefabProperty + , const FStructProperty* StructProperty + , const FString& PropertyPath + , int32 PropertyElementIndex + , TMap& PrefabItemToActorMap + ); + + void FixupCrossReferences_ArrayHelper( + UObject* ObjPtr + , void* ContainerPtr + , UPrefabricatorProperty* PrefabProperty + , const FArrayProperty* ArrayProperty + , const FString& PropertyPath + , int32 PropertyElementIndex + , TMap& PrefabItemToActorMap + ) + { + FString NewPropertyPath = GetNestedPropertyPath(PropertyPath, ArrayProperty, PropertyElementIndex); + + if (const FObjectPropertyBase* ObjectProperty = CastField(ArrayProperty->Inner)) + { + auto NestedPropertyData = PrefabProperty->NestedPropertyData.Find(NewPropertyPath); + if (!NestedPropertyData) + return; + + FScriptArrayHelper_InContainer Helper(ArrayProperty, ContainerPtr); + // The array may have different length by default. + if (Helper.Num() < NestedPropertyData->ArrayLength) + { + Helper.AddValues(NestedPropertyData->ArrayLength - Helper.Num()); + } + for (int32 Index = 0; Index < Helper.Num(); Index++) + { + FixupCrossReferences_Helper(ObjPtr, PrefabProperty, ObjectProperty, Helper.GetRawPtr(Index), NewPropertyPath, Index, PrefabItemToActorMap); + } + } + else if (const FStructProperty* StructProperty = CastField(ArrayProperty->Inner)) + { + auto NestedPropertyData = PrefabProperty->NestedPropertyData.Find(NewPropertyPath); + if (!NestedPropertyData) + return; + + FScriptArrayHelper_InContainer Helper(ArrayProperty, ContainerPtr); + // The array may have different length by default. + if (Helper.Num() < NestedPropertyData->ArrayLength) + { + Helper.AddValues(NestedPropertyData->ArrayLength - Helper.Num()); + } + for (int32 Index = 0; Index < Helper.Num(); Index++) + { + FixupCrossReferences_StructHelper(ObjPtr, Helper.GetRawPtr(Index), PrefabProperty, StructProperty, NewPropertyPath, Index, PrefabItemToActorMap); + } + } + } + + void FixupCrossReferences_StructHelper( + UObject* ObjPtr + , void* ContainerPtr + , UPrefabricatorProperty* PrefabProperty + , const FStructProperty* StructProperty + , const FString& PropertyPath + , int32 PropertyElementIndex + , TMap& PrefabItemToActorMap + ) + { + FString NewPropertyPath = GetNestedPropertyPath(PropertyPath, StructProperty, PropertyElementIndex); + + for (TFieldIterator It(StructProperty->Struct); It; ++It) { + FProperty* InnerProperty = *It; + + if (const FStructProperty* StructProperty = CastField(InnerProperty)) { + void* PropPtr = StructProperty->ContainerPtrToValuePtr(ContainerPtr, 0); + FixupCrossReferences_StructHelper(ObjPtr, PropPtr, PrefabProperty, StructProperty, NewPropertyPath, -1, PrefabItemToActorMap); + } + // Support for TArrays (TODO: Adds support for sets and maps). + else if (const FArrayProperty* ArrayProperty = CastField(InnerProperty)) + { + FixupCrossReferences_ArrayHelper(ObjPtr, ContainerPtr, PrefabProperty, ArrayProperty, NewPropertyPath, -1, PrefabItemToActorMap); + } + // Changed from FObjectProperty to FObjectPropertyBase to support also soft references. + else if (const FObjectPropertyBase* ObjProperty = CastField(InnerProperty)) + { + void* PropPtr = ObjProperty->ContainerPtrToValuePtr(ContainerPtr, 0); + FixupCrossReferences_Helper(ObjPtr, PrefabProperty, ObjProperty, PropPtr, NewPropertyPath, -1, PrefabItemToActorMap); + } + } + } +} + +void FPrefabTools::FixupCrossReferences(const TArray& PrefabProperties, UObject* ObjToWrite, TMap& PrefabItemToActorMap) +{ + for (UPrefabricatorProperty* PrefabProperty : PrefabProperties) { + if (!PrefabProperty || !PrefabProperty->bIsCrossReferencedActor) continue; + + FProperty* Property = ObjToWrite->GetClass()->FindPropertyByName(*PrefabProperty->PropertyName); + FString PropertyPath = ""; + + // Support for TArrays (TODO: Adds support for sets and maps). + if (const FStructProperty* StructProperty = CastField(Property)) + { + void* StructPtr = StructProperty->ContainerPtrToValuePtr(ObjToWrite, 0); + FixupCrossReferences_StructHelper(ObjToWrite, StructPtr, PrefabProperty, StructProperty, PropertyPath, -1, PrefabItemToActorMap); + } + else if (const FArrayProperty* ArrayProperty = CastField(Property)) + { + FixupCrossReferences_ArrayHelper(ObjToWrite, ObjToWrite, PrefabProperty, ArrayProperty, PropertyPath, -1, PrefabItemToActorMap); + } + // FObjectProperty instead of FObjectPropertyBase to support also soft references + else if (const FObjectPropertyBase* ObjectProperty = CastField(Property)) { + FixupCrossReferences_Helper(ObjToWrite, PrefabProperty, ObjectProperty, ObjectProperty->ContainerPtrToValuePtr(ObjToWrite), PropertyPath, -1, PrefabItemToActorMap); + } + // Note: there cannot be more than array, structs, and straight up object deemed as cross ref + // Nothing to do here + } } void FPrefabVersionControl::UpgradeToLatestVersion(UPrefabricatorAsset* PrefabAsset) diff --git a/Source/PrefabricatorRuntime/Public/Asset/PrefabricatorAsset.h b/Source/PrefabricatorRuntime/Public/Asset/PrefabricatorAsset.h index badff1fd..a3d86e11 100644 --- a/Source/PrefabricatorRuntime/Public/Asset/PrefabricatorAsset.h +++ b/Source/PrefabricatorRuntime/Public/Asset/PrefabricatorAsset.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" #include "Engine/EngineTypes.h" + #include "PrefabricatorAsset.generated.h" class APrefabActor; @@ -24,6 +25,20 @@ struct PREFABRICATORRUNTIME_API FPrefabricatorPropertyAssetMapping { bool bUseQuotes = false; }; + +USTRUCT() +struct PREFABRICATORRUNTIME_API FPrefabricatorNestedPropertyData +{ + GENERATED_BODY() + + /// Array length is used to initialize array inside FixupCrossReferences phase of serialization + UPROPERTY() + int32 ArrayLength = -1; + + UPROPERTY() + FGuid CrossReferencePrefabActorId; +}; + UCLASS() class PREFABRICATORRUNTIME_API UPrefabricatorProperty : public UObject { GENERATED_BODY() @@ -41,7 +56,10 @@ class PREFABRICATORRUNTIME_API UPrefabricatorProperty : public UObject { bool bIsCrossReferencedActor = false; UPROPERTY() - FGuid CrossReferencePrefabActorId; + bool bContainsStructProperty = false; + + UPROPERTY() + TMap NestedPropertyData; void SaveReferencedAssetValues(); void LoadReferencedAssetValues();