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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Linq;
#nullable enable
namespace Avalonia.Controls 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); _index = 0;
_path.Add(itemIndex); _path = new[] { groupIndex, itemIndex };
} }
internal IndexPath(IEnumerable<int> indices) public IndexPath(IEnumerable<int>? indices)
{ {
if (indices != null) if (indices != null)
{ {
_path.AddRange(indices); _index = 0;
_path = indices.ToArray();
}
else
{
_index = 0;
_path = null;
} }
} }
public int GetSize() => _path.Count; private IndexPath(int[] basePath, int index)
public int GetAt(int index) => _path[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) public int CompareTo(IndexPath other)
{ {
var rhsPath = other; var rhsPath = other;
int compareResult = 0; int compareResult = 0;
int lhsCount = _path.Count; int lhsCount = GetSize();
int rhsCount = rhsPath._path.Count; int rhsCount = rhsPath.GetSize();
if (lhsCount == 0 || rhsCount == 0) if (lhsCount == 0 || rhsCount == 0)
{ {
@ -52,12 +78,12 @@ namespace Avalonia.Controls
// both paths are non-empty, but can be of different size // both paths are non-empty, but can be of different size
for (int i = 0; i < Math.Min(lhsCount, rhsCount); i++) for (int i = 0; i < Math.Min(lhsCount, rhsCount); i++)
{ {
if (_path[i] < rhsPath._path[i]) if (GetAt(i) < rhsPath.GetAt(i))
{ {
compareResult = -1; compareResult = -1;
break; break;
} }
else if (_path[i] > rhsPath._path[i]) else if (GetAt(i) > rhsPath.GetAt(i))
{ {
compareResult = 1; compareResult = 1;
break; break;
@ -78,14 +104,34 @@ namespace Avalonia.Controls
public IndexPath CloneWithChildIndex(int childIndex) public IndexPath CloneWithChildIndex(int childIndex)
{ {
var newPath = new List<int>(_path); if (_path != null)
newPath.Add(childIndex); {
return new IndexPath(newPath); return new IndexPath(_path, childIndex);
}
else if (_index != 0)
{
return new IndexPath(_index - 1, childIndex);
}
else
{
return new IndexPath(childIndex);
}
} }
public override string ToString() 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); 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 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 get
{ {
IndexPath anchor = null; IndexPath anchor = default;
if (_rootNode.AnchorIndex >= 0) if (_rootNode.AnchorIndex >= 0)
{ {
@ -113,7 +113,7 @@ namespace Avalonia.Controls
{ {
get get
{ {
IndexPath selectedIndex = null; IndexPath selectedIndex = default;
var selectedIndices = SelectedIndices; var selectedIndices = SelectedIndices;
if (selectedIndices?.Count > 0) if (selectedIndices?.Count > 0)
@ -248,7 +248,7 @@ namespace Avalonia.Controls
(infos, index) => // callback for GetAt(index) (infos, index) => // callback for GetAt(index)
{ {
var currentIndex = 0; var currentIndex = 0;
IndexPath path = null; IndexPath path = default;
foreach (var info in infos) foreach (var info in infos)
{ {
@ -505,7 +505,7 @@ namespace Avalonia.Controls
if (resetAnchor) if (resetAnchor)
{ {
AnchorIndex = null; AnchorIndex = default;
} }
if (raiseSelectionChanged) 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) public TreeWalkNodeInfo(SelectionNode node, IndexPath indexPath, SelectionNode parent)
{ {
node = node ?? throw new ArgumentNullException(nameof(node)); node = node ?? throw new ArgumentNullException(nameof(node));
indexPath = indexPath ?? throw new ArgumentNullException(nameof(indexPath));
Node = node; Node = node;
Path = indexPath; Path = indexPath;
@ -167,7 +166,6 @@ namespace Avalonia.Controls.Utils
public TreeWalkNodeInfo(SelectionNode node, IndexPath indexPath) public TreeWalkNodeInfo(SelectionNode node, IndexPath indexPath)
{ {
node = node ?? throw new ArgumentNullException(nameof(node)); node = node ?? throw new ArgumentNullException(nameof(node));
indexPath = indexPath ?? throw new ArgumentNullException(nameof(indexPath));
Node = node; Node = node;
Path = indexPath; 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