2023-07-21 09:50:06 +00:00
/**
* limbo_hsm . cpp
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2024-03-04 14:53:39 +00:00
* Copyright 2021 - 2024 Serhii Snitsaruk
2023-07-21 09:50:06 +00:00
*
* Use of this source code is governed by an MIT - style
* license that can be found in the LICENSE file or at
* https : //opensource.org/licenses/MIT.
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
2022-09-29 10:54:07 +00:00
# include "limbo_hsm.h"
VARIANT_ENUM_CAST ( LimboHSM : : UpdateMode ) ;
void LimboHSM : : set_active ( bool p_active ) {
ERR_FAIL_COND_MSG ( agent = = nullptr , " LimboHSM is not initialized. " ) ;
ERR_FAIL_COND_MSG ( p_active & & initial_state = = nullptr , " LimboHSM has no initial substate candidate. " ) ;
if ( active = = p_active ) {
return ;
}
active = p_active ;
switch ( update_mode ) {
case UpdateMode : : IDLE : {
set_process ( p_active ) ;
set_physics_process ( false ) ;
} break ;
case UpdateMode : : PHYSICS : {
set_process ( false ) ;
set_physics_process ( p_active ) ;
} break ;
case UpdateMode : : MANUAL : {
set_process ( false ) ;
set_physics_process ( false ) ;
} break ;
}
set_process_input ( p_active ) ;
if ( active ) {
2024-01-18 10:32:32 +00:00
_enter ( ) ;
2022-09-29 10:54:07 +00:00
} else {
2024-01-18 10:32:32 +00:00
_exit ( ) ;
2022-09-29 10:54:07 +00:00
}
}
2024-07-20 16:04:41 +00:00
void LimboHSM : : change_active_state ( LimboState * p_state ) {
ERR_FAIL_NULL ( p_state ) ;
ERR_FAIL_COND_MSG ( ! is_active ( ) , " LimboHSM: Unable to change active state when HSM is not active. " ) ;
ERR_FAIL_COND_MSG ( p_state - > get_parent ( ) ! = this , " LimboHSM: Unable to perform transition to a state that is not a child of this HSM. " ) ;
2022-09-29 10:54:07 +00:00
if ( active_state ) {
2024-01-18 10:32:32 +00:00
active_state - > _exit ( ) ;
2024-07-31 10:15:15 +00:00
active_state - > set_process_input ( false ) ;
2024-03-05 11:58:55 +00:00
previous_active = active_state ;
2022-09-29 10:54:07 +00:00
}
active_state = p_state ;
2024-01-18 10:32:32 +00:00
active_state - > _enter ( ) ;
2024-07-31 10:15:15 +00:00
active_state - > set_process_input ( true ) ;
2022-12-16 10:56:41 +00:00
2024-03-05 11:58:55 +00:00
emit_signal ( LimboStringNames : : get_singleton ( ) - > active_state_changed , active_state , previous_active ) ;
2022-09-29 10:54:07 +00:00
}
2024-01-18 10:32:32 +00:00
void LimboHSM : : _enter ( ) {
2022-09-29 10:54:07 +00:00
ERR_FAIL_COND_MSG ( get_child_count ( ) = = 0 , " LimboHSM has no candidate for initial substate. " ) ;
ERR_FAIL_COND ( active_state ! = nullptr ) ;
2024-01-12 22:20:39 +00:00
ERR_FAIL_COND_MSG ( initial_state = = nullptr , " LimboHSM: Initial state is not set. " ) ;
2022-09-29 10:54:07 +00:00
2024-01-18 10:32:32 +00:00
LimboState : : _enter ( ) ;
2024-07-20 16:04:41 +00:00
change_active_state ( initial_state ) ;
2022-09-29 10:54:07 +00:00
}
2024-01-18 10:32:32 +00:00
void LimboHSM : : _exit ( ) {
2022-09-29 10:54:07 +00:00
ERR_FAIL_COND ( active_state = = nullptr ) ;
2024-01-18 10:32:32 +00:00
active_state - > _exit ( ) ;
2022-09-29 10:54:07 +00:00
active_state = nullptr ;
2024-01-18 10:32:32 +00:00
LimboState : : _exit ( ) ;
2022-09-29 10:54:07 +00:00
}
2024-01-18 10:32:32 +00:00
void LimboHSM : : _update ( double p_delta ) {
2022-09-29 10:54:07 +00:00
if ( active ) {
2024-02-26 20:34:30 +00:00
ERR_FAIL_NULL ( active_state ) ;
LimboState * last_active_state = active_state ;
2024-01-18 10:32:32 +00:00
LimboState : : _update ( p_delta ) ;
2024-02-26 20:34:30 +00:00
if ( last_active_state = = active_state ) {
active_state - > _update ( p_delta ) ;
}
2022-09-29 10:54:07 +00:00
}
}
2024-01-12 22:20:39 +00:00
void LimboHSM : : update ( double p_delta ) {
2024-04-20 19:30:26 +00:00
updating = true ;
2024-01-18 10:32:32 +00:00
_update ( p_delta ) ;
2024-04-20 19:30:26 +00:00
updating = false ;
if ( next_active ) {
2024-07-20 16:04:41 +00:00
change_active_state ( next_active ) ;
2024-04-20 19:30:26 +00:00
next_active = nullptr ;
}
2024-01-12 22:20:39 +00:00
}
2024-03-04 14:53:39 +00:00
void LimboHSM : : add_transition ( LimboState * p_from_state , LimboState * p_to_state , const StringName & p_event ) {
2024-01-28 10:56:53 +00:00
ERR_FAIL_COND_MSG ( p_from_state ! = nullptr & & p_from_state - > get_parent ( ) ! = this , " LimboHSM: Unable to add a transition from a state that is not an immediate child of mine. " ) ;
ERR_FAIL_COND_MSG ( p_to_state = = nullptr , " LimboHSM: Unable to add a transition to a null state. " ) ;
ERR_FAIL_COND_MSG ( p_to_state - > get_parent ( ) ! = this , " LimboHSM: Unable to add a transition to a state that is not an immediate child of mine. " ) ;
2024-03-04 14:53:39 +00:00
ERR_FAIL_COND_MSG ( p_event = = StringName ( ) , " LimboHSM: Failed to add transition due to empty event string. " ) ;
2022-09-29 10:54:07 +00:00
2024-07-21 11:28:13 +00:00
TransitionKey key = Transition : : make_key ( p_from_state , p_event ) ;
ERR_FAIL_COND_MSG ( transitions . has ( key ) , " LimboHSM: Unable to add another transition with the same event and origin. " ) ;
// Note: Explicit casting needed for GDExtension.
transitions [ key ] = { p_from_state ! = nullptr ? ObjectID ( p_from_state - > get_instance_id ( ) ) : ObjectID ( ) , ObjectID ( p_to_state - > get_instance_id ( ) ) , p_event } ;
2022-09-29 10:54:07 +00:00
}
2024-07-20 15:23:13 +00:00
void LimboHSM : : remove_transition ( LimboState * p_from_state , const StringName & p_event ) {
ERR_FAIL_COND_MSG ( p_from_state ! = nullptr & & p_from_state - > get_parent ( ) ! = this , " LimboHSM: Unable to remove a transition from a state that is not an immediate child of mine. " ) ;
ERR_FAIL_COND_MSG ( p_event = = StringName ( ) , " LimboHSM: Unable to remove a transition due to empty event string. " ) ;
2024-07-21 11:28:13 +00:00
TransitionKey key = Transition : : make_key ( p_from_state , p_event ) ;
2024-07-20 15:23:13 +00:00
ERR_FAIL_COND_MSG ( ! transitions . has ( key ) , " LimboHSM: Unable to remove a transition that does not exist. " ) ;
transitions . erase ( key ) ;
}
2024-07-21 11:45:36 +00:00
void LimboHSM : : _get_transition ( LimboState * p_from_state , const StringName & p_event , Transition & r_transition ) const {
2024-07-21 11:28:13 +00:00
ERR_FAIL_COND_MSG ( p_from_state ! = nullptr & & p_from_state - > get_parent ( ) ! = this , " LimboHSM: Unable to get a transition from a state that is not an immediate child of this HSM. " ) ;
ERR_FAIL_COND_MSG ( p_event = = StringName ( ) , " LimboHSM: Unable to get a transition with an empty event string. " ) ;
TransitionKey key = Transition : : make_key ( p_from_state , p_event ) ;
if ( transitions . has ( key ) ) {
r_transition = transitions [ key ] ;
}
}
2022-09-29 10:54:07 +00:00
LimboState * LimboHSM : : get_leaf_state ( ) const {
LimboHSM * hsm = const_cast < LimboHSM * > ( this ) ;
while ( hsm - > active_state ! = nullptr & & hsm - > active_state - > is_class ( " LimboHSM " ) ) {
hsm = Object : : cast_to < LimboHSM > ( hsm - > active_state ) ;
}
if ( hsm - > active_state ) {
return hsm - > active_state ;
} else {
return hsm ;
}
}
2023-07-25 16:39:41 +00:00
void LimboHSM : : set_initial_state ( LimboState * p_state ) {
2022-10-26 21:12:29 +00:00
ERR_FAIL_COND ( p_state = = nullptr | | ! p_state - > is_class ( " LimboState " ) ) ;
initial_state = Object : : cast_to < LimboState > ( p_state ) ;
}
2024-03-04 14:53:39 +00:00
bool LimboHSM : : _dispatch ( const StringName & p_event , const Variant & p_cargo ) {
ERR_FAIL_COND_V ( p_event = = StringName ( ) , false ) ;
2022-09-29 10:54:07 +00:00
bool event_consumed = false ;
if ( active_state ) {
2024-03-01 10:02:32 +00:00
event_consumed = active_state - > _dispatch ( p_event , p_cargo ) ;
2022-09-29 10:54:07 +00:00
}
if ( ! event_consumed ) {
2024-03-01 10:02:32 +00:00
event_consumed = LimboState : : _dispatch ( p_event , p_cargo ) ;
2022-09-29 10:54:07 +00:00
}
if ( ! event_consumed & & active_state ) {
2022-10-12 12:02:39 +00:00
LimboState * to_state = nullptr ;
2024-07-21 11:28:13 +00:00
Transition transition ;
2024-07-21 11:45:36 +00:00
_get_transition ( active_state , p_event , transition ) ;
2024-07-21 11:28:13 +00:00
if ( transition . is_valid ( ) ) {
to_state = Object : : cast_to < LimboState > ( ObjectDB : : get_instance ( transition . to_state ) ) ;
2022-10-12 12:02:39 +00:00
}
if ( to_state = = nullptr ) {
// Get ANYSTATE transition.
2024-07-21 11:45:36 +00:00
_get_transition ( nullptr , p_event , transition ) ;
2024-07-21 11:28:13 +00:00
if ( transition . is_valid ( ) ) {
to_state = Object : : cast_to < LimboState > ( ObjectDB : : get_instance ( transition . to_state ) ) ;
2024-02-07 12:39:45 +00:00
if ( to_state = = active_state ) {
// Transitions to self are not allowed with ANYSTATE.
to_state = nullptr ;
}
2022-10-12 12:02:39 +00:00
}
}
if ( to_state ! = nullptr ) {
2022-09-29 20:44:51 +00:00
bool permitted = true ;
2022-12-16 10:56:41 +00:00
if ( to_state - > guard_callable . is_valid ( ) ) {
Variant ret ;
2024-01-07 04:54:17 +00:00
# ifdef LIMBOAI_MODULE
Callable : : CallError ce ;
2022-12-16 10:56:41 +00:00
to_state - > guard_callable . callp ( nullptr , 0 , ret , ce ) ;
if ( unlikely ( ce . error ! = Callable : : CallError : : CALL_OK ) ) {
ERR_PRINT_ONCE ( " LimboHSM: Error calling substate's guard callable: " + Variant : : get_callable_error_text ( to_state - > guard_callable , nullptr , 0 , ce ) ) ;
}
2024-01-13 16:10:42 +00:00
# elif LIMBOAI_GDEXTENSION
2024-01-07 04:54:17 +00:00
ret = to_state - > guard_callable . call ( ) ;
2024-01-13 16:10:42 +00:00
# endif
2024-01-07 04:54:17 +00:00
2022-12-16 10:56:41 +00:00
if ( unlikely ( ret . get_type ( ) ! = Variant : : BOOL ) ) {
ERR_PRINT_ONCE ( vformat ( " State guard callable %s returned non-boolean value (%s). " , to_state - > guard_callable , to_state ) ) ;
2022-09-29 20:44:51 +00:00
} else {
2022-12-16 10:56:41 +00:00
permitted = bool ( ret ) ;
2022-09-29 20:44:51 +00:00
}
}
if ( permitted ) {
2024-04-20 19:30:26 +00:00
if ( ! updating ) {
2024-07-20 16:04:41 +00:00
change_active_state ( to_state ) ;
2024-04-20 19:30:26 +00:00
} else if ( ! next_active ) {
// Only set next_active if we are not already in the process of changing states.
next_active = to_state ;
}
2022-09-29 20:44:51 +00:00
event_consumed = true ;
}
2022-09-29 10:54:07 +00:00
}
}
2024-01-09 12:42:54 +00:00
if ( ! event_consumed & & p_event = = LW_NAME ( EVENT_FINISHED ) & & ! ( get_parent ( ) & & get_parent ( ) - > is_class ( " LimboState " ) ) ) {
2024-01-18 10:32:32 +00:00
_exit ( ) ;
2022-09-29 10:54:07 +00:00
}
return event_consumed ;
}
2022-12-16 10:56:41 +00:00
void LimboHSM : : initialize ( Node * p_agent , const Ref < Blackboard > & p_parent_scope ) {
2022-09-29 10:54:07 +00:00
ERR_FAIL_COND ( p_agent = = nullptr ) ;
2024-03-06 19:17:23 +00:00
ERR_FAIL_COND_MSG ( ! is_root ( ) , " LimboHSM: initialize() must be called on the root HSM. " ) ;
2024-03-07 19:00:27 +00:00
_initialize ( p_agent , p_parent_scope ) ;
2024-01-12 22:20:39 +00:00
if ( initial_state = = nullptr ) {
initial_state = Object : : cast_to < LimboState > ( get_child ( 0 ) ) ;
}
2022-10-31 20:30:32 +00:00
}
2022-12-16 10:56:41 +00:00
void LimboHSM : : _initialize ( Node * p_agent , const Ref < Blackboard > & p_blackboard ) {
2022-10-31 20:30:32 +00:00
ERR_FAIL_COND ( p_agent = = nullptr ) ;
ERR_FAIL_COND_MSG ( agent ! = nullptr , " LimboAI: HSM already initialized. " ) ;
2022-09-29 10:54:07 +00:00
ERR_FAIL_COND_MSG ( get_child_count ( ) = = 0 , " Cannot initialize LimboHSM: no candidate for initial substate. " ) ;
if ( initial_state = = nullptr ) {
initial_state = Object : : cast_to < LimboState > ( get_child ( 0 ) ) ;
ERR_FAIL_COND_MSG ( initial_state = = nullptr , " LimboHSM: Child at index 0 is not a LimboState. " ) ;
}
2022-10-31 20:53:33 +00:00
LimboState : : _initialize ( p_agent , p_blackboard ) ;
2022-09-29 10:54:07 +00:00
for ( int i = 0 ; i < get_child_count ( ) ; i + + ) {
LimboState * c = Object : : cast_to < LimboState > ( get_child ( i ) ) ;
if ( unlikely ( c = = nullptr ) ) {
ERR_PRINT ( vformat ( " LimboHSM: Child at index %d is not a LimboState. " , i ) ) ;
} else {
2022-10-31 20:53:33 +00:00
c - > _initialize ( agent , blackboard ) ;
2022-09-29 10:54:07 +00:00
}
}
}
void LimboHSM : : _notification ( int p_what ) {
switch ( p_what ) {
case NOTIFICATION_POST_ENTER_TREE : {
} break ;
case NOTIFICATION_PROCESS : {
2024-01-18 10:32:32 +00:00
_update ( get_process_delta_time ( ) ) ;
2022-09-29 10:54:07 +00:00
} break ;
case NOTIFICATION_PHYSICS_PROCESS : {
2024-01-18 10:32:32 +00:00
_update ( get_physics_process_delta_time ( ) ) ;
2022-09-29 10:54:07 +00:00
} break ;
}
}
void LimboHSM : : _bind_methods ( ) {
2024-03-04 20:36:16 +00:00
ClassDB : : bind_method ( D_METHOD ( " set_update_mode " , " mode " ) , & LimboHSM : : set_update_mode ) ;
2022-09-29 10:54:07 +00:00
ClassDB : : bind_method ( D_METHOD ( " get_update_mode " ) , & LimboHSM : : get_update_mode ) ;
2022-10-26 21:12:29 +00:00
2024-03-04 20:36:16 +00:00
ClassDB : : bind_method ( D_METHOD ( " set_initial_state " , " state " ) , & LimboHSM : : set_initial_state ) ;
2022-10-26 21:12:29 +00:00
ClassDB : : bind_method ( D_METHOD ( " get_initial_state " ) , & LimboHSM : : get_initial_state ) ;
2022-09-29 10:54:07 +00:00
ClassDB : : bind_method ( D_METHOD ( " get_active_state " ) , & LimboHSM : : get_active_state ) ;
2024-03-05 11:58:55 +00:00
ClassDB : : bind_method ( D_METHOD ( " get_previous_active_state " ) , & LimboHSM : : get_previous_active_state ) ;
2022-09-29 10:54:07 +00:00
ClassDB : : bind_method ( D_METHOD ( " get_leaf_state " ) , & LimboHSM : : get_leaf_state ) ;
2024-03-04 20:36:16 +00:00
ClassDB : : bind_method ( D_METHOD ( " set_active " , " active " ) , & LimboHSM : : set_active ) ;
ClassDB : : bind_method ( D_METHOD ( " update " , " delta " ) , & LimboHSM : : update ) ;
ClassDB : : bind_method ( D_METHOD ( " add_transition " , " from_state " , " to_state " , " event " ) , & LimboHSM : : add_transition ) ;
2024-07-20 15:23:13 +00:00
ClassDB : : bind_method ( D_METHOD ( " remove_transition " , " from_state " , " event " ) , & LimboHSM : : remove_transition ) ;
2024-07-21 11:35:16 +00:00
ClassDB : : bind_method ( D_METHOD ( " has_transition " , " from_state " , " event " ) , & LimboHSM : : has_transition ) ;
2022-10-12 12:02:39 +00:00
ClassDB : : bind_method ( D_METHOD ( " anystate " ) , & LimboHSM : : anystate ) ;
2024-03-04 20:36:16 +00:00
ClassDB : : bind_method ( D_METHOD ( " initialize " , " agent " , " parent_scope " ) , & LimboHSM : : initialize , Variant ( ) ) ;
2024-07-20 16:04:41 +00:00
ClassDB : : bind_method ( D_METHOD ( " change_active_state " , " state " ) , & LimboHSM : : change_active_state ) ;
2022-10-31 20:30:32 +00:00
2022-09-29 10:54:07 +00:00
BIND_ENUM_CONSTANT ( IDLE ) ;
BIND_ENUM_CONSTANT ( PHYSICS ) ;
BIND_ENUM_CONSTANT ( MANUAL ) ;
ADD_PROPERTY ( PropertyInfo ( Variant : : INT , " update_mode " , PROPERTY_HINT_ENUM , " Idle, Physics, Manual " ) , " set_update_mode " , " get_update_mode " ) ;
2022-12-16 10:56:41 +00:00
ADD_PROPERTY ( PropertyInfo ( Variant : : OBJECT , " ANYSTATE " , PROPERTY_HINT_RESOURCE_TYPE , " LimboState " , 0 ) , " " , " anystate " ) ;
2022-10-26 21:12:29 +00:00
ADD_PROPERTY ( PropertyInfo ( Variant : : OBJECT , " initial_state " , PROPERTY_HINT_RESOURCE_TYPE , " LimboState " , 0 ) , " set_initial_state " , " get_initial_state " ) ;
2022-09-29 10:54:07 +00:00
2024-03-05 11:58:55 +00:00
ADD_SIGNAL ( MethodInfo ( " active_state_changed " ,
PropertyInfo ( Variant : : OBJECT , " current " , PROPERTY_HINT_RESOURCE_TYPE , " LimboState " ) ,
PropertyInfo ( Variant : : OBJECT , " previous " , PROPERTY_HINT_RESOURCE_TYPE , " LimboState " ) ) ) ;
2022-09-29 10:54:07 +00:00
}
LimboHSM : : LimboHSM ( ) {
2022-12-16 10:56:41 +00:00
update_mode = UpdateMode : : PHYSICS ;
2022-09-29 10:54:07 +00:00
active_state = nullptr ;
2024-03-05 11:58:55 +00:00
previous_active = nullptr ;
2024-04-20 19:30:26 +00:00
next_active = nullptr ;
2022-09-29 10:54:07 +00:00
initial_state = nullptr ;
2022-10-26 21:12:29 +00:00
}