add port of game state and process management code

This commit is contained in:
Gered 2013-08-22 22:55:10 -04:00
parent 018e19b25f
commit eddd69f091
7 changed files with 1519 additions and 0 deletions

View file

@ -160,6 +160,12 @@
<Compile Include="IService.cs" /> <Compile Include="IService.cs" />
<Compile Include="ServiceContainer.cs" /> <Compile Include="ServiceContainer.cs" />
<Compile Include="Support\DictionaryExtensions.cs" /> <Compile Include="Support\DictionaryExtensions.cs" />
<Compile Include="States\GameState.cs" />
<Compile Include="States\StateManager.cs" />
<Compile Include="States\StateInfo.cs" />
<Compile Include="Processes\GameProcess.cs" />
<Compile Include="Processes\ProcessManager.cs" />
<Compile Include="Processes\ProcessInfo.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<ItemGroup> <ItemGroup>
@ -178,6 +184,8 @@
<Folder Include="Entities\" /> <Folder Include="Entities\" />
<Folder Include="Entities\SystemComponents\" /> <Folder Include="Entities\SystemComponents\" />
<Folder Include="Graphics\ScreenEffects\" /> <Folder Include="Graphics\ScreenEffects\" />
<Folder Include="States\" />
<Folder Include="Processes\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Resources\Fonts\Vera.ttf" /> <EmbeddedResource Include="Resources\Fonts\Vera.ttf" />

View file

@ -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()
{
}
}
}

View file

@ -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);
}
}
}

View file

@ -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<ProcessInfo> _processes;
Queue<ProcessInfo> _queue;
public readonly GameState GameState;
public ProcessManager(GameState state)
{
if (state == null)
throw new ArgumentNullException("state");
GameState = state;
_processes = new LinkedList<ProcessInfo>();
_queue = new Queue<ProcessInfo>();
}
#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<T>(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<T>() where T : GameProcess
{
var node = GetNodeForFirst<T>();
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<ProcessInfo> 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<ProcessInfo> GetNodeForFirst<T>() 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
}
}

View file

@ -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();
}
}
}

View file

@ -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);
}
}
}

View file

@ -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<StateInfo> _states;
Queue<StateInfo> _pushQueue;
Queue<StateInfo> _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<StateInfo>();
_pushQueue = new Queue<StateInfo>();
_swapQueue = new Queue<StateInfo>();
}
#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<StateInfo> 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<T>(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<T>(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<T>(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<T>(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
}
}