// -----------------------------------------------------------------------
//
// Copyright 2013 MIT Licence. See licence.md for more information.
//
// -----------------------------------------------------------------------
namespace Perspex.Windows.Threading
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Security;
using System.Threading;
using Perspex.Threading;
using Perspex.Windows.Interop;
[Flags]
internal enum Flags
{
ShutdownStarted = 1,
Shutdown = 2,
Disabled = 4
}
public sealed class WindowsDispatcher : Dispatcher
{
private const int TopPriority = (int)DispatcherPriority.Send;
private static Dictionary dispatchers = new Dictionary();
private static object olock = new object();
private Thread baseThread;
private PokableQueue[] priorityQueues = new PokableQueue[TopPriority + 1];
private Flags flags;
private int queueBits;
private WindowsDispatcher(Thread t)
{
this.baseThread = t;
for (int i = 1; i <= (int)DispatcherPriority.Send; i++)
{
this.priorityQueues[i] = new PokableQueue();
}
}
public event EventHandler ShutdownStarted;
public event EventHandler ShutdownFinished;
public override DispatcherFrame CurrentFrame
{
get;
set;
}
public Thread Thread
{
get { return this.baseThread; }
}
public bool HasShutdownStarted
{
get { return (this.flags & Flags.ShutdownStarted) != 0; }
}
public override bool HasShutdownFinished
{
get { return (this.flags & Flags.Shutdown) != 0; }
}
[SecurityCritical]
public static void ExitAllFrames()
{
Dispatcher dis = CurrentDispatcher;
for (DispatcherFrame frame = dis.CurrentFrame; frame != null; frame = frame.ParentFrame)
{
if (frame.ExitOnRequest)
{
frame.Continue = false;
}
else
{
break;
}
}
}
public static WindowsDispatcher FromThread(Thread thread)
{
WindowsDispatcher dis;
if (dispatchers.TryGetValue(thread, out dis))
{
return dis;
}
return null;
}
public static WindowsDispatcher GetThreadDispatcher()
{
lock (olock)
{
Thread t = Thread.CurrentThread;
WindowsDispatcher dis = FromThread(t);
if (dis != null)
{
return dis;
}
dis = new WindowsDispatcher(t);
dispatchers[t] = dis;
return dis;
}
}
public override DispatcherOperation BeginInvoke(Action method)
{
return this.BeginInvoke(DispatcherPriority.Normal, method);
}
public override DispatcherOperation BeginInvoke(DispatcherPriority priority, Action method)
{
if (priority < DispatcherPriority.Inactive || priority > DispatcherPriority.Send)
{
throw new InvalidEnumArgumentException("priority");
}
if (priority == DispatcherPriority.Inactive)
{
throw new ArgumentException("priority can not be inactive", "priority");
}
if (method == null)
{
throw new ArgumentNullException("method");
}
DispatcherOperation op = new DispatcherOperation(this, priority, method);
this.Queue(priority, op);
return op;
}
[SecurityCritical]
public void InvokeShutdown()
{
this.flags |= Flags.ShutdownStarted;
UnmanagedMethods.PostMessage(
IntPtr.Zero,
(int)UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM,
IntPtr.Zero,
IntPtr.Zero);
}
protected override void Reprioritize(DispatcherOperation op, DispatcherPriority oldpriority)
{
int oldp = (int)oldpriority;
PokableQueue q = this.priorityQueues[oldp];
lock (q)
{
q.Remove(op);
}
this.Queue(op.Priority, op);
}
private void Queue(DispatcherPriority priority, DispatcherOperation x)
{
int p = (int)priority;
PokableQueue q = this.priorityQueues[p];
lock (q)
{
int flag = 1 << p;
q.Enqueue(x);
this.queueBits |= flag;
}
if (Thread.CurrentThread != this.baseThread)
{
UnmanagedMethods.PostMessage(
IntPtr.Zero,
(int)UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM,
IntPtr.Zero,
IntPtr.Zero);
}
}
private void PerformShutdown()
{
EventHandler h;
h = this.ShutdownStarted;
if (h != null)
{
h(this, new EventArgs());
}
this.flags |= Flags.Shutdown;
h = this.ShutdownFinished;
if (h != null)
{
h(this, new EventArgs());
}
this.priorityQueues = null;
}
protected override void RunFrame(DispatcherFrame frame)
{
do
{
while (this.queueBits != 0)
{
for (int i = TopPriority; i > 0 && this.queueBits != 0; i--)
{
int currentBit = this.queueBits & (1 << i);
if (currentBit != 0)
{
PokableQueue q = this.priorityQueues[i];
do
{
DispatcherOperation task;
lock (q)
{
task = (DispatcherOperation)q.Dequeue();
}
task.Invoke();
if (!frame.Continue)
{
return;
}
if (this.HasShutdownStarted)
{
this.PerformShutdown();
return;
}
lock (q)
{
if (q.Count == 0)
{
this.queueBits &= ~(1 << i);
break;
}
}
if (currentBit < (this.queueBits & ~currentBit))
{
break;
}
}
while (true);
}
}
}
UnmanagedMethods.MSG msg;
UnmanagedMethods.GetMessage(out msg, IntPtr.Zero, 0, 0);
UnmanagedMethods.TranslateMessage(ref msg);
UnmanagedMethods.DispatchMessage(ref msg);
if (this.HasShutdownStarted)
{
this.PerformShutdown();
return;
}
}
while (frame.Continue);
}
private class PokableQueue
{
private const int InitialCapacity = 32;
private int size, head, tail;
private object[] array;
internal PokableQueue(int capacity)
{
this.array = new object[capacity];
}
internal PokableQueue()
: this(InitialCapacity)
{
}
public int Count
{
get
{
return this.size;
}
}
public void Enqueue(object obj)
{
if (this.size == this.array.Length)
{
this.Grow();
}
this.array[this.tail] = obj;
this.tail = (this.tail + 1) % this.array.Length;
this.size++;
}
public object Dequeue()
{
if (this.size < 1)
{
throw new InvalidOperationException();
}
object result = this.array[this.head];
this.array[this.head] = null;
this.head = (this.head + 1) % this.array.Length;
this.size--;
return result;
}
public void Remove(object obj)
{
for (int i = 0; i < this.size; i++)
{
if (this.array[(this.head + i) % this.array.Length] == obj)
{
for (int j = i; j < this.size - i; j++)
{
this.array[(this.head + j) % this.array.Length] = this.array[(this.head + j + 1) % this.array.Length];
}
this.size--;
if (this.size < 0)
{
this.size = this.array.Length - 1;
}
this.tail--;
}
}
}
private void Grow()
{
int newc = this.array.Length * 2;
object[] newContents = new object[newc];
this.array.CopyTo(newContents, 0);
this.array = newContents;
this.head = 0;
this.tail = this.head + this.size;
}
}
}
}