Dashboard/Quik/CommandMachine/CommandEngine.cs

163 lines
5.2 KiB
C#

using System;
using System.Collections.Generic;
namespace Quik.CommandMachine
{
public class CommandEngine
{
private int _zIndex = 0;
private readonly Stack<int> _zStack = new Stack<int>();
public int ZIndex => _zIndex;
private QRectangle _viewport;
private readonly Stack<QRectangle> _viewportStack = new Stack<QRectangle>();
private readonly Stack<object> _matrixStack = new Stack<object>();
private Command _customCommandBase = Command.CustomCommandBase;
private readonly List<QuikCommandHandler> _customCommands = new List<QuikCommandHandler>();
public QRectangle Viewport => _viewport;
// TODO: Make a real matrix class.
public float[] ActiveTransforms { get; }
public StyleStack Style { get; } = new StyleStack(new Quik.Style());
protected CommandEngine()
{
Reset();
}
public Command RegisterCustomCommand(QuikCommandHandler handler)
{
Command id = _customCommandBase++;
_customCommands.Insert(id - Command.CustomCommandBase, handler);
return id;
}
public void ProcessCommands(QRectangle bounds, CommandQueue queue)
{
if (!queue.Peek().IsCommand)
throw new ArgumentException("The first element in the queue must be a command frame.");
Reset();
_viewport = bounds;
_viewportStack.Push(_viewport);
Frame frame;
while (queue.TryDequeue(out frame))
{
Command cmd = (Command)frame;
switch (cmd)
{
default:
if (cmd > Command.CustomCommandBase)
{
_customCommands[cmd - Command.CustomCommandBase].Invoke(this, queue);
}
else
{
ChildProcessCommand(cmd, queue);
}
break;
case Command.ConditionalBegin: ConditionalHandler(queue); break;
case Command.ConditionalEnd: /* nop */ break;
case Command.Invoke:
queue.Dequeue().As<QuikCommandHandler>().Invoke(this, queue);
break;
case Command.PushViewport:
_viewportStack.Push(_viewport);
break;
case Command.IntersectViewport:
_viewport = QRectangle.Intersect((QRectangle)queue.Dequeue(), _viewport);
break;
case Command.StoreViewport:
_viewport = (QRectangle)queue.Dequeue();
break;
case Command.PopViewport:
_viewport = _viewportStack.TryPop(out QRectangle viewport) ? viewport : bounds;
break;
case Command.PushZ:
_zStack.Push(_zIndex);
break;
case Command.IncrementZ:
_zIndex++;
break;
case Command.AddZ:
_zIndex += (int)queue.Dequeue();
break;
case Command.StoreZ:
_zIndex = (int)queue.Dequeue();
break;
case Command.DecrementZ:
_zIndex--;
break;
case Command.PopZ:
_zIndex = _zStack.TryPop(out int zindex) ? zindex : 0;
break;
}
}
}
protected virtual void ChildProcessCommand(Command name, CommandQueue queue)
{
}
public virtual void Reset()
{
_zIndex = 0;
_zStack.Clear();
_viewport = new QRectangle(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue);
_viewportStack.Clear();
_matrixStack.Clear();
_matrixStack.Push(null); // TODO: replace with identity matrix.
}
private void ConditionalHandler(CommandQueue queue)
{
Frame frame = queue.Dequeue();
if (
frame.IsInteger && (int)frame != 0 ||
frame.As<Func<bool>>().Invoke())
{
// Take this branch.
return;
}
// Skip this branch.
int depth = 1;
while (queue.TryPeek(out frame))
{
if (!frame.IsCommand)
{
queue.Dequeue();
continue;
}
switch ((Command)frame)
{
case Command.ConditionalBegin:
// Increment conditional depth.
depth++;
break;
case Command.ConditionalEnd:
// Decrement condional depth, exit if zero.
if (--depth == 0)
{
queue.Dequeue();
return;
}
break;
}
queue.Dequeue();
}
}
}
}