Created
March 6, 2011 22:58
-
-
Save robfe/857831 to your computer and use it in GitHub Desktop.
A lock-free, thread-safe RX IScheduler that runs as part of an XNA game loop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class NodeList<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> where TKey : IComparable<TKey> | |
{ | |
readonly Node start = new Node(default(TKey), default(TValue)); | |
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() | |
{ | |
for (Node n = start.GetNext(); n != null; n = n.GetNext()) | |
{ | |
yield return new KeyValuePair<TKey, TValue>(n.key, n.value); | |
} | |
} | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
public Node Add(TKey key, TValue value) | |
{ | |
Node parent; | |
Node node; | |
var newNode = new Node(key, value); | |
do | |
{ | |
node = FindNode(key, out parent); | |
} while (!parent.InsertChild(newNode, node)); | |
return newNode; | |
} | |
Node FindNode(TKey key, out Node parent) | |
{ | |
Node n = start; | |
Node node = n.GetNext(); | |
while ((node != null) && node.key.CompareTo(key) <= 0) | |
{ | |
n = node; | |
node = n.GetNext(); | |
} | |
parent = n; | |
return node; | |
} | |
public Node FirstNode() | |
{ | |
return start.GetNext(); | |
} | |
public class Node : IDisposable | |
{ | |
internal readonly TKey key; | |
internal readonly TValue value; | |
NodeState state; | |
public Node(TKey key, TValue value) | |
{ | |
this.key = key; | |
this.value = value; | |
state = new NodeState(false, null); | |
} | |
public TKey Key | |
{ | |
get { return key; } | |
} | |
public TValue Value | |
{ | |
get { return value; } | |
} | |
public override string ToString() | |
{ | |
return key + ", " + value; | |
} | |
public void Dispose() | |
{ | |
FlagAsDeleted(); | |
} | |
internal bool InsertChild(Node newNode, Node successor) | |
{ | |
NodeState oldState = state; | |
if ((!oldState.isDeleted) && (oldState.next == successor)) | |
{ | |
var newState = new NodeState(false, newNode); | |
newNode.state = new NodeState(false, oldState.next); | |
return CasState(oldState, newState); | |
} | |
return false; | |
} | |
public Node GetNext() | |
{ | |
Node node = state.next; | |
while ((node != null) && (node.state.isDeleted)) | |
{ | |
TryDeleteNext(node); | |
node = state.next; | |
} | |
return node; | |
} | |
void TryDeleteNext(Node next) | |
{ | |
NodeState oldState = state; | |
if (oldState.next == next) | |
{ | |
var newState = new NodeState(oldState.isDeleted, next.state.next); | |
CasState(oldState, newState); | |
} | |
} | |
public void FlagAsDeleted() | |
{ | |
NodeState newState; | |
NodeState oldState; | |
do | |
{ | |
oldState = state; | |
newState = new NodeState(true, oldState.next); | |
} while (!CasState(oldState, newState)); | |
} | |
bool CasState(NodeState oldState, NodeState newState) | |
{ | |
return oldState == Interlocked.CompareExchange(ref state, newState, oldState); | |
} | |
} | |
class NodeState | |
{ | |
internal readonly bool isDeleted; | |
internal readonly Node next; | |
public NodeState(bool isDeleted, Node next) | |
{ | |
this.isDeleted = isDeleted; | |
this.next = next; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class XnaScheduler : GameComponent, IScheduler | |
{ | |
static readonly DateTimeOffset beginningOfTime = DateTimeOffset.MinValue; | |
TimeSpan totalGameTime; | |
NodeList<DateTimeOffset, Action> tasks = new NodeList<DateTimeOffset, Action>(); | |
public XnaScheduler(Game game, bool autoAdd) | |
: base(game) | |
{ | |
if (autoAdd) | |
{ | |
game.Components.Add(this); | |
} | |
} | |
public IDisposable Schedule(Action action) | |
{ | |
return tasks.Add(Now.AddTicks(1), action); | |
} | |
public IDisposable Schedule(Action action, TimeSpan dueTime) | |
{ | |
return tasks.Add(Now + dueTime, action); | |
} | |
public DateTimeOffset Now | |
{ | |
get { return beginningOfTime + totalGameTime; } | |
} | |
public override void Update(GameTime gameTime) | |
{ | |
totalGameTime = gameTime.TotalGameTime; | |
DateTimeOffset now = Now; | |
var node = tasks.FirstNode(); | |
while (node != null && node.Key <= now) | |
{ | |
node.FlagAsDeleted(); | |
node.Value(); | |
node = tasks.FirstNode(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment