2024-12-13 19:57:07 +01:00
|
|
|
|
using System;
|
2024-12-14 18:44:27 +01:00
|
|
|
|
using System.Collections;
|
2024-12-13 19:57:07 +01:00
|
|
|
|
using System.Collections.Generic;
|
2024-12-14 18:44:27 +01:00
|
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2024-12-14 14:27:40 +01:00
|
|
|
|
using System.Drawing;
|
2024-12-13 19:57:07 +01:00
|
|
|
|
using System.IO;
|
2024-12-14 18:44:27 +01:00
|
|
|
|
using System.Runtime.CompilerServices;
|
2024-12-13 19:57:07 +01:00
|
|
|
|
|
|
|
|
|
namespace Dashboard.Drawing
|
|
|
|
|
{
|
2024-12-14 18:44:27 +01:00
|
|
|
|
public class DrawQueue : IEnumerable<ICommandFrame>, IDisposable
|
2024-12-13 19:57:07 +01:00
|
|
|
|
{
|
|
|
|
|
private readonly HashList<IDrawExtension> _extensions = new HashList<IDrawExtension>();
|
|
|
|
|
private readonly HashList<IDrawCommand> _commands = new HashList<IDrawCommand>();
|
|
|
|
|
private readonly HashList<IDrawResource> _resources = new HashList<IDrawResource>();
|
|
|
|
|
private readonly DrawController _controller;
|
|
|
|
|
private readonly MemoryStream _commandStream = new MemoryStream();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2024-12-14 14:27:40 +01:00
|
|
|
|
/// The absolute boundary of all graphics objects.
|
2024-12-13 19:57:07 +01:00
|
|
|
|
/// </summary>
|
2024-12-14 14:27:40 +01:00
|
|
|
|
public Box3d Bounds { get; set; }
|
2024-12-13 19:57:07 +01:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The extensions required to draw the image.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public IReadOnlyList<IDrawExtension> Extensions => _extensions;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The resources used by this draw queue.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public IReadOnlyList<IDrawResource> Resources => _resources;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The list of commands used by the extension.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public IReadOnlyList<IDrawCommand> Command => _commands;
|
|
|
|
|
|
|
|
|
|
public DrawQueue()
|
|
|
|
|
{
|
|
|
|
|
_controller = new DrawController(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Clear the queue.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Clear()
|
|
|
|
|
{
|
|
|
|
|
_resources.Clear();
|
|
|
|
|
_commands.Clear();
|
|
|
|
|
_extensions.Clear();
|
|
|
|
|
_commandStream.Capacity = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int RequireExtension(IDrawExtension extension)
|
|
|
|
|
{
|
|
|
|
|
foreach (IDrawExtension super in extension.Requires)
|
|
|
|
|
RequireExtension(super);
|
|
|
|
|
|
|
|
|
|
return _extensions.Intern(extension);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int RequireResource(IDrawResource resource)
|
|
|
|
|
{
|
|
|
|
|
RequireExtension(resource.Kind);
|
|
|
|
|
return _resources.Intern(resource);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal IDrawController GetController(IDrawExtension extension)
|
|
|
|
|
{
|
|
|
|
|
_extensions.Intern(extension);
|
|
|
|
|
return _controller;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Write(IDrawCommand command)
|
|
|
|
|
{
|
|
|
|
|
if (command.Length > 0)
|
|
|
|
|
throw new InvalidOperationException("This command has a finite length argument.");
|
|
|
|
|
|
|
|
|
|
int cmdIndex = _commands.Intern(command);
|
|
|
|
|
|
|
|
|
|
Span<byte> cmd = stackalloc byte[6];
|
|
|
|
|
int sz;
|
|
|
|
|
|
|
|
|
|
if (command.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
// Write a fixed command.
|
|
|
|
|
sz = ToVlq(cmdIndex, cmd);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Write a variadic with zero length.
|
|
|
|
|
sz = ToVlq(cmdIndex, cmd);
|
|
|
|
|
cmd[sz++] = 0;
|
|
|
|
|
}
|
2024-12-14 14:27:40 +01:00
|
|
|
|
|
2024-12-13 19:57:07 +01:00
|
|
|
|
_commandStream.Write(cmd[..sz]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Write(IDrawCommand command, ReadOnlySpan<byte> param)
|
|
|
|
|
{
|
|
|
|
|
if (command.Length < 0)
|
|
|
|
|
{
|
|
|
|
|
Span<byte> cmd = stackalloc byte[10];
|
|
|
|
|
int cmdIndex = _commands.Intern(command);
|
|
|
|
|
int sz = ToVlq(cmdIndex, cmd);
|
|
|
|
|
sz += ToVlq(param.Length, cmd[sz..]);
|
|
|
|
|
_commandStream.Write(cmd[..sz]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (command.Length != param.Length)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(param.Length), "Length of the parameter does not match the command.");
|
|
|
|
|
|
|
|
|
|
Span<byte> cmd = stackalloc byte[5];
|
|
|
|
|
int cmdIndex = _commands.Intern(command);
|
|
|
|
|
int sz = ToVlq(cmdIndex, cmd);
|
|
|
|
|
|
|
|
|
|
_commandStream.Write(cmd[..sz]);
|
|
|
|
|
_commandStream.Write(param);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-14 18:44:27 +01:00
|
|
|
|
public Enumerator GetEnumerator() => new Enumerator(this);
|
|
|
|
|
IEnumerator<ICommandFrame> IEnumerable<ICommandFrame>.GetEnumerator() => GetEnumerator();
|
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
|
|
2024-12-13 19:57:07 +01:00
|
|
|
|
private static int ToVlq(int value, Span<byte> bytes)
|
|
|
|
|
{
|
|
|
|
|
if (value < 0)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(value), "Must be a positive integer.");
|
|
|
|
|
else if (bytes.Length < 5)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(bytes), "Must at least be five bytes long.");
|
|
|
|
|
|
2024-12-14 18:44:27 +01:00
|
|
|
|
if (value == 0)
|
|
|
|
|
{
|
|
|
|
|
bytes[0] = 0;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2024-12-13 19:57:07 +01:00
|
|
|
|
|
2024-12-14 18:44:27 +01:00
|
|
|
|
int i;
|
2024-12-13 19:57:07 +01:00
|
|
|
|
for (i = 0; i < 5 && value != 0; i++, value >>= 7)
|
|
|
|
|
{
|
|
|
|
|
if (i > 0)
|
|
|
|
|
bytes[i - 1] |= 1 << 7;
|
|
|
|
|
|
2024-12-14 14:27:40 +01:00
|
|
|
|
bytes[i] = (byte)(value & 0x7F);
|
2024-12-13 19:57:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-14 18:44:27 +01:00
|
|
|
|
private static int FromVlq(ReadOnlySpan<byte> bytes, out int value)
|
2024-12-13 19:57:07 +01:00
|
|
|
|
{
|
2024-12-14 18:44:27 +01:00
|
|
|
|
value = 0;
|
2024-12-13 19:57:07 +01:00
|
|
|
|
|
2024-12-14 18:44:27 +01:00
|
|
|
|
int i;
|
|
|
|
|
for (i = 0; i < bytes.Length; i++)
|
2024-12-13 19:57:07 +01:00
|
|
|
|
{
|
|
|
|
|
byte b = bytes[i];
|
|
|
|
|
|
|
|
|
|
value = (value << 7) | b;
|
|
|
|
|
|
|
|
|
|
if ((b & (1 << 7)) == 0)
|
2024-12-14 18:44:27 +01:00
|
|
|
|
{
|
|
|
|
|
i++;
|
2024-12-13 19:57:07 +01:00
|
|
|
|
break;
|
2024-12-14 18:44:27 +01:00
|
|
|
|
}
|
2024-12-13 19:57:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-14 18:44:27 +01:00
|
|
|
|
return i;
|
2024-12-13 19:57:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class DrawController(DrawQueue Queue) : IDrawController
|
|
|
|
|
{
|
2024-12-14 14:27:40 +01:00
|
|
|
|
public void EnsureBounds(Box3d bounds)
|
2024-12-13 19:57:07 +01:00
|
|
|
|
{
|
2024-12-14 14:27:40 +01:00
|
|
|
|
Queue.Bounds = Box3d.Union(Queue.Bounds, bounds);
|
2024-12-13 19:57:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Write(IDrawCommand command)
|
|
|
|
|
{
|
|
|
|
|
Queue.Write(command);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Write(IDrawCommand command, ReadOnlySpan<byte> bytes)
|
|
|
|
|
{
|
|
|
|
|
Queue.Write(command, bytes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Write<T>(IDrawCommand command, T param) where T : IParameterSerializer<T>
|
|
|
|
|
{
|
|
|
|
|
int length = param.Serialize(Queue, Span<byte>.Empty);
|
|
|
|
|
Span<byte> bytes = stackalloc byte[length];
|
|
|
|
|
|
|
|
|
|
param.Serialize(Queue, bytes);
|
|
|
|
|
Write(command, bytes);
|
|
|
|
|
}
|
2024-12-14 17:46:47 +01:00
|
|
|
|
|
|
|
|
|
public void Write<T1, T2>(T2 command, T1 param) where T2 : IDrawCommand<T1>
|
|
|
|
|
{
|
|
|
|
|
int length = command.WriteParams(Queue, param, Span<byte>.Empty);
|
|
|
|
|
Span<byte> bytes = stackalloc byte[length];
|
|
|
|
|
|
|
|
|
|
command.WriteParams(Queue, param, bytes);
|
|
|
|
|
Write(command, bytes);
|
|
|
|
|
}
|
2024-12-13 19:57:07 +01:00
|
|
|
|
}
|
2024-12-14 18:44:27 +01:00
|
|
|
|
|
|
|
|
|
public class Enumerator : ICommandFrame, IEnumerator<ICommandFrame>
|
|
|
|
|
{
|
|
|
|
|
private readonly DrawQueue _queue;
|
|
|
|
|
private readonly byte[] _stream;
|
|
|
|
|
private int _length;
|
|
|
|
|
private int _index = -1;
|
|
|
|
|
private int _paramsIndex = -1;
|
|
|
|
|
private int _paramLength = 0;
|
|
|
|
|
private IDrawCommand? _current = null;
|
|
|
|
|
|
|
|
|
|
public ICommandFrame Current => this;
|
|
|
|
|
|
|
|
|
|
object? IEnumerator.Current => Current;
|
|
|
|
|
|
|
|
|
|
public IDrawCommand Command => _current ?? throw new InvalidOperationException();
|
|
|
|
|
|
|
|
|
|
public bool HasParameters { get; private set; }
|
|
|
|
|
|
|
|
|
|
public Enumerator(DrawQueue queue)
|
|
|
|
|
{
|
|
|
|
|
_queue = queue;
|
|
|
|
|
_stream = queue._commandStream.GetBuffer();
|
|
|
|
|
_length = (int)queue._commandStream.Length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool MoveNext()
|
|
|
|
|
{
|
|
|
|
|
if (_index >= _length)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (_index == -1)
|
|
|
|
|
_index = 0;
|
|
|
|
|
|
|
|
|
|
_index += FromVlq(_stream[_index .. (_index + 5)], out int command);
|
|
|
|
|
_current = _queue.Command[command];
|
|
|
|
|
|
|
|
|
|
HasParameters = _current.Length != 0;
|
|
|
|
|
|
|
|
|
|
if (!HasParameters)
|
|
|
|
|
{
|
|
|
|
|
_paramsIndex = -1;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int length;
|
|
|
|
|
if (_current.Length < 0)
|
|
|
|
|
{
|
|
|
|
|
_index += FromVlq(_stream[_index .. (_index + 5)], out length);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
length = _current.Length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_paramsIndex = _index;
|
|
|
|
|
_paramLength = length;
|
|
|
|
|
_index += length;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Reset()
|
|
|
|
|
{
|
|
|
|
|
_index = -1;
|
|
|
|
|
_current = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public object? GetParameter()
|
|
|
|
|
{
|
|
|
|
|
return _current?.GetParams(_queue, _stream.AsSpan(_paramsIndex, _paramLength));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public T GetParameter<T>()
|
|
|
|
|
{
|
|
|
|
|
if (_current is IDrawCommand<T> command)
|
|
|
|
|
{
|
|
|
|
|
return command.GetParams(_queue, _stream.AsSpan(_paramsIndex, _paramLength))!;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool TryGetParameter<T>([NotNullWhen(true)] out T? parameter)
|
|
|
|
|
{
|
|
|
|
|
if (_current is IDrawCommand<T> command)
|
|
|
|
|
{
|
|
|
|
|
parameter = command.GetParams(_queue, _stream.AsSpan(_paramsIndex, _paramLength))!;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
parameter = default;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public interface ICommandFrame
|
|
|
|
|
{
|
|
|
|
|
public IDrawCommand Command { get; }
|
|
|
|
|
|
|
|
|
|
public bool HasParameters { get; }
|
|
|
|
|
|
|
|
|
|
public object? GetParameter();
|
|
|
|
|
|
|
|
|
|
public T GetParameter<T>();
|
|
|
|
|
|
|
|
|
|
public bool TryGetParameter<T>([NotNullWhen(true)] out T? parameter);
|
2024-12-13 19:57:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-14 18:44:27 +01:00
|
|
|
|
|
2024-12-13 19:57:07 +01:00
|
|
|
|
public interface IDrawController
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Ensures that the canvas is at least a certain size.
|
|
|
|
|
/// </summary>
|
2024-12-14 14:27:40 +01:00
|
|
|
|
/// <param name="bounds">The bounding box.</param>
|
|
|
|
|
void EnsureBounds(Box3d bounds);
|
2024-12-13 19:57:07 +01:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Write into the command stream.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="command">The command to write.</param>
|
|
|
|
|
void Write(IDrawCommand command);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Write into the command stream.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="command">The command to write.</param>
|
|
|
|
|
/// <param name="param">Any data associated with the command.</param>
|
|
|
|
|
void Write(IDrawCommand command, ReadOnlySpan<byte> param);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Write into the command stream.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="command">The command to write.</param>
|
|
|
|
|
/// <param name="param">Any data associated with the command.</param>
|
|
|
|
|
void Write<T>(IDrawCommand command, T param) where T : IParameterSerializer<T>;
|
2024-12-14 17:46:47 +01:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Write into the command stream.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="command">The command to write.</param>
|
|
|
|
|
/// <param name="param">Any data associated with the command.</param>
|
|
|
|
|
void Write<T1, T2>(T2 command, T1 param) where T2 : IDrawCommand<T1>;
|
2024-12-13 19:57:07 +01:00
|
|
|
|
}
|
|
|
|
|
}
|