// Copyright Epic Games, Inc. All Rights Reserved. #include "InstancedReferenceSubobjectHelper.h" #include "UObject/PropertyOptional.h" #include "UObject/Package.h" #include "UObject/Package.h" PRAGMA_DISABLE_DEPRECATION_WARNINGS UObject* FInstancedPropertyPath::Resolve(const UObject* Container) const { UStruct* CurrentContainerType = Container->GetClass(); const TArray& PropChainRef = PropertyChain; auto GetProperty = [&CurrentContainerType, &PropChainRef](int32 ChainIndex)->FProperty* { const FProperty* SrcProperty = PropChainRef[ChainIndex].PropertyPtr; return FindFProperty(CurrentContainerType, SrcProperty->GetFName()); }; auto GetArrayIndex = [&PropChainRef](int32 ChainIndex)->int32 { return PropChainRef[ChainIndex].ArrayIndex == INDEX_NONE ? 0 : PropChainRef[ChainIndex].ArrayIndex; }; const FProperty* CurrentProp = GetProperty(0); const uint8* ValuePtr = (CurrentProp) ? CurrentProp->ContainerPtrToValuePtr(Container, GetArrayIndex(0)) : nullptr; for (int32 ChainIndex = 1; CurrentProp && ChainIndex < PropertyChain.Num(); ++ChainIndex) { const FPropertyLink& PropertyLink = PropertyChain[ChainIndex]; if (const FArrayProperty* ArrayProperty = CastField(CurrentProp)) { if (!PropertyLink.PropertyPtr->SameType(ArrayProperty->Inner)) { CurrentProp = nullptr; break; } const int32 TargetIndex = PropertyLink.ArrayIndex; check(TargetIndex != INDEX_NONE); FScriptArrayHelper ArrayHelper(ArrayProperty, ValuePtr); if (TargetIndex >= ArrayHelper.Num()) { CurrentProp = nullptr; break; } CurrentProp = ArrayProperty->Inner; ValuePtr = ArrayHelper.GetRawPtr(TargetIndex); } else if (const FSetProperty* SetProperty = CastField(CurrentProp)) { if (!PropertyLink.PropertyPtr->SameType(SetProperty->ElementProp)) { CurrentProp = nullptr; break; } FScriptSetHelper SetHelper(SetProperty, ValuePtr); // Convert the logical index (recorded in the path) to the actual index used internally by the set. const int32 TargetIndex = SetHelper.FindInternalIndex(PropertyLink.ArrayIndex); if (SetHelper.IsValidIndex(TargetIndex)) { CurrentProp = SetProperty->ElementProp; ValuePtr = SetHelper.GetElementPtr(TargetIndex); } else { CurrentProp = nullptr; } } else if (const FMapProperty* MapProperty = CastField(CurrentProp)) { FScriptMapHelper MapHelper(MapProperty, ValuePtr); // Convert the logical index (recorded in the path) to the actual index used internally by the map. const int32 TargetIndex = MapHelper.FindInternalIndex(PropertyLink.ArrayIndex); if (!MapHelper.IsValidIndex(TargetIndex)) { CurrentProp = nullptr; } else if (!PropertyLink.bIsMapValue && PropertyLink.PropertyPtr->SameType(MapProperty->KeyProp)) { ValuePtr = MapHelper.GetKeyPtr(TargetIndex); CurrentProp = MapProperty->KeyProp; } else if (PropertyLink.bIsMapValue && PropertyLink.PropertyPtr->SameType(MapProperty->ValueProp)) { ValuePtr = MapHelper.GetValuePtr(TargetIndex); CurrentProp = MapProperty->ValueProp; } else { CurrentProp = nullptr; } } else if(const FOptionalProperty* OptionalProperty = CastField(CurrentProp)) { if(const uint8* InnerValuePtr = static_cast(OptionalProperty->GetValuePointerForReadIfSet(ValuePtr))) { ValuePtr = InnerValuePtr; CurrentProp = OptionalProperty->GetValueProperty(); } else { CurrentProp = nullptr; } } else { if (const FStructProperty* StructProperty = CastField(CurrentProp)) { CurrentContainerType = StructProperty->Struct; } CurrentProp = GetProperty(ChainIndex); ValuePtr = (CurrentProp) ? CurrentProp->ContainerPtrToValuePtr(ValuePtr, GetArrayIndex(ChainIndex)) : nullptr; } } const FObjectProperty* TargetProperty = CastField(CurrentProp); if (TargetProperty && TargetProperty->HasAnyPropertyFlags(CPF_InstancedReference)) { return TargetProperty->GetObjectPropertyValue(ValuePtr); } return nullptr; } template void FFindInstancedReferenceSubobjectHelper::ForEachInstancedSubObject(FInstancedPropertyPath& PropertyPath, T ContainerAddress, TFunctionRef ObjRefFunc) { check(ContainerAddress); const FProperty* TargetProp = PropertyPath.Head(); if (const FArrayProperty* ArrayProperty = CastField(TargetProp)) { // Exit now if the array doesn't contain any instanced references. if (!ArrayProperty->HasAnyPropertyFlags(CPF_ContainsInstancedReference)) { return; } FScriptArrayHelper ArrayHelper(ArrayProperty, ContainerAddress); for (int32 ElementIndex = 0; ElementIndex < ArrayHelper.Num(); ++ElementIndex) { T ValueAddress = ArrayHelper.GetRawPtr(ElementIndex); PropertyPath.Push(ArrayProperty->Inner, ElementIndex); ForEachInstancedSubObject(PropertyPath, ValueAddress, ObjRefFunc); PropertyPath.Pop(); } } else if (const FMapProperty* MapProperty = CastField(TargetProp)) { // Exit now if the map doesn't contain any instanced references. if (!MapProperty->HasAnyPropertyFlags(CPF_ContainsInstancedReference)) { return; } FScriptMapHelper MapHelper(MapProperty, ContainerAddress); for (FScriptMapHelper::FIterator It(MapHelper); It; ++It) { T KeyAddress = MapHelper.GetKeyPtr(It); T ValueAddress = MapHelper.GetValuePtr(It); // Note: Keep these as the logical (Nth) index in case the map changes internally after we construct the path or in case we resolve using a different object. PropertyPath.Push(MapProperty->KeyProp, It.GetLogicalIndex()); ForEachInstancedSubObject(PropertyPath, KeyAddress, ObjRefFunc); PropertyPath.Pop(); PropertyPath.Push(MapProperty->ValueProp, It.GetLogicalIndex(), true); ForEachInstancedSubObject(PropertyPath, ValueAddress, ObjRefFunc); PropertyPath.Pop(); } } else if (const FSetProperty* SetProperty = CastField(TargetProp)) { // Exit now if the set doesn't contain any instanced references. if (!SetProperty->HasAnyPropertyFlags(CPF_ContainsInstancedReference)) { return; } FScriptSetHelper SetHelper(SetProperty, ContainerAddress); for (FScriptSetHelper::FIterator It(SetHelper); It; ++It) { T ValueAddress = SetHelper.GetElementPtr(It); // Note: Keep this as the logical (Nth) index in case the set changes internally after we construct the path or in case we resolve using a different object. PropertyPath.Push(SetProperty->ElementProp, It.GetLogicalIndex()); ForEachInstancedSubObject(PropertyPath, ValueAddress, ObjRefFunc); PropertyPath.Pop(); } } else if (const FOptionalProperty* OptionalProperty = CastField(TargetProp)) { // Exit early if the optional does not contain any instanced references. if (!OptionalProperty->HasAnyPropertyFlags(CPF_ContainsInstancedReference)) { return; } if (T ValueAddress = static_cast(OptionalProperty->GetValuePointerForReadOrReplaceIfSet(ContainerAddress))) { PropertyPath.Push(OptionalProperty->GetValueProperty()); ForEachInstancedSubObject(PropertyPath, ValueAddress, ObjRefFunc); PropertyPath.Pop(); } } else if (const FStructProperty* StructProperty = CastField(TargetProp)) { // Exit early if the struct does not contain any instanced references or if the struct is invalid. if (!StructProperty->HasAnyPropertyFlags(CPF_ContainsInstancedReference) || !StructProperty->Struct) { return; } for (FProperty* StructProp = StructProperty->Struct->RefLink; StructProp; StructProp = StructProp->NextRef) { for (int32 ArrayIdx = 0; ArrayIdx < StructProp->ArrayDim; ++ArrayIdx) { T ValueAddress = StructProp->ContainerPtrToValuePtr(ContainerAddress, ArrayIdx); PropertyPath.Push(StructProp, ArrayIdx); ForEachInstancedSubObject(PropertyPath, ValueAddress, ObjRefFunc); PropertyPath.Pop(); } } } else if (TargetProp->HasAllPropertyFlags(CPF_PersistentInstance)) { ensure(TargetProp->HasAllPropertyFlags(CPF_InstancedReference)); if (const FObjectProperty* ObjectProperty = CastField(TargetProp)) { if (UObject* ObjectValue = ObjectProperty->GetObjectPropertyValue(ContainerAddress)) { // don't need to push to PropertyPath, since this property is already at its head ObjRefFunc(FInstancedSubObjRef(ObjectValue, PropertyPath), ContainerAddress); } } else { // @todo figure out how to support verse property types //UE_LOG(LogBlueprint, Error, TEXT("%s: Skipping unknow property type(%s)"), ANSI_TO_TCHAR(__FUNCTION__), *TargetProp->GetName()); } } } template COREUOBJECT_API void FFindInstancedReferenceSubobjectHelper::ForEachInstancedSubObject(FInstancedPropertyPath& PropertyPath, void* ContainerAddress, TFunctionRef ObjRefFunc); template COREUOBJECT_API void FFindInstancedReferenceSubobjectHelper::ForEachInstancedSubObject(FInstancedPropertyPath& PropertyPath, const void* ContainerAddress, TFunctionRef ObjRefFunc); void FFindInstancedReferenceSubobjectHelper::Duplicate( UObject* OldObject, UObject* NewObject, TMap& ReferenceReplacementMap, TArray& DuplicatedObjects, TMap* OptionalMappings) { if (OldObject->GetClass()->HasAnyClassFlags(CLASS_HasInstancedReference) && NewObject->GetClass()->HasAnyClassFlags(CLASS_HasInstancedReference)) { TArray OldInstancedSubObjects; GetInstancedSubObjects(OldObject, OldInstancedSubObjects); if (OldInstancedSubObjects.Num() > 0) { TArray NewInstancedSubObjects; GetInstancedSubObjects(NewObject, NewInstancedSubObjects); for (const FInstancedSubObjRef& Obj : NewInstancedSubObjects) { const bool bNewObjectHasOldOuter = (Obj->GetOuter() == OldObject); if (bNewObjectHasOldOuter) { const bool bKeptByOld = OldInstancedSubObjects.Contains(Obj); const bool bNotHandledYet = !ReferenceReplacementMap.Contains(Obj) && (!OptionalMappings || !OptionalMappings->Contains(Obj)); if (bKeptByOld) { if (bNotHandledYet) { // This name may have been taken by an instanced subobject, since we're copying from // an object we want to recreate it here with a duplicate of the old object. I'm // using a rename call here to reserve the name for ourself. These objects come from // FObjectInitializer::InstanceSubobjects() UObject* ExistingObject = StaticFindObjectFast(UObject::StaticClass(), NewObject, Obj->GetFName()); if (ExistingObject) { ExistingObject->Rename( nullptr, GetTransientPackage(), REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional); } UObject* NewEditInlineSubobject = StaticDuplicateObject(Obj, NewObject, Obj->GetFName()); ReferenceReplacementMap.Add(Obj, NewEditInlineSubobject); if(OptionalMappings) { OptionalMappings->Add(Obj, NewEditInlineSubobject); } // NOTE: we cannot patch OldObject's linker table here, since we don't // know the relation between the two objects (one could be of a // super class, and the other a child) // We also need to make sure to fixup any properties here DuplicatedObjects.Add(NewEditInlineSubobject); } else { // make sure the object is outered correctly: check(bNewObjectHasOldOuter); UObject* RealNewSubobject = nullptr; if (OptionalMappings) { UObject* const* Entry = OptionalMappings->Find(Obj); if (Entry) { RealNewSubobject = *Entry; } } if (!RealNewSubobject) { RealNewSubobject = ReferenceReplacementMap.FindChecked(Obj); } if (RealNewSubobject->GetOuter() != NewObject) { UObject* ExistingObject = StaticFindObjectFast(UObject::StaticClass(), NewObject, RealNewSubobject->GetFName()); if (ExistingObject) { ExistingObject->Rename( nullptr, GetTransientPackage(), REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional); ReferenceReplacementMap.Add(ExistingObject, RealNewSubobject); } RealNewSubobject->Rename( *(RealNewSubobject->GetName()), NewObject, REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional); } } } } } } } } PRAGMA_ENABLE_DEPRECATION_WARNINGS