// 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 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> TempIndirections; for (FStateTreePropertyPathIndirection& PathIndirection : PathIndirections) { FStateTreePropertyIndirection& Indirection = TempIndirections.AddDefaulted_GetRef(); check(PathIndirection.GetPropertyOffset() >= MIN_uint16 && PathIndirection.GetPropertyOffset() <= MAX_uint16); Indirection.Offset = static_cast(PathIndirection.GetPropertyOffset()); Indirection.Type = PathIndirection.GetAccessType(); if (Indirection.Type == EStateTreePropertyAccessType::IndexArray) { if (const FArrayProperty* ArrayProperty = CastField(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(TargetProperty)) { if (TargetStructProperty->Struct == SourceStruct) { OutCopy.Type = EStateTreePropertyCopyType::CopyStruct; return true; } } else if (const FObjectPropertyBase* TargetObjectProperty = CastField(TargetProperty)) { if (SourceStruct->IsChildOf(TargetObjectProperty->PropertyClass)) { OutCopy.Type = EStateTreePropertyCopyType::CopyObject; return true; } } return false; } // Handle FStateTreeStructRef if (const FStructProperty* TargetStructProperty = CastField(TargetProperty)) { if (TargetStructProperty->Struct == TBaseStructure::Get()) { if (const FStructProperty* SourceStructProperty = CastField(SourceProperty)) { // FStateTreeStructRef to FStateTreeStructRef is copied as usual. if (SourceStructProperty->Struct != TBaseStructure::Get()) { OutCopy.Type = EStateTreePropertyCopyType::StructReference; return true; } } } } const EStateTreePropertyAccessCompatibility Compatibility = FStateTreePropertyBindings::GetPropertyCompatibility(SourceProperty, TargetProperty); // Extract underlying types for enums if (const FEnumProperty* EnumPropertyA = CastField(SourceProperty)) { SourceProperty = EnumPropertyA->GetUnderlyingProperty(); } if (const FEnumProperty* EnumPropertyB = CastField(TargetProperty)) { TargetProperty = EnumPropertyB->GetUnderlyingProperty(); } if (Compatibility == EStateTreePropertyAccessCompatibility::Compatible) { if (CastField(TargetProperty)) { OutCopy.Type = EStateTreePropertyCopyType::CopyName; return true; } else if (CastField(TargetProperty)) { OutCopy.Type = EStateTreePropertyCopyType::CopyBool; return true; } else if (CastField(TargetProperty)) { OutCopy.Type = EStateTreePropertyCopyType::CopyStruct; return true; } else if (CastField(TargetProperty)) { OutCopy.Type = EStateTreePropertyCopyType::CopyObject; return true; } else if (CastField(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()) { if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToByte; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToInt32; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToUInt32; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToInt64; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToFloat; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteBoolToDouble; return true; } } else if (SourceProperty->IsA()) { if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteByteToInt32; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteByteToUInt32; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteByteToInt64; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteByteToFloat; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteByteToDouble; return true; } } else if (SourceProperty->IsA()) { if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteInt32ToInt64; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteInt32ToFloat; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteInt32ToDouble; return true; } } else if (SourceProperty->IsA()) { if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteUInt32ToInt64; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteUInt32ToFloat; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteUInt32ToDouble; return true; } } else if (SourceProperty->IsA()) { if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteFloatToInt32; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteFloatToInt64; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::PromoteFloatToDouble; return true; } } else if (SourceProperty->IsA()) { if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::DemoteDoubleToInt32; return true; } else if (TargetProperty->IsA()) { OutCopy.Type = EStateTreePropertyCopyType::DemoteDoubleToInt64; return true; } else if (TargetProperty->IsA()) { 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() && ToProperty->IsA()) { const FObjectPropertyBase* SourceProperty = CastField(FromProperty); const FObjectPropertyBase* TargetProperty = CastField(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(Property)) { return ByteProperty->GetIntPropertyEnum(); } if (const FEnumProperty* EnumProperty = CastField(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(FromProperty)) { FromProperty = EnumPropertyA->GetUnderlyingProperty(); } if (FromProperty->SameType(ToProperty)) { return EStateTreePropertyAccessCompatibility::Compatible; } else { // Not directly compatible, check for promotions if (FromProperty->IsA()) { if (ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA()) { return EStateTreePropertyAccessCompatibility::Promotable; } } else if (FromProperty->IsA()) { if (ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA()) { return EStateTreePropertyAccessCompatibility::Promotable; } } else if (FromProperty->IsA()) { if (ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA()) { return EStateTreePropertyAccessCompatibility::Promotable; } } else if (FromProperty->IsA()) { if (ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA()) { return EStateTreePropertyAccessCompatibility::Promotable; } } else if (FromProperty->IsA()) { if (ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA()) { return EStateTreePropertyAccessCompatibility::Promotable; } } else if (FromProperty->IsA()) { if (ToProperty->IsA() || ToProperty->IsA() || ToProperty->IsA()) { 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(Address + Indirection->Offset); Address = reinterpret_cast(Object); break; } case EStateTreePropertyAccessType::WeakObject: { TWeakObjectPtr& WeakObjectPtr = *reinterpret_cast*>(Address + Indirection->Offset); UObject* Object = WeakObjectPtr.Get(); Address = reinterpret_cast(Object); break; } case EStateTreePropertyAccessType::SoftObject: { FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast(Address + Indirection->Offset); UObject* Object = SoftObjectPtr.Get(); Address = reinterpret_cast(Object); break; } case EStateTreePropertyAccessType::ObjectInstance: { check(Indirection->InstanceStruct); UObject* Object = *reinterpret_cast(Address + Indirection->Offset); if (Object && Object->GetClass()->IsChildOf(Indirection->InstanceStruct)) { Address = reinterpret_cast(Object); } else { // Failed indirection, will be reported by caller. return nullptr; } break; } case EStateTreePropertyAccessType::StructInstance: { check(Indirection->InstanceStruct); FInstancedStruct& InstancedStruct = *reinterpret_cast(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()->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(Copy.TargetLeafProperty)->SetPropertyValue(TargetAddress, static_cast(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(Copy.TargetLeafProperty)->Struct->CopyScriptStruct(TargetAddress, SourceAddress); break; case EStateTreePropertyCopyType::CopyObject: if (Copy.SourceLeafProperty == nullptr) { // Source is pointing at object directly. static_cast(Copy.TargetLeafProperty)->SetObjectPropertyValue(TargetAddress, (UObject*)SourceAddress); } else { static_cast(Copy.TargetLeafProperty)->SetObjectPropertyValue(TargetAddress, static_cast(Copy.SourceLeafProperty)->GetObjectPropertyValue(SourceAddress)); } break; case EStateTreePropertyCopyType::CopyName: static_cast(Copy.TargetLeafProperty)->SetPropertyValue(TargetAddress, static_cast(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(Copy.SourceLeafProperty); const FArrayProperty* TargetArrayProperty = static_cast(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(Copy.SourceLeafProperty); FStateTreeStructRef* Target = (FStateTreeStructRef*)TargetAddress; Target->Set(FStructView(SourceStructProperty->Struct, SourceAddress)); break; } // Bool promotions case EStateTreePropertyCopyType::PromoteBoolToByte: *reinterpret_cast(TargetAddress) = (uint8)static_cast(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress); break; case EStateTreePropertyCopyType::PromoteBoolToInt32: *reinterpret_cast(TargetAddress) = (int32)static_cast(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress); break; case EStateTreePropertyCopyType::PromoteBoolToUInt32: *reinterpret_cast(TargetAddress) = (uint32)static_cast(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress); break; case EStateTreePropertyCopyType::PromoteBoolToInt64: *reinterpret_cast(TargetAddress) = (int64)static_cast(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress); break; case EStateTreePropertyCopyType::PromoteBoolToFloat: *reinterpret_cast(TargetAddress) = (float)static_cast(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress); break; case EStateTreePropertyCopyType::PromoteBoolToDouble: *reinterpret_cast(TargetAddress) = (double)static_cast(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress); break; // Byte promotions case EStateTreePropertyCopyType::PromoteByteToInt32: *reinterpret_cast(TargetAddress) = (int32)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::PromoteByteToUInt32: *reinterpret_cast(TargetAddress) = (uint32)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::PromoteByteToInt64: *reinterpret_cast(TargetAddress) = (int64)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::PromoteByteToFloat: *reinterpret_cast(TargetAddress) = (float)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::PromoteByteToDouble: *reinterpret_cast(TargetAddress) = (double)*reinterpret_cast(SourceAddress); break; // Int32 promotions case EStateTreePropertyCopyType::PromoteInt32ToInt64: *reinterpret_cast(TargetAddress) = (int64)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::PromoteInt32ToFloat: *reinterpret_cast(TargetAddress) = (float)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::PromoteInt32ToDouble: *reinterpret_cast(TargetAddress) = (double)*reinterpret_cast(SourceAddress); break; // Uint32 promotions case EStateTreePropertyCopyType::PromoteUInt32ToInt64: *reinterpret_cast(TargetAddress) = (int64)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::PromoteUInt32ToFloat: *reinterpret_cast(TargetAddress) = (float)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::PromoteUInt32ToDouble: *reinterpret_cast(TargetAddress) = (double)*reinterpret_cast(SourceAddress); break; // Float promotions case EStateTreePropertyCopyType::PromoteFloatToInt32: *reinterpret_cast(TargetAddress) = (int32)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::PromoteFloatToInt64: *reinterpret_cast(TargetAddress) = (int64)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::PromoteFloatToDouble: *reinterpret_cast(TargetAddress) = (double)*reinterpret_cast(SourceAddress); break; // Double promotions case EStateTreePropertyCopyType::DemoteDoubleToInt32: *reinterpret_cast(TargetAddress) = (int32)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::DemoteDoubleToInt64: *reinterpret_cast(TargetAddress) = (int64)*reinterpret_cast(SourceAddress); break; case EStateTreePropertyCopyType::DemoteDoubleToFloat: *reinterpret_cast(TargetAddress) = (float)*reinterpret_cast(SourceAddress); break; default: ensureMsgf(false, TEXT("FStateTreePropertyBindings::PerformCopy: Unhandled copy type %s between '%s' and '%s'"), *StaticEnum()->GetValueAsString(Copy.Type), *Copy.SourceLeafProperty->GetNameCPP(), *Copy.TargetLeafProperty->GetNameCPP()); break; } } bool FStateTreePropertyBindings::CopyTo(TConstArrayView 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(Copy.TargetLeafProperty)->Struct->ClearScriptStruct(TargetAddress); break; case EStateTreePropertyCopyType::CopyObject: static_cast(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(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& 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 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 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 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& OutIndirections, FString* OutError, bool bHandleRedirects) const { return ResolveIndirectionsWithValue(FStateTreeDataView(BaseStruct, nullptr), OutIndirections, OutError, bHandleRedirects); } bool FStateTreePropertyPath::ResolveIndirectionsWithValue(const FStateTreeDataView BaseValueView, TArray& 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 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(CurrentStruct)) { if (const FName* Name = BlueprintClass->PropertyGuids.FindKey(Segment->GetPropertyGuid())) { RedirectedName = *Name; Property = CurrentStruct->FindPropertyByName(RedirectedName); } } else if (const UUserDefinedStruct* UserDefinedStruct = Cast(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(CurrentStruct)) { if (const FGuid* VarGuid = BlueprintClass->PropertyGuids.Find(PropertyName)) { PropertyGuid = *VarGuid; } } else if (const UUserDefinedStruct* UserDefinedStruct = Cast(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(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(Property)) { if (bWithValue) { if (StructProperty->Struct == TBaseStructure::Get()) { const FInstancedStruct& InstancedStruct = *reinterpret_cast(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(Property)) { if (bWithValue) { const UObject* Object = *reinterpret_cast(CurrentAddress + Offset); CurrentAddress = reinterpret_cast(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(Property)) { const TWeakObjectPtr& WeakObjectPtr = *reinterpret_cast*>(CurrentAddress + Offset); const UObject* Object = WeakObjectPtr.Get(); CurrentAddress = reinterpret_cast(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(Property)) { const FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast(CurrentAddress + Offset); const UObject* Object = SoftObjectPtr.Get(); CurrentAddress = reinterpret_cast(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 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