using UnityEngine;
using System;
using System.Collections.Generic;
namespace Bonsai.Utility
{
///
/// A finite state machine.
///
/// The date type to be stored by the machine states.
public class StateMachine
{
///
/// An event that fires when the machine finishes transitioning to another state.
///
public event Action OnStateChangedEvent = delegate { };
protected Dictionary _states = new Dictionary();
///
/// Get all the state data.
///
///
public IEnumerable Data()
{
return _states.Keys;
}
protected State _currentState;
public State CurrentState
{
get { return _currentState; }
}
///
/// Adds a state to the machine.
///
///
public void AddState(T data)
{
var s = new State(data);
_states.Add(data, s); ;
}
public void AddState(State s)
{
_states.Add(s.Value, s);
}
public void AddTransition(State start, State end, Func condition, Func onMakingTransition)
{
var t = new Transition(condition);
t.onMakingTransition = onMakingTransition;
AddTransition(start, end, t);
}
public void AddTransition(State start, State end, Transition t)
{
start.Add(t);
t.SetNextState(end);
}
///
/// Add a transition that goes from start to end state.
///
///
///
///
public void AddTransition(T start, T end, Transition t)
{
var startST = GetState(start);
var endST = GetState(end);
if (startST == null || endST == null) {
Debug.LogError("State(s) are not in the state machine");
return;
}
AddTransition(startST, endST, t);
}
///
/// Add two transitions.
/// One from start to end.
/// Another from end to start.
///
///
///
///
///
public void AddBiTransition(T start, T end, Transition startToEnd, Transition endToStart)
{
var startST = GetState(start);
var endST = GetState(end);
if (startST == null || endST == null) {
Debug.LogError("State(s) are not in the state machine");
return;
}
AddTransition(startST, endST, startToEnd);
AddTransition(endST, startST, endToStart);
}
///
/// Gets the state associated with the data.
///
///
///
public State GetState(T data)
{
if (_states.ContainsKey(data)) {
return _states[data];
}
return null;
}
///
/// Sets the current active state of the machine.
///
///
public void SetCurrentState(T data)
{
var state = GetState(data);
if (state == null) {
Debug.LogError(data + " is not in the state machine.");
}
else {
_currentState = state;
}
}
///
/// Handles moving to next state when conditions are met.
///
public void Update()
{
if (_currentState == null) {
return;
}
Transition validTrans = null;
// Pick the next state if the transition conditions are met.
for (int i = 0; i < _currentState.Transitions.Count; i++) {
if (_currentState.Transitions[i].AllConditionsMet()) {
validTrans = _currentState.Transitions[i];
break;
}
}
if (validTrans != null) {
// Call on making transition.
if (validTrans.onMakingTransition()) {
// Call on state exit.
if (_currentState.onStateExit != null)
_currentState.onStateExit();
// Change the state to the next one.
_currentState = validTrans.NextState;
// Call on state enter.
if (_currentState.onStateEnter != null)
_currentState.onStateEnter();
OnStateChangedEvent();
}
}
}
///
/// A transition between two states than only occurs if all
/// its conditions are satisfied.
///
public class Transition
{
private State _nextState = null;
private List> _conditions = new List>();
///
/// Called after the 'from' state exits and before the 'to' state enters.
/// If this fails, then it goes back to the starting state.
///
public Func onMakingTransition = () => { return true; };
public Transition()
{
}
///
/// Pass in initial conditions
///
///
public Transition(params Func[] conditions)
{
foreach (var c in conditions) {
AddCondition(c);
}
}
///
/// Adds a condition that must be satisfied in order to do the transition.
///
///
public void AddCondition(Func cond)
{
_conditions.Add(cond);
}
///
/// Tests if all the conditions of the transition are satisfied.
///
///
public bool AllConditionsMet()
{
for (int i = 0; i < _conditions.Count; i++) {
if (!_conditions[i]()) return false;
}
// All conditions returned true.
return true;
}
///
/// Set the state that transition goes to.
///
///
public void SetNextState(State next)
{
_nextState = next;
}
///
/// The state that transition goes to.
///
public State NextState
{
get { return _nextState; }
}
}
///
/// A state of the machine.
///
public class State
{
private T _data;
private List _transitions = new List();
///
/// Executes when the machine transitions into this state.
///
public Action onStateEnter;
///
/// Executes when the machine transitions out of this state.
///
public Action onStateExit;
///
/// Construct a state with its data.
///
///
public State(T data)
{
_data = data;
}
///
/// Adds a transition to the state.
///
///
public void Add(Transition t)
{
_transitions.Add(t);
}
///
/// The data held by the state.
///
public T Value { get { return _data; } }
///
/// The transitions connected to the state.
///
public List Transitions { get { return _transitions; } }
}
}
}