< Summary

Information
Class: SwiftCollections.Dimensions.SwiftArray2D<T>
Assembly: SwiftCollections
File(s): /home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Dimension/SwiftArray2D.cs
Line coverage
100%
Covered lines: 137
Uncovered lines: 0
Coverable lines: 137
Total lines: 434
Line coverage: 100%
Branch coverage
96%
Covered branches: 64
Total branches: 66
Branch coverage: 96.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%44100%
.ctor(...)100%11100%
get_Width()100%11100%
get_Height()100%11100%
get_InnerArray()100%11100%
get_Size()100%11100%
get_Length()100%11100%
get_Item(...)100%11100%
set_Item(...)100%11100%
get_State()100%11100%
set_State(...)100%11100%
AddRange(...)83.33%66100%
Fill(...)100%22100%
Shift(...)100%22100%
ShiftWrapped(...)100%44100%
ShiftClamped(...)100%44100%
Clear()100%11100%
Resize(...)100%88100%
Initialize(...)100%11100%
NormalizeShift(...)100%44100%
WrapForwardIndex(...)100%22100%
GetShiftRange(...)100%44100%
ValidateIndex(...)87.5%88100%
IsValidIndex(...)100%66100%
GetIndex(...)100%11100%
ToArray()100%44100%
Clone()100%44100%
GetEnumerator()100%44100%
System.Collections.IEnumerable.GetEnumerator()100%11100%

File(s)

/home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Dimension/SwiftArray2D.cs

