123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- // From unity/unity's UnityEditor.Connect at 1a53aca
- using System;
- using System.Collections.Generic;
- using UnityEngine;
-
- namespace UnityEditor.Purchasing
- {
- /// <summary>
- /// 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).
- /// </summary>
- class SimpleStateMachine<T>
- {
- readonly HashSet<T> m_Events = new HashSet<T>();
- readonly Dictionary<string, State> m_StateByName = new Dictionary<string, State>();
- bool m_Initialized;
-
- /// <summary>
- /// The current state
- /// </summary>
- public State currentState { get; private set; }
-
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="initialState"></param>
- public void Initialize(State initialState)
- {
- if (!m_Initialized)
- {
- if (!StateExists(initialState))
- {
- AddState(initialState);
- }
- m_Initialized = true;
- currentState = initialState;
- currentState.EnterState();
- }
- }
-
- /// <summary>
- /// Clears the current state
- /// Allows for a full redraw on the state machine in the activate action without reallocating memory
- /// </summary>
- public void ClearCurrentState()
- {
- if (m_Initialized)
- {
- currentState = null;
- m_Initialized = false;
- }
- }
-
- /// <summary>
- /// 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);
- /// </summary>
- /// <param name="simpleStateMachineEvent">the event</param>
- 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;
- }
-
- /// <summary>
- /// 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
- /// </summary>
- /// <returns>a copy of the state machine events</returns>
- public List<T> GetEvents()
- {
- return new List<T>(m_Events);
- }
-
- /// <summary>
- /// 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
- /// </summary>
- /// <param name="state"></param>
- public void AddState(State state)
- {
- m_StateByName.Add(state.name, state);
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="stateName">The name of the state to find</param>
- /// <returns>The state; or null if none found</returns>
- 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);
- }
-
- /// <summary>
- /// 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)
- /// </summary>
- /// <returns>a copy of the state machine states</returns>
- public List<State> GetStates()
- {
- return new List<State>(m_StateByName.Values);
- }
-
- /// <summary>
- /// 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'
- /// </summary>
- /// <param name="simpleStateMachineEvent">The event to process</param>
- 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."));
- }
- }
- }
- }
-
- /// <summary>
- /// Base state for a simple state machine. It can be used as-is or extended to gain additional functionality
- /// </summary>
- internal class State
- {
- readonly List<ActionForEvent> m_ActionForEvent = new List<ActionForEvent>();
-
- /// <summary>
- /// Access to the state machine. Mostly to GetStateByName when transitioning from one state to another
- /// </summary>
- protected SimpleStateMachine<T> stateMachine { get; }
- public string name { get; }
-
- public State(string name, SimpleStateMachine<T> 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;
- }
-
- /// <summary>
- /// Updates or Inserts a new action to execute when an event occurs when the state machine is at this state.
- /// </summary>
- /// <param name="simpleStateMachineEvent">The event</param>
- /// <param name="transitionAction">The action to accomplish when the specified event occurs</param>
- public virtual void ModifyActionForEvent(T simpleStateMachineEvent, Func<T, State> transitionAction)
- {
- foreach (var actionForEvent in m_ActionForEvent)
- {
- if (actionForEvent.simpleStateMachineEvent.Equals(simpleStateMachineEvent))
- {
- actionForEvent.action = transitionAction;
- return;
- }
- }
- m_ActionForEvent.Add(new ActionForEvent(simpleStateMachineEvent, transitionAction));
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="simpleStateMachineEvent">the event we search an action for</param>
- /// <returns>an action to execute; can be DoNothing if ModifyActionForEvent wasn't done for this event</returns>
- public virtual Func<T, State> GetActionForEvent(T simpleStateMachineEvent)
- {
- foreach (var actionForEvent in m_ActionForEvent)
- {
- if (actionForEvent.simpleStateMachineEvent.Equals(simpleStateMachineEvent))
- {
- return actionForEvent.action;
- }
- }
-
- return DoNothing;
- }
-
- /// <summary>
- /// Default action taken when no handler is found for an event.
- /// Will simply return the current state and thus, does nothing
- /// </summary>
- /// <returns></returns>
- State DoNothing(T simpleStateMachineEvent)
- {
- return this;
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- public virtual void EnterState() { }
-
- class ActionForEvent
- {
- internal T simpleStateMachineEvent { get; }
- internal Func<T, State> action { get; set; }
-
- internal ActionForEvent(T simpleStateMachineEvent, Func<T, State> action)
- {
- this.simpleStateMachineEvent = simpleStateMachineEvent;
- this.action = action;
- }
- }
- }
- }
- }
|