Files
mikko mononen d082c7fb66 StateTree: Remove ensure from ResolveIndirectionsWithValue()
- fails silenty, as we need to call it during runtiem load
- there's already a note on the method documentation about the behavior

[CL 26232241 by mikko mononen in 5.3 branch]
2023-06-26 09:15:45 -04:00

1719 lines
57 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "StateTreePropertyBindings.h"
#include "UObject/EnumProperty.h"
#include "Misc/EnumerateRange.h"
#include "PropertyPathHelpers.h"
#if WITH_EDITOR
#include "UObject/CoreRedirects.h"
#include "UObject/Package.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Engine/UserDefinedStruct.h"
#include "Kismet2/StructureEditorUtils.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();
}
} // 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();
PropertyIndirections.Reset();
bBindingsResolved = false;
}
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];
if (!Binding.GetCompiledSourceStructIndex().IsValid())
{
UE_LOG(LogStateTree, Error, TEXT("%hs: Invalid source struct for property binding %s."), __FUNCTION__, *Binding.GetSourcePath().ToString());
bResult = false;
continue;
}
Copy.SourceStructIndex = Binding.GetCompiledSourceStructIndex();
const UStruct* SourceStruct = SourceStructs[Copy.SourceStructIndex.Get()].Struct;
const UStruct* TargetStruct = Batch.TargetStruct.Struct;
if (!SourceStruct || !TargetStruct)
{
Copy.Type = EStateTreePropertyCopyType::None;
bBindingsResolved = false;
bResult = false;
continue;
}
// 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;
}
}
}
return bResult;
}
bool FStateTreePropertyBindings::ResolvePath(const UStruct* Struct, const FStateTreePropertyPath& Path, 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 = PropertyIndirections.Num();
PrevIndirection->NextIndex = FStateTreeIndex16(IndirectionIndex); // Set PrevIndirection before array add, as it can invalidate the pointer.
FStateTreePropertyIndirection& NewIndirection = PropertyIndirections.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))
{
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;
}
}
}
ensureMsgf(false, TEXT("Couldnt determine property copy type (%s -> %s)"), *SourceProperty->GetNameCPP(), *TargetProperty->GetNameCPP());
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, const FStateTreePropertyIndirection& FirstIndirection, const FProperty* LeafProperty) const
{
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() ? &PropertyIndirections[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::CopyTo(TConstArrayView<FStateTreeDataView> SourceStructViews, 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;
}
const FStateTreeDataView SourceStructView = SourceStructViews[Copy.SourceStructIndex.Get()];
if (SourceStructView.IsValid())
{
check(SourceStructView.GetStruct() == SourceStructs[Copy.SourceStructIndex.Get()].Struct
|| (SourceStructView.GetStruct() && SourceStructView.GetStruct()->IsChildOf(SourceStructs[Copy.SourceStructIndex.Get()].Struct)));
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::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;
}
}
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 | %-7s | %-20s | %-4s ]\n"), PropertyCopies.Num(),
TEXT("Src Idx"), TEXT("Off."), TEXT("Next"), TEXT("Type"),
TEXT("Tgt Idx"), TEXT("Off."), TEXT("Next"), TEXT("Type"),
TEXT("Struct"), TEXT("Copy Type"), TEXT("Size"));
for (const FStateTreePropertyCopy& PropertyCopy : PropertyCopies)
{
OutString += FString::Printf(TEXT(" | %7d | %4d | %4d | %-10s | %7d | %4d | %4d | %-10s | %7d | %-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.SourceStructIndex.Get(),
*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();
const bool bWithValue = CurrentAddress != nullptr;
for (const TEnumerateRef<const FStateTreePropertyPathSegment> Segment : EnumerateRange(Segments))
{
if (CurrentStruct == nullptr || (bWithValue && CurrentAddress == 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());
#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
{
// 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);
}
}
}
#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())
{
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);
if (Property->HasAnyPropertyFlags(CPF_PersistentInstance | CPF_InstancedReference))
{
if (!Object)
{
if (OutError)
{
*OutError = FString::Printf(TEXT("Expecting valid instanced object value at path '%s'."),
*ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
}
OutIndirections.Reset();
return false;
}
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))
{
const TWeakObjectPtr<UObject>& WeakObjectPtr = *reinterpret_cast<const TWeakObjectPtr<UObject>*>(CurrentAddress + Offset);
const UObject* Object = WeakObjectPtr.Get();
CurrentAddress = reinterpret_cast<const uint8*>(Object);
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))
{
const FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast<const FSoftObjectPtr*>(CurrentAddress + Offset);
const UObject* Object = SoftObjectPtr.Get();
CurrentAddress = reinterpret_cast<const uint8*>(Object);
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