#LineLine coverage
 1//=======================================================================
 2// SwiftArray2D.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using Chronicler;
 9using MemoryPack;
 10using SwiftCollections.Diagnostics;
 11using System;
 12using System.Collections;
 13using System.Collections.Generic;
 14using System.Text.Json.Serialization;
 15
 16namespace SwiftCollections.Dimensions;
 17
 18/// <summary>
 19/// Represents a flattened 2D array with dynamic resizing and efficient access.
 20/// </summary>
 21/// <typeparam name="T">The type of elements in the array.</typeparam>
 22[Serializable]
 23[JsonConverter(typeof(StateJsonConverterFactory))]
 24[MemoryPackable]
 25public partial class SwiftArray2D<T> : IStateBacked<Array2DState<T>>, IEnumerable<T>, IEnumerable
 26{
 27    #region Fields
 28
 29    private T[] _innerArray;
 30
 31    private int _width;
 32
 33    private int _height;
 34
 35    #endregion
 36
 37    #region Constructors
 38
 39    /// <summary>
 40    /// Initializes a new instance of the <see cref="SwiftArray2D{T}"/> class.
 41    /// </summary>
 842    public SwiftArray2D() : this(0, 0) { }
 43
 44    /// <summary>
 45    /// Initializes a new instance of the <see cref="SwiftArray2D{T}"/> class with specified dimensions.
 46    /// </summary>
 3847    public SwiftArray2D(int width, int height)
 48    {
 3849        Initialize(width, height);
 50
 51        // Ensure that the inner array is not null after initialization
 3852        SwiftThrowHelper.ThrowIfNull(_innerArray, nameof(_innerArray));
 3853    }
 54
 55    /// <summary>
 56    /// Initializes a new instance of the <see cref="SwiftArray2D{T}"/> class with specified dimensions and default valu
 57    /// </summary>
 1258    public SwiftArray2D(int width, int height, T defaultValue) : this(width, height)
 59    {
 1260        Fill(defaultValue);
 1261    }
 62
 63    /// <summary>
 64    /// Initializes a new instance of the SwiftArray2D class by copying the contents of the specified two-dimensional ar
 65    /// </summary>
 66    /// <remarks>
 67    /// The dimensions of the new SwiftArray2D instance match those of the source array.
 68    /// Changes to the source array after construction do not affect the SwiftArray2D instance.
 69    /// </remarks>
 70    /// <param name="source">The two-dimensional array whose elements are copied to the new SwiftArray2D instance. Canno
 371    public SwiftArray2D(T[,] source)
 72    {
 373        int width = source.GetLength(0);
 374        int height = source.GetLength(1);
 75
 376        Initialize(width, height);
 77
 378        SwiftThrowHelper.ThrowIfNull(_innerArray, nameof(_innerArray));
 79
 1880        for (int x = 0; x < width; x++)
 3681            for (int y = 0; y < height; y++)
 1282                this[x, y] = source[x, y];
 383    }
 84
 85    /// <summary>
 86    /// Initializes a new instance of the SwiftArray2D class with the specified array state.
 87    /// </summary>
 88    /// <param name="state">The state object that encapsulates the underlying data and configuration for the two-dimensi
 89    [MemoryPackConstructor]
 790    public SwiftArray2D(Array2DState<T> state)
 91    {
 792        State = state;
 93
 794        SwiftThrowHelper.ThrowIfNull(_innerArray, nameof(_innerArray));
 795    }
 96
 97    #endregion
 98
 99    #region Properties
 100
 101    /// <summary>
 102    /// Gets the width of the 2D array.
 103    /// </summary>
 104    [JsonIgnore]
 105    [MemoryPackIgnore]
 72106    public int Width => _width;
 107
 108    /// <summary>
 109    /// Gets the height of the 2D array.
 110    /// </summary>
 111    [JsonIgnore]
 112    [MemoryPackIgnore]
 84113    public int Height => _height;
 114
 115    /// <summary>
 116    /// Gets the underlying flattened array for direct access.
 117    /// </summary>
 118    [JsonIgnore]
 119    [MemoryPackIgnore]
 116120    public T[] InnerArray => _innerArray;
 121
 122    /// <summary>
 123    /// Total size of the array
 124    /// </summary>
 125    [JsonIgnore]
 126    [MemoryPackIgnore]
 1127    public int Size => _width * _height;
 128
 129    /// <inheritdoc cref="Array.Length" />
 130    [JsonIgnore]
 131    [MemoryPackIgnore]
 9132    public int Length => _innerArray.Length;
 133
 134    /// <summary>
 135    /// Gets or sets the element at the specified position in the 2D array.
 136    /// </summary>
 137    /// <param name="x">The zero-based X coordinate.</param>
 138    /// <param name="y">The zero-based Y coordinate.</param>
 139    [JsonIgnore]
 140    [MemoryPackIgnore]
 141    public T this[int x, int y]
 142    {
 143        get
 144        {
 1000191145            ValidateIndex(x, y);
 1000189146            return _innerArray[GetIndex(x, y)];
 147        }
 148        set
 149        {
 1000085150            ValidateIndex(x, y);
 1000085151            _innerArray[GetIndex(x, y)] = value;
 1000085152        }
 153    }
 154
 155    /// <summary>
 156    /// Gets or sets the current state of the two-dimensional array, including its dimensions and data.
 157    /// </summary>
 158    /// <remarks>
 159    /// The returned state is a snapshot and is independent of future changes to the array.
 160    /// Setting this property replaces the entire contents and dimensions of the array with those from the provided stat
 161    /// This property is intended for serialization and deserialization scenarios.
 162    /// </remarks>
 163    [JsonInclude]
 164    [MemoryPackInclude]
 165    public Array2DState<T> State
 166    {
 167        get
 168        {
 7169            var data = new T[_innerArray.Length];
 7170            Array.Copy(_innerArray, data, data.Length);
 171
 7172            return new Array2DState<T>(
 7173                _width,
 7174                _height,
 7175                data
 7176            );
 177        }
 178
 179        internal set
 180        {
 7181            SwiftThrowHelper.ThrowIfNull(value.Data, nameof(value.Data));
 182
 7183            _width = value.Width;
 7184            _height = value.Height;
 185
 7186            int capacity = value.Data.Length;
 187
 7188            _innerArray = new T[capacity];
 7189            Array.Copy(value.Data, _innerArray, capacity);
 7190        }
 191    }
 192
 193    #endregion
 194
 195    #region Collection Management
 196
 197    /// <summary>
 198    /// Overwrites the current 2D array with the provided source.
 199    /// </summary>
 200    /// <remarks>
 201    /// Existing backing storage is reused when the flattened source length matches.
 202    /// </remarks>
 203    /// <param name="source">The source values to copy.</param>
 204    public void AddRange(T[,] source)
 205    {
 2206        SwiftThrowHelper.ThrowIfNull(source, nameof(source));
 207
 2208        int width = source.GetLength(0);
 2209        int height = source.GetLength(1);
 2210        int length = source.Length;
 211
 2212        if (_innerArray.Length != length)
 2213            _innerArray = new T[length];
 214
 2215        _width = width;
 2216        _height = height;
 217
 2218        int index = 0;
 12219        for (int x = 0; x < width; x++)
 220        {
 24221            for (int y = 0; y < height; y++)
 8222                _innerArray[index++] = source[x, y];
 223        }
 2224    }
 225
 226    /// <summary>
 227    /// Fills the array with the specified value.
 228    /// </summary>
 229    public void Fill(T value)
 230    {
 4000304231        for (int i = 0; i < _innerArray.Length; i++)
 2000137232            _innerArray[i] = value;
 15233    }
 234
 235    /// <summary>
 236    /// Shifts the elements in the array by the specified X and Y offsets.
 237    /// </summary>
 238    /// <param name="xShift">The offset to apply along the X-axis.</param>
 239    /// <param name="yShift">The offset to apply along the Y-axis.</param>
 240    /// <param name="wrap">
 241    /// Specifies whether to wrap elements that exceed the array's boundaries.
 242    /// If <c>true</c>, values wrap around to the other side of the array.
 243    /// If <c>false</c>, values that exceed boundaries are discarded.
 244    /// </param>
 245    public void Shift(int xShift, int yShift, bool wrap = true)
 246    {
 7247        T[] newArray = new T[_innerArray.Length];
 248
 7249        if (wrap)
 5250            ShiftWrapped(newArray, xShift, yShift);
 251        else
 2252            ShiftClamped(newArray, xShift, yShift);
 253
 7254        _innerArray = newArray;
 7255    }
 256
 257    private void ShiftWrapped(T[] newArray, int xShift, int yShift)
 258    {
 5259        int normalizedXShift = NormalizeShift(xShift, _width);
 5260        int normalizedYShift = NormalizeShift(yShift, _height);
 261
 38262        for (int x = 0; x < _width; x++)
 263        {
 14264            int newX = WrapForwardIndex(x, normalizedXShift, _width);
 14265            int sourceBase = x * _height;
 14266            int targetBase = newX * _height;
 267
 138268            for (int y = 0; y < _height; y++)
 269            {
 55270                int newY = WrapForwardIndex(y, normalizedYShift, _height);
 55271                newArray[targetBase + newY] = _innerArray[sourceBase + y];
 272            }
 273        }
 5274    }
 275
 276    private void ShiftClamped(T[] newArray, int xShift, int yShift)
 277    {
 2278        GetShiftRange(_width, xShift, out int xStart, out int xEnd);
 2279        GetShiftRange(_height, yShift, out int yStart, out int yEnd);
 280
 8281        for (int x = xStart; x < xEnd; x++)
 282        {
 2283            int newX = x + xShift;
 2284            int sourceBase = x * _height;
 2285            int targetBase = newX * _height;
 286
 12287            for (int y = yStart; y < yEnd; y++)
 288            {
 4289                int newY = y + yShift;
 4290                newArray[targetBase + newY] = _innerArray[sourceBase + y];
 291            }
 292        }
 2293    }
 294
 295    /// <summary>
 296    /// Clears all elements in the array.
 297    /// </summary>
 1298    public void Clear() => Array.Clear(_innerArray, 0, _innerArray.Length);
 299
 300    #endregion
 301
 302    #region Capacity Management
 303
 304    /// <summary>
 305    /// Resizes the 2D array to new dimensions, preserving existing values within the new bounds.
 306    /// </summary>
 307    public void Resize(int newWidth, int newHeight)
 308    {
 3309        SwiftThrowHelper.ThrowIfNegative(newWidth, nameof(newWidth));
 3310        SwiftThrowHelper.ThrowIfNegative(newHeight, nameof(newHeight));
 311
 3312        if (newWidth == _width && newHeight == _height)
 1313            return;
 314
 2315        T[] newArray = new T[newWidth * newHeight];
 2316        int minWidth = Math.Min(_width, newWidth);
 2317        int minHeight = Math.Min(_height, newHeight);
 318
 12319        for (int x = 0; x < minWidth; x++)
 24320            for (int y = 0; y < minHeight; y++)
 8321                newArray[x * newHeight + y] = this[x, y];
 322
 2323        _innerArray = newArray;
 2324        _width = newWidth;
 2325        _height = newHeight;
 2326    }
 327
 328    #endregion
 329
 330    #region Utility Methods
 331
 332    private void Initialize(int width, int height)
 333    {
 41334        _width = Math.Max(0, width);
 41335        _height = Math.Max(0, height);
 41336        _innerArray = new T[_width * _height];
 41337    }
 338
 339    private static int NormalizeShift(int shift, int length)
 340    {
 10341        if (length == 0)
 2342            return 0;
 343
 8344        int normalized = shift % length;
 8345        return normalized < 0 ? normalized + length : normalized;
 346    }
 347
 348    private static int WrapForwardIndex(int index, int normalizedShift, int length)
 349    {
 69350        int shifted = index + normalizedShift;
 69351        return shifted >= length ? shifted - length : shifted;
 352    }
 353
 354    private static void GetShiftRange(int length, int shift, out int start, out int end)
 355    {
 4356        long startValue = shift < 0 ? -(long)shift : 0L;
 4357        long endValue = shift > 0 ? (long)length - shift : length;
 358
 4359        start = (int)Math.Min(startValue, length);
 4360        end = (int)Math.Max(0L, Math.Min(endValue, length));
 4361    }
 362
 363    /// <summary>
 364    /// Validates the specified index coordinates.
 365    /// </summary>
 366    public virtual void ValidateIndex(int x, int y)
 367    {
 2000276368        if (x < 0 || x >= _width || y < 0 || y >= _height)
 2369            throw new IndexOutOfRangeException($"Invalid index: ({x}, {y})");
 2000274370    }
 371
 372    /// <summary>
 373    /// Checks if the specified index is valid in the current array dimensions.
 374    /// </summary>
 43375    public virtual bool IsValidIndex(int x, int y) => x >= 0 && x < Width && y >= 0 && y < Height;
 376
 377    /// <summary>
 378    /// Converts 2D coordinates to a flattened index.
 379    /// </summary>
 2000274380    public virtual int GetIndex(int x, int y) => x * _height + y;
 381
 382    /// <summary>
 383    /// Converts the flattened array back to a 2D array representation.
 384    /// </summary>
 385    public T[,] ToArray()
 386    {
 2387        T[,] result = new T[_width, _height];
 12388        for (int x = 0; x < _width; x++)
 24389            for (int y = 0; y < _height; y++)
 8390                result[x, y] = this[x, y];
 2391        return result;
 392    }
 393
 394    /// <summary>
 395    /// Clones a 2D array into a new instance of Array2D.
 396    /// </summary>
 397    public SwiftArray2D<T> Clone()
 398    {
 1399        var array2D = new SwiftArray2D<T>(Width, Height);
 6400        for (int x = 0; x < Width; x++)
 401        {
 12402            for (int y = 0; y < Height; y++)
 4403                array2D[x, y] = this[x, y];
 404        }
 1405        return array2D;
 406    }
 407
 408    #endregion
 409
 410    #region IEnumerator Implementation
 411
 412    /// <summary>
 413    /// Returns an enumerator that iterates through all elements in the 2D array.
 414    /// </summary>
 415    /// <returns>An enumerator for the 2D array.</returns>
 416    public IEnumerator<T> GetEnumerator()
 417    {
 62418        for (int x = 0; x < _width; x++)
 419        {
 180420            for (int y = 0; y < _height; y++)
 421            {
 67422                yield return this[x, y];
 423            }
 424        }
 8425    }
 426
 427    /// <summary>
 428    /// Returns an enumerator that iterates through all elements in the 2D array (non-generic).
 429    /// </summary>
 430    /// <returns>An enumerator for the 2D array.</returns>
 1431    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 432
 433    #endregion
 434}