using System.Diagnostics.CodeAnalysis; using System.IO.MemoryMappedFiles; using System.Numerics; namespace core; public class SymbolGrid { private char[]? _map; [DisallowNull] public char? this[int x, int y] { get { EnsureMap(); return IsValid(x, y) ? _map[y * Height + x] : null; } set { EnsureMap(); if (IsValid(x, y)) { _map[y * Height + x] = value.Value; } } } [DisallowNull] public char? this[Vector2I i] { get => this[i.X, i.Y]; set => this[i.X, i.Y] = value; } public int Width { get; private set; } public int Height { get; private set; } public void Load(string file) { using var fileView = MemoryMappedFile.CreateFromFile(file, FileMode.Open); using var accessor = fileView.CreateViewAccessor(); (Width, var stride, Height) = Parse(accessor); _map = new char[Width * Height]; for (int x = 0; x < Width; x++) { for (int y = 0; y < Height; y++) { _map[y * Height + x] = (char)accessor.ReadByte(y * stride + x); } } static (int Width, int Stride, int Height) Parse(MemoryMappedViewAccessor map) { bool hasNewLine = false; int? width = null, stride = null; for (int i = 0; i < map.Capacity; i++) { if ((char)map.ReadByte(i) is '\r' or '\n') { if (!hasNewLine) { width = i; } hasNewLine = true; } else if (hasNewLine) { stride = i; break; } } return (width!.Value, stride!.Value, (int)((map.Capacity + width.Value) / stride.Value)); } } public SymbolEnumerator GetEnumerator() => new(this); public bool IsValid(Vector2I p) => IsValid(p.X, p.Y); public bool IsValid(int x, int y) { EnsureMap(); return x >= 0 && x < Width && y >= 0 && y < Height; } public ref struct SymbolEnumerator { private readonly SymbolGrid _grid; private readonly int _length; private int? _current; internal SymbolEnumerator(SymbolGrid grid) { _grid = grid; _length = grid.Width * grid.Height; } public (Vector2I Position, char Symbol) Current { get; private set; } public bool MoveNext() { _current = _current.HasValue ? _current + 1 : 0; if (_current == _length) { return false; } Current = (_grid.ToVector(_current.Value), _grid._map![_current.Value]); return true; } } private Vector2I ToVector(int index) => new() { Y = index / Height, X = index % Height }; [MemberNotNull(nameof(_map))] private void EnsureMap() => ArgumentNullException.ThrowIfNull(_map); }