Skip to content

Instantly share code, notes, and snippets.

@robfe
Created March 6, 2011 22:58
Show Gist options
  • Save robfe/857831 to your computer and use it in GitHub Desktop.
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
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;
}
}
}
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