Files
mikko mononen 2d1a48be9b StateTree: Fixed crash when resolving bindings without value into Weak or Soft Objects.
[CL 31041198 by mikko mononen in 5.4 branch]
2024-01-31 05:34:06 -05:00

1865 lines
61 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "StateTreePropertyBindings.h"
#include "UObject/EnumProperty.h"
#include "Misc/EnumerateRange.h"
#include "PropertyPathHelpers.h"
#include "PropertyBag.h"
#include "StateTreePropertyRef.h"
#if WITH_EDITOR
#include "UObject/CoreRedirects.h"
#include "UObject/Package.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Engine/UserDefinedStruct.h"
#include "Kismet2/StructureEditorUtils.h"
#include "UObject/Field.h"
#endif
#include UE_INLINE_GENERATED_CPP_BY_NAME(StateTreePropertyBindings)
namespace UE::StateTree
{
FString GetDescAndPathAsString(const FStateTreeBindableStructDesc& Desc, const FStateTreePropertyPath& Path)
{
FStringBuilderBase Result;
Result += Desc.ToString();
if (!Path.IsPathEmpty())
{
Result += TEXT(" ");
Result += Path.ToString();
}
return Result.ToString();
}
#if WITH_EDITOR
EStateTreePropertyUsage GetUsageFromMetaData(const FProperty* Property)
{
static const FName CategoryName(TEXT("Category"));
if (Property == nullptr)
{
return EStateTreePropertyUsage::Invalid;
}
const FString Category = Property->GetMetaData(CategoryName);
if (Category == TEXT("Input"))
{
return EStateTreePropertyUsage::Input;
}
if (Category == TEXT("Inputs"))
{
return EStateTreePropertyUsage::Input;
}
if (Category == TEXT("Output"))
{
return EStateTreePropertyUsage::Output;
}
if (Category == TEXT("Outputs"))
{
return EStateTreePropertyUsage::Output;
}
if (Category == TEXT("Context"))
{
return EStateTreePropertyUsage::Context;
}
return EStateTreePropertyUsage::Parameter;
}
#endif
} // UE::StateTree
namespace UE::StateTree::Private
{
#if WITH_EDITORONLY_DATA
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FStateTreePropertyPath ConvertEditorPath(const FStateTreeEditorPropertyPath& InEditorPath)
{
FStateTreePropertyPath Path;
Path.SetStructID(InEditorPath.StructID);
for (const FString& Segment : InEditorPath.Path)
{
const TCHAR* PropertyNamePtr = nullptr;
int32 PropertyNameLength = 0;
int32 ArrayIndex = INDEX_NONE;
PropertyPathHelpers::FindFieldNameAndArrayIndex(Segment.Len(), *Segment, PropertyNameLength, &PropertyNamePtr, ArrayIndex);
FString PropertyNameString(PropertyNameLength, PropertyNamePtr);
const FName PropertyName(*PropertyNameString, FNAME_Find);
Path.AddPathSegment(PropertyName, ArrayIndex);
}
return Path;
}
FStateTreeEditorPropertyPath ConvertEditorPath(const FStateTreePropertyPath& InPath)
{
FStateTreeEditorPropertyPath Path;
Path.StructID = InPath.GetStructID();
for (const FStateTreePropertyPathSegment& Segment : InPath.GetSegments())
{
if (Segment.GetArrayIndex() != INDEX_NONE)
{
Path.Path.Add(FString::Printf(TEXT("%s[%d]"), *Segment.GetName().ToString(), Segment.GetArrayIndex()));
}
else
{
Path.Path.Add(Segment.GetName().ToString());
}
}
return Path;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
#endif // WITH_EDITORONLY_DATA
} // UE::StateTree::Private
//----------------------------------------------------------------//
// FStateTreeBindableStructDesc
//----------------------------------------------------------------//
FString FStateTreeBindableStructDesc::ToString() const
{
FStringBuilderBase Result;
Result += UEnum::GetDisplayValueAsText(DataSource).ToString();
Result += TEXT(" '");
Result += Name.ToString();
Result += TEXT("'");
return Result.ToString();
}
//----------------------------------------------------------------//
// FStateTreePropertyPathBinding
//----------------------------------------------------------------//
void FStateTreePropertyPathBinding::PostSerialize(const FArchive& Ar)
{
#if WITH_EDITORONLY_DATA
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (SourcePath_DEPRECATED.IsValid())
{
SourcePropertyPath = UE::StateTree::Private::ConvertEditorPath(SourcePath_DEPRECATED);
SourcePath_DEPRECATED.StructID = FGuid();
SourcePath_DEPRECATED.Path.Reset();
}
if (TargetPath_DEPRECATED.IsValid())
{
TargetPropertyPath = UE::StateTree::Private::ConvertEditorPath(TargetPath_DEPRECATED);
TargetPath_DEPRECATED.StructID = FGuid();
TargetPath_DEPRECATED.Path.Reset();
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
#endif // WITH_EDITORONLY_DATA
}
//----------------------------------------------------------------//
// FStateTreePropertyBindings
//----------------------------------------------------------------//
void FStateTreePropertyBindings::Reset()
{
SourceStructs.Reset();
CopyBatches.Reset();
PropertyPathBindings.Reset();
PropertyCopies.Reset();
PropertyAccesses.Reset();
PropertyReferencePaths.Reset();
PropertyIndirections.Reset();
bBindingsResolved = false;
}
const FStateTreeBindableStructDesc* FStateTreePropertyBindings::GetSourceDescByHandle(const FStateTreeDataHandle SourceDataHandle)
{
TArray<FStateTreeBindableStructDesc> FoundDescs;
for (const FStateTreeBindableStructDesc& Desc : SourceStructs)
{
if (Desc.DataHandle == SourceDataHandle)
{
FoundDescs.Add(Desc);
}
}
if (FoundDescs.Num() > 1)
{
UE_LOG(LogStateTree, Error, TEXT("%hs: Found %d entries for handle %s."), __FUNCTION__, FoundDescs.Num(), *SourceDataHandle.Describe());
}
return SourceStructs.FindByPredicate([SourceDataHandle](const FStateTreeBindableStructDesc& Desc)
{
return Desc.DataHandle == SourceDataHandle;
});
}
bool FStateTreePropertyBindings::ResolvePaths()
{
PropertyIndirections.Reset();
PropertyCopies.SetNum(PropertyPathBindings.Num());
bBindingsResolved = true;
bool bResult = true;
for (const FStateTreePropertyCopyBatch& Batch : CopyBatches)
{
for (int32 i = Batch.BindingsBegin; i != Batch.BindingsEnd; i++)
{
const FStateTreePropertyPathBinding& Binding = PropertyPathBindings[i];
FStateTreePropertyCopy& Copy = PropertyCopies[i];
Copy.SourceDataHandle = Binding.GetSourceDataHandle();
if (!Binding.GetSourceDataHandle().IsValid())
{
UE_LOG(LogStateTree, Error, TEXT("%hs: Invalid source struct for property binding %s."), __FUNCTION__, *Binding.GetSourcePath().ToString());
Copy.Type = EStateTreePropertyCopyType::None;
bBindingsResolved = false;
bResult = false;
continue;
}
const FStateTreeBindableStructDesc* SourceDesc = GetSourceDescByHandle(Copy.SourceDataHandle);
if (!SourceDesc)
{
UE_LOG(LogStateTree, Error, TEXT("%hs: Could not find data source for binding %s."), __FUNCTION__, *Binding.GetSourcePath().ToString());
Copy.Type = EStateTreePropertyCopyType::None;
bBindingsResolved = false;
bResult = false;
continue;
}
const UStruct* SourceStruct = SourceDesc->Struct;
const UStruct* TargetStruct = Batch.TargetStruct.Struct;
if (!SourceStruct || !TargetStruct)
{
Copy.Type = EStateTreePropertyCopyType::None;
bBindingsResolved = false;
bResult = false;
continue;
}
Copy.SourceStructType = SourceStruct;
// Resolve paths and validate the copy. Stops on first failure.
bool bSuccess = true;
FStateTreePropertyPathIndirection SourceLeafIndirection;
FStateTreePropertyPathIndirection TargetLeafIndirection;
bSuccess = bSuccess && ResolvePath(SourceStruct, Binding.GetSourcePath(), Copy.SourceIndirection, SourceLeafIndirection);
bSuccess = bSuccess && ResolvePath(TargetStruct, Binding.GetTargetPath(), Copy.TargetIndirection, TargetLeafIndirection);
bSuccess = bSuccess && ResolveCopyType(SourceLeafIndirection, TargetLeafIndirection, Copy);
if (!bSuccess)
{
// Resolving or validating failed, make the copy a nop.
Copy.Type = EStateTreePropertyCopyType::None;
bResult = false;
}
}
}
PropertyAccesses.Reset();
PropertyAccesses.Reserve(PropertyReferencePaths.Num());
for (const FStateTreePropertyRefPath& ReferencePath : PropertyReferencePaths)
{
FStateTreePropertyAccess& PropertyAccess = PropertyAccesses.AddDefaulted_GetRef();
PropertyAccess.SourceDataHandle = ReferencePath.GetSourceDataHandle();
const FStateTreeBindableStructDesc* SourceDesc = GetSourceDescByHandle(PropertyAccess.SourceDataHandle);
PropertyAccess.SourceStructType = SourceDesc->Struct;
FStateTreePropertyPathIndirection SourceLeafIndirection;
if (!ResolvePath(SourceDesc->Struct, ReferencePath.GetSourcePath(), PropertyAccess.SourceIndirection, SourceLeafIndirection))
{
bResult = false;
}
PropertyAccess.SourceLeafProperty = SourceLeafIndirection.GetProperty();
}
return bResult;
}
bool FStateTreePropertyBindings::ResolvePath(const UStruct* Struct, const FStateTreePropertyPath& Path, TArray<FStateTreePropertyIndirection>& OutIndirections, FStateTreePropertyIndirection& OutFirstIndirection, FStateTreePropertyPathIndirection& OutLeafIndirection)
{
if (!Struct)
{
UE_LOG(LogStateTree, Error, TEXT("%hs: '%s' Invalid source struct."), __FUNCTION__, *Path.ToString());
return false;
}
FString Error;
TArray<FStateTreePropertyPathIndirection> PathIndirections;
if (!Path.ResolveIndirections(Struct, PathIndirections, &Error))
{
UE_LOG(LogStateTree, Error, TEXT("%hs: %s"), __FUNCTION__, *Error);
return false;
}
// Casts array index to FStateTreeIndex16 (clamping INDEX_NONE to 0) or returns false if out if index bounds.
auto CastArrayIndexToIndex16 = [](const int32 Index)
{
const int32 ClampedIndex = FMath::Max(0, Index);
if (!FStateTreeIndex16::IsValidIndex(ClampedIndex))
{
return FStateTreeIndex16();
}
return FStateTreeIndex16(ClampedIndex);
};
TArray<FStateTreePropertyIndirection, TInlineAllocator<16>> TempIndirections;
for (FStateTreePropertyPathIndirection& PathIndirection : PathIndirections)
{
FStateTreePropertyIndirection& Indirection = TempIndirections.AddDefaulted_GetRef();
check(PathIndirection.GetPropertyOffset() >= MIN_uint16 && PathIndirection.GetPropertyOffset() <= MAX_uint16);
Indirection.Offset = static_cast<uint16>(PathIndirection.GetPropertyOffset());
Indirection.Type = PathIndirection.GetAccessType();
if (Indirection.Type == EStateTreePropertyAccessType::IndexArray)
{
if (const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(PathIndirection.GetProperty()))
{
Indirection.ArrayProperty = ArrayProperty;
Indirection.ArrayIndex = CastArrayIndexToIndex16(PathIndirection.GetArrayIndex());
if (!Indirection.ArrayIndex.IsValid())
{
UE_LOG(LogStateTree, Error, TEXT("%hs: Array index %d at %s, is too large."),
__FUNCTION__, PathIndirection.GetArrayIndex(), *Path.ToString(PathIndirection.GetPathSegmentIndex(), TEXT("<"), TEXT(">")));
return false;
}
}
else
{
UE_LOG(LogStateTree, Error, TEXT("%hs: Expect property %s to be array property."),
__FUNCTION__, *Path.ToString(PathIndirection.GetPathSegmentIndex(), TEXT("<"), TEXT(">")));
return false;
}
}
else if (Indirection.Type == EStateTreePropertyAccessType::StructInstance
|| Indirection.Type == EStateTreePropertyAccessType::ObjectInstance)
{
if (PathIndirection.GetInstanceStruct())
{
Indirection.InstanceStruct = PathIndirection.GetInstanceStruct();
}
else
{
UE_LOG(LogStateTree, Error, TEXT("%hs: Expect instanced property access %s to have instance type specified."),
__FUNCTION__, *Path.ToString(PathIndirection.GetPathSegmentIndex(), TEXT("<"), TEXT(">")));
return false;
}
}
}
if (TempIndirections.Num() > 0)
{
for (int32 Index = 0; Index < TempIndirections.Num(); Index++)
{
FStateTreePropertyIndirection& Indirection = TempIndirections[Index];
if ((Index + 1) < TempIndirections.Num())
{
const FStateTreePropertyIndirection& NextIndirection = TempIndirections[Index + 1];
if (Indirection.Type == EStateTreePropertyAccessType::Offset
&& NextIndirection.Type == EStateTreePropertyAccessType::Offset)
{
// Collapse adjacent offset indirections
Indirection.Offset += NextIndirection.Offset;
TempIndirections.RemoveAt(Index + 1);
Index--;
}
else if (Indirection.Type == EStateTreePropertyAccessType::IndexArray
&& NextIndirection.Type == EStateTreePropertyAccessType::Offset
&& NextIndirection.Offset == 0)
{
// Remove empty offset after array indexing.
TempIndirections.RemoveAt(Index + 1);
Index--;
}
else if (Indirection.Type == EStateTreePropertyAccessType::StructInstance
&& NextIndirection.Type == EStateTreePropertyAccessType::Offset
&& NextIndirection.Offset == 0)
{
// Remove empty offset after struct indirection.
TempIndirections.RemoveAt(Index + 1);
Index--;
}
else if ((Indirection.Type == EStateTreePropertyAccessType::Object
|| Indirection.Type == EStateTreePropertyAccessType::ObjectInstance)
&& NextIndirection.Type == EStateTreePropertyAccessType::Offset
&& NextIndirection.Offset == 0)
{
// Remove empty offset after object indirection.
TempIndirections.RemoveAt(Index + 1);
Index--;
}
}
}
OutLeafIndirection = PathIndirections.Last();
// Store indirections
OutFirstIndirection = TempIndirections[0];
FStateTreePropertyIndirection* PrevIndirection = &OutFirstIndirection;
for (int32 Index = 1; Index < TempIndirections.Num(); Index++)
{
const int32 IndirectionIndex = OutIndirections.Num();
PrevIndirection->NextIndex = FStateTreeIndex16(IndirectionIndex); // Set PrevIndirection before array add, as it can invalidate the pointer.
FStateTreePropertyIndirection& NewIndirection = OutIndirections.Add_GetRef(TempIndirections[Index]);
PrevIndirection = &NewIndirection;
}
}
else
{
// Indirections can be empty in case we're directly binding to source structs.
// Zero offset will return the struct itself.
OutFirstIndirection.Offset = 0;
OutFirstIndirection.Type = EStateTreePropertyAccessType::Offset;
OutLeafIndirection = FStateTreePropertyPathIndirection(Struct);
}
return true;
}
bool FStateTreePropertyBindings::ResolveCopyType(const FStateTreePropertyPathIndirection& SourceIndirection,const FStateTreePropertyPathIndirection& TargetIndirection, FStateTreePropertyCopy& OutCopy)
{
// @todo: see if GetPropertyCompatibility() can be implemented as call to ResolveCopyType() instead so that we write this logic just once.
const FProperty* SourceProperty = SourceIndirection.GetProperty();
const UStruct* SourceStruct = SourceIndirection.GetContainerStruct();
const FProperty* TargetProperty = TargetIndirection.GetProperty();
const UStruct* TargetStruct = TargetIndirection.GetContainerStruct();
if (!SourceStruct || !TargetStruct)
{
return false;
}
OutCopy.SourceLeafProperty = SourceProperty;
OutCopy.TargetLeafProperty = TargetProperty;
OutCopy.CopySize = 0;
OutCopy.Type = EStateTreePropertyCopyType::None;
if (SourceProperty == nullptr)
{
// Copy directly from the source struct, target must be.
if (const FStructProperty* TargetStructProperty = CastField<FStructProperty>(TargetProperty))
{
if (TargetStructProperty->Struct == SourceStruct)
{
OutCopy.Type = EStateTreePropertyCopyType::CopyStruct;
return true;
}
}
else if (const FObjectPropertyBase* TargetObjectProperty = CastField<FObjectPropertyBase>(TargetProperty))
{
if (SourceStruct->IsChildOf(TargetObjectProperty->PropertyClass))
{
OutCopy.Type = EStateTreePropertyCopyType::CopyObject;
return true;
}
}
return false;
}
// Handle FStateTreeStructRef
if (const FStructProperty* TargetStructProperty = CastField<const FStructProperty>(TargetProperty))
{
if (TargetStructProperty->Struct == TBaseStructure<FStateTreeStructRef>::Get())
{
if (const FStructProperty* SourceStructProperty = CastField<const FStructProperty>(SourceProperty))
{
// FStateTreeStructRef to FStateTreeStructRef is copied as usual.
if (SourceStructProperty->Struct != TBaseStructure<FStateTreeStructRef>::Get())
{
OutCopy.Type = EStateTreePropertyCopyType::StructReference;
return true;
}
}
}
}
const EStateTreePropertyAccessCompatibility Compatibility = FStateTreePropertyBindings::GetPropertyCompatibility(SourceProperty, TargetProperty);
// Extract underlying types for enums
if (const FEnumProperty* EnumPropertyA = CastField<const FEnumProperty>(SourceProperty))
{
SourceProperty = EnumPropertyA->GetUnderlyingProperty();
}
if (const FEnumProperty* EnumPropertyB = CastField<const FEnumProperty>(TargetProperty))
{
TargetProperty = EnumPropertyB->GetUnderlyingProperty();
}
if (Compatibility == EStateTreePropertyAccessCompatibility::Compatible)
{
if (CastField<FNameProperty>(TargetProperty))
{
OutCopy.Type = EStateTreePropertyCopyType::CopyName;
return true;
}
else if (CastField<FBoolProperty>(TargetProperty))
{
OutCopy.Type = EStateTreePropertyCopyType::CopyBool;
return true;
}
else if (CastField<FStructProperty>(TargetProperty))
{
OutCopy.Type = EStateTreePropertyCopyType::CopyStruct;
return true;
}
else if (CastField<FObjectPropertyBase>(TargetProperty))
{
if (SourceProperty->IsA<FSoftObjectProperty>()
&& TargetProperty->IsA<FSoftObjectProperty>())
{
// Use CopyComplex when copying soft object to another soft object so that we do not try to dereference the object (just copies the path).
// This handles soft class too.
OutCopy.Type = EStateTreePropertyCopyType::CopyComplex;
}
else
{
OutCopy.Type = EStateTreePropertyCopyType::CopyObject;
}
return true;
}
else if (CastField<FArrayProperty>(TargetProperty) && TargetProperty->HasAnyPropertyFlags(CPF_EditFixedSize))
{
// only apply array copying rules if the destination array is fixed size, otherwise it will be 'complex'
OutCopy.Type = EStateTreePropertyCopyType::CopyFixedArray;
return true;
}
else if (TargetProperty->PropertyFlags & CPF_IsPlainOldData)
{
OutCopy.Type = EStateTreePropertyCopyType::CopyPlain;
OutCopy.CopySize = SourceProperty->ElementSize * SourceProperty->ArrayDim;
return true;
}
else
{
OutCopy.Type = EStateTreePropertyCopyType::CopyComplex;
return true;
}
}
else if (Compatibility == EStateTreePropertyAccessCompatibility::Promotable)
{
if (SourceProperty->IsA<FBoolProperty>())
{
if (TargetProperty->IsA<FByteProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToByte;
return true;
}
else if (TargetProperty->IsA<FIntProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToInt32;
return true;
}
else if (TargetProperty->IsA<FUInt32Property>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToUInt32;
return true;
}
else if (TargetProperty->IsA<FInt64Property>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToInt64;
return true;
}
else if (TargetProperty->IsA<FFloatProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToFloat;
return true;
}
else if (TargetProperty->IsA<FDoubleProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToDouble;
return true;
}
}
else if (SourceProperty->IsA<FByteProperty>())
{
if (TargetProperty->IsA<FIntProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteByteToInt32;
return true;
}
else if (TargetProperty->IsA<FUInt32Property>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteByteToUInt32;
return true;
}
else if (TargetProperty->IsA<FInt64Property>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteByteToInt64;
return true;
}
else if (TargetProperty->IsA<FFloatProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteByteToFloat;
return true;
}
else if (TargetProperty->IsA<FDoubleProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteByteToDouble;
return true;
}
}
else if (SourceProperty->IsA<FIntProperty>())
{
if (TargetProperty->IsA<FInt64Property>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteInt32ToInt64;
return true;
}
else if (TargetProperty->IsA<FFloatProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteInt32ToFloat;
return true;
}
else if (TargetProperty->IsA<FDoubleProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteInt32ToDouble;
return true;
}
}
else if (SourceProperty->IsA<FUInt32Property>())
{
if (TargetProperty->IsA<FInt64Property>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteUInt32ToInt64;
return true;
}
else if (TargetProperty->IsA<FFloatProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteUInt32ToFloat;
return true;
}
else if (TargetProperty->IsA<FDoubleProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteUInt32ToDouble;
return true;
}
}
else if (SourceProperty->IsA<FFloatProperty>())
{
if (TargetProperty->IsA<FIntProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteFloatToInt32;
return true;
}
else if (TargetProperty->IsA<FInt64Property>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteFloatToInt64;
return true;
}
else if (TargetProperty->IsA<FDoubleProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::PromoteFloatToDouble;
return true;
}
}
else if (SourceProperty->IsA<FDoubleProperty>())
{
if (TargetProperty->IsA<FIntProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::DemoteDoubleToInt32;
return true;
}
else if (TargetProperty->IsA<FInt64Property>())
{
OutCopy.Type = EStateTreePropertyCopyType::DemoteDoubleToInt64;
return true;
}
else if (TargetProperty->IsA<FFloatProperty>())
{
OutCopy.Type = EStateTreePropertyCopyType::DemoteDoubleToFloat;
return true;
}
}
}
return false;
}
EStateTreePropertyAccessCompatibility FStateTreePropertyBindings::GetPropertyCompatibility(const FProperty* FromProperty, const FProperty* ToProperty)
{
if (FromProperty == ToProperty)
{
return EStateTreePropertyAccessCompatibility::Compatible;
}
if (FromProperty == nullptr || ToProperty == nullptr)
{
return EStateTreePropertyAccessCompatibility::Incompatible;
}
// Special case for object properties since InPropertyA->SameType(InPropertyB) requires both properties to be of the exact same class.
// In our case we want to be able to bind a source property if its class is a child of the target property class.
if (FromProperty->IsA<FObjectPropertyBase>() && ToProperty->IsA<FObjectPropertyBase>())
{
const FObjectPropertyBase* SourceProperty = CastField<FObjectPropertyBase>(FromProperty);
const FObjectPropertyBase* TargetProperty = CastField<FObjectPropertyBase>(ToProperty);
return (SourceProperty->PropertyClass->IsChildOf(TargetProperty->PropertyClass)) ? EStateTreePropertyAccessCompatibility::Compatible : EStateTreePropertyAccessCompatibility::Incompatible;
}
// When copying to an enum property, expect FromProperty to be the same enum.
auto GetPropertyEnum = [](const FProperty* Property) -> const UEnum*
{
if (const FByteProperty* ByteProperty = CastField<FByteProperty>(Property))
{
return ByteProperty->GetIntPropertyEnum();
}
if (const FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property))
{
return EnumProperty->GetEnum();
}
return nullptr;
};
if (const UEnum* ToPropertyEnum = GetPropertyEnum(ToProperty))
{
const UEnum* FromPropertyEnum = GetPropertyEnum(FromProperty);
return (ToPropertyEnum == FromPropertyEnum) ? EStateTreePropertyAccessCompatibility::Compatible : EStateTreePropertyAccessCompatibility::Incompatible;
}
// Allow source enums to be promoted to numbers.
if (const FEnumProperty* EnumPropertyA = CastField<const FEnumProperty>(FromProperty))
{
FromProperty = EnumPropertyA->GetUnderlyingProperty();
}
if (FromProperty->SameType(ToProperty))
{
return EStateTreePropertyAccessCompatibility::Compatible;
}
else
{
// Not directly compatible, check for promotions
if (FromProperty->IsA<FBoolProperty>())
{
if (ToProperty->IsA<FByteProperty>()
|| ToProperty->IsA<FIntProperty>()
|| ToProperty->IsA<FUInt32Property>()
|| ToProperty->IsA<FInt64Property>()
|| ToProperty->IsA<FFloatProperty>()
|| ToProperty->IsA<FDoubleProperty>())
{
return EStateTreePropertyAccessCompatibility::Promotable;
}
}
else if (FromProperty->IsA<FByteProperty>())
{
if (ToProperty->IsA<FIntProperty>()
|| ToProperty->IsA<FUInt32Property>()
|| ToProperty->IsA<FInt64Property>()
|| ToProperty->IsA<FFloatProperty>()
|| ToProperty->IsA<FDoubleProperty>())
{
return EStateTreePropertyAccessCompatibility::Promotable;
}
}
else if (FromProperty->IsA<FIntProperty>())
{
if (ToProperty->IsA<FInt64Property>()
|| ToProperty->IsA<FFloatProperty>()
|| ToProperty->IsA<FDoubleProperty>())
{
return EStateTreePropertyAccessCompatibility::Promotable;
}
}
else if (FromProperty->IsA<FUInt32Property>())
{
if (ToProperty->IsA<FInt64Property>()
|| ToProperty->IsA<FFloatProperty>()
|| ToProperty->IsA<FDoubleProperty>())
{
return EStateTreePropertyAccessCompatibility::Promotable;
}
}
else if (FromProperty->IsA<FFloatProperty>())
{
if (ToProperty->IsA<FIntProperty>()
|| ToProperty->IsA<FInt64Property>()
|| ToProperty->IsA<FDoubleProperty>())
{
return EStateTreePropertyAccessCompatibility::Promotable;
}
}
else if (FromProperty->IsA<FDoubleProperty>())
{
if (ToProperty->IsA<FIntProperty>()
|| ToProperty->IsA<FInt64Property>()
|| ToProperty->IsA<FFloatProperty>())
{
return EStateTreePropertyAccessCompatibility::Promotable;
}
}
}
return EStateTreePropertyAccessCompatibility::Incompatible;
}
uint8* FStateTreePropertyBindings::GetAddress(FStateTreeDataView InStructView, TConstArrayView<FStateTreePropertyIndirection> Indirections, const FStateTreePropertyIndirection& FirstIndirection, const FProperty* LeafProperty)
{
uint8* Address = InStructView.GetMutableMemory();
if (Address == nullptr)
{
// Failed indirection, will be reported by caller.
return nullptr;
}
const FStateTreePropertyIndirection* Indirection = &FirstIndirection;
while (Indirection != nullptr && Address != nullptr)
{
switch (Indirection->Type)
{
case EStateTreePropertyAccessType::Offset:
{
Address = Address + Indirection->Offset;
break;
}
case EStateTreePropertyAccessType::Object:
{
UObject* Object = *reinterpret_cast<UObject**>(Address + Indirection->Offset);
Address = reinterpret_cast<uint8*>(Object);
break;
}
case EStateTreePropertyAccessType::WeakObject:
{
TWeakObjectPtr<UObject>& WeakObjectPtr = *reinterpret_cast<TWeakObjectPtr<UObject>*>(Address + Indirection->Offset);
UObject* Object = WeakObjectPtr.Get();
Address = reinterpret_cast<uint8*>(Object);
break;
}
case EStateTreePropertyAccessType::SoftObject:
{
FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast<FSoftObjectPtr*>(Address + Indirection->Offset);
UObject* Object = SoftObjectPtr.Get();
Address = reinterpret_cast<uint8*>(Object);
break;
}
case EStateTreePropertyAccessType::ObjectInstance:
{
check(Indirection->InstanceStruct);
UObject* Object = *reinterpret_cast<UObject**>(Address + Indirection->Offset);
if (Object
&& Object->GetClass()->IsChildOf(Indirection->InstanceStruct))
{
Address = reinterpret_cast<uint8*>(Object);
}
else
{
// Failed indirection, will be reported by caller.
return nullptr;
}
break;
}
case EStateTreePropertyAccessType::StructInstance:
{
check(Indirection->InstanceStruct);
FInstancedStruct& InstancedStruct = *reinterpret_cast<FInstancedStruct*>(Address + Indirection->Offset);
const UScriptStruct* InstanceType = InstancedStruct.GetScriptStruct();
if (InstanceType != nullptr
&& InstanceType->IsChildOf(Indirection->InstanceStruct))
{
Address = InstancedStruct.GetMutableMemory();
}
else
{
// Failed indirection, will be reported by caller.
return nullptr;
}
break;
}
case EStateTreePropertyAccessType::IndexArray:
{
check(Indirection->ArrayProperty);
FScriptArrayHelper Helper(Indirection->ArrayProperty, Address + Indirection->Offset);
if (Helper.IsValidIndex(Indirection->ArrayIndex.Get()))
{
Address = Helper.GetRawPtr(Indirection->ArrayIndex.Get());
}
else
{
// Failed indirection, will be reported by caller.
return nullptr;
}
break;
}
default:
ensureMsgf(false, TEXT("FStateTreePropertyBindings::GetAddress: Unhandled indirection type %s for '%s'"),
*StaticEnum<EStateTreePropertyAccessType>()->GetValueAsString(Indirection->Type), *LeafProperty->GetNameCPP());
}
Indirection = Indirection->NextIndex.IsValid() ? &Indirections[Indirection->NextIndex.Get()] : nullptr;
}
return Address;
}
void FStateTreePropertyBindings::PerformCopy(const FStateTreePropertyCopy& Copy, uint8* SourceAddress, uint8* TargetAddress) const
{
// Source property can be null
check(SourceAddress);
check(Copy.TargetLeafProperty);
check(TargetAddress);
switch (Copy.Type)
{
case EStateTreePropertyCopyType::CopyPlain:
FMemory::Memcpy(TargetAddress, SourceAddress, Copy.CopySize);
break;
case EStateTreePropertyCopyType::CopyComplex:
Copy.TargetLeafProperty->CopyCompleteValue(TargetAddress, SourceAddress);
break;
case EStateTreePropertyCopyType::CopyBool:
static_cast<const FBoolProperty*>(Copy.TargetLeafProperty)->SetPropertyValue(TargetAddress, static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress));
break;
case EStateTreePropertyCopyType::CopyStruct:
// If SourceProperty == nullptr (pointing to the struct source directly), the GetAddress() did the right thing and is pointing the the beginning of the struct.
static_cast<const FStructProperty*>(Copy.TargetLeafProperty)->Struct->CopyScriptStruct(TargetAddress, SourceAddress);
break;
case EStateTreePropertyCopyType::CopyObject:
if (Copy.SourceLeafProperty == nullptr)
{
// Source is pointing at object directly.
static_cast<const FObjectPropertyBase*>(Copy.TargetLeafProperty)->SetObjectPropertyValue(TargetAddress, (UObject*)SourceAddress);
}
else
{
static_cast<const FObjectPropertyBase*>(Copy.TargetLeafProperty)->SetObjectPropertyValue(TargetAddress, static_cast<const FObjectPropertyBase*>(Copy.SourceLeafProperty)->GetObjectPropertyValue(SourceAddress));
}
break;
case EStateTreePropertyCopyType::CopyName:
static_cast<const FNameProperty*>(Copy.TargetLeafProperty)->SetPropertyValue(TargetAddress, static_cast<const FNameProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress));
break;
case EStateTreePropertyCopyType::CopyFixedArray:
{
// Copy into fixed sized array (EditFixedSize). Resizable arrays are copied as Complex, and regular fixed sizes arrays via the regular copies (dim specifies array size).
const FArrayProperty* SourceArrayProperty = static_cast<const FArrayProperty*>(Copy.SourceLeafProperty);
const FArrayProperty* TargetArrayProperty = static_cast<const FArrayProperty*>(Copy.TargetLeafProperty);
FScriptArrayHelper SourceArrayHelper(SourceArrayProperty, SourceAddress);
FScriptArrayHelper TargetArrayHelper(TargetArrayProperty, TargetAddress);
const int32 MinSize = FMath::Min(SourceArrayHelper.Num(), TargetArrayHelper.Num());
for (int32 ElementIndex = 0; ElementIndex < MinSize; ++ElementIndex)
{
TargetArrayProperty->Inner->CopySingleValue(TargetArrayHelper.GetRawPtr(ElementIndex), SourceArrayHelper.GetRawPtr(ElementIndex));
}
break;
}
case EStateTreePropertyCopyType::StructReference:
{
const FStructProperty* SourceStructProperty = static_cast<const FStructProperty*>(Copy.SourceLeafProperty);
FStateTreeStructRef* Target = (FStateTreeStructRef*)TargetAddress;
Target->Set(FStructView(SourceStructProperty->Struct, SourceAddress));
break;
}
// Bool promotions
case EStateTreePropertyCopyType::PromoteBoolToByte:
*reinterpret_cast<uint8*>(TargetAddress) = (uint8)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteBoolToInt32:
*reinterpret_cast<int32*>(TargetAddress) = (int32)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteBoolToUInt32:
*reinterpret_cast<uint32*>(TargetAddress) = (uint32)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteBoolToInt64:
*reinterpret_cast<int64*>(TargetAddress) = (int64)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteBoolToFloat:
*reinterpret_cast<float*>(TargetAddress) = (float)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteBoolToDouble:
*reinterpret_cast<double*>(TargetAddress) = (double)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
break;
// Byte promotions
case EStateTreePropertyCopyType::PromoteByteToInt32:
*reinterpret_cast<int32*>(TargetAddress) = (int32)*reinterpret_cast<const uint8*>(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteByteToUInt32:
*reinterpret_cast<uint32*>(TargetAddress) = (uint32)*reinterpret_cast<const uint8*>(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteByteToInt64:
*reinterpret_cast<int64*>(TargetAddress) = (int64)*reinterpret_cast<const uint8*>(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteByteToFloat:
*reinterpret_cast<float*>(TargetAddress) = (float)*reinterpret_cast<const uint8*>(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteByteToDouble:
*reinterpret_cast<double*>(TargetAddress) = (double)*reinterpret_cast<const uint8*>(SourceAddress);
break;
// Int32 promotions
case EStateTreePropertyCopyType::PromoteInt32ToInt64:
*reinterpret_cast<int64*>(TargetAddress) = (int64)*reinterpret_cast<const int32*>(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteInt32ToFloat:
*reinterpret_cast<float*>(TargetAddress) = (float)*reinterpret_cast<const int32*>(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteInt32ToDouble:
*reinterpret_cast<double*>(TargetAddress) = (double)*reinterpret_cast<const int32*>(SourceAddress);
break;
// Uint32 promotions
case EStateTreePropertyCopyType::PromoteUInt32ToInt64:
*reinterpret_cast<int64*>(TargetAddress) = (int64)*reinterpret_cast<const uint32*>(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteUInt32ToFloat:
*reinterpret_cast<float*>(TargetAddress) = (float)*reinterpret_cast<const uint32*>(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteUInt32ToDouble:
*reinterpret_cast<double*>(TargetAddress) = (double)*reinterpret_cast<const uint32*>(SourceAddress);
break;
// Float promotions
case EStateTreePropertyCopyType::PromoteFloatToInt32:
*reinterpret_cast<int32*>(TargetAddress) = (int32)*reinterpret_cast<const float*>(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteFloatToInt64:
*reinterpret_cast<int64*>(TargetAddress) = (int64)*reinterpret_cast<const float*>(SourceAddress);
break;
case EStateTreePropertyCopyType::PromoteFloatToDouble:
*reinterpret_cast<double*>(TargetAddress) = (double)*reinterpret_cast<const float*>(SourceAddress);
break;
// Double promotions
case EStateTreePropertyCopyType::DemoteDoubleToInt32:
*reinterpret_cast<int32*>(TargetAddress) = (int32)*reinterpret_cast<const double*>(SourceAddress);
break;
case EStateTreePropertyCopyType::DemoteDoubleToInt64:
*reinterpret_cast<int64*>(TargetAddress) = (int64)*reinterpret_cast<const double*>(SourceAddress);
break;
case EStateTreePropertyCopyType::DemoteDoubleToFloat:
*reinterpret_cast<float*>(TargetAddress) = (float)*reinterpret_cast<const double*>(SourceAddress);
break;
default:
ensureMsgf(false, TEXT("FStateTreePropertyBindings::PerformCopy: Unhandled copy type %s between '%s' and '%s'"),
*StaticEnum<EStateTreePropertyCopyType>()->GetValueAsString(Copy.Type), *Copy.SourceLeafProperty->GetNameCPP(), *Copy.TargetLeafProperty->GetNameCPP());
break;
}
}
bool FStateTreePropertyBindings::CopyProperty(const FStateTreePropertyCopy& Copy, FStateTreeDataView SourceStructView, FStateTreeDataView TargetStructView) const
{
// This is made ensure so that the programmers have the change to catch it (it's usually programming error not to call ResolvePaths(), and it wont spam log for others.
if (!ensureMsgf(bBindingsResolved, TEXT("Bindings must be resolved successfully before copying. See ResolvePaths()")))
{
return false;
}
// Copies that fail to be resolved (i.e. property path does not resolve, types changed) will be marked as None, skip them.
if (Copy.Type == EStateTreePropertyCopyType::None)
{
return true;
}
bool bResult = true;
if (SourceStructView.IsValid() && TargetStructView.IsValid())
{
check(SourceStructView.GetStruct() == Copy.SourceStructType
|| (SourceStructView.GetStruct() && SourceStructView.GetStruct()->IsChildOf(Copy.SourceStructType)));
uint8* SourceAddress = GetAddress(SourceStructView, Copy.SourceIndirection, Copy.SourceLeafProperty);
uint8* TargetAddress = GetAddress(TargetStructView, Copy.TargetIndirection, Copy.TargetLeafProperty);
if (SourceAddress != nullptr && TargetAddress != nullptr)
{
PerformCopy(Copy, SourceAddress, TargetAddress);
}
else
{
bResult = false;
}
}
else
{
bResult = false;
}
return bResult;
}
void FStateTreePropertyBindings::PerformResetObjects(const FStateTreePropertyCopy& Copy, uint8* TargetAddress) const
{
// Source property can be null
check(Copy.TargetLeafProperty);
check(TargetAddress);
switch (Copy.Type)
{
case EStateTreePropertyCopyType::CopyComplex:
Copy.TargetLeafProperty->InitializeValue(TargetAddress);
break;
case EStateTreePropertyCopyType::CopyStruct:
static_cast<const FStructProperty*>(Copy.TargetLeafProperty)->Struct->ClearScriptStruct(TargetAddress);
break;
case EStateTreePropertyCopyType::CopyObject:
static_cast<const FObjectPropertyBase*>(Copy.TargetLeafProperty)->SetObjectPropertyValue(TargetAddress, nullptr);
break;
case EStateTreePropertyCopyType::StructReference:
reinterpret_cast<FStateTreeStructRef*>(TargetAddress)->Set(FStructView());
break;
case EStateTreePropertyCopyType::CopyName:
break;
case EStateTreePropertyCopyType::CopyFixedArray:
{
// Copy into fixed sized array (EditFixedSize). Resizable arrays are copied as Complex, and regular fixed sizes arrays via the regular copies (dim specifies array size).
const FArrayProperty* TargetArrayProperty = static_cast<const FArrayProperty*>(Copy.TargetLeafProperty);
FScriptArrayHelper TargetArrayHelper(TargetArrayProperty, TargetAddress);
for (int32 ElementIndex = 0; ElementIndex < TargetArrayHelper.Num(); ++ElementIndex)
{
TargetArrayProperty->Inner->InitializeValue(TargetArrayHelper.GetRawPtr(ElementIndex));
}
break;
}
default:
break;
}
}
const FStateTreePropertyAccess* FStateTreePropertyBindings::GetPropertyAccess(const FStateTreePropertyRef& PropertyRef) const
{
if (!PropertyRef.GetRefAccessIndex().IsValid())
{
return nullptr;
}
if (!ensure(PropertyAccesses.IsValidIndex(PropertyRef.GetRefAccessIndex().Get())))
{
return nullptr;
}
return &PropertyAccesses[PropertyRef.GetRefAccessIndex().Get()];
}
bool FStateTreePropertyBindings::ResetObjects(const FStateTreeIndex16 TargetBatchIndex, FStateTreeDataView TargetStructView) const
{
// This is made ensure so that the programmers have the change to catch it (it's usually programming error not to call ResolvePaths(), and it wont spam log for others.
if (!ensureMsgf(bBindingsResolved, TEXT("Bindings must be resolved successfully before copying. See ResolvePaths()")))
{
return false;
}
if (TargetBatchIndex.IsValid() == false)
{
return false;
}
check(CopyBatches.IsValidIndex(TargetBatchIndex.Get()));
const FStateTreePropertyCopyBatch& Batch = CopyBatches[TargetBatchIndex.Get()];
check(TargetStructView.IsValid());
check(TargetStructView.GetStruct() == Batch.TargetStruct.Struct);
bool bResult = true;
for (int32 i = Batch.BindingsBegin; i != Batch.BindingsEnd; i++)
{
const FStateTreePropertyCopy& Copy = PropertyCopies[i];
// Copies that fail to be resolved (i.e. property path does not resolve, types changed) will be marked as None, skip them.
if (Copy.Type == EStateTreePropertyCopyType::None)
{
continue;
}
uint8* TargetAddress = GetAddress(TargetStructView, Copy.TargetIndirection, Copy.TargetLeafProperty);
check(TargetAddress != nullptr);
PerformResetObjects(Copy, TargetAddress);
}
return bResult;
}
bool FStateTreePropertyBindings::ContainsAnyStruct(const TSet<const UStruct*>& Structs)
{
for (FStateTreeBindableStructDesc& SourceStruct : SourceStructs)
{
if (Structs.Contains(SourceStruct.Struct))
{
return true;
}
}
for (FStateTreePropertyCopyBatch& CopyBatch : CopyBatches)
{
if (Structs.Contains(CopyBatch.TargetStruct.Struct))
{
return true;
}
}
auto PathContainsStruct = [&Structs](const FStateTreePropertyPath& PropertyPath)
{
for (const FStateTreePropertyPathSegment& Segment : PropertyPath.GetSegments())
{
if (Structs.Contains(Segment.GetInstanceStruct()))
{
return true;
}
}
return false;
};
for (FStateTreePropertyPathBinding& PropertyPathBinding : PropertyPathBindings)
{
if (PathContainsStruct(PropertyPathBinding.GetSourcePath()))
{
return true;
}
if (PathContainsStruct(PropertyPathBinding.GetTargetPath()))
{
return true;
}
}
return false;
}
void FStateTreePropertyBindings::DebugPrintInternalLayout(FString& OutString) const
{
/** Array of expected source structs. */
OutString += FString::Printf(TEXT("\nBindableStructDesc (%d)\n [ %-40s | %-40s ]\n"), SourceStructs.Num(), TEXT("Type"), TEXT("Name"));
for (const FStateTreeBindableStructDesc& BindableStructDesc : SourceStructs)
{
OutString += FString::Printf(TEXT(" | %-40s | %-40s |\n"),
BindableStructDesc.Struct ? *BindableStructDesc.Struct->GetName() : TEXT("null"),
*BindableStructDesc.Name.ToString());
}
/** Array of copy batches. */
OutString += FString::Printf(TEXT("\nCopyBatches (%d)\n [ %-40s | %-40s | %-8s [%-3s:%-3s[ ]\n"), CopyBatches.Num(),
TEXT("Target Type"), TEXT("Target Name"), TEXT("Bindings"), TEXT("Beg"), TEXT("End"));
for (const FStateTreePropertyCopyBatch& CopyBatch : CopyBatches)
{
OutString += FString::Printf(TEXT(" | %-40s | %-40s | %8s [%3d:%-3d[ |\n"),
CopyBatch.TargetStruct.Struct ? *CopyBatch.TargetStruct.Struct->GetName() : TEXT("null"),
*CopyBatch.TargetStruct.Name.ToString(),
TEXT(""), CopyBatch.BindingsBegin, CopyBatch.BindingsEnd);
}
/** Array of property bindings, resolved into arrays of copies before use. */
OutString += FString::Printf(TEXT("\nPropertyPathBindings (%d)\n"), PropertyPathBindings.Num());
for (const FStateTreePropertyPathBinding& PropertyBinding : PropertyPathBindings)
{
OutString += FString::Printf(TEXT("\n Source: %s | Target: %s"),
*PropertyBinding.GetSourcePath().ToString(), *PropertyBinding.GetSourcePath().ToString());
}
/** Array of property copies */
OutString += FString::Printf(TEXT("\nPropertyCopies (%d)\n [ %-7s | %-4s | %-4s | %-10s | %-7s | %-4s | %-4s | %-10s | %-10s | %-20s | %-4s ]\n"), PropertyCopies.Num(),
TEXT("Src Idx"), TEXT("Off."), TEXT("Next"), TEXT("Type"),
TEXT("Tgt Idx"), TEXT("Off."), TEXT("Next"), TEXT("Type"),
TEXT("Source"), TEXT("Copy Type"), TEXT("Size"));
for (const FStateTreePropertyCopy& PropertyCopy : PropertyCopies)
{
OutString += FString::Printf(TEXT(" | %7d | %4d | %4d | %-10s | %7d | %4d | %4d | %-10s | %10s | %-20s | %4d |\n"),
PropertyCopy.SourceIndirection.ArrayIndex.Get(),
PropertyCopy.SourceIndirection.Offset,
PropertyCopy.SourceIndirection.NextIndex.Get(),
*UEnum::GetDisplayValueAsText(PropertyCopy.SourceIndirection.Type).ToString(),
PropertyCopy.TargetIndirection.ArrayIndex.Get(),
PropertyCopy.TargetIndirection.Offset,
PropertyCopy.TargetIndirection.NextIndex.Get(),
*UEnum::GetDisplayValueAsText(PropertyCopy.TargetIndirection.Type).ToString(),
*PropertyCopy.SourceDataHandle.Describe(),
*UEnum::GetDisplayValueAsText(PropertyCopy.Type).ToString(),
PropertyCopy.CopySize);
}
/** Array of property indirections, indexed by accesses*/
OutString += FString::Printf(TEXT("\nPropertyIndirections (%d)\n [ %-4s | %-4s | %-4s | %-10s ] \n"), PropertyIndirections.Num(),
TEXT("Idx"), TEXT("Off."), TEXT("Next"), TEXT("Access Type"));
for (const FStateTreePropertyIndirection& PropertyIndirection : PropertyIndirections)
{
OutString += FString::Printf(TEXT(" | %4d | %4d | %4d | %-10s |\n"),
PropertyIndirection.ArrayIndex.Get(),
PropertyIndirection.Offset,
PropertyIndirection.NextIndex.Get(),
*UEnum::GetDisplayValueAsText(PropertyIndirection.Type).ToString());
}
}
//----------------------------------------------------------------//
// FStateTreePropertyPath
//----------------------------------------------------------------//
bool FStateTreePropertyPath::FromString(const FString& InPath)
{
Segments.Reset();
if (InPath.IsEmpty())
{
return true;
}
bool bResult = true;
TArray<FString> PathSegments;
InPath.ParseIntoArray(PathSegments, TEXT("."), /*InCullEmpty=*/false);
for (const FString& Segment : PathSegments)
{
if (Segment.IsEmpty())
{
bResult = false;
break;
}
int32 FirstBracket = INDEX_NONE;
int32 LastBracket = INDEX_NONE;
if (Segment.FindChar(TEXT('['), FirstBracket)
&& Segment.FindLastChar(TEXT(']'), LastBracket))
{
const int32 NameStringLength = FirstBracket;
const int32 IndexStringLength = LastBracket - FirstBracket - 1;
if (NameStringLength < 1
|| IndexStringLength <= 0)
{
bResult = false;
break;
}
const FString NameString = Segment.Left(FirstBracket);
const FString IndexString = Segment.Mid(FirstBracket + 1, IndexStringLength);
int32 ArrayIndex = INDEX_NONE;
LexFromString(ArrayIndex, *IndexString);
if (ArrayIndex < 0)
{
bResult = false;
break;
}
AddPathSegment(FName(NameString), ArrayIndex);
}
else
{
AddPathSegment(FName(Segment));
}
}
if (!bResult)
{
Segments.Reset();
}
return bResult;
}
bool FStateTreePropertyPath::UpdateSegments(const UStruct* BaseStruct, FString* OutError)
{
return UpdateSegmentsFromValue(FStateTreeDataView(BaseStruct, nullptr), OutError);
}
bool FStateTreePropertyPath::UpdateSegmentsFromValue(const FStateTreeDataView BaseValueView, FString* OutError)
{
TArray<FStateTreePropertyPathIndirection> Indirections;
if (!ResolveIndirectionsWithValue(BaseValueView, Indirections, OutError, /*bHandleRedirects*/true))
{
return false;
}
for (FStateTreePropertyPathSegment& Segment : Segments)
{
Segment.SetInstanceStruct(nullptr);
}
for (const FStateTreePropertyPathIndirection& Indirection : Indirections)
{
if (Indirection.InstanceStruct != nullptr)
{
Segments[Indirection.PathSegmentIndex].SetInstanceStruct(Indirection.InstanceStruct);
}
#if WITH_EDITORONLY_DATA
if (!Indirection.GetRedirectedName().IsNone())
{
Segments[Indirection.PathSegmentIndex].SetName(Indirection.GetRedirectedName());
}
Segments[Indirection.PathSegmentIndex].SetPropertyGuid(Indirection.GetPropertyGuid());
#endif
}
return true;
}
FString FStateTreePropertyPath::ToString(const int32 HighlightedSegment, const TCHAR* HighlightPrefix, const TCHAR* HighlightPostfix, const bool bOutputInstances) const
{
FString Result;
for (TEnumerateRef<const FStateTreePropertyPathSegment> Segment : EnumerateRange(Segments))
{
if (Segment.GetIndex() > 0)
{
Result += TEXT(".");
}
if (Segment.GetIndex() == HighlightedSegment && HighlightPrefix)
{
Result += HighlightPrefix;
}
if (bOutputInstances && Segment->GetInstanceStruct())
{
Result += FString::Printf(TEXT("(%s)"), *GetNameSafe(Segment->GetInstanceStruct()));
}
if (Segment->GetArrayIndex() >= 0)
{
Result += FString::Printf(TEXT("%s[%d]"), *Segment->GetName().ToString(), Segment->GetArrayIndex());
}
else
{
Result += Segment->GetName().ToString();
}
if (Segment.GetIndex() == HighlightedSegment && HighlightPostfix)
{
Result += HighlightPostfix;
}
}
return Result;
}
bool FStateTreePropertyPath::ResolveIndirections(const UStruct* BaseStruct, TArray<FStateTreePropertyPathIndirection>& OutIndirections, FString* OutError, bool bHandleRedirects) const
{
return ResolveIndirectionsWithValue(FStateTreeDataView(BaseStruct, nullptr), OutIndirections, OutError, bHandleRedirects);
}
bool FStateTreePropertyPath::ResolveIndirectionsWithValue(const FStateTreeDataView BaseValueView, TArray<FStateTreePropertyPathIndirection>& OutIndirections, FString* OutError, bool bHandleRedirects) const
{
OutIndirections.Reset();
if (OutError)
{
OutError->Reset();
}
// Nothing to do for an empty path.
if (IsPathEmpty())
{
return true;
}
const uint8* CurrentAddress = BaseValueView.GetMemory();
const UStruct* CurrentStruct = BaseValueView.GetStruct();
for (const TEnumerateRef<const FStateTreePropertyPathSegment> Segment : EnumerateRange(Segments))
{
if (CurrentStruct == nullptr)
{
if (OutError)
{
*OutError = FString::Printf(TEXT("Malformed path '%s'."),
*ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
}
OutIndirections.Reset();
return false;
}
const FProperty* Property = CurrentStruct->FindPropertyByName(Segment->GetName());
const bool bWithValue = CurrentAddress != nullptr;
#if WITH_EDITORONLY_DATA
FName RedirectedName;
FGuid PropertyGuid = Segment->GetPropertyGuid();
// Try to fix the path in editor.
if (bHandleRedirects)
{
// Check if there's a core redirect for it.
if (!Property)
{
// Try to match by property ID (Blueprint or User Defined Struct).
if (Segment->GetPropertyGuid().IsValid())
{
if (const UBlueprintGeneratedClass* BlueprintClass = Cast<UBlueprintGeneratedClass>(CurrentStruct))
{
if (const FName* Name = BlueprintClass->PropertyGuids.FindKey(Segment->GetPropertyGuid()))
{
RedirectedName = *Name;
Property = CurrentStruct->FindPropertyByName(RedirectedName);
}
}
else if (const UUserDefinedStruct* UserDefinedStruct = Cast<UUserDefinedStruct>(CurrentStruct))
{
if (FProperty* FoundProperty = FStructureEditorUtils::GetPropertyByGuid(UserDefinedStruct, Segment->GetPropertyGuid()))
{
RedirectedName = FoundProperty->GetFName();
Property = FoundProperty;
}
}
else if (const UPropertyBag* PropertyBag = Cast<UPropertyBag>(CurrentStruct))
{
if (const FPropertyBagPropertyDesc* Desc = PropertyBag->FindPropertyDescByID(Segment->GetPropertyGuid()))
{
if (Desc->CachedProperty)
{
RedirectedName = Desc->CachedProperty->GetFName();
Property = Desc->CachedProperty;
}
}
}
}
else
{
// Try core redirect
const FCoreRedirectObjectName OldPropertyName(Segment->GetName(), CurrentStruct->GetFName(), *CurrentStruct->GetOutermost()->GetPathName());
const FCoreRedirectObjectName NewPropertyName = FCoreRedirects::GetRedirectedName(ECoreRedirectFlags::Type_Property, OldPropertyName);
if (OldPropertyName != NewPropertyName)
{
// Cached the result for later use.
RedirectedName = NewPropertyName.ObjectName;
Property = CurrentStruct->FindPropertyByName(RedirectedName);
}
}
}
// Update PropertyGuid
if (Property)
{
const FName PropertyName = !RedirectedName.IsNone() ? RedirectedName : Segment->GetName();
if (const UBlueprintGeneratedClass* BlueprintClass = Cast<UBlueprintGeneratedClass>(CurrentStruct))
{
if (const FGuid* VarGuid = BlueprintClass->PropertyGuids.Find(PropertyName))
{
PropertyGuid = *VarGuid;
}
}
else if (const UUserDefinedStruct* UserDefinedStruct = Cast<UUserDefinedStruct>(CurrentStruct))
{
// Parse Guid from UDS property name.
PropertyGuid = FStructureEditorUtils::GetGuidFromPropertyName(PropertyName);
}
else if (const UPropertyBag* PropertyBag = Cast<UPropertyBag>(CurrentStruct))
{
if (const FPropertyBagPropertyDesc* Desc = PropertyBag->FindPropertyDescByPropertyName(PropertyName))
{
PropertyGuid = Desc->ID;
}
}
}
}
#endif // WITH_EDITORONLY_DATA
if (!Property)
{
if (OutError)
{
*OutError = FString::Printf(TEXT("Malformed path '%s', could not find property '%s%s::%s'."),
*ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")),
CurrentStruct->GetPrefixCPP(), *CurrentStruct->GetName(), *Segment->GetName().ToString());
}
OutIndirections.Reset();
return false;
}
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property);
int ArrayIndex = 0;
int32 Offset = 0;
if (ArrayProperty && Segment->GetArrayIndex() != INDEX_NONE)
{
FStateTreePropertyPathIndirection& Indirection = OutIndirections.AddDefaulted_GetRef();
Indirection.Property = Property;
Indirection.ContainerAddress = CurrentAddress;
Indirection.ContainerStruct = CurrentStruct;
Indirection.InstanceStruct = nullptr;
Indirection.ArrayIndex = Segment->GetArrayIndex();
Indirection.PropertyOffset = ArrayProperty->GetOffset_ForInternal();
Indirection.PathSegmentIndex = Segment.GetIndex();
Indirection.AccessType = EStateTreePropertyAccessType::IndexArray;
#if WITH_EDITORONLY_DATA
Indirection.RedirectedName = RedirectedName;
Indirection.PropertyGuid = PropertyGuid;
#endif
ArrayIndex = 0;
Offset = 0;
Property = ArrayProperty->Inner;
if (bWithValue)
{
FScriptArrayHelper Helper(ArrayProperty, CurrentAddress + ArrayProperty->GetOffset_ForInternal());
if (!Helper.IsValidIndex(Segment->GetArrayIndex()))
{
if (OutError)
{
*OutError = FString::Printf(TEXT("Index %d out of range (num elements %d) trying to access dynamic array '%s'."),
Segment->GetArrayIndex(), Helper.Num(), *ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
}
OutIndirections.Reset();
return false;
}
CurrentAddress = Helper.GetRawPtr(Segment->GetArrayIndex());
}
}
else
{
if (Segment->GetArrayIndex() > Property->ArrayDim)
{
if (OutError)
{
*OutError = FString::Printf(TEXT("Index %d out of range %d trying to access static array '%s'."),
Segment->GetArrayIndex(), Property->ArrayDim, *ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
}
OutIndirections.Reset();
return false;
}
ArrayIndex = FMath::Max(0, Segment->GetArrayIndex());
Offset = Property->GetOffset_ForInternal() + Property->ElementSize * ArrayIndex;
}
FStateTreePropertyPathIndirection& Indirection = OutIndirections.AddDefaulted_GetRef();
Indirection.Property = Property;
Indirection.ContainerAddress = CurrentAddress;
Indirection.ContainerStruct = CurrentStruct;
Indirection.ArrayIndex = ArrayIndex;
Indirection.PropertyOffset = Offset;
Indirection.PathSegmentIndex = Segment.GetIndex();
Indirection.AccessType = EStateTreePropertyAccessType::Offset;
#if WITH_EDITORONLY_DATA
Indirection.RedirectedName = RedirectedName;
Indirection.PropertyGuid = PropertyGuid;
#endif
const bool bLastSegment = Segment.GetIndex() == (Segments.Num() - 1);
if (!bLastSegment)
{
if (const FStructProperty* StructProperty = CastField<FStructProperty>(Property))
{
if (bWithValue)
{
if (StructProperty->Struct == TBaseStructure<FInstancedStruct>::Get())
{
// The property path is pointing into the instanced struct, it must be present.
// @TODO: We could potentially check the BaseStruct metadata in editor (for similar behavior as objects)
// Omitting for now to have matching functionality in editor and runtime.
const FInstancedStruct& InstancedStruct = *reinterpret_cast<const FInstancedStruct*>(CurrentAddress + Offset);
if (!InstancedStruct.IsValid())
{
if (OutError)
{
*OutError = FString::Printf(TEXT("Expecting valid instanced struct value at path '%s'."),
*ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
}
OutIndirections.Reset();
return false;
}
const UScriptStruct* ValueInstanceStructType = InstancedStruct.GetScriptStruct();
CurrentAddress = InstancedStruct.GetMemory();
CurrentStruct = ValueInstanceStructType;
Indirection.InstanceStruct = CurrentStruct;
Indirection.AccessType = EStateTreePropertyAccessType::StructInstance;
}
else
{
CurrentAddress = CurrentAddress + Offset;
CurrentStruct = StructProperty->Struct;
Indirection.AccessType = EStateTreePropertyAccessType::Offset;
}
}
else
{
if (Segment->GetInstanceStruct())
{
CurrentStruct = Segment->GetInstanceStruct();
Indirection.InstanceStruct = CurrentStruct;
Indirection.AccessType = EStateTreePropertyAccessType::StructInstance;
}
else
{
CurrentStruct = StructProperty->Struct;
Indirection.AccessType = EStateTreePropertyAccessType::Offset;
}
}
}
else if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property))
{
if (bWithValue)
{
const UObject* Object = *reinterpret_cast<UObject* const*>(CurrentAddress + Offset);
CurrentAddress = reinterpret_cast<const uint8*>(Object);
// The property path is pointing into the object, if the object is present use it's specific type, otherwise use the type of the pointer.
if (Object)
{
CurrentStruct = Object->GetClass();
Indirection.InstanceStruct = CurrentStruct;
Indirection.AccessType = EStateTreePropertyAccessType::ObjectInstance;
}
else
{
CurrentStruct = ObjectProperty->PropertyClass;
Indirection.AccessType = EStateTreePropertyAccessType::Object;
}
}
else
{
if (Segment->GetInstanceStruct())
{
CurrentStruct = Segment->GetInstanceStruct();
Indirection.InstanceStruct = CurrentStruct;
Indirection.AccessType = EStateTreePropertyAccessType::ObjectInstance;
}
else
{
CurrentStruct = ObjectProperty->PropertyClass;
Indirection.AccessType = EStateTreePropertyAccessType::Object;
}
}
}
// Check to see if this is a simple weak object property (eg. not an array of weak objects).
else if (const FWeakObjectProperty* WeakObjectProperty = CastField<FWeakObjectProperty>(Property))
{
if (bWithValue)
{
const TWeakObjectPtr<UObject>& WeakObjectPtr = *reinterpret_cast<const TWeakObjectPtr<UObject>*>(CurrentAddress + Offset);
const UObject* Object = WeakObjectPtr.Get();
CurrentAddress = reinterpret_cast<const uint8*>(Object);
if (Object)
{
CurrentStruct = Object->GetClass();
Indirection.InstanceStruct = CurrentStruct;
}
}
else
{
CurrentStruct = WeakObjectProperty->PropertyClass;
}
Indirection.AccessType = EStateTreePropertyAccessType::WeakObject;
}
// Check to see if this is a simple soft object property (eg. not an array of soft objects).
else if (const FSoftObjectProperty* SoftObjectProperty = CastField<FSoftObjectProperty>(Property))
{
if (bWithValue)
{
const FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast<const FSoftObjectPtr*>(CurrentAddress + Offset);
const UObject* Object = SoftObjectPtr.Get();
CurrentAddress = reinterpret_cast<const uint8*>(Object);
if (Object)
{
CurrentStruct = Object->GetClass();
Indirection.InstanceStruct = CurrentStruct;
}
}
else
{
CurrentStruct = SoftObjectProperty->PropertyClass;
}
Indirection.AccessType = EStateTreePropertyAccessType::SoftObject;
}
else
{
// We get here if we encounter a property type that is not supported for indirection (e.g. Map or Set).
if (OutError)
{
*OutError = FString::Printf(TEXT("Unsupported property indirection type %s in path '%s'."),
*Property->GetCPPType(), *ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
}
OutIndirections.Reset();
return false;
}
}
}
return true;
}
bool FStateTreePropertyPath::operator==(const FStateTreePropertyPath& RHS) const
{
#if WITH_EDITORONLY_DATA
if (StructID != RHS.StructID)
{
return false;
}
#endif // WITH_EDITORONLY_DATA
if (Segments.Num() != RHS.Segments.Num())
{
return false;
}
for (TEnumerateRef<const FStateTreePropertyPathSegment> Segment : EnumerateRange(Segments))
{
if (*Segment != RHS.Segments[Segment.GetIndex()])
{
return false;
}
}
return true;
}
//----------------------------------------------------------------//
// FStateTreeEditorPropertyPath
//----------------------------------------------------------------//
FString FStateTreeEditorPropertyPath::ToString(const int32 HighlightedSegment, const TCHAR* HighlightPrefix, const TCHAR* HighlightPostfix) const
{
FString Result;
for (int32 i = 0; i < Path.Num(); i++)
{
if (i > 0)
{
Result += TEXT(".");
}
if (i == HighlightedSegment && HighlightPrefix)
{
Result += HighlightPrefix;
}
Result += Path[i];
if (i == HighlightedSegment && HighlightPostfix)
{
Result += HighlightPostfix;
}
}
return Result;
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
bool FStateTreeEditorPropertyPath::operator==(const FStateTreeEditorPropertyPath& RHS) const
{
if (StructID != RHS.StructID)
{
return false;
}
if (Path.Num() != RHS.Path.Num())
{
return false;
}
for (int32 i = 0; i < Path.Num(); i++)
{
if (Path[i] != RHS.Path[i])
{
return false;
}
}
return true;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS