using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.IO; using System.Runtime.CompilerServices; namespace Dashboard.Drawing { public class DrawQueue : IEnumerable, 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(); /// /// The absolute boundary of all graphics objects. /// public Box3d Bounds { get; set; } /// /// 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.SetLength(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); } } public Enumerator GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 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."); if (value == 0) { bytes[0] = 0; return 1; } 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, out int value) { value = 0; int i; for (i = 0; i < bytes.Length; i++) { byte b = bytes[i]; value = (value << 7) | b; if ((b & (1 << 7)) == 0) { i++; break; } } return i; } public void Dispose() { throw new NotImplementedException(); } private class DrawController(DrawQueue Queue) : IDrawController { public void EnsureBounds(Box2d bounds, float depth) { Queue.Bounds = Box3d.Union(Queue.Bounds, bounds, depth); } 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); } public void Write(T2 command, T1 param) where T2 : IDrawCommand { int length = command.WriteParams(Queue, param, Span.Empty); Span bytes = stackalloc byte[length]; command.WriteParams(Queue, param, bytes); Write(command, bytes); } } public class Enumerator : ICommandFrame, IEnumerator { 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 == -1) _index = 0; if (_index >= _length) return false; _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() { if (_current is IDrawCommand command) { return command.GetParams(_queue, _stream.AsSpan(_paramsIndex, _paramLength))!; } else { throw new InvalidOperationException(); } } public bool TryGetParameter([NotNullWhen(true)] out T? parameter) { if (_current is IDrawCommand 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(); public bool TryGetParameter([NotNullWhen(true)] out T? parameter); } public interface IDrawController { /// /// Ensures that the canvas is at least a certain size. /// /// The bounding box. void EnsureBounds(Box2d bounds, float depth); /// /// 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; /// /// Write into the command stream. /// /// The command to write. /// Any data associated with the command. void Write(T2 command, T1 param) where T2 : IDrawCommand; } }