< 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: 435
Line coverage: 100%
Branch coverage
100%
Covered branches: 66
Total branches: 66
Branch coverage: 100%
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(...)100%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(...)100%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 System;
 9using System.Collections;
 10using System.Collections.Generic;
 11using System.Text.Json.Serialization;
 12using Chronicler;
 13using Chronicler.Diagnostics;
 14using Chronicler.Serialization;
 15using MemoryPack;
 16
 17namespace SwiftCollections.Dimensions;
 18
 19/// <summary>
 20/// Represents a flattened 2D array with dynamic resizing and efficient access.
 21/// </summary>
 22/// <typeparam name="T">The type of elements in the array.</typeparam>
 23[Serializable]
 24[JsonConverter(typeof(StateJsonConverterFactory))]
 25[MemoryPackable]
 26public partial class SwiftArray2D<T> : IStateBacked<Array2DState<T>>, IEnumerable<T>, IEnumerable
 27{
 28    #region Fields
 29
 30    private T[] _innerArray;
 31
 32    private int _width;
 33
 34    private int _height;
 35
 36    #endregion
 37
 38    #region Constructors
 39
 40    /// <summary>
 41    /// Initializes a new instance of the <see cref="SwiftArray2D{T}"/> class.
 42    /// </summary>
 843    public SwiftArray2D() : this(0, 0) { }
 44
 45    /// <summary>
 46    /// Initializes a new instance of the <see cref="SwiftArray2D{T}"/> class with specified dimensions.
 47    /// </summary>
 4248    public SwiftArray2D(int width, int height)
 49    {
 4250        Initialize(width, height);
 51
 52        // Ensure that the inner array is not null after initialization
 4253        ChronicleGuard.ThrowIfNull(_innerArray, nameof(_innerArray));
 4254    }
 55
 56    /// <summary>
 57    /// Initializes a new instance of the <see cref="SwiftArray2D{T}"/> class with specified dimensions and default valu
 58    /// </summary>
 1259    public SwiftArray2D(int width, int height, T defaultValue) : this(width, height)
 60    {
 1261        Fill(defaultValue);
 1262    }
 63
 64    /// <summary>
 65    /// Initializes a new instance of the SwiftArray2D class by copying the contents of the specified two-dimensional ar
 66    /// </summary>
 67    /// <remarks>
 68    /// The dimensions of the new SwiftArray2D instance match those of the source array.
 69    /// Changes to the source array after construction do not affect the SwiftArray2D instance.
 70    /// </remarks>
 71    /// <param name="source">The two-dimensional array whose elements are copied to the new SwiftArray2D instance. Canno
 372    public SwiftArray2D(T[,] source)
 73    {
 374        int width = source.GetLength(0);
 375        int height = source.GetLength(1);
 76
 377        Initialize(width, height);
 78
 379        ChronicleGuard.ThrowIfNull(_innerArray, nameof(_innerArray));
 80
 1881        for (int x = 0; x < width; x++)
 3682            for (int y = 0; y < height; y++)
 1283                this[x, y] = source[x, y];
 384    }
 85
 86    /// <summary>
 87    /// Initializes a new instance of the SwiftArray2D class with the specified array state.
 88    /// </summary>
 89    /// <param name="state">The state object that encapsulates the underlying data and configuration for the two-dimensi
 90    [MemoryPackConstructor]
 791    public SwiftArray2D(Array2DState<T> state)
 92    {
 793        State = state;
 94
 795        ChronicleGuard.ThrowIfNull(_innerArray, nameof(_innerArray));
 796    }
 97
 98    #endregion
 99
 100    #region Properties
 101
 102    /// <summary>
 103    /// Gets the width of the 2D array.
 104    /// </summary>
 105    [JsonIgnore]
 106    [MemoryPackIgnore]
 72107    public int Width => _width;
 108
 109    /// <summary>
 110    /// Gets the height of the 2D array.
 111    /// </summary>
 112    [JsonIgnore]
 113    [MemoryPackIgnore]
 84114    public int Height => _height;
 115
 116    /// <summary>
 117    /// Gets the underlying flattened array for direct access.
 118    /// </summary>
 119    [JsonIgnore]
 120    [MemoryPackIgnore]
 119121    public T[] InnerArray => _innerArray;
 122
 123    /// <summary>
 124    /// Total size of the array
 125    /// </summary>
 126    [JsonIgnore]
 127    [MemoryPackIgnore]
 1128    public int Size => _width * _height;
 129
 130    /// <inheritdoc cref="Array.Length" />
 131    [JsonIgnore]
 132    [MemoryPackIgnore]
 9133    public int Length => _innerArray.Length;
 134
 135    /// <summary>
 136    /// Gets or sets the element at the specified position in the 2D array.
 137    /// </summary>
 138    /// <param name="x">The zero-based X coordinate.</param>
 139    /// <param name="y">The zero-based Y coordinate.</param>
 140    [JsonIgnore]
 141    [MemoryPackIgnore]
 142    public T this[int x, int y]
 143    {
 144        get
 145        {
 1000197146            ValidateIndex(x, y);
 1000193147            return _innerArray[GetIndex(x, y)];
 148        }
 149        set
 150        {
 1000089151            ValidateIndex(x, y);
 1000089152            _innerArray[GetIndex(x, y)] = value;
 1000089153        }
 154    }
 155
 156    /// <summary>
 157    /// Gets or sets the current state of the two-dimensional array, including its dimensions and data.
 158    /// </summary>
 159    /// <remarks>
 160    /// The returned state is a snapshot and is independent of future changes to the array.
 161    /// Setting this property replaces the entire contents and dimensions of the array with those from the provided stat
 162    /// This property is intended for serialization and deserialization scenarios.
 163    /// </remarks>
 164    [JsonInclude]
 165    [MemoryPackInclude]
 166    public Array2DState<T> State
 167    {
 168        get
 169        {
 7170            var data = new T[_innerArray.Length];
 7171            Array.Copy(_innerArray, data, data.Length);
 172
 7173            return new Array2DState<T>(
 7174                _width,
 7175                _height,
 7176                data
 7177            );
 178        }
 179
 180        internal set
 181        {
 7182            ChronicleGuard.ThrowIfNull(value.Data, nameof(value.Data));
 183
 7184            _width = value.Width;
 7185            _height = value.Height;
 186
 7187            int capacity = value.Data.Length;
 188
 7189            _innerArray = new T[capacity];
 7190            Array.Copy(value.Data, _innerArray, capacity);
 7191        }
 192    }
 193
 194    #endregion
 195
 196    #region Collection Management
 197
 198    /// <summary>
 199    /// Overwrites the current 2D array with the provided source.
 200    /// </summary>
 201    /// <remarks>
 202    /// Existing backing storage is reused when the flattened source length matches.
 203    /// </remarks>
 204    /// <param name="source">The source values to copy.</param>
 205    public void AddRange(T[,] source)
 206    {
 3207        ChronicleGuard.ThrowIfNull(source, nameof(source));
 208
 3209        int width = source.GetLength(0);
 3210        int height = source.GetLength(1);
 3211        int length = source.Length;
 212
 3213        if (_innerArray.Length != length)
 2214            _innerArray = new T[length];
 215
 3216        _width = width;
 3217        _height = height;
 218
 3219        int index = 0;
 18220        for (int x = 0; x < width; x++)
 221        {
 36222            for (int y = 0; y < height; y++)
 12223                _innerArray[index++] = source[x, y];
 224        }
 3225    }
 226
 227    /// <summary>
 228    /// Fills the array with the specified value.
 229    /// </summary>
 230    public void Fill(T value)
 231    {
 4000304232        for (int i = 0; i < _innerArray.Length; i++)
 2000137233            _innerArray[i] = value;
 15234    }
 235
 236    /// <summary>
 237    /// Shifts the elements in the array by the specified X and Y offsets.
 238    /// </summary>
 239    /// <param name="xShift">The offset to apply along the X-axis.</param>
 240    /// <param name="yShift">The offset to apply along the Y-axis.</param>
 241    /// <param name="wrap">
 242    /// Specifies whether to wrap elements that exceed the array's boundaries.
 243    /// If <c>true</c>, values wrap around to the other side of the array.
 244    /// If <c>false</c>, values that exceed boundaries are discarded.
 245    /// </param>
 246    public void Shift(int xShift, int yShift, bool wrap = true)
 247    {
 7248        T[] newArray = new T[_innerArray.Length];
 249
 7250        if (wrap)
 5251            ShiftWrapped(newArray, xShift, yShift);
 252        else
 2253            ShiftClamped(newArray, xShift, yShift);
 254
 7255        _innerArray = newArray;
 7256    }
 257
 258    private void ShiftWrapped(T[] newArray, int xShift, int yShift)
 259    {
 5260        int normalizedXShift = NormalizeShift(xShift, _width);
 5261        int normalizedYShift = NormalizeShift(yShift, _height);
 262
 38263        for (int x = 0; x < _width; x++)
 264        {
 14265            int newX = WrapForwardIndex(x, normalizedXShift, _width);
 14266            int sourceBase = x * _height;
 14267            int targetBase = newX * _height;
 268
 138269            for (int y = 0; y < _height; y++)
 270            {
 55271                int newY = WrapForwardIndex(y, normalizedYShift, _height);
 55272                newArray[targetBase + newY] = _innerArray[sourceBase + y];
 273            }
 274        }
 5275    }
 276
 277    private void ShiftClamped(T[] newArray, int xShift, int yShift)
 278    {
 2279        GetShiftRange(_width, xShift, out int xStart, out int xEnd);
 2280        GetShiftRange(_height, yShift, out int yStart, out int yEnd);
 281
 8282        for (int x = xStart; x < xEnd; x++)
 283        {
 2284            int newX = x + xShift;
 2285            int sourceBase = x * _height;
 2286            int targetBase = newX * _height;
 287
 12288            for (int y = yStart; y < yEnd; y++)
 289            {
 4290                int newY = y + yShift;
 4291                newArray[targetBase + newY] = _innerArray[sourceBase + y];
 292            }
 293        }
 2294    }
 295
 296    /// <summary>
 297    /// Clears all elements in the array.
 298    /// </summary>
 1299    public void Clear() => Array.Clear(_innerArray, 0, _innerArray.Length);
 300
 301    #endregion
 302
 303    #region Capacity Management
 304
 305    /// <summary>
 306    /// Resizes the 2D array to new dimensions, preserving existing values within the new bounds.
 307    /// </summary>
 308    public void Resize(int newWidth, int newHeight)
 309    {
 3310        ChronicleGuard.ThrowIfNegative(newWidth, nameof(newWidth));
 3311        ChronicleGuard.ThrowIfNegative(newHeight, nameof(newHeight));
 312
 3313        if (newWidth == _width && newHeight == _height)
 1314            return;
 315
 2316        T[] newArray = new T[newWidth * newHeight];
 2317        int minWidth = Math.Min(_width, newWidth);
 2318        int minHeight = Math.Min(_height, newHeight);
 319
 12320        for (int x = 0; x < minWidth; x++)
 24321            for (int y = 0; y < minHeight; y++)
 8322                newArray[x * newHeight + y] = this[x, y];
 323
 2324        _innerArray = newArray;
 2325        _width = newWidth;
 2326        _height = newHeight;
 2327    }
 328
 329    #endregion
 330
 331    #region Utility Methods
 332
 333    private void Initialize(int width, int height)
 334    {
 45335        _width = Math.Max(0, width);
 45336        _height = Math.Max(0, height);
 45337        _innerArray = new T[_width * _height];
 45338    }
 339
 340    private static int NormalizeShift(int shift, int length)
 341    {
 10342        if (length == 0)
 2343            return 0;
 344
 8345        int normalized = shift % length;
 8346        return normalized < 0 ? normalized + length : normalized;
 347    }
 348
 349    private static int WrapForwardIndex(int index, int normalizedShift, int length)
 350    {
 69351        int shifted = index + normalizedShift;
 69352        return shifted >= length ? shifted - length : shifted;
 353    }
 354
 355    private static void GetShiftRange(int length, int shift, out int start, out int end)
 356    {
 4357        long startValue = shift < 0 ? -(long)shift : 0L;
 4358        long endValue = shift > 0 ? (long)length - shift : length;
 359
 4360        start = (int)Math.Min(startValue, length);
 4361        end = (int)Math.Max(0L, Math.Min(endValue, length));
 4362    }
 363
 364    /// <summary>
 365    /// Validates the specified index coordinates.
 366    /// </summary>
 367    public virtual void ValidateIndex(int x, int y)
 368    {
 2000286369        if (x < 0 || x >= _width || y < 0 || y >= _height)
 4370            throw new IndexOutOfRangeException($"Invalid index: ({x}, {y})");
 2000282371    }
 372
 373    /// <summary>
 374    /// Checks if the specified index is valid in the current array dimensions.
 375    /// </summary>
 43376    public virtual bool IsValidIndex(int x, int y) => x >= 0 && x < Width && y >= 0 && y < Height;
 377
 378    /// <summary>
 379    /// Converts 2D coordinates to a flattened index.
 380    /// </summary>
 2000282381    public virtual int GetIndex(int x, int y) => x * _height + y;
 382
 383    /// <summary>
 384    /// Converts the flattened array back to a 2D array representation.
 385    /// </summary>
 386    public T[,] ToArray()
 387    {
 2388        T[,] result = new T[_width, _height];
 12389        for (int x = 0; x < _width; x++)
 24390            for (int y = 0; y < _height; y++)
 8391                result[x, y] = this[x, y];
 2392        return result;
 393    }
 394
 395    /// <summary>
 396    /// Clones a 2D array into a new instance of Array2D.
 397    /// </summary>
 398    public SwiftArray2D<T> Clone()
 399    {
 1400        var array2D = new SwiftArray2D<T>(Width, Height);
 6401        for (int x = 0; x < Width; x++)
 402        {
 12403            for (int y = 0; y < Height; y++)
 4404                array2D[x, y] = this[x, y];
 405        }
 1406        return array2D;
 407    }
 408
 409    #endregion
 410
 411    #region IEnumerator Implementation
 412
 413    /// <summary>
 414    /// Returns an enumerator that iterates through all elements in the 2D array.
 415    /// </summary>
 416    /// <returns>An enumerator for the 2D array.</returns>
 417    public IEnumerator<T> GetEnumerator()
 418    {
 76419        for (int x = 0; x < _width; x++)
 420        {
 196421            for (int y = 0; y < _height; y++)
 422            {
 71423                yield return this[x, y];
 424            }
 425        }
 11426    }
 427
 428    /// <summary>
 429    /// Returns an enumerator that iterates through all elements in the 2D array (non-generic).
 430    /// </summary>
 431    /// <returns>An enumerator for the 2D array.</returns>
 4432    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 433
 434    #endregion
 435}