Dashboard/Dashboard.Drawing/DrawQueue.cs

264 lines
7.9 KiB
C#
Raw Normal View History

2024-12-13 19:57:07 +01:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
namespace Dashboard.Drawing
{
public class DrawQueue : IDisposable
{
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>
/// Size of the image in points.
/// </summary>
public Vector3 Size { get; private set; } = Vector3.Zero;
/// <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;
}
_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);
}
}
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.");
int i;
for (i = 0; i < 5 && value != 0; i++, value >>= 7)
{
if (i > 0)
bytes[i - 1] |= 1 << 7;
bytes[i] = (byte)(value & 0x7F);
}
return i;
}
private static int FromVlq(ReadOnlySpan<byte> bytes)
{
int value = 0;
for (int i = 0; i < bytes.Length; i++)
{
byte b = bytes[i];
value = (value << 7) | b;
if ((b & (1 << 7)) == 0)
break;
}
return value;
}
public void Dispose()
{
throw new NotImplementedException();
}
private class DrawController(DrawQueue Queue) : IDrawController
{
public void EnsureSize(Vector3 size)
{
Queue.Size = Vector3.Max(Queue.Size, size);
}
public int Require(IDrawExtension extension)
{
return Queue.RequireExtension(extension);
}
public int GetResourceIndex(IDrawResource resource)
{
return Queue._resources.Intern(resource);
}
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);
}
}
private class HashList<T> : IReadOnlyList<T>
where T : notnull
{
private readonly List<T> _list = new List<T>();
private readonly Dictionary<T, int> _map = new Dictionary<T, int>();
public T this[int index] => _list[index];
public int Count => _list.Count;
public int Intern(T value)
{
if (_map.TryGetValue(value, out int index))
return index;
index = Count;
_list.Add(value);
_map.Add(value, index);
return index;
}
public void Clear()
{
_list.Clear();
_map.Clear();
}
public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
}
}
public interface IDrawController
{
/// <summary>
/// Ensures that the canvas is at least a certain size.
/// </summary>
/// <param name="size">The minimum size.</param>
void EnsureSize(Vector3 size);
/// <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>;
}
}