Files
UnrealEngine/Engine/Plugins/Runtime/StateTree/Source/StateTreeTestSuite/Private/StateTreeBindingTest.cpp
2025-05-18 13:04:45 +08:00

763 lines
39 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "StateTreeTest.h"
#include "StateTreeTestBase.h"
#include "StateTreeTestTypes.h"
#include "StateTreeCompilerLog.h"
#include "StateTreeEditorData.h"
#include "StateTreeCompiler.h"
#include "Conditions/StateTreeCommonConditions.h"
#define LOCTEXT_NAMESPACE "AITestSuite_StateTreeTest"
UE_DISABLE_OPTIMIZATION_SHIP
namespace UE::StateTree::Tests
{
struct FStateTreeTest_BindingsCompiler : FStateTreeTestBase
{
virtual bool InstantTest() override
{
FStateTreeCompilerLog Log;
FStateTreePropertyBindings Bindings;
FStateTreePropertyBindingCompiler BindingCompiler;
const bool bInitResult = BindingCompiler.Init(Bindings, Log);
AITEST_TRUE(TEXT("Expect init to succeed"), bInitResult);
FStateTreeBindableStructDesc SourceADesc;
SourceADesc.Name = FName(TEXT("SourceA"));
SourceADesc.Struct = TBaseStructure<FStateTreeTest_PropertyCopy>::Get();
SourceADesc.DataSource = EStateTreeBindableStructSource::Parameter;
SourceADesc.DataHandle = FStateTreeDataHandle(EStateTreeDataSourceType::ContextData, 0); // Used as index to SourceViews below.
SourceADesc.ID = FGuid::NewGuid();
FStateTreeBindableStructDesc SourceBDesc;
SourceBDesc.Name = FName(TEXT("SourceB"));
SourceBDesc.Struct = TBaseStructure<FStateTreeTest_PropertyCopy>::Get();
SourceBDesc.DataSource = EStateTreeBindableStructSource::Parameter;
SourceBDesc.DataHandle = FStateTreeDataHandle(EStateTreeDataSourceType::ContextData, 1); // Used as index to SourceViews below.
SourceBDesc.ID = FGuid::NewGuid();
FStateTreeBindableStructDesc TargetDesc;
TargetDesc.Name = FName(TEXT("Target"));
TargetDesc.Struct = TBaseStructure<FStateTreeTest_PropertyCopy>::Get();
TargetDesc.DataSource = EStateTreeBindableStructSource::Parameter;
TargetDesc.ID = FGuid::NewGuid();
const int32 SourceAIndex = BindingCompiler.AddSourceStruct(SourceADesc);
const int32 SourceBIndex = BindingCompiler.AddSourceStruct(SourceBDesc);
TArray<FStateTreePropertyPathBinding> PropertyBindings;
PropertyBindings.Add(MakeBinding(SourceBDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("Array[1]")));
PropertyBindings.Add(MakeBinding(SourceADesc.ID, TEXT("Item.B"), TargetDesc.ID, TEXT("Array[1].B")));
PropertyBindings.Add(MakeBinding(SourceADesc.ID, TEXT("Array"), TargetDesc.ID, TEXT("Array")));
PropertyBindings.Add(MakeBinding(SourceBDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("FixedArray[1]")));
PropertyBindings.Add(MakeBinding(SourceADesc.ID, TEXT("Item.B"), TargetDesc.ID, TEXT("FixedArray[1].B")));
PropertyBindings.Add(MakeBinding(SourceADesc.ID, TEXT("FixedArray"), TargetDesc.ID, TEXT("FixedArray")));
PropertyBindings.Add(MakeBinding(SourceBDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("CArray[1]")));
PropertyBindings.Add(MakeBinding(SourceADesc.ID, TEXT("Item.B"), TargetDesc.ID, TEXT("CArray[1].B")));
PropertyBindings.Add(MakeBinding(SourceADesc.ID, TEXT("CArray"), TargetDesc.ID, TEXT("CArray")));
int32 CopyBatchIndex = INDEX_NONE;
const bool bCompileBatchResult = BindingCompiler.CompileBatch(TargetDesc, PropertyBindings, FStateTreeIndex16::Invalid, FStateTreeIndex16::Invalid, CopyBatchIndex);
AITEST_TRUE(TEXT("CompileBatch should succeed"), bCompileBatchResult);
AITEST_NOT_EQUAL(TEXT("CopyBatchIndex should not be INDEX_NONE"), CopyBatchIndex, (int32)INDEX_NONE);
BindingCompiler.Finalize();
const bool bResolveResult = Bindings.ResolvePaths();
AITEST_TRUE(TEXT("ResolvePaths should succeed"), bResolveResult);
FStateTreeTest_PropertyCopy SourceA;
SourceA.Item.B = 123;
SourceA.Array.AddDefaulted_GetRef().A = 1;
SourceA.Array.AddDefaulted_GetRef().B = 2;
constexpr int32 FixedArraySize = 4;
SourceA.FixedArray.SetNum(FixedArraySize, EAllowShrinking::No);
SourceA.FixedArray[0].A = 1;
SourceA.FixedArray[1].B = 2;
SourceA.CArray[0].A = 1;
SourceA.CArray[0].B = 2;
FStateTreeTest_PropertyCopy SourceB;
SourceB.Item.A = 41;
SourceB.Item.B = 42;
SourceB.FixedArray.SetNum(FixedArraySize, EAllowShrinking::No);
FStateTreeTest_PropertyCopy Target;
Target.FixedArray.SetNum(FixedArraySize, EAllowShrinking::No);
AITEST_TRUE(TEXT("SourceAIndex should be less than max number of source structs."), SourceAIndex < Bindings.GetNumBindableStructDescriptors());
AITEST_TRUE(TEXT("SourceBIndex should be less than max number of source structs."), SourceBIndex < Bindings.GetNumBindableStructDescriptors());
TArray<FStateTreeDataView> SourceViews;
SourceViews.SetNum(Bindings.GetNumBindableStructDescriptors());
SourceViews[SourceAIndex] = FStateTreeDataView(FStructView::Make(SourceA));
SourceViews[SourceBIndex] = FStateTreeDataView(FStructView::Make(SourceB));
FPropertyBindingDataView TargetView(FStructView::Make(Target));
bool bCopyResult = true;
for (const FPropertyBindingCopyInfo& Copy : Bindings.Super::GetBatchCopies(FPropertyBindingIndex16(CopyBatchIndex)))
{
bCopyResult &= Bindings.Super::CopyProperty(Copy, SourceViews[Copy.SourceDataHandle.Get<FStateTreeDataHandle>().GetIndex()], TargetView);
}
AITEST_TRUE(TEXT("CopyTo should succeed"), bCopyResult);
// Due to binding sorting, we expect them to execute in this order (sorted based on target access, earliest to latest)
// SourceA.CArray -> Target.CArray
// SourceB.Item -> Target.CArray[1]
// SourceA.Item.B -> Target.CArray[1].B
// SourceA.FixedArray -> Target.FixedArray
// SourceB.Item -> Target.FixedArray[1]
// SourceA.Item.B -> Target.FixedArray[1].B
// SourceA.Array -> Target.Array
// SourceB.Item -> Target.Array[1]
// SourceA.Item.B -> Target.Array[1].B
AITEST_EQUAL(TEXT("Expect TargetArray to be copied from SourceA"), Target.Array.Num(), SourceA.Array.Num());
AITEST_EQUAL(TEXT("Expect Target.Array[0].A copied from SourceA.Array[0].A"), Target.Array[0].A, SourceA.Array[0].A);
AITEST_EQUAL(TEXT("Expect Target.Array[0].B copied from SourceA.Array[0].B"), Target.Array[0].B, SourceA.Array[0].B);
AITEST_EQUAL(TEXT("Expect Target.Array[1].A copied from SourceB.Item.A"), Target.Array[1].A, SourceB.Item.A);
AITEST_EQUAL(TEXT("Expect Target.Array[1].B copied from SourceA.Item.B"), Target.Array[1].B, SourceA.Item.B);
AITEST_EQUAL(TEXT("Expect TargetArray to be copied from SourceA"), Target.FixedArray.Num(), SourceA.FixedArray.Num());
AITEST_EQUAL(TEXT("Expect Target.FixedArray[0].A copied from SourceA.FixedArray[0].A"), Target.FixedArray[0].A, SourceA.FixedArray[0].A);
AITEST_EQUAL(TEXT("Expect Target.FixedArray[0].B copied from SourceA.FixedArray[0].B"), Target.FixedArray[0].B, SourceA.FixedArray[0].B);
AITEST_EQUAL(TEXT("Expect Target.FixedArray[1].A copied from SourceB.Item.A"), Target.FixedArray[1].A, SourceB.Item.A);
AITEST_EQUAL(TEXT("Expect Target.FixedArray[1].B copied from SourceA.Item.B"), Target.FixedArray[1].B, SourceA.Item.B);
AITEST_EQUAL(TEXT("Expect Target.FixedArray to not have changed size"), Target.FixedArray.Num(), FixedArraySize);
AITEST_EQUAL(TEXT("Expect Target.CArray[0].A copied from SourceA.CArray[0].A"), Target.CArray[0].A, SourceA.CArray[0].A);
AITEST_EQUAL(TEXT("Expect Target.CArray[0].B copied from SourceA.CArray[0].B"), Target.CArray[0].B, SourceA.CArray[0].B);
AITEST_EQUAL(TEXT("Expect Target.CArray[1].A copied from SourceB.Item.A"), Target.CArray[1].A, SourceB.Item.A);
AITEST_EQUAL(TEXT("Expect Target.CArray[1].B copied from SourceA.Item.B"), Target.CArray[1].B, SourceA.Item.B);
const int32 NumAllocated_FStateTreeTest_PropertyStructB_BeforeReset = FStateTreeTest_PropertyStructB::NumConstructed;
bool bResetResult = Bindings.FPropertyBindingBindingCollection::ResetObjects(FPropertyBindingIndex16(CopyBatchIndex), TargetView);
AITEST_TRUE(TEXT("ResetObjects should succeed"), bResetResult);
AITEST_EQUAL(TEXT("Expect Target dynamic array to be empty"), Target.Array.Num(), 0);
AITEST_EQUAL(TEXT("Expect Target fixed size Array to not have changed size."), Target.FixedArray.Num(), FixedArraySize);
AITEST_NOT_EQUAL(TEXT("Expect the count of constructed FStateTreeTest_PropertyStructB to be smaller after calling ResetObjects"), FStateTreeTest_PropertyStructB::NumConstructed, NumAllocated_FStateTreeTest_PropertyStructB_BeforeReset);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_BindingsCompiler, "System.StateTree.Binding.BindingsCompiler");
struct FStateTreeTest_PropertyFunctions : FStateTreeTestBase
{
virtual bool InstantTest() override
{
UStateTree& StateTree = NewStateTree();
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root")));
FPropertyBindingPathSegment PathSegmentToFuncResult = FPropertyBindingPathSegment(TEXT("Result"));
// Condition with property function binding.
{
TStateTreeEditorNode<FStateTreeCompareIntCondition>& EnterCond = Root.AddEnterCondition<FStateTreeCompareIntCondition>(EGenericAICheck::Equal);
EnterCond.GetInstanceData().Right = 1;
EditorData.AddPropertyBinding(CastChecked<UScriptStruct>(FTestPropertyFunction::StaticStruct()), {PathSegmentToFuncResult}, FPropertyBindingPath(EnterCond.ID, TEXT("Left")));
}
// Task with multiple nested property function bindings.
auto& TaskA = Root.AddTask<FTestTask_PrintAndResetValue>(FName(TEXT("TaskA")));
constexpr int32 TaskAPropertyFunctionsAmount = 10;
{
EditorData.AddPropertyBinding(CastChecked<UScriptStruct>(FTestPropertyFunction::StaticStruct()), {PathSegmentToFuncResult}, FPropertyBindingPath(TaskA.ID, TEXT("Value")));
for (int32 i = 0; i < TaskAPropertyFunctionsAmount - 1; ++i)
{
const FStateTreePropertyPathBinding& LastBinding = EditorData.GetPropertyEditorBindings()->GetBindings().Last();
const FGuid LastBindingPropertyFuncID = LastBinding.GetPropertyFunctionNode().Get<const FStateTreeEditorNode>().ID;
EditorData.AddPropertyBinding(CastChecked<UScriptStruct>(FTestPropertyFunction::StaticStruct()), {PathSegmentToFuncResult}, FPropertyBindingPath(LastBindingPropertyFuncID, TEXT("Input")));
}
}
// Task bound to state parameter with multiple nested property function bindings.
auto& TaskB = Root.AddTask<FTestTask_PrintAndResetValue>(FName(TEXT("TaskB")));
constexpr int32 ParameterPropertyFunctionsAmount = 5;
{
Root.Parameters.Parameters.AddProperty(FName(TEXT("Int")), EPropertyBagPropertyType::Int32);
const FPropertyBindingPath PathToProperty = FPropertyBindingPath(Root.Parameters.ID, TEXT("Int"));
EditorData.AddPropertyBinding(PathToProperty, FPropertyBindingPath(TaskB.ID, TEXT("Value")));
EditorData.AddPropertyBinding(CastChecked<UScriptStruct>(FTestPropertyFunction::StaticStruct()), {PathSegmentToFuncResult}, PathToProperty);
for (int32 i = 0; i < ParameterPropertyFunctionsAmount - 1; ++i)
{
const FStateTreePropertyPathBinding& LastBinding = EditorData.GetPropertyEditorBindings()->GetBindings().Last();
const FGuid LastBindingPropertyFuncID = LastBinding.GetPropertyFunctionNode().Get<const FStateTreeEditorNode>().ID;
EditorData.AddPropertyBinding(CastChecked<UScriptStruct>(FTestPropertyFunction::StaticStruct()), {PathSegmentToFuncResult}, FPropertyBindingPath(LastBindingPropertyFuncID, TEXT("Input")));
}
}
FStateTreeCompilerLog Log;
FStateTreeCompiler Compiler(Log);
const bool bResult = Compiler.Compile(StateTree);
AITEST_TRUE(TEXT("StateTree should get compiled"), bResult);
FStateTreeInstanceData InstanceData;
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
const bool bInitSucceeded = Exec.IsValid();
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
Exec.Start();
AITEST_TRUE(*FString::Printf(TEXT("StateTree TaskA should enter state with value %d"), TaskAPropertyFunctionsAmount), Exec.Expect(TaskA.GetName(), *FString::Printf(TEXT("EnterState%d"), TaskAPropertyFunctionsAmount)));
AITEST_TRUE(*FString::Printf(TEXT("StateTree TaskB should enter state with value %d"), ParameterPropertyFunctionsAmount), Exec.Expect(TaskB.GetName(), *FString::Printf(TEXT("EnterState%d"), ParameterPropertyFunctionsAmount)));
Exec.LogClear();
Exec.Tick(0.1f);
AITEST_TRUE(*FString::Printf(TEXT("StateTree TaskA should tick with value %d"), TaskAPropertyFunctionsAmount), Exec.Expect(TaskA.GetName(), *FString::Printf(TEXT("Tick%d"), TaskAPropertyFunctionsAmount)));
AITEST_TRUE(*FString::Printf(TEXT("StateTree TaskB should tick with value %d"), ParameterPropertyFunctionsAmount), Exec.Expect(TaskB.GetName(), *FString::Printf(TEXT("Tick%d"), ParameterPropertyFunctionsAmount)));
Exec.LogClear();
Exec.Stop(EStateTreeRunStatus::Stopped);
AITEST_TRUE(*FString::Printf(TEXT("StateTree TaskA should exit state with value %d"), TaskAPropertyFunctionsAmount), Exec.Expect(TaskA.GetName(), *FString::Printf(TEXT("ExitState%d"), TaskAPropertyFunctionsAmount)));
AITEST_TRUE(*FString::Printf(TEXT("StateTree TaskB should exit state with value %d"), ParameterPropertyFunctionsAmount), Exec.Expect(TaskB.GetName(), *FString::Printf(TEXT("ExitState%d"), ParameterPropertyFunctionsAmount)));
Exec.LogClear();
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_PropertyFunctions, "System.StateTree.Binding.PropertyFunctions");
struct FStateTreeTest_CopyObjects : FStateTreeTestBase
{
virtual bool InstantTest() override
{
FStateTreeCompilerLog Log;
FStateTreePropertyBindings Bindings;
FStateTreePropertyBindingCompiler BindingCompiler;
const bool bInitResult = BindingCompiler.Init(Bindings, Log);
AITEST_TRUE(TEXT("Expect init to succeed"), bInitResult);
FStateTreeBindableStructDesc SourceDesc;
SourceDesc.Name = FName(TEXT("Source"));
SourceDesc.Struct = TBaseStructure<FStateTreeTest_PropertyCopyObjects>::Get();
SourceDesc.DataSource = EStateTreeBindableStructSource::Parameter;
SourceDesc.DataHandle = FStateTreeDataHandle(EStateTreeDataSourceType::ContextData, 0); // Used as index to SourceViews below.
SourceDesc.ID = FGuid::NewGuid();
FStateTreeBindableStructDesc TargetADesc;
TargetADesc.Name = FName(TEXT("TargetA"));
TargetADesc.Struct = TBaseStructure<FStateTreeTest_PropertyCopyObjects>::Get();
TargetADesc.DataSource = EStateTreeBindableStructSource::Parameter;
TargetADesc.ID = FGuid::NewGuid();
FStateTreeBindableStructDesc TargetBDesc;
TargetBDesc.Name = FName(TEXT("TargetB"));
TargetBDesc.Struct = TBaseStructure<FStateTreeTest_PropertyCopyObjects>::Get();
TargetBDesc.DataSource = EStateTreeBindableStructSource::Parameter;
TargetBDesc.ID = FGuid::NewGuid();
const int32 SourceIndex = BindingCompiler.AddSourceStruct(SourceDesc);
TArray<FStateTreePropertyPathBinding> PropertyBindings;
// One-to-one copy from source to target A
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("Object"), TargetADesc.ID, TEXT("Object")));
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("SoftObject"), TargetADesc.ID, TEXT("SoftObject")));
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("Class"), TargetADesc.ID, TEXT("Class")));
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("SoftClass"), TargetADesc.ID, TEXT("SoftClass")));
// Cross copy from source to target B
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("SoftObject"), TargetBDesc.ID, TEXT("Object")));
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("Object"), TargetBDesc.ID, TEXT("SoftObject")));
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("SoftClass"), TargetBDesc.ID, TEXT("Class")));
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("Class"), TargetBDesc.ID, TEXT("SoftClass")));
int32 TargetACopyBatchIndex = INDEX_NONE;
const bool bCompileBatchResultA = BindingCompiler.CompileBatch(TargetADesc, PropertyBindings, FStateTreeIndex16::Invalid, FStateTreeIndex16::Invalid, TargetACopyBatchIndex);
AITEST_TRUE(TEXT("CompileBatchResultA should succeed"), bCompileBatchResultA);
AITEST_NOT_EQUAL(TEXT("TargetACopyBatchIndex should not be INDEX_NONE"), TargetACopyBatchIndex, (int32)INDEX_NONE);
int32 TargetBCopyBatchIndex = INDEX_NONE;
const bool bCompileBatchResultB = BindingCompiler.CompileBatch(TargetBDesc, PropertyBindings, FStateTreeIndex16::Invalid, FStateTreeIndex16::Invalid, TargetBCopyBatchIndex);
AITEST_TRUE(TEXT("CompileBatchResultB should succeed"), bCompileBatchResultB);
AITEST_NOT_EQUAL(TEXT("TargetBCopyBatchIndex should not be INDEX_NONE"), TargetBCopyBatchIndex, (int32)INDEX_NONE);
BindingCompiler.Finalize();
const bool bResolveResult = Bindings.ResolvePaths();
AITEST_TRUE(TEXT("ResolvePaths should succeed"), bResolveResult);
UStateTreeTest_PropertyObject* ObjectA = NewObject<UStateTreeTest_PropertyObject>();
UStateTreeTest_PropertyObject2* ObjectB = NewObject<UStateTreeTest_PropertyObject2>();
FStateTreeTest_PropertyCopyObjects Source;
Source.Object = ObjectA;
Source.SoftObject = ObjectB;
Source.Class = UStateTreeTest_PropertyObject::StaticClass();
Source.SoftClass = UStateTreeTest_PropertyObject::StaticClass();
AITEST_TRUE(TEXT("SourceIndex should be less than max number of source structs."), SourceIndex < Bindings.GetNumBindableStructDescriptors());
TArray<FStateTreeDataView> SourceViews;
SourceViews.SetNum(Bindings.GetNumBindableStructDescriptors());
SourceViews[SourceIndex] = FStateTreeDataView(FStructView::Make(Source));
FStateTreeTest_PropertyCopyObjects TargetA;
bool bCopyResultA = true;
for (const FPropertyBindingCopyInfo& Copy : Bindings.Super::GetBatchCopies(FStateTreeIndex16(TargetACopyBatchIndex)))
{
bCopyResultA &= Bindings.Super::CopyProperty(Copy, SourceViews[Copy.SourceDataHandle.Get<FStateTreeDataHandle>().GetIndex()], FStructView::Make(TargetA));
}
AITEST_TRUE(TEXT("CopyTo should succeed"), bCopyResultA);
AITEST_TRUE(TEXT("Expect TargetA.Object == Source.Object"), TargetA.Object == Source.Object);
AITEST_TRUE(TEXT("Expect TargetA.SoftObject == Source.SoftObject"), TargetA.SoftObject == Source.SoftObject);
AITEST_TRUE(TEXT("Expect TargetA.Class == Source.Class"), TargetA.Class == Source.Class);
AITEST_TRUE(TEXT("Expect TargetA.SoftClass == Source.SoftClass"), TargetA.SoftClass == Source.SoftClass);
// Copying to TargetB should not affect TargetA
TargetA.Object = nullptr;
FStateTreeTest_PropertyCopyObjects TargetB;
bool bCopyResultB = true;
for (const FPropertyBindingCopyInfo& Copy : Bindings.Super::GetBatchCopies(FStateTreeIndex16(TargetBCopyBatchIndex)))
{
bCopyResultB &= Bindings.Super::CopyProperty(Copy, SourceViews[Copy.SourceDataHandle.Get<FStateTreeDataHandle>().GetIndex()], FStructView::Make(TargetB));
}
AITEST_TRUE(TEXT("CopyTo should succeed"), bCopyResultB);
AITEST_TRUE(TEXT("Expect TargetB.Object == Source.SoftObject"), TSoftObjectPtr<UObject>(TargetB.Object) == Source.SoftObject);
AITEST_TRUE(TEXT("Expect TargetB.SoftObject == Source.Object"), TargetB.SoftObject == TSoftObjectPtr<UObject>(Source.Object));
AITEST_TRUE(TEXT("Expect TargetB.Class == Source.SoftClass"), TSoftClassPtr<UObject>(TargetB.Class) == Source.SoftClass);
AITEST_TRUE(TEXT("Expect TargetB.SoftClass == Source.Class"), TargetB.SoftClass == TSoftClassPtr<UObject>(Source.Class));
AITEST_TRUE(TEXT("Expect TargetA.Object == nullptr after copy of TargetB"), TargetA.Object == nullptr);
// Collect ObjectA and ObjectB, soft object paths should still copy ok.
ObjectA = nullptr;
ObjectB = nullptr;
Source.Object = nullptr;
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
FStateTreeTest_PropertyCopyObjects TargetC;
bool bCopyResultC = true;
for (const FPropertyBindingCopyInfo& Copy : Bindings.Super::GetBatchCopies(FStateTreeIndex16(TargetACopyBatchIndex)))
{
bCopyResultB &= Bindings.Super::CopyProperty(Copy, SourceViews[Copy.SourceDataHandle.Get<FStateTreeDataHandle>().GetIndex()], FStructView::Make(TargetC));
}
AITEST_TRUE(TEXT("CopyTo should succeed"), bCopyResultC);
AITEST_TRUE(TEXT("Expect TargetC.SoftObject == Source.SoftObject after GC"), TargetC.SoftObject == Source.SoftObject);
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_CopyObjects, "System.StateTree.Binding.CopyObjects");
struct FStateTreeTest_References : FStateTreeTestBase
{
virtual bool InstantTest() override
{
FStateTreeCompilerLog Log;
FStateTreePropertyBindings Bindings;
FStateTreePropertyBindingCompiler BindingCompiler;
const bool bInitResult = BindingCompiler.Init(Bindings, Log);
AITEST_TRUE(TEXT("Expect init to succeed"), bInitResult);
FStateTreeBindableStructDesc SourceDesc;
SourceDesc.Name = FName(TEXT("Source"));
SourceDesc.Struct = TBaseStructure<FStateTreeTest_PropertyRefSourceStruct>::Get();
SourceDesc.DataSource = EStateTreeBindableStructSource::Parameter;
SourceDesc.DataHandle = FStateTreeDataHandle(EStateTreeDataSourceType::ContextData, 0);
SourceDesc.ID = FGuid::NewGuid();
BindingCompiler.AddSourceStruct(SourceDesc);
FStateTreeBindableStructDesc TargetDesc;
TargetDesc.Name = FName(TEXT("Target"));
TargetDesc.Struct = TBaseStructure<FStateTreeTest_PropertyRefTargetStruct>::Get();
TargetDesc.DataSource = EStateTreeBindableStructSource::Parameter;
TargetDesc.ID = FGuid::NewGuid();
TArray<FStateTreePropertyPathBinding> PropertyBindings;
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("RefToStruct")));
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("Item.A"), TargetDesc.ID, TEXT("RefToInt")));
PropertyBindings.Add(MakeBinding(SourceDesc.ID, TEXT("Array"), TargetDesc.ID, TEXT("RefToStructArray")));
FStateTreeTest_PropertyRefSourceStruct Source;
FStateTreeDataView SourceView = FStateTreeDataView(FStructView::Make(Source));
FStateTreeTest_PropertyRefTargetStruct Target;
FStateTreeDataView TargetView(FStructView::Make(Target));
TMap<FGuid, const FStateTreeDataView> IDToStructValue;
IDToStructValue.Emplace(SourceDesc.ID, SourceView);
IDToStructValue.Emplace(TargetDesc.ID, TargetView);
const bool bCompileReferencesResult = BindingCompiler.CompileReferences(TargetDesc, PropertyBindings, TargetView, IDToStructValue);
AITEST_TRUE(TEXT("CompileReferences should succeed"), bCompileReferencesResult);
BindingCompiler.Finalize();
const bool bResolveResult = Bindings.ResolvePaths();
AITEST_TRUE(TEXT("ResolvePaths should succeed"), bResolveResult);
{
const FStateTreePropertyAccess* PropertyAccess = Bindings.GetPropertyAccess(Target.RefToStruct);
AITEST_NOT_NULL(TEXT("GetPropertyAccess should succeed"), PropertyAccess);
FStateTreeTest_PropertyStruct* Reference = Bindings.GetMutablePropertyPtr<FStateTreeTest_PropertyStruct>(SourceView, *PropertyAccess);
AITEST_EQUAL(TEXT("Expect RefToStruct to point to SourceA.Item"), Reference, &Source.Item);
}
{
const FStateTreePropertyAccess* PropertyAccess = Bindings.GetPropertyAccess(Target.RefToInt);
AITEST_NOT_NULL(TEXT("GetPropertyAccess should succeed"), PropertyAccess);
int32* Reference = Bindings.GetMutablePropertyPtr<int32>(SourceView, *PropertyAccess);
AITEST_EQUAL(TEXT("Expect RefToInt to point to SourceA.Item.A"), Reference, &Source.Item);
}
{
const FStateTreePropertyAccess* PropertyAccess = Bindings.GetPropertyAccess(Target.RefToStructArray);
AITEST_NOT_NULL(TEXT("GetPropertyAccess should succeed"), PropertyAccess);
TArray<FStateTreeTest_PropertyStruct>* Reference = Bindings.GetMutablePropertyPtr<TArray<FStateTreeTest_PropertyStruct>>(SourceView, *PropertyAccess);
AITEST_EQUAL(TEXT("Expect RefToStructArray to point to SourceA.Array"), Reference, &Source.Array);
}
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_References, "System.StateTree.Binding.References");
struct FStateTreeTest_ReferencesConstness : FStateTreeTestBase
{
virtual bool InstantTest() override
{
FStateTreeCompilerLog Log;
FStateTreePropertyBindings Bindings;
FStateTreePropertyBindingCompiler BindingCompiler;
const bool bInitResult = BindingCompiler.Init(Bindings, Log);
AITEST_TRUE(TEXT("Expect init to succeed"), bInitResult);
FStateTreeBindableStructDesc SourceAsTaskDesc;
SourceAsTaskDesc.Name = FName(TEXT("SourceTask"));
SourceAsTaskDesc.Struct = TBaseStructure<FStateTreeTest_PropertyRefSourceStruct>::Get();
SourceAsTaskDesc.DataSource = EStateTreeBindableStructSource::Task;
SourceAsTaskDesc.DataHandle = FStateTreeDataHandle(EStateTreeDataSourceType::ContextData, 0);
SourceAsTaskDesc.ID = FGuid::NewGuid();
BindingCompiler.AddSourceStruct(SourceAsTaskDesc);
FStateTreeBindableStructDesc SourceAsContextDesc;
SourceAsContextDesc.Name = FName(TEXT("SourceContext"));
SourceAsContextDesc.Struct = TBaseStructure<FStateTreeTest_PropertyRefSourceStruct>::Get();
SourceAsContextDesc.DataSource = EStateTreeBindableStructSource::Context;
SourceAsContextDesc.DataHandle = FStateTreeDataHandle(EStateTreeDataSourceType::ContextData, 0);
SourceAsContextDesc.ID = FGuid::NewGuid();
BindingCompiler.AddSourceStruct(SourceAsContextDesc);
FStateTreeBindableStructDesc TargetDesc;
TargetDesc.Name = FName(TEXT("Target"));
TargetDesc.Struct = TBaseStructure<FStateTreeTest_PropertyRefTargetStruct>::Get();
TargetDesc.DataSource = EStateTreeBindableStructSource::Parameter;
TargetDesc.ID = FGuid::NewGuid();
FStateTreePropertyPathBinding TaskPropertyBinding = MakeBinding(SourceAsTaskDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("RefToStruct"));
FStateTreePropertyPathBinding TaskOutputPropertyBinding = MakeBinding(SourceAsTaskDesc.ID, TEXT("OutputItem"), TargetDesc.ID, TEXT("RefToStruct"));
FStateTreePropertyPathBinding ContextPropertyBinding = MakeBinding(SourceAsTaskDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("RefToStruct"));
FStateTreePropertyPathBinding ContextOutputPropertyBinding = MakeBinding(SourceAsTaskDesc.ID, TEXT("Item"), TargetDesc.ID, TEXT("RefToStruct"));
FStateTreeTest_PropertyRefSourceStruct SourceAsTask;
FStateTreeDataView SourceAsTaskView(FStructView::Make(SourceAsTask));
FStateTreeTest_PropertyRefSourceStruct SourceAsContext;
FStateTreeDataView SourceAsContextView(FStructView::Make(SourceAsContext));
FStateTreeTest_PropertyRefTargetStruct Target;
FStateTreeDataView TargetView(FStructView::Make(Target));
TMap<FGuid, const FStateTreeDataView> IDToStructValue;
IDToStructValue.Emplace(SourceAsTaskDesc.ID, SourceAsTaskView);
IDToStructValue.Emplace(SourceAsContextDesc.ID, SourceAsContextView);
IDToStructValue.Emplace(TargetDesc.ID, TargetView);
{
const bool bCompileReferenceResult = BindingCompiler.CompileReferences(TargetDesc, { TaskPropertyBinding }, TargetView, IDToStructValue);
AITEST_FALSE(TEXT("CompileReferences should fail"), bCompileReferenceResult);
}
{
const bool bCompileReferenceResult = BindingCompiler.CompileReferences(TargetDesc, { TaskOutputPropertyBinding }, TargetView, IDToStructValue);
AITEST_TRUE(TEXT("CompileReferences should succeed"), bCompileReferenceResult);
}
{
const bool bCompileReferenceResult = BindingCompiler.CompileReferences(TargetDesc, { ContextPropertyBinding }, TargetView, IDToStructValue);
AITEST_FALSE(TEXT("CompileReferences should fail"), bCompileReferenceResult);
}
{
const bool bCompileReferenceResult = BindingCompiler.CompileReferences(TargetDesc, { ContextOutputPropertyBinding }, TargetView, IDToStructValue);
AITEST_FALSE(TEXT("CompileReferences should fail"), bCompileReferenceResult);
}
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_ReferencesConstness, "System.StateTree.Binding.ReferencesConstness");
struct FStateTreeTest_MutableArray : FStateTreeTestBase
{
virtual bool InstantTest() override
{
//Tree 1
// Root
// StateA -> Succeeded(Root)
FStateTreeCompilerLog Log;
FStateTreePropertyBindings Bindings;
FStateTreePropertyBindingCompiler BindingCompiler;
UStateTree& StateTree = NewStateTree();
{
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
{
// Parameters
FInstancedPropertyBag& RootPropertyBag = GetRootPropertyBag(EditorData);
RootPropertyBag.AddProperty("Value", EPropertyBagPropertyType::Int32);
RootPropertyBag.SetValueInt32("Value", -111);
RootPropertyBag.AddContainerProperty("ArrayValue", FPropertyBagContainerTypes(EPropertyBagContainerType::Array), EPropertyBagPropertyType::Int32, nullptr);
RootPropertyBag.AddProperty("ArrayValue", EPropertyBagPropertyType::Int32);
FPropertyBagArrayRef ValueArrayRef = RootPropertyBag.GetMutableArrayRef("ArrayValue").GetValue();
ValueArrayRef.EmptyAndAddValues(4);
ValueArrayRef.SetValueInt32(0, -11);
ValueArrayRef.SetValueInt32(1, -22);
ValueArrayRef.SetValueInt32(2, -33);
ValueArrayRef.SetValueInt32(3, -44);
// Global
TStateTreeEditorNode<FTestTask_PrintValue>& TaskA = EditorData.AddGlobalTask<FTestTask_PrintValue>("Tree1GlobalTaskA");
TaskA.GetInstanceData().Value = -2;
TaskA.GetInstanceData().ArrayValue = {-1, -2};
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(TaskA.ID, TEXT("Value")));
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("ArrayValue")), FPropertyBindingPath(TaskA.ID, TEXT("ArrayValue")));
}
UStateTreeState& Root = EditorData.AddSubTree("Tree1StateRoot");
{
UStateTreeState& State = Root.AddChildState("Tree1StateA", EStateTreeStateType::State);
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
Transition.bDelayTransition = true;
Transition.DelayDuration = 60.0;
FPropertyBindingPath RootParametersArrayValue3(EditorData.GetRootParametersGuid());
RootParametersArrayValue3.AddPathSegment("ArrayValue", 3);
TStateTreeEditorNode<FTestTask_PrintAndResetValue>& TaskA = State.AddTask<FTestTask_PrintAndResetValue>("Tree1StateATaskA");
TaskA.GetInstanceData().Value = -2;
TaskA.GetInstanceData().ArrayValue = { -1, -2, -3, -4 };
TaskA.GetNode().ResetValue = 22;
TaskA.GetNode().ResetArrayValue = {200, 201, 202, 203, 204, 205};
FPropertyBindingPath TaskAArrayValue3(TaskA.ID);
TaskAArrayValue3.AddPathSegment("ArrayValue", 3);
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), "Value"), FPropertyBindingPath(TaskA.ID, TEXT("Value")));
EditorData.AddPropertyBinding(RootParametersArrayValue3, TaskAArrayValue3);
TStateTreeEditorNode<FTestTask_PrintAndResetValue>& TaskB = State.AddTask<FTestTask_PrintAndResetValue>("Tree1StateATaskB");
TaskB.GetInstanceData().Value = -2;
TaskB.GetInstanceData().ArrayValue = { -1, -2, -3, -4 };
TaskB.GetNode().ResetValue = 33;
TaskB.GetNode().ResetArrayValue = { 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315 };
FPropertyBindingPath TaskBArrayValue3(TaskB.ID);
TaskBArrayValue3.AddPathSegment("ArrayValue", 3);
EditorData.AddPropertyBinding(FPropertyBindingPath(TaskA.ID, TEXT("Value")), FPropertyBindingPath(TaskB.ID, TEXT("Value")));
EditorData.AddPropertyBinding(TaskAArrayValue3, TaskBArrayValue3);
}
}
{
FStateTreeCompiler Compiler(Log);
const bool bResult = Compiler.Compile(StateTree);
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult);
}
{
FStateTreeInstanceData InstanceData;
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
const bool bInitSucceeded = Exec.IsValid();
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
FInstancedPropertyBag GlobalParameters = StateTree.GetDefaultParameters();
{
GlobalParameters.SetValueInt32("Value", 11);
FPropertyBagArrayRef ValueArrayRef = GlobalParameters.GetMutableArrayRef("ArrayValue").GetValue();
ValueArrayRef.EmptyAndAddValues(2);
ValueArrayRef.SetValueInt32(0, 911);
ValueArrayRef.SetValueInt32(1, 922);
Status = Exec.Start(&GlobalParameters);
}
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("Start should enter Global tasks"), Exec.Expect("Tree1GlobalTaskA", TEXT("EnterState11"))
.Then("Tree1GlobalTaskA", TEXT("EnterState:{911, 922}")) // should copy the full array
.Then("Tree1StateATaskA", TEXT("EnterState11"))
.Then("Tree1StateATaskA", TEXT("EnterState:{-1, -2, -3, -4}")) // should not copy anything since [3] is out of scope
.Then("Tree1StateATaskB", TEXT("EnterState22")) // TaskA set the value to 22 and {200, 201, 202, 203, 204, 205} (in EnterTask)
.Then("Tree1StateATaskB", TEXT("EnterState:{-1, -2, -3, 203}"))
);
Exec.LogClear();
Exec.Stop();
}
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_MutableArray, "System.StateTree.Binding.MutableArray");
struct FStateTreeTest_TransitionTaskWithBinding : FStateTreeTestBase
{
virtual bool InstantTest() override
{
//Tree 1
// Root
// StateA -> Succeeded(Root)
FStateTreeCompilerLog Log;
FStateTreePropertyBindings Bindings;
FStateTreePropertyBindingCompiler BindingCompiler;
UStateTree& StateTree = NewStateTree();
{
UStateTreeEditorData& EditorData = *Cast<UStateTreeEditorData>(StateTree.EditorData);
{
// Parameters
FInstancedPropertyBag& RootPropertyBag = GetRootPropertyBag(EditorData);
RootPropertyBag.AddProperty("Value", EPropertyBagPropertyType::Int32);
RootPropertyBag.SetValueInt32("Value", -111);
// Global
TStateTreeEditorNode<FTestTask_PrintValue>& TaskA = EditorData.AddGlobalTask<FTestTask_PrintValue>("Tree1GlobalTaskA");
TaskA.GetInstanceData().Value = -2;
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(TaskA.ID, TEXT("Value")));
TStateTreeEditorNode<FTestTask_PrintValue_TransitionTick>& TaskB = EditorData.AddGlobalTask<FTestTask_PrintValue_TransitionTick>("Tree1GlobalTaskB");
TaskB.GetInstanceData().Value = -2;
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(TaskB.ID, TEXT("Value")));
TStateTreeEditorNode<FTestTask_PrintValue_TransitionNoTick>& TaskC = EditorData.AddGlobalTask<FTestTask_PrintValue_TransitionNoTick>("Tree1GlobalTaskC");
TaskC.GetInstanceData().Value = -2;
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(TaskC.ID, TEXT("Value")));
}
UStateTreeState& Root = EditorData.AddSubTree("Tree1StateRoot");
{
TStateTreeEditorNode<FTestTask_PrintValue>& TaskA = Root.AddTask<FTestTask_PrintValue>("Tree1StateRootTaskA");
TaskA.GetInstanceData().Value = -2;
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(TaskA.ID, TEXT("Value")));
TStateTreeEditorNode<FTestTask_PrintValue_TransitionTick>& TaskB = Root.AddTask<FTestTask_PrintValue_TransitionTick>("Tree1StateRootTaskB");
TaskB.GetInstanceData().Value = -2;
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(TaskB.ID, TEXT("Value")));
TStateTreeEditorNode<FTestTask_PrintValue_TransitionNoTick>& TaskC = Root.AddTask<FTestTask_PrintValue_TransitionNoTick>("Tree1StateRootTaskC");
TaskC.GetInstanceData().Value = -2;
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(TaskC.ID, TEXT("Value")));
}
{
UStateTreeState& State = Root.AddChildState("Tree1StateA", EStateTreeStateType::State);
FStateTreeTransition& Transition = State.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::Succeeded);
Transition.bDelayTransition = true;
Transition.DelayDuration = 5.0;
TStateTreeEditorNode<FTestTask_PrintValue>& TaskA = State.AddTask<FTestTask_PrintValue>("Tree1StateATaskA");
TaskA.GetInstanceData().Value = -2;
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(TaskA.ID, TEXT("Value")));
TStateTreeEditorNode<FTestTask_PrintValue_TransitionTick>& TaskB = State.AddTask<FTestTask_PrintValue_TransitionTick>("Tree1StateATaskB");
TaskB.GetInstanceData().Value = -2;
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(TaskB.ID, TEXT("Value")));
TStateTreeEditorNode<FTestTask_PrintValue_TransitionNoTick>& TaskC = State.AddTask<FTestTask_PrintValue_TransitionNoTick>("Tree1StateATaskC");
TaskC.GetInstanceData().Value = -2;
EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(TaskC.ID, TEXT("Value")));
}
}
{
FStateTreeCompiler Compiler(Log);
const bool bResult = Compiler.Compile(StateTree);
AITEST_TRUE(TEXT("StateTree2 should get compiled"), bResult);
}
{
FStateTreeInstanceData InstanceData;
FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData);
const bool bInitSucceeded = Exec.IsValid();
AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded);
EStateTreeRunStatus Status = EStateTreeRunStatus::Unset;
FInstancedPropertyBag GlobalParameters = StateTree.GetDefaultParameters();
{
GlobalParameters.SetValueInt32("Value", 99);
Status = Exec.Start(&GlobalParameters);
}
AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1StateA"));
AITEST_TRUE(TEXT("Start should enter Global tasks"), Exec.Expect("Tree1GlobalTaskA", TEXT("EnterState99"))
.Then("Tree1GlobalTaskB", TEXT("EnterState99"))
.Then("Tree1GlobalTaskC", TEXT("EnterState99"))
.Then("Tree1StateRootTaskA", TEXT("EnterState99"))
.Then("Tree1StateRootTaskB", TEXT("EnterState99"))
.Then("Tree1StateRootTaskC", TEXT("EnterState99"))
.Then("Tree1StateATaskA", TEXT("EnterState99"))
.Then("Tree1StateATaskB", TEXT("EnterState99"))
.Then("Tree1StateATaskC", TEXT("EnterState99"))
);
Exec.LogClear();
GlobalParameters.SetValueInt32("Value", 88);
InstanceData.GetMutableStorage().SetGlobalParameters(GlobalParameters);
Status = Exec.Tick(1.0f);
AITEST_EQUAL(TEXT("2nd Tick should complete with Running"), Status, EStateTreeRunStatus::Running);
AITEST_TRUE(TEXT("In correct states"), Exec.ExpectInActiveStates("Tree1StateRoot", "Tree1StateA"));
AITEST_TRUE(TEXT("2nd Tick should tick tasks"), Exec.Expect("Tree1GlobalTaskA", TEXT("Tick88"))
.Then("Tree1GlobalTaskB", TEXT("Tick88"))
.Then("Tree1StateRootTaskA", TEXT("Tick88"))
.Then("Tree1StateRootTaskB", TEXT("Tick88"))
.Then("Tree1StateATaskA", TEXT("Tick88"))
.Then("Tree1StateATaskB", TEXT("Tick88"))
.Then("Tree1StateATaskC", TEXT("TriggerTransitions88"))
.Then("Tree1StateATaskB", TEXT("TriggerTransitions88"))
.Then("Tree1StateRootTaskC", TEXT("TriggerTransitions88"))
.Then("Tree1StateRootTaskB", TEXT("TriggerTransitions88"))
.Then("Tree1GlobalTaskC", TEXT("TriggerTransitions88"))
.Then("Tree1GlobalTaskB", TEXT("TriggerTransitions88"))
);
Exec.LogClear();
Exec.Stop();
}
return true;
}
};
IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionTaskWithBinding, "System.StateTree.Binding.TransitionTaskWithBinding");
} // namespace UE::StateTree::Tests
UE_ENABLE_OPTIMIZATION_SHIP
#undef LOCTEXT_NAMESPACE