diff --git a/Blarg.GameFramework/Blarg.GameFramework.csproj b/Blarg.GameFramework/Blarg.GameFramework.csproj
index c555f52..a61edce 100644
--- a/Blarg.GameFramework/Blarg.GameFramework.csproj
+++ b/Blarg.GameFramework/Blarg.GameFramework.csproj
@@ -160,6 +160,12 @@
+
+
+
+
+
+
@@ -178,6 +184,8 @@
+
+
diff --git a/Blarg.GameFramework/Processes/GameProcess.cs b/Blarg.GameFramework/Processes/GameProcess.cs
new file mode 100644
index 0000000..5c03c25
--- /dev/null
+++ b/Blarg.GameFramework/Processes/GameProcess.cs
@@ -0,0 +1,108 @@
+using System;
+using Blarg.GameFramework.Events;
+using Blarg.GameFramework.States;
+
+namespace Blarg.GameFramework.Processes
+{
+ public class GameProcess : EventListener, IDisposable
+ {
+ public readonly IGameApp GameApp;
+ public readonly GameState GameState;
+ public readonly ProcessManager ProcessManager;
+
+ public bool IsFinished { get; private set; }
+
+ public bool IsTransitioning
+ {
+ get { return ProcessManager.IsProcessTransitioning(this); }
+ }
+
+ public GameProcess(ProcessManager processManager, EventManager eventManager)
+ : base(eventManager)
+ {
+ if (processManager == null)
+ throw new ArgumentNullException("processManager");
+ if (eventManager == null)
+ throw new ArgumentNullException("eventManager");
+
+ GameState = processManager.GameState;
+ ProcessManager = processManager;
+ GameApp = GameState.GameApp;
+ }
+
+ protected void SetFinished()
+ {
+ IsFinished = true;
+ }
+
+ #region Events
+
+ public virtual void OnAdd()
+ {
+ }
+
+ public virtual void OnRemove()
+ {
+ }
+
+ public virtual void OnPause(bool dueToOverlay)
+ {
+ }
+
+ public virtual void OnResume(bool fromOverlay)
+ {
+ }
+
+ public virtual void OnAppGainFocus()
+ {
+ }
+
+ public virtual void OnAppLostFocus()
+ {
+ }
+
+ public virtual void OnAppPause()
+ {
+ }
+
+ public virtual void OnAppResume()
+ {
+ }
+
+ public virtual void OnLostContext()
+ {
+ }
+
+ public virtual void OnNewContext()
+ {
+ }
+
+ public virtual void OnRender(float delta)
+ {
+ }
+
+ public virtual void OnResize()
+ {
+ }
+
+ public virtual void OnUpdate(float delta)
+ {
+ }
+
+ public virtual bool OnTransition(float delta, bool isTransitioningOut, bool started)
+ {
+ return true;
+ }
+
+ public override bool Handle(Event e)
+ {
+ return false;
+ }
+
+ #endregion
+
+ public virtual void Dispose()
+ {
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Processes/ProcessInfo.cs b/Blarg.GameFramework/Processes/ProcessInfo.cs
new file mode 100644
index 0000000..49625c0
--- /dev/null
+++ b/Blarg.GameFramework/Processes/ProcessInfo.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Blarg.GameFramework.Processes
+{
+ internal class ProcessInfo
+ {
+ public readonly GameProcess Process;
+ public readonly string Name;
+ public readonly string Descriptor;
+
+ public bool IsTransitioning;
+ public bool IsTransitioningOut;
+ public bool IsTransitionStarting;
+ public bool IsInactive;
+ public bool IsBeingRemoved;
+
+ public ProcessInfo(GameProcess process, string name = null)
+ {
+ if (process == null)
+ throw new ArgumentNullException("process");
+
+ Process = process;
+ Name = name;
+ IsInactive = true;
+
+ if (String.IsNullOrEmpty(Name))
+ Descriptor = process.GetType().Name;
+ else
+ Descriptor = String.Format("{0}[{1}]", process.GetType().Name, Name);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Processes/ProcessManager.cs b/Blarg.GameFramework/Processes/ProcessManager.cs
new file mode 100644
index 0000000..3881029
--- /dev/null
+++ b/Blarg.GameFramework/Processes/ProcessManager.cs
@@ -0,0 +1,477 @@
+using System;
+using System.Collections.Generic;
+using Blarg.GameFramework.States;
+
+namespace Blarg.GameFramework.Processes
+{
+ public class ProcessManager : IDisposable
+ {
+ const string LOG_TAG = "PROCESSES";
+
+ LinkedList _processes;
+ Queue _queue;
+
+ public readonly GameState GameState;
+
+ public ProcessManager(GameState state)
+ {
+ if (state == null)
+ throw new ArgumentNullException("state");
+
+ GameState = state;
+ _processes = new LinkedList();
+ _queue = new Queue();
+ }
+
+ #region Complex Properties
+
+ public bool IsTransitioning
+ {
+ get
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ if (node.Value.IsTransitioning)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public bool IsEmpty
+ {
+ get
+ {
+ return (_processes.Count == 0 && _queue.Count == 0);
+ }
+ }
+
+ #endregion
+
+ #region Events
+
+ public void OnPause(bool dueToOverlay)
+ {
+ if (_processes.Count == 0)
+ return;
+
+ if (dueToOverlay)
+ {
+ Platform.Logger.Info(LOG_TAG, "Pausing all active processes due to state being overlayed on to the parent state.");
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ {
+ Platform.Logger.Info(LOG_TAG, "Pausing process {0} due to parent state overlay.", processInfo.Descriptor);
+ processInfo.Process.OnPause(true);
+ }
+ }
+ }
+ else
+ {
+ Platform.Logger.Info(LOG_TAG, "Transitioning out all active processes pending pause.");
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ StartTransitionOut(processInfo, false);
+ }
+ }
+ }
+
+ public void OnResume(bool fromOverlay)
+ {
+ if (_processes.Count == 0)
+ return;
+
+ if (fromOverlay)
+ {
+ Platform.Logger.Info(LOG_TAG, "Resuming all active processes due to overlay state being removed from overtop of parent state.");
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ {
+ Platform.Logger.Info(LOG_TAG, "Resuming process {0} due to overlay state removal.", processInfo.Descriptor);
+ processInfo.Process.OnResume(true);
+ }
+ }
+ }
+ else
+ {
+ Platform.Logger.Info(LOG_TAG, "Resuming processes.");
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (processInfo.IsInactive && !processInfo.IsBeingRemoved)
+ {
+ Platform.Logger.Info(LOG_TAG, "Resuming process {0}", processInfo.Descriptor);
+ processInfo.Process.OnResume(false);
+
+ StartTransitionIn(processInfo);
+ }
+ }
+ }
+ }
+
+ public void OnAppGainFocus()
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ processInfo.Process.OnAppGainFocus();
+ }
+ }
+
+ public void OnAppLostFocus()
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ processInfo.Process.OnAppLostFocus();
+ }
+ }
+
+ public void OnAppPause()
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ processInfo.Process.OnAppPause();
+ }
+ }
+
+ public void OnAppResume()
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ processInfo.Process.OnAppResume();
+ }
+ }
+
+ public void OnLostContext()
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ processInfo.Process.OnLostContext();
+ }
+ }
+
+ public void OnNewContext()
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ processInfo.Process.OnNewContext();
+ }
+ }
+
+ public void OnRender(float delta)
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ processInfo.Process.OnRender(delta);
+ }
+ }
+
+ public void OnResize()
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ processInfo.Process.OnResize();
+ }
+ }
+
+ public void OnUpdate(float delta)
+ {
+ CleanupInactiveProcesses();
+ CheckForFinishedProcesses();
+ ProcessQueue();
+ UpdateTransitions(delta);
+
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive)
+ processInfo.Process.OnUpdate(delta);
+ }
+ }
+
+ #endregion
+
+ #region Add / Remove
+
+ public T Add(string name = null) where T : GameProcess
+ {
+ var newProcess = (T)Activator.CreateInstance(typeof(T), this);
+ var processInfo = new ProcessInfo(newProcess, name);
+ Queue(processInfo);
+ return newProcess;
+ }
+
+ public void Remove(string name)
+ {
+ var node = GetNodeFor(name);
+ if (node == null)
+ throw new Exception("No process with given name.");
+ StartTransitionOut(node.Value, true);
+ }
+
+ public void RemoveFirstOf() where T : GameProcess
+ {
+ var node = GetNodeForFirst();
+ if (node == null)
+ throw new Exception("No process of given type.");
+ StartTransitionOut(node.Value, true);
+ }
+
+ public void RemoveAll()
+ {
+ Platform.Logger.Info(LOG_TAG, "Transitioning out all processes pending removal.");
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsTransitioning && !processInfo.IsInactive)
+ StartTransitionOut(processInfo, true);
+ }
+ }
+
+ private void Queue(ProcessInfo newProcessInfo)
+ {
+ if (newProcessInfo == null)
+ throw new ArgumentNullException("newProcessInfo");
+ if (newProcessInfo.Process == null)
+ throw new ArgumentException("No GameProcess provided.");
+
+ Platform.Logger.Info(LOG_TAG, "Queueing process {0}.", newProcessInfo.Descriptor);
+ _queue.Enqueue(newProcessInfo);
+ }
+
+ #endregion
+
+ #region Internal Process Management
+
+ private void StartTransitionIn(ProcessInfo processInfo)
+ {
+ if (processInfo == null)
+ throw new ArgumentNullException("processInfo");
+ if (!processInfo.IsInactive || processInfo.IsTransitioning)
+ throw new InvalidOperationException();
+
+ processInfo.IsInactive = false;
+ processInfo.IsTransitioning = true;
+ processInfo.IsTransitioningOut = false;
+ processInfo.IsTransitionStarting = true;
+ Platform.Logger.Info(LOG_TAG, "Transition into process {0} started.", processInfo.Descriptor);
+ }
+
+ private void StartTransitionOut(ProcessInfo processInfo, bool forRemoval)
+ {
+ if (processInfo == null)
+ throw new ArgumentNullException("processInfo");
+ if (processInfo.IsInactive || processInfo.IsTransitioning)
+ throw new InvalidOperationException();
+
+ processInfo.IsTransitioning = true;
+ processInfo.IsTransitioningOut = true;
+ processInfo.IsTransitionStarting = true;
+ processInfo.IsBeingRemoved = forRemoval;
+ Platform.Logger.Info(LOG_TAG, "Transition out of process {0} started pending {1}.", processInfo.Descriptor, (forRemoval ? "removal" : "pause"));
+ }
+
+ private void CleanupInactiveProcesses()
+ {
+ var node = _processes.First;
+ while (node != null)
+ {
+ var processInfo = node.Value;
+ if (processInfo.IsInactive && processInfo.IsBeingRemoved)
+ {
+ var next = node.Next;
+ _processes.Remove(node);
+ node = next;
+
+ Platform.Logger.Info(LOG_TAG, "Deleting inactive process {0}.", processInfo.Descriptor);
+ processInfo.Process.Dispose();
+ processInfo = null;
+ }
+ else
+ node = node.Next;
+ }
+ }
+
+ private void CheckForFinishedProcesses()
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (!processInfo.IsInactive && processInfo.Process.IsFinished && !processInfo.IsTransitioning)
+ {
+ Platform.Logger.Info(LOG_TAG, "Process {0} marked as finished.", processInfo.Descriptor);
+ StartTransitionOut(processInfo, true);
+ }
+ }
+ }
+
+ private void ProcessQueue()
+ {
+ while (_queue.Count > 0)
+ {
+ var processInfo = _queue.Dequeue();
+
+ Platform.Logger.Info(LOG_TAG, "Adding process {0} from queue.", processInfo.Descriptor);
+ _processes.AddLast(processInfo);
+ processInfo.Process.OnAdd();
+
+ StartTransitionIn(processInfo);
+ }
+ }
+
+ private void UpdateTransitions(float delta)
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ var processInfo = node.Value;
+ if (processInfo.IsTransitioning)
+ {
+ bool isDone = processInfo.Process.OnTransition(delta, processInfo.IsTransitioningOut, processInfo.IsTransitionStarting);
+ if (isDone)
+ {
+ Platform.Logger.Info(LOG_TAG, "Transition {0} into process {1} finished.",
+ (processInfo.IsTransitioningOut ? "out of" : "into"),
+ processInfo.Descriptor);
+
+ // if the process was being transitioned out, then we should mark
+ // it as inactive, and trigger it's OnRemove event now
+ if (processInfo.IsTransitioningOut)
+ {
+ if (processInfo.IsBeingRemoved)
+ {
+ Platform.Logger.Info(LOG_TAG, "Removing process {0}.", processInfo.Descriptor);
+ processInfo.Process.OnRemove();
+ }
+ else
+ {
+ Platform.Logger.Info(LOG_TAG, "Pausing process {0}.", processInfo.Descriptor);
+ processInfo.Process.OnPause(false);
+ }
+ processInfo.IsInactive = true;
+ }
+
+ // done transitioning
+ processInfo.IsTransitioning = false;
+ processInfo.IsTransitioningOut = false;
+ }
+ processInfo.IsTransitionStarting = false;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Misc
+
+ public bool IsProcessTransitioning(GameProcess process)
+ {
+ var processInfo = GetProcessInfoFor(process);
+ if (processInfo == null)
+ return false;
+ else
+ return processInfo.IsTransitioning;
+ }
+
+ public bool HasProcess(string name)
+ {
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ if (!String.IsNullOrEmpty(node.Value.Name) && node.Value.Name == name)
+ return true;
+ }
+ return false;
+ }
+
+ private LinkedListNode GetNodeFor(string processName)
+ {
+ if (String.IsNullOrEmpty(processName))
+ throw new ArgumentNullException("processName");
+
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ if (node.Value.Name == processName)
+ return node;
+ }
+ return null;
+ }
+
+ private LinkedListNode GetNodeForFirst() where T : GameProcess
+ {
+ var type = typeof(T);
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ if (node.Value.Process.GetType() == type)
+ return node;
+ }
+ return null;
+ }
+
+ private ProcessInfo GetProcessInfoFor(GameProcess process)
+ {
+ if (process == null)
+ throw new ArgumentNullException("process");
+
+ for (var node = _processes.First; node != null; node = node.Next)
+ {
+ if (node.Value.Process == process)
+ return node.Value;
+ }
+ return null;
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ if (_processes == null)
+ return;
+
+ Platform.Logger.Info(LOG_TAG, "ProcessManager disposing.");
+
+ while (_processes.Count > 0)
+ {
+ var processInfo = _processes.Last.Value;
+ Platform.Logger.Info(LOG_TAG, "Removing process {0} as part of ProcessManager shutdown.", processInfo.Descriptor);
+ processInfo.Process.OnRemove();
+ processInfo.Process.Dispose();
+ _processes.RemoveLast();
+ }
+
+ // the queues will likely not have anything in it, but just in case ...
+ while (_queue.Count > 0)
+ {
+ var processInfo = _queue.Dequeue();
+ Platform.Logger.Info(LOG_TAG, "Removing queued process {0} as part of ProcessManager shutdown.", processInfo.Descriptor);
+ processInfo.Process.Dispose();
+ }
+
+ _processes = null;
+ _queue = null;
+ }
+
+ #endregion
+ }
+}
diff --git a/Blarg.GameFramework/States/GameState.cs b/Blarg.GameFramework/States/GameState.cs
new file mode 100644
index 0000000..5884002
--- /dev/null
+++ b/Blarg.GameFramework/States/GameState.cs
@@ -0,0 +1,148 @@
+using System;
+using Blarg.GameFramework.Events;
+using Blarg.GameFramework.Graphics.ScreenEffects;
+using Blarg.GameFramework.Processes;
+
+namespace Blarg.GameFramework.States
+{
+ public abstract class GameState : EventListener, IDisposable
+ {
+ public IGameApp GameApp { get; private set; }
+ public readonly ProcessManager ProcessManager;
+ public readonly ScreenEffectManager EffectManager;
+ public readonly StateManager StateManager;
+
+ public bool IsFinished { get; private set; }
+ public int? ReturnValue { get; private set; }
+
+ public bool IsTransitioning
+ {
+ get { return StateManager.IsStateTransitioning(this); }
+ }
+
+ public bool IsTopState
+ {
+ get { return StateManager.IsTopState(this); }
+ }
+
+ public GameState(StateManager stateManager, EventManager eventManager)
+ : base(eventManager)
+ {
+ if (stateManager == null)
+ throw new ArgumentNullException("stateManager");
+
+ GameApp = stateManager.GameApp;
+ StateManager = stateManager;
+
+ EffectManager = new ScreenEffectManager();
+ ProcessManager = new ProcessManager(this);
+ }
+
+ protected void SetFinished()
+ {
+ IsFinished = true;
+ ReturnValue = null;
+ }
+
+ protected void SetFinished(int returnValue)
+ {
+ IsFinished = true;
+ ReturnValue = returnValue;
+ }
+
+ #region Events
+
+ public virtual void OnPush()
+ {
+ }
+
+ public virtual void OnPop()
+ {
+ }
+
+ public virtual void OnPause(bool dueToOverlay)
+ {
+ ProcessManager.OnPause(dueToOverlay);
+ }
+
+ public virtual void OnResume(bool fromOverlay)
+ {
+ ProcessManager.OnResume(fromOverlay);
+ }
+
+ public virtual void OnAppGainFocus()
+ {
+ ProcessManager.OnAppGainFocus();
+ EffectManager.OnAppGainFocus();
+ }
+
+ public virtual void OnAppLostFocus()
+ {
+ ProcessManager.OnAppLostFocus();
+ EffectManager.OnAppLostFocus();
+ }
+
+ public virtual void OnAppPause()
+ {
+ ProcessManager.OnAppPause();
+ EffectManager.OnAppPause();
+ }
+
+ public virtual void OnAppResume()
+ {
+ ProcessManager.OnAppResume();
+ EffectManager.OnAppResume();
+ }
+
+ public virtual void OnLostContext()
+ {
+ ProcessManager.OnLostContext();
+ EffectManager.OnLostContext();
+ }
+
+ public virtual void OnNewContext()
+ {
+ ProcessManager.OnNewContext();
+ EffectManager.OnNewContext();
+ }
+
+ public virtual void OnRender(float delta)
+ {
+ // switch it up and do effects before processes here so that processes
+ // (which would commonly be used for UI overlay elements) don't get
+ // overwritten by local effects (e.g. flashes, etc.)
+ EffectManager.OnRenderLocal(delta);
+ ProcessManager.OnRender(delta);
+ }
+
+ public virtual void OnResize()
+ {
+ ProcessManager.OnResize();
+ EffectManager.OnResize();
+ }
+
+ public virtual void OnUpdate(float delta)
+ {
+ ProcessManager.OnUpdate(delta);
+ EffectManager.OnUpdate(delta);
+ }
+
+ public virtual bool OnTransition(float delta, bool isTransitioningOut, bool started)
+ {
+ return true;
+ }
+
+ public override bool Handle(Event e)
+ {
+ return false;
+ }
+
+ #endregion
+
+ public virtual void Dispose()
+ {
+ EffectManager.Dispose();
+ ProcessManager.Dispose();
+ }
+ }
+}
diff --git a/Blarg.GameFramework/States/StateInfo.cs b/Blarg.GameFramework/States/StateInfo.cs
new file mode 100644
index 0000000..3d78c57
--- /dev/null
+++ b/Blarg.GameFramework/States/StateInfo.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Blarg.GameFramework.States
+{
+ internal class StateInfo
+ {
+ public readonly GameState State;
+ public readonly string Name;
+ public readonly string Descriptor;
+
+ public bool IsOverlay;
+ public bool IsOverlayed;
+ public bool IsTransitioning;
+ public bool IsTransitioningOut;
+ public bool IsTransitionStarting;
+ public bool IsInactive;
+ public bool IsBeingPopped;
+
+ public StateInfo(GameState state, string name = null)
+ {
+ if (state == null)
+ throw new ArgumentNullException("state");
+
+ State = state;
+ Name = name;
+ IsInactive = true;
+
+ if (String.IsNullOrEmpty(Name))
+ Descriptor = state.GetType().Name;
+ else
+ Descriptor = String.Format("{0}[{1}]", state.GetType().Name, Name);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/States/StateManager.cs b/Blarg.GameFramework/States/StateManager.cs
new file mode 100644
index 0000000..5d6ac28
--- /dev/null
+++ b/Blarg.GameFramework/States/StateManager.cs
@@ -0,0 +1,712 @@
+using System;
+using System.Collections.Generic;
+using Blarg.GameFramework.Events;
+
+namespace Blarg.GameFramework.States
+{
+ public class StateManager : IDisposable
+ {
+ const string LOG_TAG = "STATES";
+
+ LinkedList _states;
+ Queue _pushQueue;
+ Queue _swapQueue;
+
+ bool _pushQueueHasOverlay;
+ bool _swapQueueHasOverlay;
+ bool _lastCleanedStatesWereAllOverlays;
+
+ public readonly IGameApp GameApp;
+ public readonly EventManager EventManager;
+
+ public int? LastReturnValue { get; private set; }
+
+ public StateManager(IGameApp gameApp, EventManager eventManager)
+ {
+ if (gameApp == null)
+ throw new ArgumentNullException("gameApp");
+ if (eventManager == null)
+ throw new ArgumentNullException("eventManager");
+
+ GameApp = gameApp;
+ EventManager = eventManager;
+
+ _states = new LinkedList();
+ _pushQueue = new Queue();
+ _swapQueue = new Queue();
+ }
+
+ #region Complex Properties
+
+ public GameState TopState
+ {
+ get
+ {
+ var topInfo = Top;
+ if (topInfo == null)
+ return null;
+ else
+ return topInfo.State;
+ }
+ }
+
+ public GameState TopNonOverlayState
+ {
+ get
+ {
+ var topInfo = TopNonOverlay;
+ if (topInfo == null)
+ return null;
+ else
+ return topInfo.State;
+ }
+ }
+
+ public bool IsTransitioning
+ {
+ get
+ {
+ for (var node = _states.First; node != null; node = node.Next)
+ {
+ if (node.Value.IsTransitioning)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public bool IsEmpty
+ {
+ get
+ {
+ return (_states.Count == 0 && _pushQueue.Count == 0 && _swapQueue.Count == 0);
+ }
+ }
+
+ private StateInfo Top
+ {
+ get
+ {
+ if (_states.Count == 0)
+ return null;
+ else
+ return _states.Last.Value;
+ }
+ }
+
+ private StateInfo TopNonOverlay
+ {
+ get
+ {
+ var node = TopNonOverlayNode;
+ if (node != null)
+ return node.Value;
+ else
+ return null;
+ }
+ }
+
+ private LinkedListNode TopNonOverlayNode
+ {
+ get
+ {
+ var node = _states.Last;
+ while (node != null && node.Value.IsOverlay)
+ node = node.Previous;
+ return node;
+ }
+ }
+
+ #endregion
+
+ #region Events
+
+ public void OnAppGainFocus()
+ {
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ if (!node.Value.IsInactive)
+ node.Value.State.OnAppGainFocus();
+ }
+ }
+
+ public void OnAppLostFocus()
+ {
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ if (!node.Value.IsInactive)
+ node.Value.State.OnAppLostFocus();
+ }
+ }
+
+ public void OnAppPause()
+ {
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ if (!node.Value.IsInactive)
+ node.Value.State.OnAppPause();
+ }
+ }
+
+ public void OnAppResume()
+ {
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ if (!node.Value.IsInactive)
+ node.Value.State.OnAppResume();
+ }
+ }
+
+ public void OnLostContext()
+ {
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ if (!node.Value.IsInactive)
+ node.Value.State.OnLostContext();
+ }
+ }
+
+ public void OnNewContext()
+ {
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ if (!node.Value.IsInactive)
+ node.Value.State.OnNewContext();
+ }
+ }
+
+ public void OnRender(float delta)
+ {
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ if (!node.Value.IsInactive)
+ {
+ node.Value.State.OnRender(delta);
+ node.Value.State.EffectManager.OnRenderGlobal(delta);
+ }
+ }
+ }
+
+ public void OnResize()
+ {
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ if (!node.Value.IsInactive)
+ node.Value.State.OnResize();
+ }
+ }
+
+ public void OnUpdate(float delta)
+ {
+ // clear return values (ensuring they're only accessible for 1 tick)
+ LastReturnValue = null;
+ _lastCleanedStatesWereAllOverlays = false;
+
+ CleanupInactiveStates();
+ CheckForFinishedStates();
+ ProcessQueues();
+ ResumeStatesIfNeeded();
+ UpdateTransitions(delta);
+
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ if (!node.Value.IsInactive)
+ node.Value.State.OnUpdate(delta);
+ }
+ }
+
+ #endregion
+
+ #region Push / Pop / Overlay / Swap / Queue
+
+ public T Push(string name = null) where T : GameState
+ {
+ var newState = (T)Activator.CreateInstance(typeof(T), this);
+ var stateInfo = new StateInfo(newState, name);
+ QueueForPush(stateInfo);
+ return newState;
+ }
+
+ public T Overlay(string name = null) where T : GameState
+ {
+ var newState = (T)Activator.CreateInstance(typeof(T), this);
+ var stateInfo = new StateInfo(newState, name);
+ stateInfo.IsOverlay = true;
+ QueueForPush(stateInfo);
+ return newState;
+ }
+
+ public T SwapTopWith(string name = null) where T : GameState
+ {
+ // figure out if the current top state is an overlay or not. use that
+ // same setting for the new state that is to be swapped in
+ var currentTopStateInfo = Top;
+ if (currentTopStateInfo == null)
+ throw new InvalidOperationException("Cannot swap, no existing states.");
+ bool isOverlay = currentTopStateInfo.IsOverlay;
+
+ var newState = (T)Activator.CreateInstance(typeof(T), this);
+ var stateInfo = new StateInfo(newState, name);
+ stateInfo.IsOverlay = isOverlay;
+ QueueForSwap(stateInfo, false);
+ return newState;
+ }
+
+ public T SwapTopNonOverlayWith(string name = null) where T : GameState
+ {
+ var newState = (T)Activator.CreateInstance(typeof(T), this);
+ var stateInfo = new StateInfo(newState, name);
+ QueueForSwap(stateInfo, true);
+ return newState;
+ }
+
+ public void Pop()
+ {
+ if (IsTransitioning)
+ throw new InvalidOperationException();
+
+ Platform.Logger.Info(LOG_TAG, "Pop initiated for top-most state only.");
+ StartOnlyTopStateTransitioningOut(false);
+ }
+
+ public void PopTopNonOverlay()
+ {
+ if (IsTransitioning)
+ throw new InvalidOperationException();
+
+ Platform.Logger.Info(LOG_TAG, "Pop initiated for all top active states");
+ StartTopStatesTransitioningOut(false);
+ }
+
+ private void QueueForPush(StateInfo newStateInfo)
+ {
+ if (newStateInfo == null)
+ throw new ArgumentNullException("newStateInfo");
+ if (newStateInfo.State == null)
+ throw new ArgumentException("No GameState provided.");
+ if (_pushQueueHasOverlay && !newStateInfo.IsOverlay)
+ throw new InvalidOperationException("Cannot queue new non-overlay state while queue is active with overlay states.");
+
+ Platform.Logger.Info(LOG_TAG, "Queueing state {0} for pushing.", newStateInfo.Descriptor);
+
+ if (!newStateInfo.IsOverlay)
+ StartTopStatesTransitioningOut(true);
+
+ _pushQueue.Enqueue(newStateInfo);
+
+ if (newStateInfo.IsOverlay)
+ _pushQueueHasOverlay = true;
+ }
+
+ private void QueueForSwap(StateInfo newStateInfo, bool swapTopNonOverlay)
+ {
+ if (newStateInfo == null)
+ throw new ArgumentNullException("newStateInfo");
+ if (newStateInfo.State == null)
+ throw new ArgumentException("No GameState provided.");
+ if (_swapQueueHasOverlay && !newStateInfo.IsOverlay)
+ throw new InvalidOperationException("Cannot queue new non-overlay state while queue is active with overlay states.");
+
+ Platform.Logger.Info(LOG_TAG, "Queueing state {0} for swapping with {1}.", newStateInfo.Descriptor, (swapTopNonOverlay ? "all top active states" : "only top-most active state."));
+
+ if (swapTopNonOverlay)
+ StartTopStatesTransitioningOut(false);
+ else
+ StartOnlyTopStateTransitioningOut(false);
+
+ _swapQueue.Enqueue(newStateInfo);
+
+ if (newStateInfo.IsOverlay)
+ _swapQueueHasOverlay = true;
+ }
+
+ #endregion
+
+ #region Internal State Management
+
+ private void StartTopStatesTransitioningOut(bool pausing)
+ {
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ // only look at active states, since inactive ones have already
+ // been transitioned out and will be removed on the next OnUpdate()
+ if (!node.Value.IsInactive)
+ TransitionOut(node.Value, !pausing);
+ }
+ }
+
+ private void StartOnlyTopStateTransitioningOut(bool pausing)
+ {
+ var stateInfo = Top;
+ // if it's not active, then it's just been transitioned out and will be
+ // removed on the next OnUpdate()
+ if (!stateInfo.IsInactive)
+ TransitionOut(stateInfo, !pausing);
+ }
+
+ private void CleanupInactiveStates()
+ {
+ // we don't want to remove any states until everything is done transitioning.
+ // this is to avoid the scenario where the top non-overlay state finishes
+ // transitioning before one of the overlays. if we removed it, the overlays
+ // would then be overlayed over an inactive non-overlay (which wouldn't get
+ // resumed until the current active overlays were done being transitioned)
+ if (IsTransitioning)
+ return;
+
+ bool cleanedUpSomething = false;
+ bool cleanedUpNonOverlay = false;
+
+ var node = _states.First;
+ while (node != null)
+ {
+ var stateInfo = node.Value;
+ if (stateInfo.IsInactive && stateInfo.IsBeingPopped)
+ {
+ cleanedUpSomething = true;
+ if (!stateInfo.IsOverlay)
+ cleanedUpNonOverlay = true;
+
+ // remove this state and move to the next node
+ var next = node.Next;
+ _states.Remove(node);
+ node = next;
+
+ Platform.Logger.Info(LOG_TAG, "Deleting inactive popped state {0}.", stateInfo.Descriptor);
+ stateInfo.State.Dispose();
+ stateInfo = null;
+ }
+ else
+ node = node.Next;
+ }
+
+ if (cleanedUpSomething && !cleanedUpNonOverlay)
+ _lastCleanedStatesWereAllOverlays = true;
+ }
+
+ private void CheckForFinishedStates()
+ {
+ if (_states.Count == 0)
+ return;
+
+ // don't do anything if something is currently transitioning
+ if (IsTransitioning)
+ return;
+
+ bool needToAlsoTransitionOutOverlays = false;
+
+ // check the top non-overlay state first to see if it's finished
+ // and should be transitioned out
+ var topNonOverlayStateInfo = TopNonOverlay;
+ if (!topNonOverlayStateInfo.IsInactive && topNonOverlayStateInfo.State.IsFinished)
+ {
+ Platform.Logger.Info(LOG_TAG, "State {0} marked as finished.", topNonOverlayStateInfo.Descriptor);
+ TransitionOut(topNonOverlayStateInfo, true);
+
+ needToAlsoTransitionOutOverlays = true;
+ }
+
+ // now also check the overlay states (if there were any). we force them to
+ // transition out if the non-overlay state started to transition out so that
+ // we don't end up with overlay states without a parent non-overlay state
+
+ // start the loop off 1 beyond the top non-overlay (which is where the
+ // overlays are, if any)
+ var node = TopNonOverlayNode;
+ if (node != null)
+ {
+ for (node = node.Next; node != null; node = node.Next)
+ {
+ var stateInfo = node.Value;
+ if (!stateInfo.IsInactive && (stateInfo.State.IsFinished || needToAlsoTransitionOutOverlays))
+ {
+ Platform.Logger.Info(LOG_TAG, "State {0} marked as finished.", stateInfo.Descriptor);
+ TransitionOut(stateInfo, true);
+ }
+ }
+ }
+ }
+
+ private void ProcessQueues()
+ {
+ // don't do anything if stuff is currently transitioning
+ if (IsTransitioning)
+ return;
+
+ if (_pushQueue.Count > 0 && _swapQueue.Count > 0)
+ throw new InvalidOperationException("Cannot process queues when both the swap and push queues have items in them.");
+
+ // for each state in the queue, add it to the main list and start
+ // transitioning it in
+ // (note, only one of these queues will be processed each tick due to the above check!)
+
+ while (_pushQueue.Count > 0)
+ {
+ var stateInfo = _pushQueue.Dequeue();
+
+ if (_states.Count > 0)
+ {
+ // if this new state is an overlay, and the current top state is both
+ // currently active and is not currently marked as being overlay-ed
+ // then we should pause it due to overlay
+ var currentTopStateInfo = Top;
+ if (stateInfo.IsOverlay && !currentTopStateInfo.IsInactive && !currentTopStateInfo.IsOverlayed)
+ {
+ Platform.Logger.Info(LOG_TAG, "Pausing {0}state {1} due to overlay.", (currentTopStateInfo.IsOverlay ? "overlay " : ""), currentTopStateInfo.Descriptor);
+ currentTopStateInfo.State.OnPause(true);
+
+ // also mark the current top state as being overlay-ed
+ currentTopStateInfo.IsOverlayed = true;
+ }
+ }
+
+ Platform.Logger.Info(LOG_TAG, "Pushing {0}state {1} from push-queue.", (stateInfo.IsOverlay ? "overlay " : ""), stateInfo.Descriptor);
+ stateInfo.State.OnPush();
+
+ TransitionIn(stateInfo, false);
+
+ _states.AddLast(stateInfo);
+ }
+
+ while (_swapQueue.Count > 0)
+ {
+ var stateInfo = _swapQueue.Dequeue();
+
+ // if this new state is an overlay, and the current top state is both
+ // currently active and is not currently marked as being overlay-ed
+ // then we should pause it due to overlay
+ var currentTopStateInfo = Top;
+ if (stateInfo.IsOverlay && !currentTopStateInfo.IsInactive && !currentTopStateInfo.IsOverlayed)
+ {
+ Platform.Logger.Info(LOG_TAG, "Pausing {0}state {1} due to overlay.", (currentTopStateInfo.IsOverlay ? "overlay " : ""), currentTopStateInfo.Descriptor);
+ currentTopStateInfo.State.OnPause(true);
+
+ // also mark the current top state as being overlay-ed
+ currentTopStateInfo.IsOverlayed = true;
+ }
+
+ Platform.Logger.Info(LOG_TAG, "Pushing {0}state {1} from swap-queue.", (stateInfo.IsOverlay ? "overlay " : ""), stateInfo.Descriptor);
+ stateInfo.State.OnPush();
+
+ TransitionIn(stateInfo, false);
+
+ _states.AddLast(stateInfo);
+ }
+
+ _pushQueueHasOverlay = false;
+ _swapQueueHasOverlay = false;
+ }
+
+ private void ResumeStatesIfNeeded()
+ {
+ if (_states.Count == 0)
+ return;
+
+ // don't do anything if stuff is currently transitioning
+ if (IsTransitioning)
+ return;
+
+ // did we just clean up one or more overlay states?
+ if (_lastCleanedStatesWereAllOverlays)
+ {
+ // then we need to resume the current top state
+ // (those paused with the flag "from an overlay")
+ var stateInfo = Top;
+ if (stateInfo.IsInactive || !stateInfo.IsOverlayed)
+ throw new InvalidOperationException();
+
+ Platform.Logger.Info(LOG_TAG, "Resuming {0}state {1} due to overlay removal.", (stateInfo.IsOverlay ? "overlay " : ""), stateInfo.Descriptor);
+ stateInfo.State.OnResume(true);
+
+ stateInfo.IsOverlayed = false;
+
+ return;
+ }
+
+ // if the top state is not inactive, then we don't need to resume anything
+ if (!Top.IsInactive)
+ return;
+
+ Platform.Logger.Info(LOG_TAG, "Top-most state is inactive. Resuming all top states up to and including the next non-overlay.");
+
+ // top state is inactive, time to resume one or more states...
+ // find the topmost non-overlay state and take it and all overlay
+ // states that are above it and transition them in
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ var stateInfo = node.Value;
+ Platform.Logger.Info(LOG_TAG, "Resuming {0}state {1}.", (stateInfo.IsOverlay ? "overlay " : ""), stateInfo.Descriptor);
+ stateInfo.State.OnResume(false);
+
+ TransitionIn(stateInfo, true);
+ }
+
+ }
+
+ private void UpdateTransitions(float delta)
+ {
+ for (var node = TopNonOverlayNode; node != null; node = node.Next)
+ {
+ var stateInfo = node.Value;
+ if (stateInfo.IsTransitioning)
+ {
+ bool isDone = stateInfo.State.OnTransition(delta, stateInfo.IsTransitioningOut, stateInfo.IsTransitionStarting);
+ if (isDone)
+ {
+ Platform.Logger.Info(LOG_TAG, "Transition {0} {1}state {2} finished.",
+ (stateInfo.IsTransitioningOut ? "out of" : "into"),
+ (stateInfo.IsOverlay ? "overlay " : ""),
+ stateInfo.Descriptor);
+
+ // if the state was being transitioned out, then we should mark
+ // it as inactive, and trigger it's OnPop or OnPause event now
+ if (stateInfo.IsTransitioningOut)
+ {
+ if (stateInfo.IsBeingPopped)
+ {
+ Platform.Logger.Info(LOG_TAG, "Popping {0}state {1}", (stateInfo.IsOverlay ? "overlay " : ""), stateInfo.Descriptor);
+ stateInfo.State.OnPop();
+
+ if (stateInfo.State.ReturnValue != null)
+ {
+ LastReturnValue = stateInfo.State.ReturnValue;
+ Platform.Logger.Info(LOG_TAG, "Return value of {0} retrieved from {1}.", LastReturnValue.Value, stateInfo.Descriptor);
+ }
+ }
+ else
+ {
+ Platform.Logger.Info(LOG_TAG, "Pausing {0}state {1}.", (stateInfo.IsOverlay ? "overlay " : ""), stateInfo.Descriptor);
+ stateInfo.State.OnPause(false);
+ }
+ stateInfo.IsInactive = true;
+ }
+
+ // done transitioning
+ stateInfo.IsTransitioning = false;
+ stateInfo.IsTransitioningOut = false;
+ }
+
+ stateInfo.IsTransitionStarting = false;
+ }
+ }
+ }
+
+ private void TransitionIn(StateInfo stateInfo, bool forResuming)
+ {
+ stateInfo.IsInactive = false;
+ stateInfo.IsTransitioning = true;
+ stateInfo.IsTransitioningOut = false;
+ stateInfo.IsTransitionStarting = true;
+ Platform.Logger.Info(LOG_TAG, "Transition into {0}state {1} started.", (stateInfo.IsOverlay ? "overlay " : ""), stateInfo.Descriptor);
+
+ if (forResuming)
+ stateInfo.State.ProcessManager.OnResume(false);
+ }
+
+ private void TransitionOut(StateInfo stateInfo, bool forPopping)
+ {
+ stateInfo.IsTransitioning = true;
+ stateInfo.IsTransitioningOut = true;
+ stateInfo.IsTransitionStarting = true;
+ stateInfo.IsBeingPopped = forPopping;
+ Platform.Logger.Info(LOG_TAG, "Transition out of {0}state {1} started.", (stateInfo.IsOverlay ? "overlay " : ""), stateInfo.Descriptor);
+
+ if (forPopping)
+ stateInfo.State.ProcessManager.RemoveAll();
+ else
+ stateInfo.State.ProcessManager.OnPause(false);
+ }
+
+ #endregion
+
+ #region Misc
+
+ private StateInfo GetStateInfoFor(GameState state)
+ {
+ if (state == null)
+ throw new ArgumentNullException("state");
+
+ for (var node = _states.First; node != null; node = node.Next)
+ {
+ if (node.Value.State == state)
+ return node.Value;
+ }
+ return null;
+ }
+
+ public bool IsStateTransitioning(GameState state)
+ {
+ if (state == null)
+ throw new ArgumentNullException("state");
+
+ var info = GetStateInfoFor(state);
+ if (info == null)
+ return false;
+ else
+ return info.IsTransitioning;
+ }
+
+ public bool IsTopState(GameState state)
+ {
+ if (state == null)
+ throw new ArgumentNullException("state");
+
+ var top = Top;
+ if (top == null)
+ return false;
+ else
+ return (top.State == state);
+ }
+
+ public bool HasState(string name)
+ {
+ for (var node = _states.First; node != null; node = node.Next)
+ {
+ if (!String.IsNullOrEmpty(node.Value.Name) && node.Value.Name == name)
+ return true;
+ }
+ return false;
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ if (_states == null)
+ return;
+
+ Platform.Logger.Info(LOG_TAG, "StateManager disposing.");
+
+ while (_states.Count > 0)
+ {
+ var stateInfo = _states.Last.Value;
+ Platform.Logger.Info(LOG_TAG, "Popping state {0} as part of StateManager shutdown.", stateInfo.Descriptor);
+ stateInfo.State.OnPop();
+ stateInfo.State.Dispose();
+ _states.RemoveLast();
+ }
+
+ // these queues will likely not have anything in them, but just in case ...
+ while (_pushQueue.Count > 0)
+ {
+ var stateInfo = _pushQueue.Dequeue();
+ Platform.Logger.Info(LOG_TAG, "Deleting push-queued state {0} as part of StateManager shutdown.", stateInfo.Descriptor);
+ stateInfo.State.Dispose();
+ }
+ while (_swapQueue.Count > 0)
+ {
+ var stateInfo = _swapQueue.Dequeue();
+ Platform.Logger.Info(LOG_TAG, "Deleting swap-queued state {0} as part of StateManager shutdown.", stateInfo.Descriptor);
+ stateInfo.State.Dispose();
+ }
+
+ _states = null;
+ _pushQueue = null;
+ _swapQueue = null;
+ }
+
+ #endregion
+ }
+}