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 _extensions = new HashList(); private readonly HashList _commands = new HashList(); private readonly HashList _resources = new HashList(); private readonly DrawController _controller; private readonly MemoryStream _commandStream = new MemoryStream(); /// /// Size of the image in points. /// public Vector3 Size { get; private set; } = Vector3.Zero; /// /// The extensions required to draw the image. /// public IReadOnlyList Extensions => _extensions; /// /// The resources used by this draw queue. /// public IReadOnlyList Resources => _resources; /// /// The list of commands used by the extension. /// public IReadOnlyList Command => _commands; public DrawQueue() { _controller = new DrawController(this); } /// /// Clear the queue. /// 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 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 param) { if (command.Length < 0) { Span 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 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 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 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 bytes) { Queue.Write(command, bytes); } public void Write(IDrawCommand command, T param) where T : IParameterSerializer { int length = param.Serialize(Queue, Span.Empty); Span bytes = stackalloc byte[length]; param.Serialize(Queue, bytes); Write(command, bytes); } } private class HashList : IReadOnlyList where T : notnull { private readonly List _list = new List(); private readonly Dictionary _map = new Dictionary(); 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 GetEnumerator() => _list.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); } } public interface IDrawController { /// /// Ensures that the canvas is at least a certain size. /// /// The minimum size. void EnsureSize(Vector3 size); /// /// Write into the command stream. /// /// The command to write. void Write(IDrawCommand command); /// /// Write into the command stream. /// /// The command to write. /// Any data associated with the command. void Write(IDrawCommand command, ReadOnlySpan param); /// /// Write into the command stream. /// /// The command to write. /// Any data associated with the command. void Write(IDrawCommand command, T param) where T : IParameterSerializer; } }