232 lines
8.0 KiB
C#

using System;
using System.Collections.Generic;
namespace Dashboard.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<QMat4> _matrixStack = new Stack<QMat4>();
private Command _customCommandBase = Command.CustomCommandBase;
private readonly List<CommandHandler> _customCommands = new List<CommandHandler>();
public QRectangle Viewport => _viewport;
public QMat4 ActiveTransforms { get; }
public StyleStack Style { get; } = new StyleStack(new Style());
protected CommandEngine()
{
Reset();
}
public Command RegisterCustomCommand(CommandHandler handler)
{
Command id = _customCommandBase++;
_customCommands.Insert(id - Command.CustomCommandBase, handler);
return id;
}
public void ProcessCommands(QRectangle bounds, CommandList queue)
{
CommandQueue iterator = queue.GetEnumerator();
if (!iterator.Peek().IsCommand)
throw new ArgumentException("The first element in the iterator must be a command frame.");
Reset();
_viewport = bounds;
_viewportStack.Push(_viewport);
Frame frame;
while (iterator.TryDequeue(out frame))
{
Command cmd = (Command)frame;
switch (cmd)
{
default:
if (cmd > Command.CustomCommandBase)
{
_customCommands[cmd - Command.CustomCommandBase].Invoke(this, iterator);
}
else
{
ChildProcessCommand(cmd, iterator);
}
break;
case Command.ConditionalBegin: ConditionalHandler(iterator); break;
case Command.ConditionalEnd: /* nop */ break;
case Command.Invoke:
iterator.Dequeue().As<CommandHandler>().Invoke(this, iterator);
break;
case Command.PushViewport:
_viewportStack.Push(_viewport);
break;
case Command.IntersectViewport:
_viewport = QRectangle.Intersect((QRectangle)iterator.Dequeue(), _viewport);
break;
case Command.StoreViewport:
_viewport = (QRectangle)iterator.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)iterator.Dequeue();
break;
case Command.StoreZ:
_zIndex = (int)iterator.Dequeue();
break;
case Command.DecrementZ:
_zIndex--;
break;
case Command.PopZ:
_zIndex = _zStack.TryPop(out int zindex) ? zindex : 0;
break;
case Command.PushStyle:
Style.Push(iterator.Dequeue().As<Style>());
break;
case Command.StoreStyle:
Style.Pop();
Style.Push(iterator.Dequeue().As<Style>());
break;
case Command.PopStyle:
Style.Pop();
break;
}
}
}
protected virtual void ChildProcessCommand(Command name, CommandQueue queue)
{
}
public virtual void Reset()
{
_zIndex = 0;
_zStack.Clear();
_viewport = new QRectangle(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue);
_viewportStack.Clear();
_matrixStack.Clear();
_matrixStack.Push(QMat4.Identity);
}
private void ConditionalHandler(CommandQueue iterator)
{
Frame frame = iterator.Dequeue();
if (
frame.IsInteger && (int)frame != 0 ||
frame.As<Func<bool>>().Invoke())
{
// Take this branch.
return;
}
// Skip this branch.
int depth = 1;
while (iterator.TryPeek(out frame))
{
if (!frame.IsCommand)
{
iterator.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)
{
iterator.Dequeue();
return;
}
break;
}
iterator.Dequeue();
}
}
private static readonly Dictionary<Type, ICommandListSerializer> s_serializers = new Dictionary<Type, ICommandListSerializer>();
/// <summary>
/// Add a custom serializer to the command engine.
/// </summary>
/// <typeparam name="T">Type object type.</typeparam>
/// <param name="serializer">The serializer.</param>
/// <param name="overwrite">True to allow overwriting.</param>
public static void AddSerializer<T>(ICommandListSerializer serializer, bool overwrite = false)
{
if (overwrite)
{
s_serializers[typeof(T)] = serializer;
}
else
{
s_serializers.Add(typeof(T), serializer);
}
}
/// <summary>
/// Add a custom serializer to the command engine.
/// </summary>
/// <typeparam name="T">Type object type.</typeparam>
/// <param name="serializer">The serializer.</param>
/// <param name="overwrite">True to allow overwriting.</param>
public static void AddSerializer<T>(ICommandListSerializer<T> serializer, bool overwrite = false)
=> AddSerializer<T>((ICommandListSerializer)serializer, overwrite);
/// <summary>
/// Get a serializer for the given object.
/// </summary>
/// <typeparam name="T">The object type.</typeparam>
/// <param name="value">Required parameter for the C# type inference to work.</param>
/// <returns>The serializer.</returns>
public static ICommandListSerializer<T> GetSerializer<T>(ICommandListSerializable<T>? value)
where T : ICommandListSerializable<T>, new()
{
if (!s_serializers.TryGetValue(typeof(T), out var serializer))
{
serializer = new CommandListSerializableSerializer<T>();
AddSerializer<T>(serializer);
}
return (ICommandListSerializer<T>)serializer;
}
/// <summary>
/// Get a serializer for the given object.
/// </summary>
/// <typeparam name="T">The object type.</typeparam>
/// <param name="value">Required parameter for the C# type inference to work.</param>
/// <returns>The serializer.</returns>
public static ICommandListSerializer<T> GetSerializer<T>(T? value)
{
return (ICommandListSerializer<T>)s_serializers[typeof(T)];
}
}
}