Skip to content

Commit 27c66f4

Browse files
author
msftbot[bot]
authored
Deprecated 2D array extensions from Microsoft.Toolkit (#3444)
## Part of #3062, related to #3435 <!-- Add the relevant issue number after the "#" mentioned above (for ex: Fixes #1234) which will automatically close the issue once the PR is merged. --> <!-- Add a brief overview here of the feature/bug & fix. --> ## PR Type What kind of change does this PR introduce? <!-- Please uncomment one or more that apply to this PR. --> - Optimization - Deprecation <!-- - Bugfix --> <!-- - Feature --> <!-- - Code style update (formatting) --> <!-- - Refactoring (no functional changes, no api changes) --> <!-- - Build or CI related changes --> <!-- - Documentation content changes --> <!-- - Sample app changes --> <!-- - Other... Please describe: --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying, or link to a relevant issue. --> The `Microsoft.Toolkit` package has a number of extensions for 2D arrays that are inefficient (they're both a bit slow and causing unnecessary memory allocations) and replaced by equivalent APIs in the `Microsoft.Toolkit.HighPerformance` package. https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/30452cf0bbf627f12825cf50bbd31b0e526b9abe/Microsoft.Toolkit/Extensions/ArrayExtensions.cs#L27 https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/30452cf0bbf627f12825cf50bbd31b0e526b9abe/Microsoft.Toolkit/Extensions/ArrayExtensions.cs#L48 https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/30452cf0bbf627f12825cf50bbd31b0e526b9abe/Microsoft.Toolkit/Extensions/ArrayExtensions.cs#L68 ## What is the new behavior? <!-- Describe how was this issue resolved or changed? --> This PR makes these APIs obsolete, and also includes a number of small optimizations to existing code that was added during the refactoring to remove dependencies on these APIs, so that it'll be possible to remove them in the future with no issues. ## PR Checklist Please check if your PR fulfills the following requirements: - [X] Tested code with current [supported SDKs](../readme.md#supported) - [ ] Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). Link: <!-- docs PR link --> - [ ] ~~Sample in sample app has been added / updated (for bug fixes / features)~~ - [ ] ~~Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets)~~ - [X] Tests for the changes have been added (for bug fixes / features) (if applicable) - [X] Header has been added to all new source files (run *build/UpdateHeaders.bat*) - [X] Contains **NO** breaking changes
2 parents 9087194 + 162962a commit 27c66f4

File tree

6 files changed

+215
-341
lines changed

6 files changed

+215
-341
lines changed

Microsoft.Toolkit.Uwp.UI.Controls/UniformGrid/TakenSpotsReferenceHolder.cs

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System;
6-
using System.Collections.Generic;
7-
using System.Linq;
8-
using System.Text;
9-
using System.Threading.Tasks;
5+
using System.Collections;
6+
using System.Drawing;
7+
using Microsoft.Toolkit.Diagnostics;
108

119
namespace Microsoft.Toolkit.Uwp.UI.Controls
1210
{
@@ -16,23 +14,81 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
1614
/// <see cref="UniformGrid.GetFreeSpot"/> iterator.
1715
/// This is used so we can better isolate our logic and make it easier to test.
1816
/// </summary>
19-
internal class TakenSpotsReferenceHolder
17+
internal sealed class TakenSpotsReferenceHolder
2018
{
2119
/// <summary>
22-
/// Gets or sets the array to hold taken spots.
23-
/// True value indicates an item in the layout is fixed to that position.
24-
/// False values indicate free openings where an item can be placed.
20+
/// The <see cref="BitArray"/> instance used to efficiently track empty spots.
2521
/// </summary>
26-
public bool[,] SpotsTaken { get; set; }
22+
private readonly BitArray spotsTaken;
2723

24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="TakenSpotsReferenceHolder"/> class.
26+
/// </summary>
27+
/// <param name="rows">The number of rows to track.</param>
28+
/// <param name="columns">The number of columns to track.</param>
2829
public TakenSpotsReferenceHolder(int rows, int columns)
2930
{
30-
SpotsTaken = new bool[rows, columns];
31+
Guard.IsGreaterThanOrEqualTo(rows, 0, nameof(rows));
32+
Guard.IsGreaterThanOrEqualTo(columns, 0, nameof(columns));
33+
34+
Height = rows;
35+
Width = columns;
36+
37+
this.spotsTaken = new BitArray(rows * columns);
38+
}
39+
40+
/// <summary>
41+
/// Gets the height of the grid to monitor.
42+
/// </summary>
43+
public int Height { get; }
44+
45+
/// <summary>
46+
/// Gets the width of the grid to monitor.
47+
/// </summary>
48+
public int Width { get; }
49+
50+
/// <summary>
51+
/// Gets or sets the value of a specified grid cell.
52+
/// </summary>
53+
/// <param name="i">The vertical offset.</param>
54+
/// <param name="j">The horizontal offset.</param>
55+
public bool this[int i, int j]
56+
{
57+
get => this.spotsTaken[(i * Width) + j];
58+
set => this.spotsTaken[(i * Width) + j] = value;
59+
}
60+
61+
/// <summary>
62+
/// Fills the specified area in the current grid with a given value.
63+
/// If invalid coordinates are given, they will simply be ignored and no exception will be thrown.
64+
/// </summary>
65+
/// <param name="value">The value to fill the target area with.</param>
66+
/// <param name="row">The row to start on (inclusive, 0-based index).</param>
67+
/// <param name="column">The column to start on (inclusive, 0-based index).</param>
68+
/// <param name="width">The positive width of area to fill.</param>
69+
/// <param name="height">The positive height of area to fill.</param>
70+
public void Fill(bool value, int row, int column, int width, int height)
71+
{
72+
Rectangle bounds = new Rectangle(0, 0, Width, Height);
73+
74+
// Precompute bounds to skip branching in main loop
75+
bounds.Intersect(new Rectangle(column, row, width, height));
76+
77+
for (int i = bounds.Top; i < bounds.Bottom; i++)
78+
{
79+
for (int j = bounds.Left; j < bounds.Right; j++)
80+
{
81+
this[i, j] = value;
82+
}
83+
}
3184
}
3285

33-
public TakenSpotsReferenceHolder(bool[,] array)
86+
/// <summary>
87+
/// Resets the current reference holder.
88+
/// </summary>
89+
public void Reset()
3490
{
35-
SpotsTaken = array;
91+
this.spotsTaken.SetAll(false);
3692
}
3793
}
3894
}

Microsoft.Toolkit.Uwp.UI.Controls/UniformGrid/UniformGrid.Helpers.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ public partial class UniformGrid : Grid
2222
{
2323
if (topdown)
2424
{
25-
var rows = arrayref.SpotsTaken.GetLength(0);
25+
var rows = arrayref.Height;
2626

2727
// Layout spots from Top-Bottom, Left-Right (right-left handled automatically by Grid with Flow-Direction).
2828
// Effectively transpose the Grid Layout.
29-
for (int c = 0; c < arrayref.SpotsTaken.GetLength(1); c++)
29+
for (int c = 0; c < arrayref.Width; c++)
3030
{
3131
int start = (c == 0 && firstcolumn > 0 && firstcolumn < rows) ? firstcolumn : 0;
3232
for (int r = start; r < rows; r++)
3333
{
34-
if (!arrayref.SpotsTaken[r, c])
34+
if (!arrayref[r, c])
3535
{
3636
yield return (r, c);
3737
}
@@ -40,17 +40,17 @@ public partial class UniformGrid : Grid
4040
}
4141
else
4242
{
43-
var columns = arrayref.SpotsTaken.GetLength(1);
43+
var columns = arrayref.Width;
4444

4545
// Layout spots as normal from Left-Right.
4646
// (right-left handled automatically by Grid with Flow-Direction
4747
// during its layout, internal model is always left-right).
48-
for (int r = 0; r < arrayref.SpotsTaken.GetLength(0); r++)
48+
for (int r = 0; r < arrayref.Height; r++)
4949
{
5050
int start = (r == 0 && firstcolumn > 0 && firstcolumn < columns) ? firstcolumn : 0;
5151
for (int c = start; c < columns; c++)
5252
{
53-
if (!arrayref.SpotsTaken[r, c])
53+
if (!arrayref[r, c])
5454
{
5555
yield return (r, c);
5656
}

Microsoft.Toolkit.Uwp.UI.Controls/UniformGrid/UniformGrid.cs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Linq;
8-
using Microsoft.Toolkit.Extensions;
98
using Windows.Foundation;
109
using Windows.UI.Xaml;
1110
using Windows.UI.Xaml.Controls;
@@ -20,6 +19,11 @@ public partial class UniformGrid : Grid
2019
// Internal list we use to keep track of items that we don't have space to layout.
2120
private List<UIElement> _overflow = new List<UIElement>();
2221

22+
/// <summary>
23+
/// The <see cref="TakenSpotsReferenceHolder"/> instance in use, if any.
24+
/// </summary>
25+
private TakenSpotsReferenceHolder _spotref;
26+
2327
/// <inheritdoc/>
2428
protected override Size MeasureOverride(Size availableSize)
2529
{
@@ -36,7 +40,20 @@ protected override Size MeasureOverride(Size availableSize)
3640
SetupRowDefinitions(rows);
3741
SetupColumnDefinitions(columns);
3842

39-
var spotref = new TakenSpotsReferenceHolder(rows, columns);
43+
TakenSpotsReferenceHolder spotref;
44+
45+
// If the last spot holder matches the size currently in use, just reset
46+
// that instance and reuse it to avoid allocating a new bit array.
47+
if (_spotref != null && _spotref.Height == rows && _spotref.Width == columns)
48+
{
49+
spotref = _spotref;
50+
51+
spotref.Reset();
52+
}
53+
else
54+
{
55+
spotref = _spotref = new TakenSpotsReferenceHolder(rows, columns);
56+
}
4057

4158
// Figure out which children we should automatically layout and where available openings are.
4259
foreach (var child in visible)
@@ -56,7 +73,8 @@ protected override Size MeasureOverride(Size availableSize)
5673
else
5774
{
5875
SetAutoLayout(child, false);
59-
spotref.SpotsTaken.Fill(true, row, col, colspan, rowspan); // row, col, width, height
76+
77+
spotref.Fill(true, row, col, colspan, rowspan);
6078
}
6179
}
6280

@@ -100,7 +118,7 @@ protected override Size MeasureOverride(Size availableSize)
100118
if (rowspan > 1 || colspan > 1)
101119
{
102120
// TODO: Need to tie this into iterator
103-
spotref.SpotsTaken.Fill(true, row, column, GetColumnSpan(child), GetRowSpan(child)); // row, col, width, height
121+
spotref.Fill(true, row, column, colspan, rowspan);
104122
}
105123
}
106124
else
@@ -135,7 +153,7 @@ protected override Size MeasureOverride(Size availableSize)
135153
}
136154

137155
// Return our desired size based on the largest child we found, our dimensions, and spacing.
138-
var desiredSize = new Size((maxWidth * (double)columns) + columnSpacingSize, (maxHeight * (double)rows) + rowSpacingSize);
156+
var desiredSize = new Size((maxWidth * columns) + columnSpacingSize, (maxHeight * rows) + rowSpacingSize);
139157

140158
// Required to perform regular grid measurement, but ignore result.
141159
base.MeasureOverride(desiredSize);

0 commit comments

Comments
 (0)