Browse Source

Refactor IndexPath.

- #nullable enable
- Make it a `readonly struct`
- Most of the time it will only hold a single `int`, so optimize for the common case by having an `int _index` field
- Make `_path` an array rather than a list as it will be fixed-size
- Implement equality
- Implement operators
pull/3469/head
Steven Kirk 7 years ago
parent
commit
e4c6c85826
  1. 115
      src/Avalonia.Controls/IndexPath.cs
  2. 8
      src/Avalonia.Controls/SelectionModel.cs
  3. 2
      src/Avalonia.Controls/Utils/SelectionTreeHelper.cs
  4. 81
      tests/Avalonia.Controls.UnitTests/IndexPathTests.cs

115
src/Avalonia.Controls/IndexPath.cs

@ -5,42 +5,68 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
#nullable enable
namespace Avalonia.Controls
{
public sealed class IndexPath : IComparable<IndexPath>
public readonly struct IndexPath : IComparable<IndexPath>, IEquatable<IndexPath>
{
private readonly List<int> _path = new List<int>();
public static readonly IndexPath Unselected = default;
private readonly int _index;
private readonly int[]? _path;
internal IndexPath(int index)
public IndexPath(int index)
{
_path.Add(index);
_index = index + 1;
_path = null;
}
internal IndexPath(int groupIndex, int itemIndex)
public IndexPath(int groupIndex, int itemIndex)
{
_path.Add(groupIndex);
_path.Add(itemIndex);
_index = 0;
_path = new[] { groupIndex, itemIndex };
}
internal IndexPath(IEnumerable<int> indices)
public IndexPath(IEnumerable<int>? indices)
{
if (indices != null)
{
_path.AddRange(indices);
_index = 0;
_path = indices.ToArray();
}
else
{
_index = 0;
_path = null;
}
}
public int GetSize() => _path.Count;
public int GetAt(int index) => _path[index];
private IndexPath(int[] basePath, int index)
{
basePath = basePath ?? throw new ArgumentNullException(nameof(basePath));
_index = 0;
_path = new int[basePath.Length + 1];
Array.Copy(basePath, _path, basePath.Length);
_path[basePath.Length] = index;
}
public int GetSize() => _path?.Length ?? (_index == 0 ? 0 : 1);
public int GetAt(int index)
{
return _path?[index] ?? (_index - 1);
}
public int CompareTo(IndexPath other)
{
var rhsPath = other;
int compareResult = 0;
int lhsCount = _path.Count;
int rhsCount = rhsPath._path.Count;
int lhsCount = GetSize();
int rhsCount = rhsPath.GetSize();
if (lhsCount == 0 || rhsCount == 0)
{
@ -52,12 +78,12 @@ namespace Avalonia.Controls
// both paths are non-empty, but can be of different size
for (int i = 0; i < Math.Min(lhsCount, rhsCount); i++)
{
if (_path[i] < rhsPath._path[i])
if (GetAt(i) < rhsPath.GetAt(i))
{
compareResult = -1;
break;
}
else if (_path[i] > rhsPath._path[i])
else if (GetAt(i) > rhsPath.GetAt(i))
{
compareResult = 1;
break;
@ -78,14 +104,34 @@ namespace Avalonia.Controls
public IndexPath CloneWithChildIndex(int childIndex)
{
var newPath = new List<int>(_path);
newPath.Add(childIndex);
return new IndexPath(newPath);
if (_path != null)
{
return new IndexPath(_path, childIndex);
}
else if (_index != 0)
{
return new IndexPath(_index - 1, childIndex);
}
else
{
return new IndexPath(childIndex);
}
}
public override string ToString()
{
return "R." + string.Join(".", _path);
if (_path != null)
{
return "R" + string.Join(".", _path);
}
else if (_index != 0)
{
return "R" + (_index - 1);
}
else
{
return "R";
}
}
public static IndexPath CreateFrom(int index) => new IndexPath(index);
@ -94,5 +140,34 @@ namespace Avalonia.Controls
public static IndexPath CreateFromIndices(IList<int> indices) => new IndexPath(indices);
public override bool Equals(object obj) => obj is IndexPath other && Equals(other);
public bool Equals(IndexPath other) => CompareTo(other) == 0;
public override int GetHashCode()
{
var hashCode = -504981047;
if (_path != null)
{
foreach (var i in _path)
{
hashCode = hashCode * -1521134295 + i.GetHashCode();
}
}
else
{
hashCode = hashCode * -1521134295 + _index.GetHashCode();
}
return hashCode;
}
public static bool operator <(IndexPath x, IndexPath y) { return x.CompareTo(y) < 0; }
public static bool operator >(IndexPath x, IndexPath y) { return x.CompareTo(y) > 0; }
public static bool operator <=(IndexPath x, IndexPath y) { return x.CompareTo(y) <= 0; }
public static bool operator >=(IndexPath x, IndexPath y) { return x.CompareTo(y) >= 0; }
public static bool operator ==(IndexPath x, IndexPath y) { return x.CompareTo(y) == 0; }
public static bool operator !=(IndexPath x, IndexPath y) { return x.CompareTo(y) != 0; }
}
}

8
src/Avalonia.Controls/SelectionModel.cs

@ -72,7 +72,7 @@ namespace Avalonia.Controls
{
get
{
IndexPath anchor = null;
IndexPath anchor = default;
if (_rootNode.AnchorIndex >= 0)
{
@ -113,7 +113,7 @@ namespace Avalonia.Controls
{
get
{
IndexPath selectedIndex = null;
IndexPath selectedIndex = default;
var selectedIndices = SelectedIndices;
if (selectedIndices?.Count > 0)
@ -248,7 +248,7 @@ namespace Avalonia.Controls
(infos, index) => // callback for GetAt(index)
{
var currentIndex = 0;
IndexPath path = null;
IndexPath path = default;
foreach (var info in infos)
{
@ -505,7 +505,7 @@ namespace Avalonia.Controls
if (resetAnchor)
{
AnchorIndex = null;
AnchorIndex = default;
}
if (raiseSelectionChanged)

2
src/Avalonia.Controls/Utils/SelectionTreeHelper.cs

@ -157,7 +157,6 @@ namespace Avalonia.Controls.Utils
public TreeWalkNodeInfo(SelectionNode node, IndexPath indexPath, SelectionNode parent)
{
node = node ?? throw new ArgumentNullException(nameof(node));
indexPath = indexPath ?? throw new ArgumentNullException(nameof(indexPath));
Node = node;
Path = indexPath;
@ -167,7 +166,6 @@ namespace Avalonia.Controls.Utils
public TreeWalkNodeInfo(SelectionNode node, IndexPath indexPath)
{
node = node ?? throw new ArgumentNullException(nameof(node));
indexPath = indexPath ?? throw new ArgumentNullException(nameof(indexPath));
Node = node;
Path = indexPath;

81
tests/Avalonia.Controls.UnitTests/IndexPathTests.cs

@ -0,0 +1,81 @@
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class IndexPathTests
{
[Fact]
public void Simple_Index()
{
var a = new IndexPath(1);
Assert.Equal(1, a.GetSize());
Assert.Equal(1, a.GetAt(0));
}
[Fact]
public void Equal_Paths()
{
var a = new IndexPath(1);
var b = new IndexPath(1);
Assert.True(a == b);
Assert.False(a != b);
Assert.True(a.Equals(b));
Assert.Equal(0, a.CompareTo(b));
Assert.Equal(a.GetHashCode(), b.GetHashCode());
}
[Fact]
public void Unequal_Paths()
{
var a = new IndexPath(1);
var b = new IndexPath(2);
Assert.False(a == b);
Assert.True(a != b);
Assert.False(a.Equals(b));
Assert.Equal(-1, a.CompareTo(b));
Assert.NotEqual(a.GetHashCode(), b.GetHashCode());
}
[Fact]
public void Equal_Null_Path()
{
var a = new IndexPath(null);
var b = new IndexPath(null);
Assert.True(a == b);
Assert.False(a != b);
Assert.True(a.Equals(b));
Assert.Equal(0, a.CompareTo(b));
Assert.Equal(a.GetHashCode(), b.GetHashCode());
}
[Fact]
public void Unequal_Null_Path()
{
var a = new IndexPath(null);
var b = new IndexPath(2);
Assert.False(a == b);
Assert.True(a != b);
Assert.False(a.Equals(b));
Assert.Equal(-1, a.CompareTo(b));
Assert.NotEqual(a.GetHashCode(), b.GetHashCode());
}
[Fact]
public void Default_Is_Null_Path()
{
var a = new IndexPath(null);
var b = default(IndexPath);
Assert.True(a == b);
Assert.False(a != b);
Assert.True(a.Equals(b));
Assert.Equal(0, a.CompareTo(b));
Assert.Equal(a.GetHashCode(), b.GetHashCode());
}
}
}
Loading…
Cancel
Save