extends RefCounted class_name StateMachine const MIN_PRIORITY = -2147483648 signal state_changed(f_id: StringName, t_id: StringName) var states: Dictionary[StringName, State] = {} var transitions: Array[StateTransition] = [] var current_state: State var _pending_id: StringName = &"" var _pending_priority = MIN_PRIORITY func setup(state_list: Array[State], transition_list: Array[StateTransition], initial_id: StringName) -> void: states.clear() for s in state_list: if s != null: states[s.id] = s transitions = transition_list current_state = states.get(initial_id) if current_state == null and not states.is_empty(): current_state = states.values()[0] push_warning("State machine initial state '%s' not found, falling back to '%s'" % [initial_id, current_state.id]) func process(data: Object, delta: float) -> void: if current_state == null: return current_state.process(data, delta) _evaluate_transitions(data) # Escape hatch for an immediate transition func request_transition(to_id: StringName, priority: int = 999) -> void: if priority >= _pending_priority: _pending_id = to_id _pending_priority = priority func _evaluate_transitions(data: Object) -> void: var best_id: StringName = &"" var best_priority: int = MIN_PRIORITY # Is there a next state pending that isn't our current state? if _pending_id != &"" and _pending_id != current_state.id and states.has(_pending_id): best_id = _pending_id best_priority = _pending_priority for t in transitions: # Skip the current state if not t.matches_current(current_state.id) or t.to == current_state.id: continue if not states.has(t.to): push_warning("StateMachine: transition targets unknown state '%s'" % t.to) continue if t.priority < best_priority: continue if t.is_satisfied(data): best_id = t.to best_priority = t.priority _pending_id = &"" _pending_priority = MIN_PRIORITY if best_id != &"" and best_id != current_state.id: var from_id = current_state.id current_state.exit(data) current_state = states[best_id] current_state.enter(data) state_changed.emit(from_id, current_state.id)