2022-05-31 04:51:18 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "StateTreeCompiler.h"
# include "StateTree.h"
2023-01-19 00:48:07 -05:00
# include "StateTreeAnyEnum.h"
2023-01-25 02:42:36 -05:00
# include "StateTreeCompilerLog.h"
2022-05-31 04:51:18 -04:00
# include "StateTreeEditorData.h"
# include "StateTreeEvaluatorBase.h"
# include "StateTreeTaskBase.h"
# include "StateTreeConditionBase.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 ) ;
}
2022-06-29 04:52:18 -04:00
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 < UScriptStruct > ( OutBaseStructName ) ;
if ( ! Result )
{
Result = LoadObject < UScriptStruct > ( nullptr , * OutBaseStructName ) ;
}
}
return Result ;
}
2022-09-19 19:47:11 -04:00
EStateTreePropertyUsage GetUsageFromMetaData ( const FProperty * Property )
{
static const FName CategoryName ( TEXT ( " Category " ) ) ;
if ( Property = = nullptr )
{
return EStateTreePropertyUsage : : Invalid ;
}
const FString Category = Property - > GetMetaData ( CategoryName ) ;
if ( Category = = TEXT ( " Input " ) )
{
return EStateTreePropertyUsage : : Input ;
}
if ( Category = = TEXT ( " Inputs " ) )
{
return EStateTreePropertyUsage : : Input ;
}
if ( Category = = TEXT ( " Output " ) )
{
return EStateTreePropertyUsage : : Output ;
}
if ( Category = = TEXT ( " Outputs " ) )
{
return EStateTreePropertyUsage : : Output ;
}
if ( Category = = TEXT ( " Context " ) )
{
return EStateTreePropertyUsage : : Context ;
}
return EStateTreePropertyUsage : : Parameter ;
}
2022-05-31 04:51:18 -04:00
} ; // UE::StateTree::Compiler
bool FStateTreeCompiler : : Compile ( UStateTree & InStateTree )
{
StateTree = & InStateTree ;
2022-09-19 19:47:11 -04:00
EditorData = Cast < UStateTreeEditorData > ( StateTree - > EditorData ) ;
if ( ! EditorData )
2022-05-31 04:51:18 -04:00
{
return false ;
}
// Cleanup existing state
StateTree - > ResetCompiled ( ) ;
if ( ! BindingsCompiler . Init ( StateTree - > PropertyBindings , Log ) )
{
StateTree - > ResetCompiled ( ) ;
return false ;
}
// Copy schema the EditorData
2022-09-19 19:47:11 -04:00
StateTree - > Schema = DuplicateObject ( EditorData - > Schema , StateTree ) ;
2022-05-31 04:51:18 -04:00
// Copy parameters from EditorData
2022-09-19 19:47:11 -04:00
StateTree - > Parameters = EditorData - > RootParameters . Parameters ;
2022-05-31 04:51:18 -04:00
// Mark parameters as binding source
const FStateTreeBindableStructDesc ParametersDesc = {
TEXT ( " Parameters " ) ,
StateTree - > Parameters . GetPropertyBagStruct ( ) ,
2022-09-19 19:47:11 -04:00
EStateTreeBindableStructSource : : Parameter ,
EditorData - > RootParameters . ID
2022-05-31 04:51:18 -04:00
} ;
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 )
{
2022-09-19 19:47:11 -04:00
StateTree - > ContextDataDescs = StateTree - > Schema - > GetContextDataDescs ( ) ;
for ( FStateTreeExternalDataDesc & Desc : StateTree - > ContextDataDescs )
2022-05-31 04:51:18 -04:00
{
const FStateTreeBindableStructDesc ExtDataDesc = {
Desc . Name ,
Desc . Struct ,
2022-09-19 19:47:11 -04:00
EStateTreeBindableStructSource : : Context ,
2022-05-31 04:51:18 -04:00
Desc . ID
} ;
const int32 ExternalStructIndex = BindingsCompiler . AddSourceStruct ( ExtDataDesc ) ;
2023-01-11 12:44:01 -05:00
if ( const auto Validation = UE : : StateTree : : Compiler : : IsValidIndex16 ( ExternalStructIndex ) ; Validation . DidFail ( ) )
2022-05-31 04:51:18 -04:00
{
Validation . Log ( Log , TEXT ( " ExternalStructIndex " ) , ParametersDesc ) ;
return false ;
}
2023-01-11 12:44:01 -05:00
Desc . Handle . DataViewIndex = FStateTreeIndex16 ( ExternalStructIndex ) ;
2022-05-31 04:51:18 -04:00
}
}
if ( ! CreateStates ( ) )
{
StateTree - > ResetCompiled ( ) ;
return false ;
}
if ( ! CreateEvaluators ( ) )
{
StateTree - > ResetCompiled ( ) ;
return false ;
}
2023-01-10 15:44:28 -05:00
if ( ! CreateGlobalTasks ( ) )
{
StateTree - > ResetCompiled ( ) ;
return false ;
}
2022-05-31 04:51:18 -04:00
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 ( ) ;
2023-02-10 07:22:48 -05:00
Log . Reportf ( EMessageSeverity : : Error , TEXT ( " Unexpected failure to link the StateTree asset. See log for more info. " ) ) ;
2022-05-31 04:51:18 -04:00
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 ) ) ;
}
2023-03-03 11:22:29 -05:00
UStateTreeState * FStateTreeCompiler : : GetState ( const FGuid & StateID ) const
2022-05-31 04:51:18 -04:00
{
const int32 * Idx = IDToState . Find ( StateID ) ;
if ( Idx = = nullptr )
{
return nullptr ;
}
return SourceStates [ * Idx ] ;
}
bool FStateTreeCompiler : : CreateStates ( )
{
2023-01-23 12:48:04 -05:00
check ( EditorData ) ;
2022-05-31 04:51:18 -04:00
// Create item for the runtime execution state
InstanceStructs . Add ( FInstancedStruct : : Make < FStateTreeExecutionState > ( ) ) ;
// Create main tree (omit subtrees)
2022-09-19 19:47:11 -04:00
for ( UStateTreeState * SubTree : EditorData - > SubTrees )
2022-05-31 04:51:18 -04:00
{
2023-02-09 04:23:32 -05:00
if ( SubTree ! = nullptr
& & SubTree - > Type ! = EStateTreeStateType : : Subtree )
2022-05-31 04:51:18 -04:00
{
if ( ! CreateStateRecursive ( * SubTree , FStateTreeStateHandle : : Invalid ) )
{
return false ;
}
}
}
// Create Subtrees
2022-09-19 19:47:11 -04:00
for ( UStateTreeState * SubTree : EditorData - > SubTrees )
2022-05-31 04:51:18 -04:00
{
TArray < UStateTreeState * > 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 )
{
2023-01-23 12:48:04 -05:00
check ( StateTree ) ;
2022-05-31 04:51:18 -04:00
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 < FStateTreeEditorNode > 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 ( )
{
2023-01-23 12:48:04 -05:00
check ( EditorData ) ;
check ( StateTree ) ;
2022-05-31 04:51:18 -04:00
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 ) ;
2022-09-19 19:47:11 -04:00
for ( FStateTreeEditorNode & EvalNode : EditorData - > Evaluators )
2022-05-31 04:51:18 -04:00
{
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 ;
}
2023-01-10 15:44:28 -05:00
bool FStateTreeCompiler : : CreateGlobalTasks ( )
{
2023-01-23 12:48:04 -05:00
check ( EditorData ) ;
check ( StateTree ) ;
2023-01-10 15:44:28 -05:00
const int32 GlobalTasksBegin = Nodes . Num ( ) ;
if ( const auto Validation = UE : : StateTree : : Compiler : : IsValidCount16 ( GlobalTasksBegin ) ; Validation . DidFail ( ) )
{
Validation . Log ( Log , TEXT ( " GlobalTasksBegin " ) ) ;
return false ;
}
StateTree - > GlobalTasksBegin = uint16 ( GlobalTasksBegin ) ;
2023-01-23 12:48:04 -05:00
StateTree - > bHasGlobalTransitionTasks = false ;
2023-01-10 15:44:28 -05:00
for ( FStateTreeEditorNode & TaskNode : EditorData - > GlobalTasks )
{
2023-01-23 12:48:04 -05:00
if ( ! CreateTask ( nullptr , TaskNode , StateTree - > bHasGlobalTransitionTasks ) )
2023-01-10 15:44:28 -05:00
{
return false ;
}
}
const int32 GlobalTasksNum = Nodes . Num ( ) - GlobalTasksBegin ;
if ( const auto Validation = UE : : StateTree : : Compiler : : IsValidCount16 ( GlobalTasksNum ) ; Validation . DidFail ( ) )
{
Validation . Log ( Log , TEXT ( " GlobalTasksNum " ) ) ;
return false ;
}
StateTree - > GlobalTasksNum = uint16 ( GlobalTasksNum ) ;
return true ;
}
2022-05-31 04:51:18 -04:00
bool FStateTreeCompiler : : CreateStateTasksAndParameters ( )
{
2023-01-23 12:48:04 -05:00
check ( StateTree ) ;
2022-05-31 04:51:18 -04:00
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 > ( ) ;
FCompactStateTreeParameters & CompactParams = Instance . GetMutable < FCompactStateTreeParameters > ( ) ;
CompactParams . Parameters = SourceState - > Parameters . Parameters ;
if ( SourceState - > Type = = EStateTreeStateType : : Subtree )
{
2022-09-23 19:58:36 -04:00
// Register a binding source if we have parameters
int32 SourceStructIndex = INDEX_NONE ;
if ( SourceState - > Parameters . Parameters . IsValid ( ) )
{
const FStateTreeBindableStructDesc SubtreeParamsDesc = {
2022-05-31 04:51:18 -04:00
SourceState - > Name ,
SourceState - > Parameters . Parameters . GetPropertyBagStruct ( ) ,
2022-09-19 19:47:11 -04:00
EStateTreeBindableStructSource : : State ,
2022-05-31 04:51:18 -04:00
SourceState - > Parameters . ID
} ;
2022-09-23 19:58:36 -04:00
SourceStructIndex = BindingsCompiler . AddSourceStruct ( SubtreeParamsDesc ) ;
if ( const auto Validation = UE : : StateTree : : Compiler : : IsValidIndex16 ( SourceStructIndex ) ; Validation . DidFail ( ) )
{
Validation . Log ( Log , TEXT ( " SourceStructIndex " ) , SubtreeParamsDesc ) ;
return false ;
}
2022-05-31 04:51:18 -04:00
}
2022-09-23 19:58:36 -04:00
2022-05-31 04:51:18 -04:00
CompactState . ParameterDataViewIndex = FStateTreeIndex16 ( SourceStructIndex ) ;
}
else if ( SourceState - > Type = = EStateTreeStateType : : Linked )
{
2022-09-23 19:58:36 -04:00
int32 BatchIndex = INDEX_NONE ;
if ( SourceState - > Parameters . Parameters . IsValid ( ) )
{
// Binding target
FStateTreeBindableStructDesc LinkedParamsDesc = {
2022-05-31 04:51:18 -04:00
SourceState - > Name ,
SourceState - > Parameters . Parameters . GetPropertyBagStruct ( ) ,
2022-09-19 19:47:11 -04:00
EStateTreeBindableStructSource : : State ,
2022-05-31 04:51:18 -04:00
SourceState - > Parameters . ID
} ;
2022-09-23 19:58:36 -04:00
// Check that the bindings for this struct are still all valid.
2023-02-10 07:22:48 -05:00
TArray < FStateTreePropertyPathBinding > Bindings ;
if ( ! GetAndValidateBindings ( LinkedParamsDesc , FStateTreeDataView ( SourceState - > Parameters . Parameters . GetMutableValue ( ) ) , Bindings ) )
2022-09-23 19:58:36 -04:00
{
return false ;
}
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 ;
}
2022-05-31 04:51:18 -04:00
}
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 ) ;
2023-01-23 12:48:04 -05:00
CompactState . bHasTransitionTasks = false ;
2022-05-31 04:51:18 -04:00
for ( FStateTreeEditorNode & TaskNode : SourceState - > Tasks )
{
2023-01-23 12:48:04 -05:00
bool bHasTransitionTasks = CompactState . bHasTransitionTasks ; // bHasTransitionTasks is a bit flag.
if ( ! CreateTask ( SourceState , TaskNode , bHasTransitionTasks ) )
2022-05-31 04:51:18 -04:00
{
return false ;
}
2023-01-23 12:48:04 -05:00
CompactState . bHasTransitionTasks = bHasTransitionTasks ;
2022-05-31 04:51:18 -04:00
}
2023-01-23 12:48:04 -05:00
bool bSingleStateHasTransitionTasks = CompactState . bHasTransitionTasks ; // bHasTransitionTasks is a bit flag.
if ( ! CreateTask ( SourceState , SourceState - > SingleTask , bSingleStateHasTransitionTasks ) )
2022-05-31 04:51:18 -04:00
{
return false ;
}
2023-01-23 12:48:04 -05:00
CompactState . bHasTransitionTasks = bSingleStateHasTransitionTasks ;
2022-05-31 04:51:18 -04:00
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 ( )
{
2023-01-23 12:48:04 -05:00
check ( StateTree ) ;
2022-05-31 04:51:18 -04:00
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 )
{
2022-09-30 11:31:57 -04:00
if ( State - > ID = = SourceState - > LinkedSubtree . ID )
2022-05-31 04:51:18 -04:00
{
LinkedParentState = State ;
break ;
}
}
if ( LinkedParentState ! = nullptr )
{
Log . Reportf ( EMessageSeverity : : Error ,
2022-09-30 11:31:57 -04:00
TEXT ( " State is linked to it's parent subtree '%s', which will create infinite loop. " ) ,
2022-05-31 04:51:18 -04:00
* LinkedParentState - > Name . ToString ( ) ) ;
return false ;
}
2022-07-21 08:19:56 -04:00
// The linked state must be a subtree.
2022-09-30 11:31:57 -04:00
const UStateTreeState * TargetState = GetState ( SourceState - > LinkedSubtree . ID ) ;
2022-07-21 08:19:56 -04:00
if ( TargetState = = nullptr )
{
Log . Reportf ( EMessageSeverity : : Error ,
2022-09-30 11:31:57 -04:00
TEXT ( " Failed to resolve linked subtree '%s'. " ) ,
* SourceState - > LinkedSubtree . Name . ToString ( ) ) ;
2022-07-21 08:19:56 -04:00
return false ;
}
if ( TargetState - > Type ! = EStateTreeStateType : : Subtree )
{
Log . Reportf ( EMessageSeverity : : Error ,
2022-09-30 11:31:57 -04:00
TEXT ( " State '%s' is linked to subtree '%s', which is not a subtree. " ) ,
2022-07-21 08:19:56 -04:00
* SourceState - > Name . ToString ( ) , * TargetState - > Name . ToString ( ) ) ;
return false ;
}
2022-05-31 04:51:18 -04:00
2022-09-30 11:31:57 -04:00
CompactState . LinkedState = GetStateHandle ( SourceState - > LinkedSubtree . ID ) ;
2022-05-31 04:51:18 -04:00
if ( ! CompactState . LinkedState . IsValid ( ) )
{
Log . Reportf ( EMessageSeverity : : Error ,
2022-09-30 11:31:57 -04:00
TEXT ( " Failed to resolve linked subtree '%s'. " ) ,
* SourceState - > LinkedSubtree . Name . ToString ( ) ) ;
2022-05-31 04:51:18 -04:00
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 ( ) ;
2022-09-01 09:06:53 -04:00
CompactTransition . Trigger = Transition . Trigger ;
2023-01-23 12:48:04 -05:00
CompactTransition . Priority = Transition . Priority ;
2022-09-01 09:06:53 -04:00
CompactTransition . EventTag = Transition . EventTag ;
2022-12-02 07:57:31 -05:00
if ( Transition . bDelayTransition )
{
CompactTransition . Delay . Set ( Transition . DelayDuration , Transition . DelayRandomVariance ) ;
}
if ( EnumHasAnyFlags ( Transition . Trigger , EStateTreeTransitionTrigger : : OnStateCompleted ) )
{
2023-01-23 12:48:04 -05:00
// Completion transitions dont have priority.
CompactTransition . Priority = EStateTreeTransitionPriority : : None ;
2022-12-02 07:57:31 -05:00
// Completion transitions cannot have delay.
CompactTransition . Delay . Reset ( ) ;
// Completion transitions must have valid target state.
2023-01-23 12:48:04 -05:00
if ( Transition . State . LinkType = = EStateTreeTransitionType : : None )
2022-12-02 07:57:31 -05:00
{
Log . Reportf ( EMessageSeverity : : Error ,
TEXT ( " State completion transition to '%s' must have transition to valid state, 'None' not accepted. " ) ,
* Transition . State . Name . ToString ( ) ) ;
}
}
2022-05-31 04:51:18 -04:00
CompactTransition . State = FStateTreeStateHandle : : Invalid ;
2023-01-23 12:48:04 -05:00
if ( ! ResolveTransitionState ( SourceState , Transition . State , CompactTransition . State ) )
2022-05-31 04:51:18 -04:00
{
return false ;
}
2022-12-02 07:57:31 -05:00
2022-05-31 04:51:18 -04:00
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 ;
}
2023-01-23 12:48:04 -05:00
bool FStateTreeCompiler : : ResolveTransitionState ( const UStateTreeState * SourceState , const FStateTreeStateLink & Link , FStateTreeStateHandle & OutTransitionHandle ) const
2022-05-31 04:51:18 -04:00
{
2023-01-23 12:48:04 -05:00
if ( Link . LinkType = = EStateTreeTransitionType : : GotoState )
2022-05-31 04:51:18 -04:00
{
2023-03-03 11:22:29 -05:00
// Warn if goto state points to another subtree.
if ( const UStateTreeState * TargetState = GetState ( Link . ID ) )
{
if ( TargetState - > GetRootState ( ) ! = SourceState - > GetRootState ( ) )
{
Log . Reportf ( EMessageSeverity : : Warning ,
TEXT ( " Target state '%s' is in different subtree. Verify that this is intentional. " ) ,
* Link . Name . ToString ( ) ) ;
}
}
2022-05-31 04:51:18 -04:00
OutTransitionHandle = GetStateHandle ( Link . ID ) ;
if ( ! OutTransitionHandle . IsValid ( ) )
{
Log . Reportf ( EMessageSeverity : : Error ,
TEXT ( " Failed to resolve transition to state '%s'. " ) ,
* Link . Name . ToString ( ) ) ;
return false ;
}
}
2023-01-23 12:48:04 -05:00
else if ( Link . LinkType = = EStateTreeTransitionType : : NextState )
2022-05-31 04:51:18 -04:00
{
// Find next state.
2023-01-23 12:48:04 -05:00
const UStateTreeState * NextState = SourceState ? SourceState - > GetNextSiblingState ( ) : nullptr ;
2022-05-31 04:51:18 -04:00
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 ;
}
}
2023-01-23 12:48:04 -05:00
else if ( Link . LinkType = = EStateTreeTransitionType : : Failed )
{
OutTransitionHandle = FStateTreeStateHandle : : Failed ;
return true ;
}
else if ( Link . LinkType = = EStateTreeTransitionType : : Succeeded )
{
OutTransitionHandle = FStateTreeStateHandle : : Succeeded ;
return true ;
}
else if ( Link . LinkType = = EStateTreeTransitionType : : None )
{
OutTransitionHandle = FStateTreeStateHandle : : Invalid ;
return true ;
}
2022-05-31 04:51:18 -04:00
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
2023-01-10 15:44:28 -05:00
FInstancedStruct & Node = Nodes . Add_GetRef ( CondNode . Node ) ;
2022-05-31 04:51:18 -04:00
FStateTreeConditionBase & Cond = Node . GetMutable < FStateTreeConditionBase > ( ) ;
Cond . Operand = Operand ;
Cond . DeltaIndent = DeltaIndent ;
2023-02-10 07:22:48 -05:00
FStateTreeDataView InstanceDataView ;
2022-05-31 04:51:18 -04:00
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 ;
2023-02-10 07:22:48 -05:00
InstanceDataView = FStateTreeDataView ( SharedInstanceStructs [ InstanceIndex ] ) ;
2022-05-31 04:51:18 -04:00
}
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 ;
2023-02-10 07:22:48 -05:00
InstanceDataView = FStateTreeDataView ( Instance ) ;
}
2022-11-18 08:38:31 -05:00
2023-02-10 07:22:48 -05:00
if ( ! CompileAndValidateNode ( & State , StructDesc , Node , InstanceDataView ) )
{
return false ;
2022-05-31 04:51:18 -04:00
}
// Mark the struct as binding source.
const int32 SourceStructIndex = BindingsCompiler . AddSourceStruct ( StructDesc ) ;
// Check that the bindings for this struct are still all valid.
2023-02-10 07:22:48 -05:00
TArray < FStateTreePropertyPathBinding > Bindings ;
if ( ! GetAndValidateBindings ( StructDesc , InstanceDataView , Bindings ) )
2022-05-31 04:51:18 -04:00
{
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 ;
}
2023-02-10 07:22:48 -05:00
bool FStateTreeCompiler : : CompileAndValidateNode ( const UStateTreeState * SourceState , const FStateTreeBindableStructDesc & NodeDesc , FStructView NodeView , const FStateTreeDataView InstanceData )
2022-11-18 08:38:31 -05:00
{
2023-01-23 12:48:04 -05:00
if ( ! NodeView . IsValid ( ) )
{
return false ;
}
2022-11-18 08:38:31 -05:00
2023-02-13 20:06:02 -05:00
FStateTreeNodeBase & Node = NodeView . Get < FStateTreeNodeBase > ( ) ;
2023-01-23 12:48:04 -05:00
check ( InstanceData . IsValid ( ) ) ;
auto ValidateStateLinks = [ this , SourceState ] ( TPropertyValueIterator < FStructProperty > It )
{
for ( ; It ; + + It )
{
if ( It - > Key - > Struct = = TBaseStructure < FStateTreeStateLink > : : Get ( ) )
{
FStateTreeStateLink & StateLink = * static_cast < FStateTreeStateLink * > ( const_cast < void * > ( It - > Value ) ) ;
if ( ! ResolveTransitionState ( SourceState , StateLink , StateLink . StateHandle ) )
{
return false ;
}
}
}
return true ;
} ;
// Validate any state links.
if ( ! ValidateStateLinks ( TPropertyValueIterator < FStructProperty > ( InstanceData . GetStruct ( ) , InstanceData . GetMutableMemory ( ) ) ) )
{
return false ;
}
2023-02-13 20:06:02 -05:00
if ( ! ValidateStateLinks ( TPropertyValueIterator < FStructProperty > ( NodeView . GetScriptStruct ( ) , NodeView . GetMemory ( ) ) ) )
2023-01-23 12:48:04 -05:00
{
return false ;
}
2022-11-18 08:38:31 -05:00
TArray < FText > ValidationErrors ;
const EDataValidationResult Result = Node . Compile ( InstanceData , ValidationErrors ) ;
if ( Result = = EDataValidationResult : : Invalid & & ValidationErrors . IsEmpty ( ) )
{
Log . Report ( EMessageSeverity : : Error , NodeDesc , TEXT ( " Node validation failed. " ) ) ;
}
else
{
const EMessageSeverity : : Type Severity = Result = = EDataValidationResult : : Invalid ? EMessageSeverity : : Error : EMessageSeverity : : Warning ;
for ( const FText & Error : ValidationErrors )
{
Log . Report ( Severity , NodeDesc , Error . ToString ( ) ) ;
}
}
return Result ! = EDataValidationResult : : Invalid ;
}
2023-01-23 12:48:04 -05:00
bool FStateTreeCompiler : : CreateTask ( UStateTreeState * State , const FStateTreeEditorNode & TaskNode , bool & bOutHasTransitionTasks )
2022-05-31 04:51:18 -04:00
{
// 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
2023-01-10 15:44:28 -05:00
FInstancedStruct & Node = Nodes . Add_GetRef ( TaskNode . Node ) ;
2022-05-31 04:51:18 -04:00
FStateTreeTaskBase & Task = Node . GetMutable < FStateTreeTaskBase > ( ) ;
2023-02-10 07:22:48 -05:00
FStateTreeDataView InstanceDataView ;
2022-05-31 04:51:18 -04:00
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 ;
2023-02-10 07:22:48 -05:00
InstanceDataView = FStateTreeDataView ( InstanceStructs [ InstanceIndex ] ) ;
2022-05-31 04:51:18 -04:00
}
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 ;
2023-02-10 07:22:48 -05:00
InstanceDataView = FStateTreeDataView ( Instance ) ;
}
2022-11-18 08:38:31 -05:00
2023-02-10 07:22:48 -05:00
if ( ! CompileAndValidateNode ( State , StructDesc , Node , InstanceDataView ) )
{
return false ;
2022-05-31 04:51:18 -04:00
}
// Mark the instance as binding source.
const int32 SourceStructIndex = BindingsCompiler . AddSourceStruct ( StructDesc ) ;
2023-02-10 07:22:48 -05:00
2022-05-31 04:51:18 -04:00
// Check that the bindings for this struct are still all valid.
2023-02-10 07:22:48 -05:00
TArray < FStateTreePropertyPathBinding > Bindings ;
if ( ! GetAndValidateBindings ( StructDesc , InstanceDataView , Bindings ) )
2022-05-31 04:51:18 -04:00
{
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 ) ;
2023-01-23 12:48:04 -05:00
if ( Task . bShouldAffectTransitions )
{
bOutHasTransitionTasks = true ;
}
2022-05-31 04:51:18 -04:00
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
2023-01-10 15:44:28 -05:00
FInstancedStruct & Node = Nodes . Add_GetRef ( EvalNode . Node ) ;
2022-05-31 04:51:18 -04:00
FStateTreeEvaluatorBase & Eval = Node . GetMutable < FStateTreeEvaluatorBase > ( ) ;
2023-02-10 07:22:48 -05:00
FStateTreeDataView InstanceDataView ;
2022-05-31 04:51:18 -04:00
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 ;
2023-02-10 07:22:48 -05:00
InstanceDataView = FStateTreeDataView ( InstanceStructs [ InstanceIndex ] ) ;
2022-05-31 04:51:18 -04:00
}
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 ;
2023-02-10 07:22:48 -05:00
InstanceDataView = FStateTreeDataView ( Instance ) ;
2022-05-31 04:51:18 -04:00
}
2023-02-10 07:22:48 -05:00
if ( ! CompileAndValidateNode ( nullptr , StructDesc , Node , InstanceDataView ) )
{
return false ;
}
2022-05-31 04:51:18 -04:00
// Mark the instance as binding source.
const int32 SourceStructIndex = BindingsCompiler . AddSourceStruct ( StructDesc ) ;
// Check that the bindings for this struct are still all valid.
2023-02-10 07:22:48 -05:00
TArray < FStateTreePropertyPathBinding > Bindings ;
if ( ! GetAndValidateBindings ( StructDesc , InstanceDataView , Bindings ) )
2022-05-31 04:51:18 -04:00
{
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 ;
}
2023-02-10 07:22:48 -05:00
bool FStateTreeCompiler : : IsPropertyAnyEnum ( const FStateTreeBindableStructDesc & Struct , FStateTreePropertyPath Path ) const
2022-05-31 04:51:18 -04:00
{
bool bIsAnyEnum = false ;
2023-02-10 07:22:48 -05:00
TArray < FStateTreePropertyPathIndirection > Indirection ;
const bool bResolved = Path . ResolveIndirections ( Struct . Struct , Indirection ) ;
if ( bResolved & & Indirection . Num ( ) > 0 )
2022-05-31 04:51:18 -04:00
{
2023-02-10 07:22:48 -05:00
check ( Indirection . Last ( ) . GetProperty ( ) ) ;
if ( const FProperty * OwnerProperty = Indirection . Last ( ) . GetProperty ( ) - > GetOwnerProperty ( ) )
2022-05-31 04:51:18 -04:00
{
if ( const FStructProperty * OwnerStructProperty = CastField < FStructProperty > ( OwnerProperty ) )
{
2022-06-29 04:52:18 -04:00
bIsAnyEnum = OwnerStructProperty - > Struct = = TBaseStructure < FStateTreeAnyEnum > : : Get ( ) ;
2022-05-31 04:51:18 -04:00
}
}
}
return bIsAnyEnum ;
}
2023-02-10 07:22:48 -05:00
bool FStateTreeCompiler : : ValidateStructRef ( const FStateTreeBindableStructDesc & SourceStruct , FStateTreePropertyPath SourcePath ,
const FStateTreeBindableStructDesc & TargetStruct , FStateTreePropertyPath TargetPath ) const
2022-06-29 04:52:18 -04:00
{
2023-02-10 07:22:48 -05:00
FString ResolveError ;
TArray < FStateTreePropertyPathIndirection > TargetIndirection ;
if ( ! TargetPath . ResolveIndirections ( TargetStruct . Struct , TargetIndirection , & ResolveError ) )
2022-06-29 04:52:18 -04:00
{
// This will later be reported by the bindings compiler.
2023-02-10 07:22:48 -05:00
Log . Reportf ( EMessageSeverity : : Error , TargetStruct , TEXT ( " Failed to resolve binding path in %s: %s " ) , * TargetStruct . Name . ToString ( ) , * ResolveError ) ;
return false ;
2022-06-29 04:52:18 -04:00
}
2023-02-10 07:22:48 -05:00
const FProperty * TargetLeafProperty = TargetIndirection . Num ( ) > 0 ? TargetIndirection . Last ( ) . GetProperty ( ) : nullptr ;
2022-06-29 04:52:18 -04:00
// Early out if the target is not FStateTreeStructRef.
const FStructProperty * TargetStructProperty = CastField < FStructProperty > ( TargetLeafProperty ) ;
if ( TargetStructProperty = = nullptr | | TargetStructProperty - > Struct ! = TBaseStructure < FStateTreeStructRef > : : 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 ;
}
2023-02-10 07:22:48 -05:00
TArray < FStateTreePropertyPathIndirection > SourceIndirection ;
if ( ! SourcePath . ResolveIndirections ( SourceStruct . Struct , SourceIndirection , & ResolveError ) )
2022-06-29 04:52:18 -04:00
{
// This will later be reported by the bindings compiler.
2023-02-10 07:22:48 -05:00
Log . Reportf ( EMessageSeverity : : Error , SourceStruct , TEXT ( " Failed to resolve binding path in %s: %s " ) , * SourceStruct . Name . ToString ( ) , * ResolveError ) ;
return false ;
2022-06-29 04:52:18 -04:00
}
2023-02-10 07:22:48 -05:00
const FProperty * SourceLeafProperty = SourceIndirection . Num ( ) > 0 ? SourceIndirection . Last ( ) . GetProperty ( ) : nullptr ;
2022-06-29 04:52:18 -04:00
// Exit if the source is not a struct property.
const FStructProperty * SourceStructProperty = CastField < FStructProperty > ( SourceLeafProperty ) ;
if ( SourceStructProperty = = nullptr )
{
return true ;
}
if ( SourceStructProperty - > Struct = = TBaseStructure < FStateTreeStructRef > : : 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 ;
}
2023-02-10 07:22:48 -05:00
FStateTreeDataView FStateTreeCompiler : : GetBindingSourceValue ( const int32 SourceIndex )
{
for ( const FInstancedStruct & Node : Nodes )
{
if ( const FStateTreeNodeBase * NodeBase = Node . GetPtr < FStateTreeNodeBase > ( ) )
{
if ( NodeBase - > DataViewIndex . Get ( ) = = SourceIndex )
{
if ( NodeBase - > bInstanceIsObject )
{
return FStateTreeDataView ( InstanceObjects [ NodeBase - > InstanceIndex . Get ( ) ] ) ;
}
else
{
return FStateTreeDataView ( InstanceStructs [ NodeBase - > InstanceIndex . Get ( ) ] ) ;
}
}
}
}
return { } ;
}
bool FStateTreeCompiler : : GetAndValidateBindings ( const FStateTreeBindableStructDesc & TargetStruct , FStateTreeDataView TargetValue , TArray < FStateTreePropertyPathBinding > & OutBindings ) const
2022-05-31 04:51:18 -04:00
{
2023-01-23 12:48:04 -05:00
check ( EditorData ) ;
2022-09-23 19:58:36 -04:00
if ( TargetStruct . Struct = = nullptr )
{
Log . Reportf ( EMessageSeverity : : Error , TargetStruct ,
TEXT ( " The type of binding target '%s' is invalid. " ) ,
* TargetStruct . Name . ToString ( ) ) ;
return false ;
}
2022-09-19 19:47:11 -04:00
2022-05-31 04:51:18 -04:00
OutBindings . Reset ( ) ;
2023-02-10 07:22:48 -05:00
for ( FStateTreePropertyPathBinding & Binding : EditorData - > EditorBindings . GetMutableBindings ( ) )
2022-05-31 04:51:18 -04:00
{
2023-02-10 07:22:48 -05:00
if ( Binding . GetTargetPath ( ) . GetStructID ( ) ! = TargetStruct . ID )
2022-05-31 04:51:18 -04:00
{
continue ;
}
// Source must be one of the source structs we have discovered in the tree.
2023-02-10 07:22:48 -05:00
const FGuid SourceStructID = Binding . GetSourcePath ( ) . GetStructID ( ) ;
2022-05-31 04:51:18 -04:00
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'. " ) ,
2023-02-10 07:22:48 -05:00
* Binding . GetSourcePath ( ) . ToString ( ) , * TargetStruct . Name . ToString ( ) , * Binding . GetTargetPath ( ) . ToString ( ) ) ;
2022-05-31 04:51:18 -04:00
return false ;
}
const FStateTreeBindableStructDesc & SourceStruct = BindingsCompiler . GetSourceStructDesc ( SourceStructIdx ) ;
2023-02-10 07:22:48 -05:00
// Source path should not have any instanced indirections.
if ( Binding . GetSourcePath ( ) . HasAnyInstancedIndirection ( ) )
{
Log . Reportf ( EMessageSeverity : : Error , TargetStruct ,
TEXT ( " Malformed source property path for binding source property '%s' for target '%s:%s'. " ) ,
* Binding . GetSourcePath ( ) . ToString ( ) , * TargetStruct . Name . ToString ( ) , * Binding . GetTargetPath ( ) . ToString ( ) ) ;
return false ;
}
// Update path instance types from latest data. E.g. binding may have been created for instanced object of type FooB, and changed to FooA.
// @todo: not liking how this mutates the Binding.TargetPath, but currently we dont track well the instanced object changes.
if ( ! Binding . GetMutableTargetPath ( ) . UpdateInstanceStructsFromValue ( TargetValue ) )
{
Log . Reportf ( EMessageSeverity : : Error , TargetStruct ,
TEXT ( " Malformed target property path for binding source property '%s' for target '%s:%s'. " ) ,
* Binding . GetSourcePath ( ) . ToString ( ) , * TargetStruct . Name . ToString ( ) , * Binding . GetTargetPath ( ) . ToString ( ) ) ;
return false ;
}
2022-05-31 04:51:18 -04:00
// Source must be accessible by the target struct via all execution paths.
TArray < FStateTreeBindableStructDesc > AccessibleStructs ;
2023-02-10 07:22:48 -05:00
EditorData - > GetAccessibleStructs ( Binding . GetTargetPath ( ) . GetStructID ( ) , AccessibleStructs ) ;
2022-05-31 04:51:18 -04:00
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. " ) ,
2023-02-10 07:22:48 -05:00
* SourceStruct . Name . ToString ( ) , * Binding . GetSourcePath ( ) . ToString ( ) ,
* TargetStruct . Name . ToString ( ) , * Binding . GetTargetPath ( ) . ToString ( ) ,
2022-05-31 04:51:18 -04:00
* SourceStruct . Name . ToString ( ) , * TargetStruct . Name . ToString ( ) ) ;
return false ;
}
2023-02-10 07:22:48 -05:00
2022-05-31 04:51:18 -04:00
// 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.
2023-02-10 07:22:48 -05:00
const bool bSourceIsAnyEnum = IsPropertyAnyEnum ( SourceStruct , Binding . GetSourcePath ( ) ) ;
const bool bTargetIsAnyEnum = IsPropertyAnyEnum ( TargetStruct , Binding . GetTargetPath ( ) ) ;
2022-05-31 04:51:18 -04:00
if ( bSourceIsAnyEnum | | bTargetIsAnyEnum )
{
2023-02-10 07:22:48 -05:00
FStateTreePropertyPathBinding ModifiedBinding ( Binding ) ;
2022-05-31 04:51:18 -04:00
if ( bSourceIsAnyEnum )
{
2023-02-10 07:22:48 -05:00
ModifiedBinding . GetMutableSourcePath ( ) . AddPathSegment ( GET_MEMBER_NAME_STRING_CHECKED ( FStateTreeAnyEnum , Value ) ) ;
2022-05-31 04:51:18 -04:00
}
if ( bTargetIsAnyEnum )
{
2023-02-10 07:22:48 -05:00
ModifiedBinding . GetMutableTargetPath ( ) . AddPathSegment ( GET_MEMBER_NAME_STRING_CHECKED ( FStateTreeAnyEnum , Value ) ) ;
2022-05-31 04:51:18 -04:00
}
OutBindings . Add ( ModifiedBinding ) ;
}
else
{
OutBindings . Add ( Binding ) ;
}
2022-06-29 04:52:18 -04:00
// Check if the bindings is for struct ref and validate the types.
2023-02-10 07:22:48 -05:00
if ( ! ValidateStructRef ( SourceStruct , Binding . GetSourcePath ( ) , TargetStruct , Binding . GetTargetPath ( ) ) )
2022-06-29 04:52:18 -04:00
{
return false ;
}
2022-05-31 04:51:18 -04:00
}
2022-09-19 19:47:11 -04:00
2023-02-10 07:22:48 -05:00
auto IsPropertyBound = [ & OutBindings ] ( const FName & PropertyName )
2022-09-19 19:47:11 -04:00
{
2023-02-10 07:22:48 -05:00
return OutBindings . ContainsByPredicate ( [ & PropertyName ] ( const FStateTreePropertyPathBinding & Binding )
2022-09-19 19:47:11 -04:00
{
2022-10-26 13:01:21 -04:00
// We're looping over just the first level of properties on the struct, so we assume that the path is just one item
// (or two in case of AnyEnum, because we expand the path to Property.Value, see code above).
2023-02-10 07:22:48 -05:00
return Binding . GetTargetPath ( ) . GetSegments ( ) . Num ( ) > = 1 & & Binding . GetTargetPath ( ) . GetSegments ( ) [ 0 ] . GetName ( ) = = PropertyName ;
2022-09-19 19:47:11 -04:00
} ) ;
} ;
bool bResult = true ;
// Validate that Input and Context bindings
for ( TFieldIterator < FProperty > It ( TargetStruct . Struct , EFieldIterationFlags : : None ) ; It ; + + It )
{
const FProperty * Property = * It ;
2022-09-21 10:45:14 -04:00
check ( Property ) ;
2023-02-10 07:22:48 -05:00
const FName PropertyName = Property - > GetFName ( ) ;
2022-09-19 19:47:11 -04:00
const EStateTreePropertyUsage Usage = UE : : StateTree : : Compiler : : GetUsageFromMetaData ( Property ) ;
if ( Usage = = EStateTreePropertyUsage : : Input )
{
2022-09-21 10:45:14 -04:00
const bool bIsOptional = Property - > HasMetaData ( TEXT ( " Optional " ) ) ;
// Make sure that an Input property is bound unless marked optional.
if ( bIsOptional = = false & & ! IsPropertyBound ( PropertyName ) )
2022-09-19 19:47:11 -04:00
{
Log . Reportf ( EMessageSeverity : : Error , TargetStruct ,
TEXT ( " Input property '%s:%s' is expected to have a binding. " ) ,
2023-02-10 07:22:48 -05:00
* TargetStruct . Name . ToString ( ) , * PropertyName . ToString ( ) ) ;
2022-09-19 19:47:11 -04:00
bResult = false ;
}
}
else if ( Usage = = EStateTreePropertyUsage : : Context )
{
// Make sure that an Context property is manually or automatically bound.
const UStruct * ContextObjectType = nullptr ;
if ( const FStructProperty * StructProperty = CastField < FStructProperty > ( Property ) )
{
ContextObjectType = StructProperty - > Struct ;
}
else if ( const FObjectPropertyBase * ObjectProperty = CastField < FObjectPropertyBase > ( Property ) )
{
ContextObjectType = ObjectProperty - > PropertyClass ;
}
if ( ContextObjectType = = nullptr )
{
Log . Reportf ( EMessageSeverity : : Error , TargetStruct ,
TEXT ( " Context property '%s:%s' type is expected to be Object Reference or Struct. " ) ,
2023-02-10 07:22:48 -05:00
* TargetStruct . Name . ToString ( ) , * PropertyName . ToString ( ) ) ;
2022-09-19 19:47:11 -04:00
bResult = false ;
continue ;
}
const bool bIsBound = IsPropertyBound ( PropertyName ) ;
if ( ! bIsBound )
{
2023-02-10 07:22:48 -05:00
const FStateTreeBindableStructDesc Desc = EditorData - > FindContextData ( ContextObjectType , PropertyName . ToString ( ) ) ;
2022-09-19 19:47:11 -04:00
if ( Desc . IsValid ( ) )
{
// Add automatic binding to Context data.
2023-02-10 07:22:48 -05:00
OutBindings . Emplace ( FStateTreePropertyPath ( Desc . ID ) , FStateTreePropertyPath ( TargetStruct . ID , PropertyName ) ) ;
2022-09-19 19:47:11 -04:00
}
else
{
Log . Reportf ( EMessageSeverity : : Error , TargetStruct ,
TEXT ( " Cound not find matching Context object for Context property '%s:%s'. Property must have munual binding. " ) ,
2023-02-10 07:22:48 -05:00
* TargetStruct . Name . ToString ( ) , * PropertyName . ToString ( ) ) ;
2022-09-19 19:47:11 -04:00
bResult = false ;
}
}
}
}
return bResult ;
2022-05-31 04:51:18 -04:00
}