// From unity/unity's UnityEditor.Connect at 1a53aca using System; using System.Collections.Generic; using UnityEngine; namespace UnityEditor.Purchasing { /// /// A very lightweight state machine which uses generics as events /// (unless you have specific needs, enums are recommended) and states /// (either instance of the base state or your own extensions). /// class SimpleStateMachine { readonly HashSet m_Events = new HashSet(); readonly Dictionary m_StateByName = new Dictionary(); bool m_Initialized; /// /// The current state /// public State currentState { get; private set; } /// /// Initializes the state machine. /// This should be called after instantiating the state machine and instantiating the initial state. /// Since the state machine needs an initial state instance and the state needs a state machine instance, the /// proper order is: /// 1- instantiate state machine /// 2- instantiate initial state /// 3- initialize state machine with initial state /// A state machine cannot be initialized multiple times. Further attempts will be ignored. /// /// public void Initialize(State initialState) { if (!m_Initialized) { if (!StateExists(initialState)) { AddState(initialState); } m_Initialized = true; currentState = initialState; currentState.EnterState(); } } /// /// Clears the current state /// Allows for a full redraw on the state machine in the activate action without reallocating memory /// public void ClearCurrentState() { if (m_Initialized) { currentState = null; m_Initialized = false; } } /// /// Adds a new event to the state machine /// Don't forget that events are sent to states. So when adding an event to the state machine, /// it generally means that this event should also be configured on some states by using /// mySimpleStateMachineState.ModifyActionForEvent(myEvent, myHandler); /// /// the event public void AddEvent(T simpleStateMachineEvent) { m_Events.Add(simpleStateMachineEvent); } public bool EventExists(T simpleStateMachineEvent) { foreach (var knownEvent in m_Events) { if (knownEvent.Equals(simpleStateMachineEvent)) { return true; } } return false; } /// /// Gets a copy of the state machine events. /// Copy means you cannot alter the state machine by altering this list. /// It's a shallow copy though, be careful what you do with the list entries /// /// a copy of the state machine events public List GetEvents() { return new List(m_Events); } /// /// Adds a new state to the state machine. /// This state can be generic (straight instance of SimpleStateMachineState) or an extended version if you need /// to override the EnterState() method of the SimpleStateMachineState. /// A state should react to events. Configure the state by using ModifyActionForEvent /// /// public void AddState(State state) { m_StateByName.Add(state.name, state); } /// /// This method is exposed to allow states to return a new state when an event action is completed successfully /// and a transition is made to another state. /// /// The name of the state to find /// The state; or null if none found public State GetStateByName(string stateName) { return m_StateByName.ContainsKey(stateName) ? m_StateByName[stateName] : null; } public bool StateExists(State state) { return m_StateByName.ContainsKey(state.name); } /// /// Gets a shallow copy of the state machine states. /// Copy means you cannot alter the state machine by altering this list /// (but changing the configuration of a state will modify the state machine: shallow copy) /// /// a copy of the state machine states public List GetStates() { return new List(m_StateByName.Values); } /// /// Call this method when an event occurs on the state machine. /// This will often result in a state change, but it's not mandatory. /// Nothing will happen if the event does not exist within the state machine events' /// /// The event to process public void ProcessEvent(T simpleStateMachineEvent) { if (m_Initialized && EventExists(simpleStateMachineEvent)) { var previousState = currentState; currentState = currentState.GetActionForEvent(simpleStateMachineEvent).Invoke(simpleStateMachineEvent); if (currentState != previousState) { if (currentState != null) { currentState.EnterState(); } else { Debug.LogError("SimpleStateMachine.ProcessEvent: " + L10n.Tr("Attempting to change to an undefined state. Contact Unity Support.")); } } } } /// /// Base state for a simple state machine. It can be used as-is or extended to gain additional functionality /// internal class State { readonly List m_ActionForEvent = new List(); /// /// Access to the state machine. Mostly to GetStateByName when transitioning from one state to another /// protected SimpleStateMachine stateMachine { get; } public string name { get; } public State(string name, SimpleStateMachine simpleStateMachine) { this.name = name; stateMachine = simpleStateMachine; } public virtual bool ActionExistsForEvent(T simpleStateMachineEvent) { foreach (var actionForEvent in m_ActionForEvent) { if (actionForEvent.simpleStateMachineEvent.Equals(simpleStateMachineEvent)) { return true; } } return false; } /// /// Updates or Inserts a new action to execute when an event occurs when the state machine is at this state. /// /// The event /// The action to accomplish when the specified event occurs public virtual void ModifyActionForEvent(T simpleStateMachineEvent, Func transitionAction) { foreach (var actionForEvent in m_ActionForEvent) { if (actionForEvent.simpleStateMachineEvent.Equals(simpleStateMachineEvent)) { actionForEvent.action = transitionAction; return; } } m_ActionForEvent.Add(new ActionForEvent(simpleStateMachineEvent, transitionAction)); } /// /// This method is called by the state machine when an event occurs. By default, /// if the state machine does not find an action to execute, the state will remain unchanged /// and the event will be ignored. /// /// the event we search an action for /// an action to execute; can be DoNothing if ModifyActionForEvent wasn't done for this event public virtual Func GetActionForEvent(T simpleStateMachineEvent) { foreach (var actionForEvent in m_ActionForEvent) { if (actionForEvent.simpleStateMachineEvent.Equals(simpleStateMachineEvent)) { return actionForEvent.action; } } return DoNothing; } /// /// Default action taken when no handler is found for an event. /// Will simply return the current state and thus, does nothing /// /// State DoNothing(T simpleStateMachineEvent) { return this; } /// /// This method will be called by the state machine when the a state becomes active. /// It allows to do a common operation on the current state without having all the other states repeat this /// code within their transition actions. /// public virtual void EnterState() { } class ActionForEvent { internal T simpleStateMachineEvent { get; } internal Func action { get; set; } internal ActionForEvent(T simpleStateMachineEvent, Func action) { this.simpleStateMachineEvent = simpleStateMachineEvent; this.action = action; } } } } }