2021-09-28 13:33:00 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "StateTreeBaker.h"
# include "StateTree.h"
# include "StateTreeEditorData.h"
# include "StateTreeTypes.h"
# include "Conditions/StateTreeCondition_Common.h"
# include "StateTreeEvaluatorBase.h"
# include "StateTreeTaskBase.h"
2021-10-21 04:12:07 -04:00
# include "StateTreeConditionBase.h"
2021-09-28 13:33:00 -04:00
# include "StateTreeState.h"
# include "StateTreeExecutionContext.h"
# include "CoreMinimal.h"
# include "StateTreePropertyBindingCompiler.h"
bool FStateTreeBaker : : ResolveTransitionState ( const UStateTreeState & SourceState , const TCHAR * ContextStr , const FStateTreeStateLink & Link , FStateTreeHandle & OutTransitionHandle ) const
{
if ( Link . Type = = EStateTreeTransitionType : : GotoState )
{
OutTransitionHandle = GetStateHandle ( Link . ID ) ;
if ( ! OutTransitionHandle . IsValid ( ) )
{
UE_LOG ( LogStateTree , Error , TEXT ( " %s: '%s':%s: Failed to resolve %s to %s. " ) , ANSI_TO_TCHAR ( __FUNCTION__ ) , * GetNameSafe ( StateTree ) , * SourceState . Name . ToString ( ) , ContextStr , * Link . Name . ToString ( ) ) ;
return false ;
}
}
else if ( Link . Type = = EStateTreeTransitionType : : NextState )
{
// Find next state.
const UStateTreeState * NextState = SourceState . GetNextSiblingState ( ) ;
if ( NextState = = nullptr )
{
UE_LOG ( LogStateTree , Error , TEXT ( " %s: '%s':%s: Failed to resolve default transition, there's no next State after %s. " ) , ANSI_TO_TCHAR ( __FUNCTION__ ) , * GetNameSafe ( StateTree ) , * SourceState . Name . ToString ( ) , ContextStr , * SourceState . Name . ToString ( ) ) ;
return false ;
}
OutTransitionHandle = GetStateHandle ( NextState - > ID ) ;
if ( ! OutTransitionHandle . IsValid ( ) )
{
UE_LOG ( LogStateTree , Error , TEXT ( " %s: '%s':%s: Failed to resolve default transition next state %s. " ) , ANSI_TO_TCHAR ( __FUNCTION__ ) , * GetNameSafe ( StateTree ) , * SourceState . Name . ToString ( ) , ContextStr , * Link . Name . ToString ( ) ) ;
return false ;
}
}
return true ;
}
FStateTreeHandle FStateTreeBaker : : GetStateHandle ( const FGuid & StateID ) const
{
const int32 * Idx = IDToState . Find ( StateID ) ;
if ( Idx = = nullptr )
{
return FStateTreeHandle : : Invalid ;
}
return FStateTreeHandle ( uint16 ( * Idx ) ) ;
}
2021-10-21 04:12:07 -04:00
bool FStateTreeBaker : : Bake ( UStateTree & InStateTree )
2021-09-28 13:33:00 -04:00
{
StateTree = & InStateTree ;
TreeData = Cast < UStateTreeEditorData > ( StateTree - > EditorData ) ;
if ( ! TreeData )
{
return false ;
}
// Cleanup existing state
StateTree - > ResetBaked ( ) ;
BindingsCompiler . Init ( StateTree - > PropertyBindings ) ;
2021-10-21 04:12:07 -04:00
if ( ! CreateStates ( ) )
2021-09-28 13:33:00 -04:00
{
StateTree - > ResetBaked ( ) ;
return false ;
}
2021-10-21 04:12:07 -04:00
if ( ! CreateStateTransitions ( ) )
2021-09-28 13:33:00 -04:00
{
StateTree - > ResetBaked ( ) ;
return false ;
}
BindingsCompiler . Finalize ( ) ;
// TODO: This is just for testing. It should be called just before use.
StateTree - > ResolvePropertyPaths ( ) ;
2021-10-27 06:11:44 -04:00
StateTree - > Link ( ) ;
StateTree - > InitRuntimeStorage ( ) ;
2021-09-28 13:33:00 -04:00
return true ;
}
2021-10-21 04:12:07 -04:00
bool FStateTreeBaker : : CreateStates ( )
2021-09-28 13:33:00 -04:00
{
// Create item for the runtime execution state
StateTree - > RuntimeStorageItems . Add ( FInstancedStruct : : Make < FStateTreeExecutionState > ( ) ) ;
for ( UStateTreeState * Routine : TreeData - > Routines )
{
if ( Routine )
{
2021-10-21 04:12:07 -04:00
if ( ! CreateStateRecursive ( * Routine , FStateTreeHandle : : Invalid ) )
2021-09-28 13:33:00 -04:00
{
return false ;
}
}
}
return true ;
}
2021-10-21 04:12:07 -04:00
bool FStateTreeBaker : : CreateStateTransitions ( )
2021-09-28 13:33:00 -04:00
{
for ( int32 i = 0 ; i < StateTree - > States . Num ( ) ; i + + )
{
FBakedStateTreeState & BakedState = StateTree - > States [ i ] ;
UStateTreeState * SourceState = SourceStates [ i ] ;
check ( SourceState ) ;
// Enter conditions.
2021-10-21 04:12:07 -04:00
BakedState . EnterConditionsBegin = uint16 ( StateTree - > Conditions . Num ( ) ) ;
for ( FStateTreeConditionItem & ConditionItem : SourceState - > EnterConditions )
2021-09-28 13:33:00 -04:00
{
2021-10-21 04:12:07 -04:00
if ( ! CreateCondition ( ConditionItem ) )
2021-09-28 13:33:00 -04:00
{
UE_LOG ( LogStateTree , Error , TEXT ( " %s: '%s':%s: Failed to create state enter condition. " ) , ANSI_TO_TCHAR ( __FUNCTION__ ) , * GetNameSafe ( StateTree ) , * SourceState - > Name . ToString ( ) ) ;
return false ;
}
}
2021-10-21 04:12:07 -04:00
BakedState . EnterConditionsNum = uint8 ( uint16 ( StateTree - > Conditions . Num ( ) ) - BakedState . EnterConditionsBegin ) ;
2021-09-28 13:33:00 -04:00
// Transitions
BakedState . TransitionsBegin = uint16 ( StateTree - > Transitions . Num ( ) ) ;
2021-10-21 04:12:07 -04:00
for ( FStateTreeTransition & Transition : SourceState - > Transitions )
2021-09-28 13:33:00 -04:00
{
FBakedStateTransition & BakedTransition = StateTree - > Transitions . AddDefaulted_GetRef ( ) ;
BakedTransition . Event = Transition . Event ;
BakedTransition . Type = Transition . State . Type ;
BakedTransition . GateDelay = ( uint8 ) FMath : : Clamp ( FMath : : CeilToInt ( Transition . GateDelay * 10.0f ) , 0 , 255 ) ;
BakedTransition . State = FStateTreeHandle : : Invalid ;
if ( ! ResolveTransitionState ( * SourceState , TEXT ( " transition " ) , Transition . State , BakedTransition . State ) )
{
return false ;
}
// Note: Unset transition is allowed here. It can be used to mask a transition at parent.
2021-10-21 04:12:07 -04:00
BakedTransition . ConditionsBegin = uint16 ( StateTree - > Conditions . Num ( ) ) ;
2021-09-28 13:33:00 -04:00
for ( FStateTreeConditionItem & ConditionItem : Transition . Conditions )
{
2021-10-21 04:12:07 -04:00
if ( ! CreateCondition ( ConditionItem ) )
2021-09-28 13:33:00 -04:00
{
UE_LOG ( LogStateTree , Error , TEXT ( " %s: '%s':%s: Failed to create transition condition to %s. " ) , ANSI_TO_TCHAR ( __FUNCTION__ ) , * GetNameSafe ( StateTree ) , * SourceState - > Name . ToString ( ) , * Transition . State . Name . ToString ( ) ) ;
return false ;
}
}
2021-10-21 04:12:07 -04:00
BakedTransition . ConditionsNum = uint8 ( uint16 ( StateTree - > Conditions . Num ( ) ) - BakedTransition . ConditionsBegin ) ;
2021-09-28 13:33:00 -04:00
}
BakedState . TransitionsNum = uint8 ( uint16 ( StateTree - > Transitions . Num ( ) ) - BakedState . TransitionsBegin ) ;
}
return true ;
}
2021-10-21 04:12:07 -04:00
bool FStateTreeBaker : : CreateCondition ( const FStateTreeConditionItem & CondItem )
2021-09-28 13:33:00 -04:00
{
if ( ! CondItem . Type . IsValid ( ) )
{
// Empty line in conditions array, just silently ignore.
return true ;
}
// Copy the condition
2021-10-21 04:12:07 -04:00
FInstancedStruct & CondPtr = StateTree - > Conditions . AddDefaulted_GetRef ( ) ;
2021-09-28 13:33:00 -04:00
CondPtr = CondItem . Type ;
check ( CondPtr . IsValid ( ) ) ;
FStateTreeConditionBase & Cond = CondPtr . GetMutable < FStateTreeConditionBase > ( ) ;
// Create binding source struct descriptor. Note: not exposing the struct for reading.
FStateTreeBindableStructDesc StructDesc ;
StructDesc . Struct = CondPtr . GetScriptStruct ( ) ;
StructDesc . Name = CondPtr . GetScriptStruct ( ) - > GetFName ( ) ;
StructDesc . ID = Cond . ID ;
// Check that the bindings for this struct are still all valid.
TArray < FStateTreeEditorPropertyBinding > 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 ;
}
check ( BatchIndex < int32 ( MAX_uint16 ) ) ;
Cond . BindingsBatch = BatchIndex = = INDEX_NONE ? FStateTreeHandle : : Invalid : FStateTreeHandle ( uint16 ( BatchIndex ) ) ;
return true ;
}
bool FStateTreeBaker : : IsPropertyAnyEnum ( const FStateTreeBindableStructDesc & Struct , FStateTreeEditorPropertyPath Path ) const
{
bool bIsAnyEnum = false ;
TArray < FStateTreePropertySegment > Segments ;
FProperty * LeafProperty = nullptr ;
int32 LeafArrayIndex = INDEX_NONE ;
const bool bResolved = FStateTreePropertyBindingCompiler : : ResolvePropertyPath ( Struct , Path , Segments , LeafProperty , LeafArrayIndex ) ;
if ( bResolved & & LeafProperty )
{
if ( FProperty * OwnerProperty = LeafProperty - > GetOwnerProperty ( ) )
{
if ( const FStructProperty * OwnerStructProperty = CastField < FStructProperty > ( OwnerProperty ) )
{
bIsAnyEnum = OwnerStructProperty - > Struct = = FStateTreeAnyEnum : : StaticStruct ( ) ;
}
}
}
return bIsAnyEnum ;
}
bool FStateTreeBaker : : GetAndValidateBindings ( const FStateTreeBindableStructDesc & TargetStruct , TArray < FStateTreeEditorPropertyBinding > & 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 )
{
UE_LOG ( LogStateTree , Error , TEXT ( " %s: '%s': Failed to find container for path %s in %s. " ) , ANSI_TO_TCHAR ( __FUNCTION__ ) , * GetNameSafe ( StateTree ) , * Binding . SourcePath . ToString ( ) , * TargetStruct . Name . ToString ( ) ) ;
return false ;
}
const FStateTreeBindableStructDesc & SourceStruct = BindingsCompiler . GetSourceStructDesc ( SourceStructIdx ) ;
// Source must be accessible by the target struct.
TArray < FStateTreeBindableStructDesc > AccessibleStructs ;
TreeData - > GetAccessibleStructs ( Binding . TargetPath . StructID , AccessibleStructs ) ;
const bool SourceAccessible = AccessibleStructs . ContainsByPredicate ( [ SourceStructID ] ( const FStateTreeBindableStructDesc & Structs )
{
return ( Structs . ID = = SourceStructID ) ;
} ) ;
if ( ! SourceAccessible )
{
UE_LOG ( LogStateTree , Error , TEXT ( " %s: '%s': %s:%s is not accessible to %s:%s. " ) , ANSI_TO_TCHAR ( __FUNCTION__ ) , * GetNameSafe ( StateTree ) ,
* SourceStruct . Name . ToString ( ) , * Binding . SourcePath . ToString ( ) , * TargetStruct . Name . ToString ( ) , * Binding . TargetPath . 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 ) ;
}
}
return true ;
}
2021-10-21 04:12:07 -04:00
bool FStateTreeBaker : : CreateStateRecursive ( UStateTreeState & State , const FStateTreeHandle Parent )
2021-09-28 13:33:00 -04:00
{
const int32 StateIdx = StateTree - > States . AddDefaulted ( ) ;
FBakedStateTreeState & BakedState = StateTree - > States [ StateIdx ] ;
BakedState . Name = State . Name ;
BakedState . Parent = Parent ;
SourceStates . Add ( & State ) ;
IDToState . Add ( State . ID , StateIdx ) ;
// Collect evaluators
check ( StateTree - > RuntimeStorageItems . Num ( ) < = int32 ( MAX_uint16 ) ) ;
BakedState . EvaluatorsBegin = uint16 ( StateTree - > RuntimeStorageItems . Num ( ) ) ;
2021-10-21 04:12:07 -04:00
for ( FStateTreeEvaluatorItem & EvaluatorItem : State . Evaluators )
2021-09-28 13:33:00 -04:00
{
if ( EvaluatorItem . Type . IsValid ( ) )
{
// Copy the evaluator
FInstancedStruct & EvalPtr = StateTree - > RuntimeStorageItems . AddDefaulted_GetRef ( ) ;
EvalPtr = EvaluatorItem . Type ;
check ( EvalPtr . IsValid ( ) ) ;
2021-10-21 04:53:16 -04:00
FStateTreeEvaluatorBase & Eval = EvalPtr . GetMutable < FStateTreeEvaluatorBase > ( ) ;
2021-09-28 13:33:00 -04:00
// Create binding source struct descriptor.
FStateTreeBindableStructDesc StructDesc ;
StructDesc . Struct = EvalPtr . GetScriptStruct ( ) ;
StructDesc . Name = Eval . Name ;
StructDesc . ID = Eval . ID ;
// Mark the struct as binding source.
const int32 SourceStructIndex = BindingsCompiler . AddSourceStruct ( StructDesc ) ;
// Check that the bindings for this struct are still all valid.
TArray < FStateTreeEditorPropertyBinding > 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 ;
}
check ( BatchIndex < int32 ( MAX_uint16 ) ) ;
Eval . BindingsBatch = BatchIndex = = INDEX_NONE ? FStateTreeHandle : : Invalid : FStateTreeHandle ( uint16 ( BatchIndex ) ) ;
check ( SourceStructIndex < = int32 ( MAX_uint16 ) ) ;
Eval . SourceStructIndex = uint16 ( SourceStructIndex ) ;
}
}
const int32 EvaluatorsNum = StateTree - > RuntimeStorageItems . Num ( ) - int32 ( BakedState . EvaluatorsBegin ) ;
check ( EvaluatorsNum < = int32 ( MAX_uint8 ) ) ;
BakedState . EvaluatorsNum = uint8 ( EvaluatorsNum ) ;
// Collect tasks
check ( StateTree - > RuntimeStorageItems . Num ( ) < = int32 ( MAX_uint16 ) ) ;
BakedState . TasksBegin = uint16 ( StateTree - > RuntimeStorageItems . Num ( ) ) ;
2021-10-21 04:12:07 -04:00
for ( FStateTreeTaskItem & TaskItem : State . Tasks )
2021-09-28 13:33:00 -04:00
{
if ( TaskItem . Type . IsValid ( ) )
{
// Copy the task
FInstancedStruct & TaskPtr = StateTree - > RuntimeStorageItems . AddDefaulted_GetRef ( ) ;
TaskPtr = TaskItem . Type ;
check ( TaskPtr . IsValid ( ) ) ;
2021-10-21 04:53:16 -04:00
FStateTreeTaskBase & Task = TaskPtr . GetMutable < FStateTreeTaskBase > ( ) ;
2021-09-28 13:33:00 -04:00
// Create binding source struct descriptor.
FStateTreeBindableStructDesc StructDesc ;
StructDesc . Struct = TaskPtr . GetScriptStruct ( ) ;
StructDesc . Name = Task . Name ;
StructDesc . ID = Task . ID ;
// Mark the struct as binding source.
const int32 SourceStructIndex = BindingsCompiler . AddSourceStruct ( StructDesc ) ;
// Check that the bindings for this struct are still all valid.
TArray < FStateTreeEditorPropertyBinding > 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 ;
}
check ( BatchIndex < int32 ( MAX_uint16 ) ) ;
Task . BindingsBatch = BatchIndex = = INDEX_NONE ? FStateTreeHandle : : Invalid : FStateTreeHandle ( uint16 ( BatchIndex ) ) ;
check ( SourceStructIndex < = int32 ( MAX_uint16 ) ) ;
Task . SourceStructIndex = uint16 ( SourceStructIndex ) ;
}
}
const int32 TasksNum = StateTree - > RuntimeStorageItems . Num ( ) - int32 ( BakedState . TasksBegin ) ;
check ( TasksNum < = int32 ( MAX_uint8 ) ) ;
BakedState . TasksNum = uint8 ( TasksNum ) ;
// Child states
check ( StateTree - > States . Num ( ) < = int32 ( MAX_uint16 ) ) ;
BakedState . ChildrenBegin = uint16 ( StateTree - > States . Num ( ) ) ;
for ( UStateTreeState * Child : State . Children )
{
if ( Child )
{
2021-10-21 04:12:07 -04:00
if ( ! CreateStateRecursive ( * Child , FStateTreeHandle ( ( uint16 ) StateIdx ) ) )
2021-09-28 13:33:00 -04:00
{
return false ;
}
}
}
check ( StateTree - > States . Num ( ) < = int32 ( MAX_uint16 ) ) ;
StateTree - > States [ StateIdx ] . ChildrenEnd = uint16 ( StateTree - > States . Num ( ) ) ; // Cannot use BakedState here, it may be invalid due to array resize.
return true ;
}