using System; using System.Collections; using System.Collections.Generic; using Chernobyl.Collections.Generic; namespace Chernobyl.StateMachine { /// <summary> /// A default implementation of the <see cref="IState"/> interface. This /// class is used to provide a basic state changing mechanism for states and /// make it easier to create new <see cref="IState"/> types. /// </summary> public class State : IState { /// <summary> /// This event is raised when this state has become the active state. /// This can happen if this instances' <see cref="ParentState"/> has been /// set and the <see cref="ParentState"/> <see cref="IState"/> has had /// its <see cref="ChildState"/> set to this instance. It can also /// happen if this instance has decided to activate itself. This event /// will only be raised once when the state has become active. It will not be /// invoked a second time unless it is deactivated first. /// </summary> public event EventHandler Entered { add { _entered += value; } remove { _entered -= value; } } /// <summary> /// This event is raised when this state is no longer an active state. /// This can happen if this instances' <see cref="ParentState"/> has /// been set and the <see cref="ParentState"/> <see cref="IState"/> has /// had its <see cref="ChildState"/> set to this instance. It can also /// happen if this instance has decided to deactivate itself. This event /// will only be raised once when the state has been deactivated. It will /// not be raised a second time unless it was previously in an active /// state. /// </summary> public event EventHandler Left { add { _left += value; } remove { _left -= value; } } /// <summary> /// The <see cref="IState"/> that this <see cref="IState"/> is a /// sub-state of or null if this <see cref="IState"/> is not sub-state /// of another state. When set this property will set the incoming /// <see cref="IState"/> instances <see cref="ChildState"/> property /// to this instance (unless the value set is null of course). If this /// property is set to null (or in other words, this state is /// deactivated), two things will occur: (1) the <see cref="Left"/> event /// will be raised and (2), immediately prior to raising the /// <see cref="Left"/> event, this state will deactivate its child state /// (or sub-state) which will deactivate its child states and so and so /// forth. Deactivating sub-states is done prior to invoking the /// <see cref="Left"/> event so that sub-states will be deactivated from /// the lowest sub-state to the highest sub-state. For example, in a /// state machine setup like so: state1 -> state2 -> state3 -> state4 /// If state2 was deactivated, then state4 would be deactivated first /// (i.e., its <see cref="IState.Left"/> event would be raised first), /// then state3, and finally state2. /// </summary> public IState ParentState { get { return _parentState; } set { SetParent(ref _parentState, value, this, _entered, _left); } } /// <summary> /// The currently active sub-state of this <see cref="IState"/> or null /// if this <see cref="IState"/> doesn't currently have an active /// sub-state. When set this property will set the incoming /// <see cref="IState"/> instances <see cref="ParentState"/> property /// to this instance (unless the value set is null, in which case, the /// <see cref="IState.ChildState"/> this instances /// <see cref="ParentState"/> is set to null). /// </summary> public IState ChildState { get { return _childState; } set { SetChild(ref _childState, value, this); } } /// <summary> /// Returns an enumerator that iterates through a down the children of /// an <see cref="IState"/> system starting at this <see cref="IState"/>. /// </summary> /// <returns> /// The <see cref="IEnumerator{T}"/> that allows iteration over /// a set of linked instance. /// </returns> public IEnumerator<IState> GetLinkedEnumerator() { return new LinkedEnumerator<IState>(this, LinkedNextState); } /// <summary> /// Returns an enumerator that iterates through the collection. /// </summary> /// <returns> /// A <see cref="IEnumerator{T}"/> that can be used to iterate through /// the collection. /// </returns> public IEnumerator<IState> GetEnumerator() { return GetLinkedEnumerator(); } /// <summary> /// A helper method for types deriving from <see cref="IState"/>. This /// method is used in the <see cref="ParentState"/> property to properly /// set the parent and handle any other tasks that are associated with /// setting that property. This method will ensure that the parent isn't /// set twice if the <paramref name="parent"/> is the same as the /// <paramref name="newParent"/>, ensures that the /// <see cref="IState.ChildState"/> of the old parent is set to null (if /// applicable), ensures that the <see cref="IState.ChildState"/> of the /// <paramref name="child"/> is set to null (if applicable), and ensures /// the <see cref="IState.Entered"/> and <see cref="IState.Left"/> are /// invoked using <paramref name="entered"/> and <paramref name="left"/> /// respectively. /// </summary> /// <param name="parent">The previous value of the /// <see cref="IState.ParentState"/> and the instance that is to take the /// new value (if applicable).</param> /// <param name="newParent">The value being set on /// <see cref="IState.ParentState"/>.</param> /// <param name="child">The instance that was the child of /// <paramref name="parent"/> and will be the child of /// <paramref name="newParent"/>. Typically this is the value of 'this'.</param> /// <param name="entered">The method that is to be invoked if the /// <see cref="IState.Entered"/> event is to be raised.</param> /// <param name="left">The method that is to be invoked if the /// <see cref="IState.Left"/> event is to be raised.</param> public static void SetParent(ref IState parent, IState newParent, IState child, EventHandler entered, EventHandler left) { // Make sure we don't enter or leave a state more than once. if (parent != newParent) { IState previousParentState = parent; parent = newParent; // The previous parent should no longer reference this // IState as it's child. if (previousParentState != null && previousParentState.ChildState == child) previousParentState.ChildState = null; if (parent != null) { if (entered != null) entered(child, EventArgs.Empty); parent.ChildState = child; } else { // Because this state is being deactivated, that means // that all of the sub-states of this state need to be // deactivated. We can do that by setting our child // state to null which will deactivate our sub-state and // it's sub-states and so on and so forth. child.ChildState = null; if (left != null) left(child, EventArgs.Empty); } } } /// <summary> /// A helper method for types deriving from <see cref="IState"/>. This /// method is used in the <see cref="ChildState"/> property to properly /// set the child and handle any other tasks that are associated with /// setting that property. This method will ensure that the parent isn't /// set twice if the <paramref name="child"/> is the same as the /// <paramref name="newChild"/>, and ensures that the /// <see cref="IState.ParentState"/> of the old old child is set to null /// (if applicable). /// </summary> /// <param name="child">The previous value of the /// <see cref="IState.ChildState"/> and the instance that is to take the /// new value (if applicable).</param> /// <param name="newChild">The value being set on /// <see cref="IState.ChildState"/>.</param> /// <param name="parent">The instance that was the parent of /// <paramref name="child"/> and will be the parent of /// <paramref name="newChild"/>. Typically this is the value of 'this'.</param> public static void SetChild(ref IState child, IState newChild, IState parent) { if (child != newChild) { IState previousChildState = child; child = newChild; if (child != null) child.ParentState = parent; if (previousChildState != null && previousChildState.ParentState == parent) { // The previous child should no longer reference this // IState as it's parent. previousChildState.ParentState = null; } } } /// <summary> /// Retrieves the <see cref="ChildState"/> of the passed in /// <paramref name="current"/> state if it has one. This method is /// passed to the <see cref="LinkedEnumerator{T}"/> to allow it to /// iterate over the linked <see cref="IState"/> machine. /// </summary> /// <param name="current">The <see cref="IState"/> whose /// <see cref="IState.ChildState"/> is to be retrieved.</param> /// <param name="next">The next <see cref="IState"/> in the link or the /// <see cref="IState.ChildState"/>.</param> /// <returns>True if this <see cref="IState"/> has a child state and /// <paramref name="next"/> is valid upon exit of this method. False if /// otherwise.</returns> public static bool LinkedNextState(IState current, out IState next) { next = current.ChildState; return next != null; } /// <summary> /// Returns an enumerator that iterates through a collection. /// </summary> /// <returns> /// An <see cref="IEnumerator"/> object that can be used to iterate /// through the collection. /// </returns> IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// <summary> /// The backing field to <see cref="Entered"/>. /// </summary> EventHandler _entered; /// <summary> /// The backing field to <see cref="Left"/>. /// </summary> EventHandler _left; /// <summary> /// The backing field to <see cref="ParentState"/>. /// </summary> IState _parentState; /// <summary> /// The backing field to <see cref="ChildState"/>. /// </summary> IState _childState; } }