// Copyright Epic Games, Inc. All Rights Reserved. #include "StateTreeCompiler.h" #include "StateTree.h" #include "StateTreeEditorData.h" #include "StateTreeTypes.h" #include "Conditions/StateTreeCommonConditions.h" #include "StateTreeEvaluatorBase.h" #include "StateTreeTaskBase.h" #include "StateTreeConditionBase.h" #include "StateTreeState.h" #include "StateTreeExecutionContext.h" #include "StateTreePropertyBindingCompiler.h" namespace UE::StateTree::Compiler { void FValidationResult::Log(FStateTreeCompilerLog& Log, const TCHAR* ContextText, const FStateTreeBindableStructDesc& ContextStruct) const { Log.Reportf(EMessageSeverity::Error, ContextStruct, TEXT("The StateTree is too complex. Compact index %s out of range %d/%d."), ContextText, Value, MaxValue); } const UScriptStruct* GetBaseStructFromMetaData(const FProperty* Property, FString& OutBaseStructName) { static const FName NAME_BaseStruct = "BaseStruct"; const UScriptStruct* Result = nullptr; OutBaseStructName = Property->GetMetaData(NAME_BaseStruct); if (!OutBaseStructName.IsEmpty()) { Result = UClass::TryFindTypeSlow(OutBaseStructName); if (!Result) { Result = LoadObject(nullptr, *OutBaseStructName); } } return Result; } }; // UE::StateTree::Compiler bool FStateTreeCompiler::Compile(UStateTree& InStateTree) { StateTree = &InStateTree; TreeData = Cast(StateTree->EditorData); if (!TreeData) { return false; } // Cleanup existing state StateTree->ResetCompiled(); if (!BindingsCompiler.Init(StateTree->PropertyBindings, Log)) { StateTree->ResetCompiled(); return false; } // Copy schema the EditorData StateTree->Schema = DuplicateObject(TreeData->Schema, StateTree); // Copy parameters from EditorData StateTree->Parameters = TreeData->RootParameters.Parameters; // Mark parameters as binding source const FStateTreeBindableStructDesc ParametersDesc = { TEXT("Parameters"), StateTree->Parameters.GetPropertyBagStruct(), EStateTreeBindableStructSource::TreeParameter, TreeData->RootParameters.ID }; const int32 ParametersDataViewIndex = BindingsCompiler.AddSourceStruct(ParametersDesc); if (const auto Validation = UE::StateTree::Compiler::IsValidIndex8(ParametersDataViewIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("ParametersDataViewIndex"), ParametersDesc); return false; } StateTree->ParametersDataViewIndex = FStateTreeIndex8(ParametersDataViewIndex); // Mark all named external values as binding source if (StateTree->Schema) { StateTree->NamedExternalDataDescs = StateTree->Schema->GetNamedExternalDataDescs(); for (FStateTreeExternalDataDesc& Desc : StateTree->NamedExternalDataDescs) { const FStateTreeBindableStructDesc ExtDataDesc = { Desc.Name, Desc.Struct, EStateTreeBindableStructSource::TreeData, Desc.ID }; const int32 ExternalStructIndex = BindingsCompiler.AddSourceStruct(ExtDataDesc); if (const auto Validation = UE::StateTree::Compiler::IsValidIndex8(ExternalStructIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("ExternalStructIndex"), ParametersDesc); return false; } Desc.Handle.DataViewIndex = FStateTreeIndex8(ExternalStructIndex); } } if (!CreateStates()) { StateTree->ResetCompiled(); return false; } if (!CreateEvaluators()) { StateTree->ResetCompiled(); return false; } if (!CreateStateTasksAndParameters()) { StateTree->ResetCompiled(); return false; } if (!CreateStateTransitions()) { StateTree->ResetCompiled(); return false; } StateTree->Nodes = Nodes; StateTree->DefaultInstanceData.Init(*StateTree, InstanceStructs, InstanceObjects); StateTree->SharedInstanceData.Init(*StateTree, SharedInstanceStructs, SharedInstanceObjects); BindingsCompiler.Finalize(); if (!StateTree->Link()) { StateTree->ResetCompiled(); return false; } return true; } FStateTreeStateHandle FStateTreeCompiler::GetStateHandle(const FGuid& StateID) const { const int32* Idx = IDToState.Find(StateID); if (Idx == nullptr) { return FStateTreeStateHandle::Invalid; } return FStateTreeStateHandle(uint16(*Idx)); } UStateTreeState* FStateTreeCompiler::GetState(const FGuid& StateID) { const int32* Idx = IDToState.Find(StateID); if (Idx == nullptr) { return nullptr; } return SourceStates[*Idx]; } bool FStateTreeCompiler::CreateStates() { // Create item for the runtime execution state InstanceStructs.Add(FInstancedStruct::Make()); // Create main tree (omit subtrees) for (UStateTreeState* SubTree : TreeData->SubTrees) { if (SubTree != nullptr) { if (!CreateStateRecursive(*SubTree, FStateTreeStateHandle::Invalid)) { return false; } } } // Create Subtrees for (UStateTreeState* SubTree : TreeData->SubTrees) { TArray Stack; Stack.Push(SubTree); while (!Stack.IsEmpty()) { if (UStateTreeState* State = Stack.Pop()) { if (State->Type == EStateTreeStateType::Subtree) { if (!CreateStateRecursive(*State, FStateTreeStateHandle::Invalid)) { return false; } } Stack.Append(State->Children); } } } return true; } bool FStateTreeCompiler::CreateStateRecursive(UStateTreeState& State, const FStateTreeStateHandle Parent) { FStateTreeCompilerLogStateScope LogStateScope(&State, Log); const int32 StateIdx = StateTree->States.AddDefaulted(); FCompactStateTreeState& CompactState = StateTree->States[StateIdx]; CompactState.Name = State.Name; CompactState.Parent = Parent; CompactState.Type = State.Type; SourceStates.Add(&State); IDToState.Add(State.ID, StateIdx); // Child states const int32 ChildrenBegin = StateTree->States.Num(); if (const auto Validation = UE::StateTree::Compiler::IsValidCount16(ChildrenBegin); Validation.DidFail()) { Validation.Log(Log, TEXT("ChildrenBegin")); return false; } CompactState.ChildrenBegin = uint16(ChildrenBegin); for (UStateTreeState* Child : State.Children) { if (Child != nullptr && Child->Type != EStateTreeStateType::Subtree) { if (!CreateStateRecursive(*Child, FStateTreeStateHandle((uint16)StateIdx))) { return false; } } } const int32 ChildrenEnd = StateTree->States.Num(); if (const auto Validation = UE::StateTree::Compiler::IsValidCount16(ChildrenEnd); Validation.DidFail()) { Validation.Log(Log, TEXT("ChildrenEnd")); return false; } StateTree->States[StateIdx].ChildrenEnd = uint16(ChildrenEnd); return true; } bool FStateTreeCompiler::CreateConditions(UStateTreeState& State, TConstArrayView Conditions) { for (int32 Index = 0; Index < Conditions.Num(); Index++) { const bool bIsFirst = Index == 0; const FStateTreeEditorNode& CondNode = Conditions[Index]; // First operand should be copy as we dont have a previous item to operate on. const EStateTreeConditionOperand Operand = bIsFirst ? EStateTreeConditionOperand::Copy : CondNode.ConditionOperand; // First indent must be 0 to make the parentheses calculation match. const int32 CurrIndent = bIsFirst ? 0 : FMath::Clamp((int32)CondNode.ConditionIndent, 0, UE::StateTree::MaxConditionIndent); // Next indent, or terminate at zero. const int32 NextIndent = Conditions.IsValidIndex(Index + 1) ? FMath::Clamp((int32)Conditions[Index].ConditionIndent, 0, UE::StateTree::MaxConditionIndent) : 0; const int32 DeltaIndent = NextIndent - CurrIndent; if (!CreateCondition(State, CondNode, Operand, (int8)DeltaIndent)) { return false; } } return true; } bool FStateTreeCompiler::CreateEvaluators() { const int32 EvaluatorsBegin = Nodes.Num(); if (const auto Validation = UE::StateTree::Compiler::IsValidCount16(EvaluatorsBegin); Validation.DidFail()) { Validation.Log(Log, TEXT("EvaluatorsBegin")); return false; } StateTree->EvaluatorsBegin = uint16(EvaluatorsBegin); for (FStateTreeEditorNode& EvalNode : TreeData->Evaluators) { if (!CreateEvaluator(EvalNode)) { return false; } } const int32 EvaluatorsNum = Nodes.Num() - EvaluatorsBegin; if (const auto Validation = UE::StateTree::Compiler::IsValidCount16(EvaluatorsNum); Validation.DidFail()) { Validation.Log(Log, TEXT("EvaluatorsNum")); return false; } StateTree->EvaluatorsNum = uint16(EvaluatorsNum); return true; } bool FStateTreeCompiler::CreateStateTasksAndParameters() { for (int32 i = 0; i < StateTree->States.Num(); i++) { FCompactStateTreeState& CompactState = StateTree->States[i]; UStateTreeState* SourceState = SourceStates[i]; check(SourceState != nullptr); FStateTreeCompilerLogStateScope LogStateScope(SourceState, Log); // Create parameters if (SourceState->Type == EStateTreeStateType::Linked || SourceState->Type == EStateTreeStateType::Subtree) { // Both linked and subtree has instance data describing their parameters. // This allows to resolve the binding paths and lets us have bindable parameters when transitioned into a parameterized subtree directly. FInstancedStruct& Instance = InstanceStructs.AddDefaulted_GetRef(); const int32 InstanceIndex = InstanceStructs.Num() - 1; if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("InstanceIndex")); return false; } CompactState.ParameterInstanceIndex = FStateTreeIndex16(InstanceIndex); Instance.InitializeAs(); FCompactStateTreeParameters& CompactParams = Instance.GetMutable(); CompactParams.Parameters = SourceState->Parameters.Parameters; if (SourceState->Type == EStateTreeStateType::Subtree) { // Register a binding source const FStateTreeBindableStructDesc SubtreeParamsDesc = { SourceState->Name, SourceState->Parameters.Parameters.GetPropertyBagStruct(), EStateTreeBindableStructSource::StateParameter, SourceState->Parameters.ID }; const int32 SourceStructIndex = BindingsCompiler.AddSourceStruct(SubtreeParamsDesc); if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(SourceStructIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("SourceStructIndex"), SubtreeParamsDesc); return false; } CompactState.ParameterDataViewIndex = FStateTreeIndex16(SourceStructIndex); } else if (SourceState->Type == EStateTreeStateType::Linked) { // Binding target FStateTreeBindableStructDesc LinkedParamsDesc = { SourceState->Name, SourceState->Parameters.Parameters.GetPropertyBagStruct(), EStateTreeBindableStructSource::StateParameter, SourceState->Parameters.ID }; // Check that the bindings for this struct are still all valid. TArray Bindings; if (!GetAndValidateBindings(LinkedParamsDesc, Bindings)) { return false; } int32 BatchIndex = INDEX_NONE; if (!BindingsCompiler.CompileBatch(LinkedParamsDesc, Bindings, BatchIndex)) { return false; } if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(BatchIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("BatchIndex"), LinkedParamsDesc); return false; } CompactParams.BindingsBatch = FStateTreeIndex16(BatchIndex); } } // Create tasks const int32 TasksBegin = Nodes.Num(); if (const auto Validation = UE::StateTree::Compiler::IsValidCount16(TasksBegin); Validation.DidFail()) { Validation.Log(Log, TEXT("TasksBegin")); return false; } CompactState.TasksBegin = uint16(TasksBegin); for (FStateTreeEditorNode& TaskNode : SourceState->Tasks) { if (!CreateTask(*SourceState, TaskNode)) { return false; } } if (!CreateTask(*SourceState, SourceState->SingleTask)) { return false; } const int32 TasksNum = Nodes.Num() - TasksBegin; if (const auto Validation = UE::StateTree::Compiler::IsValidCount8(TasksNum); Validation.DidFail()) { Validation.Log(Log, TEXT("TasksNum")); return false; } CompactState.TasksNum = uint8(TasksNum); } return true; } bool FStateTreeCompiler::CreateStateTransitions() { for (int32 i = 0; i < StateTree->States.Num(); i++) { FCompactStateTreeState& CompactState = StateTree->States[i]; UStateTreeState* SourceState = SourceStates[i]; check(SourceState != nullptr); FStateTreeCompilerLogStateScope LogStateScope(SourceState, Log); // Enter conditions. const int32 EnterConditionsBegin = Nodes.Num(); if (const auto Validation = UE::StateTree::Compiler::IsValidCount16(EnterConditionsBegin); Validation.DidFail()) { Validation.Log(Log, TEXT("EnterConditionsBegin")); return false; } CompactState.EnterConditionsBegin = uint16(EnterConditionsBegin); if (!CreateConditions(*SourceState, SourceState->EnterConditions)) { Log.Reportf(EMessageSeverity::Error, TEXT("Failed to create state enter condition.")); return false; } const int32 EnterConditionsNum = Nodes.Num() - EnterConditionsBegin; if (const auto Validation = UE::StateTree::Compiler::IsValidCount8(EnterConditionsNum); Validation.DidFail()) { Validation.Log(Log, TEXT("EnterConditionsNum")); return false; } CompactState.EnterConditionsNum = uint8(EnterConditionsNum); // Linked state if (SourceState->Type == EStateTreeStateType::Linked) { // Make sure the linked state is not self or parent to this state. const UStateTreeState* LinkedParentState = nullptr; for (const UStateTreeState* State = SourceState; State != nullptr; State = State->Parent) { if (State->ID == SourceState->LinkedState.ID) { LinkedParentState = State; break; } } if (LinkedParentState != nullptr) { Log.Reportf(EMessageSeverity::Error, TEXT("State is linked to it's parent state '%s', which will create infinite loop."), *LinkedParentState->Name.ToString()); return false; } // The linked state must be a subtree. const UStateTreeState* TargetState = GetState(SourceState->LinkedState.ID); if (TargetState == nullptr) { Log.Reportf(EMessageSeverity::Error, TEXT("Failed to resolve linked state '%s'."), *SourceState->LinkedState.Name.ToString()); return false; } if (TargetState->Type != EStateTreeStateType::Subtree) { Log.Reportf(EMessageSeverity::Error, TEXT("State '%s' is linked to state '%s', which is not a subtree."), *SourceState->Name.ToString(), *TargetState->Name.ToString()); return false; } CompactState.LinkedState = GetStateHandle(SourceState->LinkedState.ID); if (!CompactState.LinkedState.IsValid()) { Log.Reportf(EMessageSeverity::Error, TEXT("Failed to resolve linked state '%s'."), *SourceState->LinkedState.Name.ToString()); return false; } } // Transitions const int32 TransitionsBegin = StateTree->Transitions.Num(); if (const auto Validation = UE::StateTree::Compiler::IsValidCount16(TransitionsBegin); Validation.DidFail()) { Validation.Log(Log, TEXT("TransitionsBegin")); return false; } CompactState.TransitionsBegin = uint16(TransitionsBegin); for (FStateTreeTransition& Transition : SourceState->Transitions) { FCompactStateTransition& CompactTransition = StateTree->Transitions.AddDefaulted_GetRef(); CompactTransition.Event = Transition.Event; CompactTransition.Type = Transition.State.Type; CompactTransition.GateDelay = (uint8)FMath::Clamp(FMath::CeilToInt(Transition.GateDelay * 10.0f), 0, 255); CompactTransition.State = FStateTreeStateHandle::Invalid; if (!ResolveTransitionState(*SourceState, Transition.State, CompactTransition.State)) { return false; } // Note: Unset transition is allowed here. It can be used to mask a transition at parent. const int32 ConditionsBegin = Nodes.Num(); if (const auto Validation = UE::StateTree::Compiler::IsValidCount16(ConditionsBegin); Validation.DidFail()) { Validation.Log(Log, TEXT("ConditionsBegin")); return false; } CompactTransition.ConditionsBegin = uint16(ConditionsBegin); if (!CreateConditions(*SourceState, Transition.Conditions)) { Log.Reportf(EMessageSeverity::Error, TEXT("Failed to create condition for transition to '%s'."), *Transition.State.Name.ToString()); return false; } const int32 ConditionsNum = Nodes.Num() - ConditionsBegin; if (const auto Validation = UE::StateTree::Compiler::IsValidCount8(ConditionsNum); Validation.DidFail()) { Validation.Log(Log, TEXT("ConditionsNum")); return false; } CompactTransition.ConditionsNum = uint8(ConditionsNum); } const int32 TransitionsNum = StateTree->Transitions.Num() - TransitionsBegin; if (const auto Validation = UE::StateTree::Compiler::IsValidCount8(TransitionsNum); Validation.DidFail()) { Validation.Log(Log, TEXT("TransitionsNum")); return false; } CompactState.TransitionsNum = uint8(TransitionsNum); } // @todo: Add test to check that all success/failure transition is possible (see editor). return true; } bool FStateTreeCompiler::ResolveTransitionState(const UStateTreeState& SourceState, const FStateTreeStateLink& Link, FStateTreeStateHandle& OutTransitionHandle) const { if (Link.Type == EStateTreeTransitionType::GotoState) { OutTransitionHandle = GetStateHandle(Link.ID); if (!OutTransitionHandle.IsValid()) { Log.Reportf(EMessageSeverity::Error, TEXT("Failed to resolve transition to state '%s'."), *Link.Name.ToString()); return false; } } else if (Link.Type == EStateTreeTransitionType::NextState) { // Find next state. const UStateTreeState* NextState = SourceState.GetNextSiblingState(); if (NextState == nullptr) { Log.Reportf(EMessageSeverity::Error, TEXT("Failed to resolve transition, there's no next state.")); return false; } OutTransitionHandle = GetStateHandle(NextState->ID); if (!OutTransitionHandle.IsValid()) { Log.Reportf(EMessageSeverity::Error, TEXT("Failed to resolve transition next state, no handle found for '%s'."), *NextState->Name.ToString()); return false; } } return true; } bool FStateTreeCompiler::CreateCondition(UStateTreeState& State, const FStateTreeEditorNode& CondNode, const EStateTreeConditionOperand Operand, const int8 DeltaIndent) { if (!CondNode.Node.IsValid()) { // Empty line in conditions array, just silently ignore. return true; } FStateTreeBindableStructDesc StructDesc; StructDesc.ID = CondNode.ID; StructDesc.Name = CondNode.Node.GetScriptStruct()->GetFName(); StructDesc.DataSource = EStateTreeBindableStructSource::Condition; // Check that item has valid instance initialized. if (!CondNode.Instance.IsValid() && CondNode.InstanceObject == nullptr) { Log.Reportf(EMessageSeverity::Error, StructDesc, TEXT("Malformed condition, missing instance value.")); return false; } // Copy the condition const FInstancedStruct& Node = Nodes.Add_GetRef(CondNode.Node); FStateTreeConditionBase& Cond = Node.GetMutable(); Cond.Operand = Operand; Cond.DeltaIndent = DeltaIndent; if (CondNode.Instance.IsValid()) { // Struct instance const int32 InstanceIndex = SharedInstanceStructs.Add(CondNode.Instance); // Create binding source struct descriptor. StructDesc.Struct = CondNode.Instance.GetScriptStruct(); StructDesc.Name = Cond.Name; if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("InstanceIndex"), StructDesc); return false; } Cond.InstanceIndex = FStateTreeIndex16(InstanceIndex); Cond.bInstanceIsObject = false; } else { // Object Instance check(CondNode.InstanceObject != nullptr); UObject* Instance = DuplicateObject(CondNode.InstanceObject, StateTree); const int32 InstanceIndex = SharedInstanceObjects.Add(Instance); // Create binding source struct descriptor. StructDesc.Struct = Instance->GetClass(); StructDesc.Name = Cond.Name; if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("InstanceIndex"), StructDesc); return false; } Cond.InstanceIndex = FStateTreeIndex16(InstanceIndex); Cond.bInstanceIsObject = true; } // Mark the struct as binding source. const int32 SourceStructIndex = BindingsCompiler.AddSourceStruct(StructDesc); // Check that the bindings for this struct are still all valid. TArray Bindings; if (!GetAndValidateBindings(StructDesc, Bindings)) { return false; } // Compile batch copy for this struct, we pass in all the bindings, the compiler will pick up the ones for the target structs. int32 BatchIndex = INDEX_NONE; if (!BindingsCompiler.CompileBatch(StructDesc, Bindings, BatchIndex)) { return false; } if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(BatchIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("BatchIndex"), StructDesc); return false; } Cond.BindingsBatch = FStateTreeIndex16(BatchIndex); if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(SourceStructIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("SourceStructIndex"), StructDesc); return false; } Cond.DataViewIndex = FStateTreeIndex16(SourceStructIndex); return true; } bool FStateTreeCompiler::CreateTask(UStateTreeState& State, const FStateTreeEditorNode& TaskNode) { // Silently ignore empty nodes. if (!TaskNode.Node.IsValid()) { return true; } // Create binding source struct descriptor. FStateTreeBindableStructDesc StructDesc; StructDesc.ID = TaskNode.ID; StructDesc.Name = TaskNode.Node.GetScriptStruct()->GetFName(); StructDesc.DataSource = EStateTreeBindableStructSource::Task; // Check that node has valid instance initialized. if (!TaskNode.Instance.IsValid() && TaskNode.InstanceObject == nullptr) { Log.Reportf(EMessageSeverity::Error, StructDesc, TEXT("Malformed task, missing instance value.")); return false; } // Copy the task const FInstancedStruct& Node = Nodes.Add_GetRef(TaskNode.Node); FStateTreeTaskBase& Task = Node.GetMutable(); if (TaskNode.Instance.IsValid()) { // Struct Instance const int32 InstanceIndex = InstanceStructs.Add(TaskNode.Instance); // Create binding source struct descriptor. StructDesc.Struct = TaskNode.Instance.GetScriptStruct(); StructDesc.Name = Task.Name; if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("InstanceIndex"), StructDesc); return false; } Task.InstanceIndex = FStateTreeIndex16(InstanceIndex); Task.bInstanceIsObject = false; } else { // Object Instance check(TaskNode.InstanceObject != nullptr); UObject* Instance = DuplicateObject(TaskNode.InstanceObject, StateTree); const int32 InstanceIndex = InstanceObjects.Add(Instance); // Create binding source struct descriptor. StructDesc.Struct = Instance->GetClass(); StructDesc.Name = Task.Name; if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("InstanceIndex"), StructDesc); return false; } Task.InstanceIndex = FStateTreeIndex16(InstanceIndex); Task.bInstanceIsObject = true; } // Mark the instance as binding source. const int32 SourceStructIndex = BindingsCompiler.AddSourceStruct(StructDesc); // Check that the bindings for this struct are still all valid. TArray Bindings; if (!GetAndValidateBindings(StructDesc, Bindings)) { return false; } // Compile batch copy for this struct, we pass in all the bindings, the compiler will pick up the ones for the target structs. int32 BatchIndex = INDEX_NONE; if (!BindingsCompiler.CompileBatch(StructDesc, Bindings, BatchIndex)) { return false; } if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(BatchIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("BatchIndex"), StructDesc); return false; } Task.BindingsBatch = FStateTreeIndex16(BatchIndex); if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(SourceStructIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("SourceStructIndex"), StructDesc); return false; } Task.DataViewIndex = FStateTreeIndex16(SourceStructIndex); return true; } bool FStateTreeCompiler::CreateEvaluator(const FStateTreeEditorNode& EvalNode) { // Silently ignore empty nodes. if (!EvalNode.Node.IsValid()) { return true; } // Create binding source struct descriptor. FStateTreeBindableStructDesc StructDesc; StructDesc.ID = EvalNode.ID; StructDesc.Name = EvalNode.Node.GetScriptStruct()->GetFName(); StructDesc.DataSource = EStateTreeBindableStructSource::Evaluator; // Check that node has valid instance initialized. if (!EvalNode.Instance.IsValid() && EvalNode.InstanceObject == nullptr) { Log.Reportf(EMessageSeverity::Error, StructDesc, TEXT("Malformed evaluator, missing instance value.")); return false; } // Copy the evaluator const FInstancedStruct& Node = Nodes.Add_GetRef(EvalNode.Node); FStateTreeEvaluatorBase& Eval = Node.GetMutable(); if (EvalNode.Instance.IsValid()) { // Struct Instance const int32 InstanceIndex = InstanceStructs.Add(EvalNode.Instance); // Create binding source struct descriptor. StructDesc.Struct = EvalNode.Instance.GetScriptStruct(); StructDesc.Name = Eval.Name; if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("InstanceIndex"), StructDesc); return false; } Eval.InstanceIndex = FStateTreeIndex16(InstanceIndex); Eval.bInstanceIsObject = false; } else { // Object Instance check(EvalNode.InstanceObject != nullptr); UObject* Instance = DuplicateObject(EvalNode.InstanceObject, StateTree); const int32 InstanceIndex = InstanceObjects.Add(Instance); // Create binding source struct descriptor. StructDesc.Struct = Instance->GetClass(); StructDesc.Name = Eval.Name; if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(InstanceIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("InstanceIndex"), StructDesc); return false; } Eval.InstanceIndex = FStateTreeIndex16(InstanceIndex); Eval.bInstanceIsObject = true; } // Mark the instance as binding source. const int32 SourceStructIndex = BindingsCompiler.AddSourceStruct(StructDesc); // Check that the bindings for this struct are still all valid. TArray Bindings; if (!GetAndValidateBindings(StructDesc, Bindings)) { return false; } // Compile batch copy for this struct, we pass in all the bindings, the compiler will pick up the ones for the target structs. int32 BatchIndex = INDEX_NONE; if (!BindingsCompiler.CompileBatch(StructDesc, Bindings, BatchIndex)) { return false; } if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(BatchIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("BatchIndex"), StructDesc); return false; } Eval.BindingsBatch = FStateTreeIndex16(BatchIndex); if (const auto Validation = UE::StateTree::Compiler::IsValidIndex16(SourceStructIndex); Validation.DidFail()) { Validation.Log(Log, TEXT("SourceStructIndex"), StructDesc); return false; } Eval.DataViewIndex = FStateTreeIndex16(SourceStructIndex); return true; } bool FStateTreeCompiler::IsPropertyAnyEnum(const FStateTreeBindableStructDesc& Struct, FStateTreeEditorPropertyPath Path) const { bool bIsAnyEnum = false; TArray Segments; const FProperty* LeafProperty = nullptr; int32 LeafArrayIndex = INDEX_NONE; const bool bResolved = FStateTreePropertyBindingCompiler::ResolvePropertyPath(Struct, Path, Segments, LeafProperty, LeafArrayIndex); if (bResolved && LeafProperty) { if (const FProperty* OwnerProperty = LeafProperty->GetOwnerProperty()) { if (const FStructProperty* OwnerStructProperty = CastField(OwnerProperty)) { bIsAnyEnum = OwnerStructProperty->Struct == TBaseStructure::Get(); } } } return bIsAnyEnum; } bool FStateTreeCompiler::ValidateStructRef(const FStateTreeBindableStructDesc& SourceStruct, FStateTreeEditorPropertyPath SourcePath, const FStateTreeBindableStructDesc& TargetStruct, FStateTreeEditorPropertyPath TargetPath) const { TArray Segments; const FProperty* TargetLeafProperty = nullptr; int32 TargetLeafArrayIndex = INDEX_NONE; if (FStateTreePropertyBindingCompiler::ResolvePropertyPath(TargetStruct, TargetPath, Segments, TargetLeafProperty, TargetLeafArrayIndex) == false) { // This will later be reported by the bindings compiler. return true; } // Early out if the target is not FStateTreeStructRef. const FStructProperty* TargetStructProperty = CastField(TargetLeafProperty); if (TargetStructProperty == nullptr || TargetStructProperty->Struct != TBaseStructure::Get()) { return true; } FString TargetBaseStructName; const UScriptStruct* TargetBaseStruct = UE::StateTree::Compiler::GetBaseStructFromMetaData(TargetStructProperty, TargetBaseStructName); if (TargetBaseStruct == nullptr) { Log.Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Could not find base struct '%s' for target '%s:%s'."), *TargetBaseStructName, *TargetStruct.Name.ToString(), *TargetPath.ToString()); return false; } const FProperty* SourceLeafProperty = nullptr; int32 SourceLeafArrayIndex = INDEX_NONE; if (FStateTreePropertyBindingCompiler::ResolvePropertyPath(SourceStruct, SourcePath, Segments, SourceLeafProperty, SourceLeafArrayIndex) == false) { // This will later be reported by the bindings compiler. return true; } // Exit if the source is not a struct property. const FStructProperty* SourceStructProperty = CastField(SourceLeafProperty); if (SourceStructProperty == nullptr) { return true; } if (SourceStructProperty->Struct == TBaseStructure::Get()) { // Source is struct ref too, check the types match. FString SourceBaseStructName; const UScriptStruct* SourceBaseStruct = UE::StateTree::Compiler::GetBaseStructFromMetaData(SourceStructProperty, SourceBaseStructName); if (SourceBaseStruct == nullptr) { Log.Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Could not find base struct '%s' for bidning source '%s:%s'."), *SourceBaseStructName, *SourceStruct.Name.ToString(), *SourcePath.ToString()); return false; } if (SourceBaseStruct->IsChildOf(TargetBaseStruct) == false) { Log.Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Type mismatch between source '%s:%s' and target '%s:%s' types, '%s' is not child of '%s'."), *SourceStruct.Name.ToString(), *SourcePath.ToString(), *TargetStruct.Name.ToString(), *TargetPath.ToString(), *GetNameSafe(SourceBaseStruct), *GetNameSafe(TargetBaseStruct)); return false; } } else { if (!SourceStructProperty->Struct || SourceStructProperty->Struct->IsChildOf(TargetBaseStruct) == false) { Log.Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Type mismatch between source '%s:%s' and target '%s:%s' types, '%s' is not child of '%s'."), *SourceStruct.Name.ToString(), *SourcePath.ToString(), *TargetStruct.Name.ToString(), *TargetPath.ToString(), *GetNameSafe(SourceStructProperty->Struct), *GetNameSafe(TargetBaseStruct)); return false; } } return true; } bool FStateTreeCompiler::GetAndValidateBindings(const FStateTreeBindableStructDesc& TargetStruct, TArray& OutBindings) const { OutBindings.Reset(); for (const FStateTreeEditorPropertyBinding& Binding : TreeData->EditorBindings.GetBindings()) { if (Binding.TargetPath.StructID != TargetStruct.ID) { continue; } // Source must be one of the source structs we have discovered in the tree. const FGuid SourceStructID = Binding.SourcePath.StructID; const int32 SourceStructIdx = BindingsCompiler.GetSourceStructIndexByID(SourceStructID); if (SourceStructIdx == INDEX_NONE) { Log.Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Failed to find binding source property '%s' for target '%s:%s'."), *Binding.SourcePath.ToString(), *TargetStruct.Name.ToString(), *Binding.TargetPath.ToString()); return false; } const FStateTreeBindableStructDesc& SourceStruct = BindingsCompiler.GetSourceStructDesc(SourceStructIdx); // Source must be accessible by the target struct via all execution paths. TArray AccessibleStructs; TreeData->GetAccessibleStructs(Binding.TargetPath.StructID, AccessibleStructs); const bool bSourceAccessible = AccessibleStructs.ContainsByPredicate([SourceStructID](const FStateTreeBindableStructDesc& Structs) { return (Structs.ID == SourceStructID); }); if (!bSourceAccessible) { Log.Reportf(EMessageSeverity::Error, TargetStruct, TEXT("Property '%s:%s' cannot be bound to '%s:%s', because the binding source '%s' is not updated before '%s' in the tree."), *SourceStruct.Name.ToString(), *Binding.SourcePath.ToString(), *TargetStruct.Name.ToString(), *Binding.TargetPath.ToString(), *SourceStruct.Name.ToString(), *TargetStruct.Name.ToString()); return false; } // Special case fo AnyEnum. StateTreeBindingExtension allows AnyEnums to bind to other enum types. // The actual copy will be done via potential type promotion copy, into the value property inside the AnyEnum. // We amend the paths here to point to the 'Value' property. const bool bSourceIsAnyEnum = IsPropertyAnyEnum(SourceStruct, Binding.SourcePath); const bool bTargetIsAnyEnum = IsPropertyAnyEnum(TargetStruct, Binding.TargetPath); if (bSourceIsAnyEnum || bTargetIsAnyEnum) { FStateTreeEditorPropertyBinding ModifiedBinding(Binding); if (bSourceIsAnyEnum) { ModifiedBinding.SourcePath.Path.Add(GET_MEMBER_NAME_STRING_CHECKED(FStateTreeAnyEnum, Value)); } if (bTargetIsAnyEnum) { ModifiedBinding.TargetPath.Path.Add(GET_MEMBER_NAME_STRING_CHECKED(FStateTreeAnyEnum, Value)); } OutBindings.Add(ModifiedBinding); } else { OutBindings.Add(Binding); } // Check if the bindings is for struct ref and validate the types. if (!ValidateStructRef(SourceStruct, Binding.SourcePath, TargetStruct, Binding.TargetPath)) { return false; } } return true; }