Files
UnrealEngineUWP/Engine/Source/Runtime/Engine/Private/PropertyAccess.cpp
thomas sarkanen c2b30f6d9b Added critical section to Property Access Library to prevent race conditions
In async loading thread-enabled builds, copying the property access library during CDO serialization can race with the parent classes postload-fixup (i.e. PatchPropertyOffsets). Adding a critical section prevents this race, and is rarely contended.


#ROBOMERGE-AUTHOR: thomas.sarkanen
#ROBOMERGE-SOURCE: CL 20062318 via CL 20062325 via CL 20062337 via CL 20062347 via CL 20062356
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v943-19904690)

[CL 20066261 by thomas sarkanen in ue5-main branch]
2022-05-05 18:42:08 -04:00

670 lines
27 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PropertyAccess.h"
#include "Misc/MemStack.h"
#include "UObject/ScriptCastingUtils.h"
#define LOCTEXT_NAMESPACE "PropertyAccess"
namespace PropertyAccess
{
static FCriticalSection CriticalSection;
}
struct FPropertyAccessSystem
{
struct FResolveIndirectionsOnLoadContext
{
FResolveIndirectionsOnLoadContext(TArrayView<FPropertyAccessSegment> InSegments, FPropertyAccessIndirectionChain& InAccess, FPropertyAccessLibrary& InLibrary)
: Segments(InSegments)
, Access(InAccess)
, Library(InLibrary)
, AccumulatedOffset(0)
{}
TArrayView<FPropertyAccessSegment> Segments;
TArray<FPropertyAccessIndirection> Indirections;
FPropertyAccessIndirectionChain& Access;
FPropertyAccessLibrary& Library;
uint32 AccumulatedOffset;
FText ErrorMessage;
};
static uint32 GetPropertyOffset(const FProperty* InProperty, int32 InArrayIndex = 0)
{
return (uint32)(InProperty->GetOffset_ForInternal() + InProperty->ElementSize * InArrayIndex);
}
// Called on load to resolve all path segments to indirections
static bool ResolveIndirectionsOnLoad(FResolveIndirectionsOnLoadContext& InContext)
{
for(int32 SegmentIndex = 0; SegmentIndex < InContext.Segments.Num(); ++SegmentIndex)
{
const FPropertyAccessSegment& Segment = InContext.Segments[SegmentIndex];
const bool bLastSegment = SegmentIndex == InContext.Segments.Num() - 1;
if(Segment.Property.Get() == nullptr)
{
return false;
}
if(EnumHasAllFlags((EPropertyAccessSegmentFlags)Segment.Flags, EPropertyAccessSegmentFlags::Function))
{
if(Segment.Function == nullptr)
{
return false;
}
FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef();
Indirection.Type = Segment.Function->HasAnyFunctionFlags(FUNC_Native) ? EPropertyAccessIndirectionType::NativeFunction : EPropertyAccessIndirectionType::ScriptFunction;
switch((EPropertyAccessSegmentFlags)Segment.Flags & ~EPropertyAccessSegmentFlags::ModifierFlags)
{
case EPropertyAccessSegmentFlags::Struct:
case EPropertyAccessSegmentFlags::Leaf:
Indirection.ObjectType = EPropertyAccessObjectType::None;
break;
case EPropertyAccessSegmentFlags::Object:
Indirection.ObjectType = EPropertyAccessObjectType::Object;
break;
case EPropertyAccessSegmentFlags::WeakObject:
Indirection.ObjectType = EPropertyAccessObjectType::WeakObject;
break;
case EPropertyAccessSegmentFlags::SoftObject:
Indirection.ObjectType = EPropertyAccessObjectType::SoftObject;
break;
default:
check(false);
break;
}
Indirection.Function = Segment.Function;
Indirection.Property = Segment.Function->GetReturnProperty();
Indirection.ReturnBufferSize = Segment.Property.Get()->GetSize();
Indirection.ReturnBufferAlignment = Segment.Property.Get()->GetMinAlignment();
}
else
{
const int32 ArrayIndex = Segment.ArrayIndex == INDEX_NONE ? 0 : Segment.ArrayIndex;
const EPropertyAccessSegmentFlags UnmodifiedFlags = (EPropertyAccessSegmentFlags)Segment.Flags & ~EPropertyAccessSegmentFlags::ModifierFlags;
switch(UnmodifiedFlags)
{
case EPropertyAccessSegmentFlags::Struct:
case EPropertyAccessSegmentFlags::Leaf:
{
FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef();
Indirection.Offset = GetPropertyOffset(Segment.Property.Get(), ArrayIndex);
Indirection.Type = EPropertyAccessIndirectionType::Offset;
break;
}
case EPropertyAccessSegmentFlags::Object:
{
FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef();
Indirection.Offset = GetPropertyOffset(Segment.Property.Get(), ArrayIndex);
Indirection.Type = EPropertyAccessIndirectionType::Object;
Indirection.ObjectType = EPropertyAccessObjectType::Object;
break;
}
case EPropertyAccessSegmentFlags::WeakObject:
{
FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef();
Indirection.Offset = GetPropertyOffset(Segment.Property.Get(), ArrayIndex);
Indirection.Type = EPropertyAccessIndirectionType::Object;
Indirection.ObjectType = EPropertyAccessObjectType::WeakObject;
break;
}
case EPropertyAccessSegmentFlags::SoftObject:
{
FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef();
Indirection.Offset = GetPropertyOffset(Segment.Property.Get(), ArrayIndex);
Indirection.Type = EPropertyAccessIndirectionType::Object;
Indirection.ObjectType = EPropertyAccessObjectType::SoftObject;
break;
}
case EPropertyAccessSegmentFlags::Array:
case EPropertyAccessSegmentFlags::ArrayOfStructs:
case EPropertyAccessSegmentFlags::ArrayOfObjects:
{
FPropertyAccessIndirection& Indirection = InContext.Indirections.AddDefaulted_GetRef();
Indirection.Offset = GetPropertyOffset(Segment.Property.Get());
Indirection.Type = EPropertyAccessIndirectionType::Array;
Indirection.Property = CastFieldChecked<FArrayProperty>(Segment.Property.Get());
Indirection.ArrayIndex = ArrayIndex;
if(UnmodifiedFlags == EPropertyAccessSegmentFlags::ArrayOfObjects)
{
if(!bLastSegment)
{
// Object arrays need an object dereference adding if non-leaf
FPropertyAccessIndirection& ExtraIndirection = InContext.Indirections.AddDefaulted_GetRef();
ExtraIndirection.Offset = 0;
ExtraIndirection.Type = EPropertyAccessIndirectionType::Object;
FProperty* InnerProperty = CastFieldChecked<FArrayProperty>(Indirection.Property.Get())->Inner;
if(FObjectProperty* ObjectProperty = CastField<FObjectProperty>(InnerProperty))
{
ExtraIndirection.ObjectType = EPropertyAccessObjectType::Object;
}
else if(FWeakObjectProperty* WeakObjectProperty = CastField<FWeakObjectProperty>(InnerProperty))
{
ExtraIndirection.ObjectType = EPropertyAccessObjectType::WeakObject;
}
else if(FSoftObjectProperty* SoftObjectProperty = CastField<FSoftObjectProperty>(InnerProperty))
{
ExtraIndirection.ObjectType = EPropertyAccessObjectType::SoftObject;
}
}
}
break;
}
default:
check(false);
break;
}
}
}
// Collapse adjacent offset indirections
for(int32 IndirectionIndex = 0; IndirectionIndex < InContext.Indirections.Num(); ++IndirectionIndex)
{
FPropertyAccessIndirection& StartIndirection = InContext.Indirections[IndirectionIndex];
if(StartIndirection.Type == EPropertyAccessIndirectionType::Offset)
{
for(int32 NextIndirectionIndex = IndirectionIndex + 1; NextIndirectionIndex < InContext.Indirections.Num(); ++NextIndirectionIndex)
{
FPropertyAccessIndirection& RunIndirection = InContext.Indirections[NextIndirectionIndex];
if(RunIndirection.Type == EPropertyAccessIndirectionType::Offset)
{
StartIndirection.Offset += RunIndirection.Offset;
InContext.Indirections.RemoveAt(NextIndirectionIndex);
}
else
{
// No run, exit
break;
}
}
}
}
// Concatenate indirections into the library and update the access
InContext.Access.IndirectionStartIndex = InContext.Library.Indirections.Num();
InContext.Library.Indirections.Append(InContext.Indirections);
InContext.Access.IndirectionEndIndex = InContext.Library.Indirections.Num();
// Copy leaf property to access
FPropertyAccessSegment& LastSegment = InContext.Segments.Last();
switch((EPropertyAccessSegmentFlags)LastSegment.Flags & ~EPropertyAccessSegmentFlags::ModifierFlags)
{
case EPropertyAccessSegmentFlags::Struct:
case EPropertyAccessSegmentFlags::Leaf:
case EPropertyAccessSegmentFlags::Object:
case EPropertyAccessSegmentFlags::WeakObject:
case EPropertyAccessSegmentFlags::SoftObject:
InContext.Access.Property = LastSegment.Property.Get();
break;
case EPropertyAccessSegmentFlags::Array:
case EPropertyAccessSegmentFlags::ArrayOfStructs:
case EPropertyAccessSegmentFlags::ArrayOfObjects:
InContext.Access.Property = CastFieldChecked<FArrayProperty>(LastSegment.Property.Get())->Inner;
break;
}
return true;
}
static void PatchPropertyOffsets(FPropertyAccessLibrary& InLibrary)
{
// Need to perform a lock as copying can race with PatchPropertyOffsets in async loading thread-enabled builds
FScopeLock Lock(&PropertyAccess::CriticalSection);
InLibrary.Indirections.Reset();
const int32 SrcCount = InLibrary.SrcPaths.Num();
InLibrary.SrcAccesses.Reset();
InLibrary.SrcAccesses.SetNum(SrcCount);
const int32 DestCount = InLibrary.DestPaths.Num();
InLibrary.DestAccesses.Reset();
InLibrary.DestAccesses.SetNum(DestCount);
// @TODO: ParallelFor this if required
for(int32 SrcIndex = 0; SrcIndex < SrcCount; ++SrcIndex)
{
TArrayView<FPropertyAccessSegment> Segments(&InLibrary.PathSegments[InLibrary.SrcPaths[SrcIndex].PathSegmentStartIndex], InLibrary.SrcPaths[SrcIndex].PathSegmentCount);
FResolveIndirectionsOnLoadContext Context(Segments, InLibrary.SrcAccesses[SrcIndex], InLibrary);
if(!ResolveIndirectionsOnLoad(Context))
{
Context.Indirections.Empty();
Context.Access.IndirectionStartIndex = Context.Access.IndirectionEndIndex = INDEX_NONE;
}
}
for(int32 DestIndex = 0; DestIndex < DestCount; ++DestIndex)
{
TArrayView<FPropertyAccessSegment> Segments(&InLibrary.PathSegments[InLibrary.DestPaths[DestIndex].PathSegmentStartIndex], InLibrary.DestPaths[DestIndex].PathSegmentCount);
FResolveIndirectionsOnLoadContext Context(Segments, InLibrary.DestAccesses[DestIndex], InLibrary);
if(!ResolveIndirectionsOnLoad(Context))
{
Context.Indirections.Empty();
Context.Access.IndirectionStartIndex = Context.Access.IndirectionEndIndex = INDEX_NONE;
}
}
InLibrary.bHasBeenPostLoaded = true;
}
static void PerformCopy(const FPropertyAccessCopy& Copy, const FProperty* InSrcProperty, const void* InSrcAddr, const FProperty* InDestProperty, void* InDestAddr, TFunctionRef<void(const FProperty*, void*)> InPostCopyOperation)
{
switch(Copy.Type)
{
case EPropertyAccessCopyType::Plain:
checkSlow(InSrcProperty->PropertyFlags & CPF_IsPlainOldData);
checkSlow(InDestProperty->PropertyFlags & CPF_IsPlainOldData);
FMemory::Memcpy(InDestAddr, InSrcAddr, InSrcProperty->ElementSize);
break;
case EPropertyAccessCopyType::Complex:
InSrcProperty->CopyCompleteValue(InDestAddr, InSrcAddr);
break;
case EPropertyAccessCopyType::Bool:
checkSlow(InSrcProperty->IsA<FBoolProperty>());
checkSlow(InDestProperty->IsA<FBoolProperty>());
static_cast<const FBoolProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, static_cast<const FBoolProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::Struct:
checkSlow(InSrcProperty->IsA<FStructProperty>());
checkSlow(InDestProperty->IsA<FStructProperty>());
static_cast<const FStructProperty*>(InDestProperty)->Struct->CopyScriptStruct(InDestAddr, InSrcAddr);
break;
case EPropertyAccessCopyType::Object:
checkSlow(InSrcProperty->IsA<FObjectPropertyBase>());
checkSlow(InDestProperty->IsA<FObjectPropertyBase>());
static_cast<const FObjectPropertyBase*>(InDestProperty)->SetObjectPropertyValue(InDestAddr, static_cast<const FObjectPropertyBase*>(InSrcProperty)->GetObjectPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::Name:
checkSlow(InSrcProperty->IsA<FNameProperty>());
checkSlow(InDestProperty->IsA<FNameProperty>());
static_cast<const FNameProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, static_cast<const FNameProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::Array:
{
checkSlow(InSrcProperty->IsA<FArrayProperty>());
checkSlow(InDestProperty->IsA<FArrayProperty>());
const FArrayProperty* SrcArrayProperty = static_cast<const FArrayProperty*>(InSrcProperty);
const FArrayProperty* DestArrayProperty = static_cast<const FArrayProperty*>(InDestProperty);
FScriptArrayHelper SourceArrayHelper(SrcArrayProperty, InSrcAddr);
FScriptArrayHelper DestArrayHelper(DestArrayProperty, InDestAddr);
// Copy the minimum number of elements to the destination array without resizing
const int32 MinSize = FMath::Min(SourceArrayHelper.Num(), DestArrayHelper.Num());
for(int32 ElementIndex = 0; ElementIndex < MinSize; ++ElementIndex)
{
SrcArrayProperty->Inner->CopySingleValue(DestArrayHelper.GetRawPtr(ElementIndex), SourceArrayHelper.GetRawPtr(ElementIndex));
}
break;
}
case EPropertyAccessCopyType::PromoteBoolToByte:
checkSlow(InSrcProperty->IsA<FBoolProperty>());
checkSlow(InDestProperty->IsA<FByteProperty>());
static_cast<const FByteProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (uint8)static_cast<const FBoolProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteBoolToInt32:
checkSlow(InSrcProperty->IsA<FBoolProperty>());
checkSlow(InDestProperty->IsA<FIntProperty>());
static_cast<const FIntProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (int32)static_cast<const FBoolProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteBoolToInt64:
checkSlow(InSrcProperty->IsA<FBoolProperty>());
checkSlow(InDestProperty->IsA<FInt64Property>());
static_cast<const FInt64Property*>(InDestProperty)->SetPropertyValue(InDestAddr, (int64)static_cast<const FBoolProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteBoolToFloat:
checkSlow(InSrcProperty->IsA<FBoolProperty>());
checkSlow(InDestProperty->IsA<FFloatProperty>());
static_cast<const FFloatProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (float)static_cast<const FBoolProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteBoolToDouble:
checkSlow(InSrcProperty->IsA<FBoolProperty>());
checkSlow(InDestProperty->IsA<FDoubleProperty>());
static_cast<const FDoubleProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (double)static_cast<const FBoolProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteByteToInt32:
checkSlow(InSrcProperty->IsA<FByteProperty>());
checkSlow(InDestProperty->IsA<FIntProperty>());
static_cast<const FIntProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (int32)static_cast<const FByteProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteByteToInt64:
checkSlow(InSrcProperty->IsA<FByteProperty>());
checkSlow(InDestProperty->IsA<FInt64Property>());
static_cast<const FInt64Property*>(InDestProperty)->SetPropertyValue(InDestAddr, (int64)static_cast<const FByteProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteByteToFloat:
checkSlow(InSrcProperty->IsA<FByteProperty>());
checkSlow(InDestProperty->IsA<FFloatProperty>());
static_cast<const FFloatProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (float)static_cast<const FByteProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteByteToDouble:
checkSlow(InSrcProperty->IsA<FByteProperty>());
checkSlow(InDestProperty->IsA<FDoubleProperty>());
static_cast<const FDoubleProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (double)static_cast<const FByteProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteInt32ToInt64:
checkSlow(InSrcProperty->IsA<FIntProperty>());
checkSlow(InDestProperty->IsA<FInt64Property>());
static_cast<const FInt64Property*>(InDestProperty)->SetPropertyValue(InDestAddr, (int64)static_cast<const FIntProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteInt32ToFloat:
checkSlow(InSrcProperty->IsA<FIntProperty>());
checkSlow(InDestProperty->IsA<FFloatProperty>());
static_cast<const FFloatProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (float)static_cast<const FIntProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteInt32ToDouble:
checkSlow(InSrcProperty->IsA<FIntProperty>());
checkSlow(InDestProperty->IsA<FDoubleProperty>());
static_cast<const FDoubleProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (double)static_cast<const FIntProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteFloatToDouble:
checkSlow(InSrcProperty->IsA<FFloatProperty>());
checkSlow(InDestProperty->IsA<FDoubleProperty>());
static_cast<const FDoubleProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (double)static_cast<const FFloatProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::DemoteDoubleToFloat:
checkSlow(InSrcProperty->IsA<FDoubleProperty>());
checkSlow(InDestProperty->IsA<FFloatProperty>());
static_cast<const FFloatProperty*>(InDestProperty)->SetPropertyValue(InDestAddr, (float)static_cast<const FDoubleProperty*>(InSrcProperty)->GetPropertyValue(InSrcAddr));
break;
case EPropertyAccessCopyType::PromoteArrayFloatToDouble:
{
checkSlow(InSrcProperty->IsA<FArrayProperty>());
checkSlow(InDestProperty->IsA<FArrayProperty>());
const FArrayProperty* SrcArrayProperty = ExactCastField<const FArrayProperty>(InSrcProperty);
const FArrayProperty* DestArrayProperty = ExactCastField<const FArrayProperty>(InDestProperty);
CopyAndCastArray<float, double>(SrcArrayProperty, InSrcAddr, DestArrayProperty, InDestAddr);
break;
}
case EPropertyAccessCopyType::DemoteArrayDoubleToFloat:
{
checkSlow(InSrcProperty->IsA<FArrayProperty>());
checkSlow(InDestProperty->IsA<FArrayProperty>());
const FArrayProperty* SrcArrayProperty = ExactCastField<const FArrayProperty>(InSrcProperty);
const FArrayProperty* DestArrayProperty = ExactCastField<const FArrayProperty>(InDestProperty);
CopyAndCastArray<double, float>(SrcArrayProperty, InSrcAddr, DestArrayProperty, InDestAddr);
break;
}
default:
check(false);
break;
}
InPostCopyOperation(InDestProperty, InDestAddr);
}
static void CallNativeAccessor(UObject* InObject, UFunction* InFunction, void* OutRetValue)
{
// Func must be local
check((InObject->GetFunctionCallspace(InFunction, nullptr) & FunctionCallspace::Local) != 0);
// Function must be native
check(InFunction->HasAnyFunctionFlags(FUNC_Native));
// Function must have a return property
check(InFunction->GetReturnProperty() != nullptr);
// Function must only have one param - its return value
check(InFunction->NumParms == 1);
FFrame Stack(InObject, InFunction, nullptr, nullptr, InFunction->ChildProperties);
InFunction->Invoke(InObject, Stack, OutRetValue);
}
// Gets the address that corresponds to a property access.
// Forwards the address onto the passed-in function. This callback-style approach is used because in some cases
// (e.g. functions), the address may be memory allocated on the stack.
template<typename PredicateType>
static void GetAccessAddress(void* InContainer, const FPropertyAccessLibrary& InLibrary, const FPropertyAccessIndirectionChain& InAccess, PredicateType InAddressFunction)
{
// Buffer for function return values
TArray<uint8, TNonRelocatableInlineAllocator<64>> ArrayBuffer;
void* Address = InContainer;
for(int32 IndirectionIndex = InAccess.IndirectionStartIndex; Address != nullptr && IndirectionIndex < InAccess.IndirectionEndIndex; ++IndirectionIndex)
{
const FPropertyAccessIndirection& Indirection = InLibrary.Indirections[IndirectionIndex];
switch(Indirection.Type)
{
case EPropertyAccessIndirectionType::Offset:
Address = static_cast<void*>(static_cast<uint8*>(Address) + Indirection.Offset);
break;
case EPropertyAccessIndirectionType::Object:
{
switch(Indirection.ObjectType)
{
case EPropertyAccessObjectType::Object:
{
UObject* Object = *reinterpret_cast<UObject**>(static_cast<uint8*>(Address) + Indirection.Offset);
Address = static_cast<void*>(Object);
break;
}
case EPropertyAccessObjectType::WeakObject:
{
TWeakObjectPtr<UObject>& WeakObjectPtr = *reinterpret_cast<TWeakObjectPtr<UObject>*>(static_cast<uint8*>(Address) + Indirection.Offset);
UObject* Object = WeakObjectPtr.Get();
Address = static_cast<void*>(Object);
break;
}
case EPropertyAccessObjectType::SoftObject:
{
FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast<FSoftObjectPtr*>(static_cast<uint8*>(Address) + Indirection.Offset);
UObject* Object = SoftObjectPtr.Get();
Address = static_cast<void*>(Object);
break;
}
default:
check(false);
}
break;
}
case EPropertyAccessIndirectionType::Array:
{
if(FArrayProperty* ArrayProperty = CastFieldChecked<FArrayProperty>(Indirection.Property.Get()))
{
FScriptArrayHelper Helper(ArrayProperty, static_cast<uint8*>(Address) + Indirection.Offset);
if(Helper.IsValidIndex(Indirection.ArrayIndex))
{
Address = static_cast<void*>(Helper.GetRawPtr(Indirection.ArrayIndex));
}
else
{
Address = nullptr;
}
}
else
{
Address = nullptr;
}
break;
}
case EPropertyAccessIndirectionType::ScriptFunction:
case EPropertyAccessIndirectionType::NativeFunction:
{
if(Indirection.Function != nullptr)
{
UObject* CalleeObject = static_cast<UObject*>(Address);
// Allocate buffer + alignment slack for the return value
ArrayBuffer.SetNumUninitialized(Indirection.ReturnBufferSize + Indirection.ReturnBufferAlignment, false);
Address = Align(ArrayBuffer.GetData(), Indirection.ReturnBufferAlignment);
// Init value
check(Indirection.Function->GetReturnProperty());
check(Indirection.Function->GetReturnProperty() == Indirection.Property.Get());
Indirection.Property.Get()->InitializeValue(Address);
if(Indirection.Type == EPropertyAccessIndirectionType::NativeFunction)
{
CallNativeAccessor(CalleeObject, Indirection.Function, Address);
}
else
{
CalleeObject->ProcessEvent(Indirection.Function, Address);
}
// Function access may return an object, so we need to follow that ptr
switch(Indirection.ObjectType)
{
case EPropertyAccessObjectType::Object:
{
UObject* Object = *static_cast<UObject**>(Address);
Address = static_cast<void*>(Object);
break;
}
case EPropertyAccessObjectType::WeakObject:
{
TWeakObjectPtr<UObject>& WeakObjectPtr = *static_cast<TWeakObjectPtr<UObject>*>(Address);
UObject* Object = WeakObjectPtr.Get();
Address = static_cast<void*>(Object);
break;
}
case EPropertyAccessObjectType::SoftObject:
{
FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast<FSoftObjectPtr*>(Address);
UObject* Object = SoftObjectPtr.Get();
Address = static_cast<void*>(Object);
break;
}
default:
break;
}
break;
}
else
{
Address = nullptr;
}
}
default:
check(false);
}
}
if(Address != nullptr)
{
InAddressFunction(Address);
}
}
// Process a single copy
static void ProcessCopy(UStruct* InStruct, void* InContainer, const FPropertyAccessLibrary& InLibrary, int32 InCopyIndex, int32 InBatchId, TFunctionRef<void(const FProperty*, void*)> InPostCopyOperation)
{
if(InLibrary.bHasBeenPostLoaded)
{
if(InLibrary.CopyBatchArray.IsValidIndex(InBatchId))
{
const FPropertyAccessCopy& Copy = InLibrary.CopyBatchArray[InBatchId].Copies[InCopyIndex];
const FPropertyAccessIndirectionChain& SrcAccess = InLibrary.SrcAccesses[Copy.AccessIndex];
if(FProperty* SourceProperty = SrcAccess.Property.Get())
{
GetAccessAddress(InContainer, InLibrary, SrcAccess, [InContainer, &InLibrary, &Copy, SourceProperty, &InPostCopyOperation](void* InSrcAddress)
{
for(int32 DestAccessIndex = Copy.DestAccessStartIndex; DestAccessIndex < Copy.DestAccessEndIndex; ++DestAccessIndex)
{
const FPropertyAccessIndirectionChain& DestAccess = InLibrary.DestAccesses[DestAccessIndex];
if(FProperty* DestProperty = DestAccess.Property.Get())
{
GetAccessAddress(InContainer, InLibrary, DestAccess, [&InSrcAddress, &Copy, SourceProperty, DestProperty, &InPostCopyOperation](void* InDestAddress)
{
PerformCopy(Copy, SourceProperty, InSrcAddress, DestProperty, InDestAddress, InPostCopyOperation);
});
}
}
});
}
}
}
}
static void ProcessCopies(UStruct* InStruct, void* InContainer, const FPropertyAccessLibrary& InLibrary, int32 InBatchId)
{
if(InLibrary.bHasBeenPostLoaded)
{
if(InLibrary.CopyBatchArray.IsValidIndex(InBatchId))
{
// Copy all valid properties
// Parallelization opportunity: ParallelFor all the property copies we need to make
const int32 NumCopies = InLibrary.CopyBatchArray[InBatchId].Copies.Num();
for(int32 CopyIndex = 0; CopyIndex < NumCopies; ++CopyIndex)
{
ProcessCopy(InStruct, InContainer, InLibrary, CopyIndex, InBatchId, [](const FProperty*, void*){});
}
}
}
}
};
const FPropertyAccessLibrary& FPropertyAccessLibrary::operator =(const FPropertyAccessLibrary& Other)
{
// Need to perform a lock as copying can race with PatchPropertyOffsets in async loading thread-enabled builds
FScopeLock Lock(&PropertyAccess::CriticalSection);
PathSegments = Other.PathSegments;
SrcPaths = Other.SrcPaths;
DestPaths = Other.DestPaths;
CopyBatchArray = Other.CopyBatchArray;
SrcAccesses = Other.SrcAccesses;
DestAccesses = Other.DestAccesses;
Indirections = Other.Indirections;
bHasBeenPostLoaded = Other.bHasBeenPostLoaded;
return *this;
}
namespace PropertyAccess
{
void PostLoadLibrary(FPropertyAccessLibrary& InLibrary)
{
::FPropertyAccessSystem::PatchPropertyOffsets(InLibrary);
}
void PatchPropertyOffsets(FPropertyAccessLibrary& InLibrary)
{
::FPropertyAccessSystem::PatchPropertyOffsets(InLibrary);
}
void ProcessCopies(UObject* InObject, const FPropertyAccessLibrary& InLibrary, EPropertyAccessCopyBatch InBatchType)
{
ProcessCopies(InObject, InLibrary, FCopyBatchId((__underlying_type(EPropertyAccessCopyBatch))InBatchType));
}
void ProcessCopies(UObject* InObject, const FPropertyAccessLibrary& InLibrary, const FCopyBatchId& InBatchId)
{
::FPropertyAccessSystem::ProcessCopies(InObject->GetClass(), InObject, InLibrary, InBatchId.Id);
}
void ProcessCopy(UObject* InObject, const FPropertyAccessLibrary& InLibrary, EPropertyAccessCopyBatch InBatchType, int32 InCopyIndex, TFunctionRef<void(const FProperty*, void*)> InPostCopyOperation)
{
ProcessCopy(InObject, InLibrary, FCopyBatchId((__underlying_type(EPropertyAccessCopyBatch))InBatchType), InCopyIndex, InPostCopyOperation);
}
void ProcessCopy(UObject* InObject, const FPropertyAccessLibrary& InLibrary, const FCopyBatchId& InBatchId, int32 InCopyIndex, TFunctionRef<void(const FProperty*, void*)> InPostCopyOperation)
{
::FPropertyAccessSystem::ProcessCopy(InObject->GetClass(), InObject, InLibrary, InCopyIndex, InBatchId.Id, InPostCopyOperation);
}
void BindEvents(UObject* InObject, const FPropertyAccessLibrary& InLibrary)
{
}
int32 GetEventId(const UClass* InClass, TArrayView<const FName> InPath)
{
return INDEX_NONE;
}
}
#undef LOCTEXT_NAMESPACE