Skip to content

Commit

Permalink
Merge pull request #3893 from smoogipoo/weaklist-improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
peppy committed Sep 25, 2020
2 parents f0a0ecc + ba2f666 commit 65424f4
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 106 deletions.
17 changes: 17 additions & 0 deletions osu.Framework.Benchmarks/BenchmarkWeakList.cs
Expand Up @@ -41,6 +41,23 @@ public void RemoveAllIteratively()
weakList.Remove(objects[i]);
}

[Benchmark]
public void RemoveAllStaggered()
{
init();

if (ItemCount == 0)
return;

weakList.Remove(objects[ItemCount / 2]);

for (int i = 1; i < ItemCount / 2; i++)
{
weakList.Remove(objects[ItemCount / 2 - i]);
weakList.Remove(objects[ItemCount / 2 + i]);
}
}

[Benchmark]
public void Clear()
{
Expand Down
10 changes: 6 additions & 4 deletions osu.Framework/Lists/LockedWeakList.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
Expand Down Expand Up @@ -70,11 +72,11 @@ public void Clear()

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public struct Enumerator : IEnumerator<T>
public struct Enumerator : IEnumerator<T?>
{
private readonly WeakList<T> list;

private WeakList<T>.Enumerator listEnumerator;
private WeakList<T>.ValidItemsEnumerator listEnumerator;

private readonly bool lockTaken;

Expand All @@ -92,9 +94,9 @@ internal Enumerator(WeakList<T> list)

public void Reset() => listEnumerator.Reset();

public readonly T Current => listEnumerator.Current;
public readonly T? Current => listEnumerator.Current;

readonly object IEnumerator.Current => Current;
readonly object? IEnumerator.Current => Current;

public void Dispose()
{
Expand Down
157 changes: 55 additions & 102 deletions osu.Framework/Lists/WeakList.cs
@@ -1,19 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;

namespace osu.Framework.Lists
{
/// <summary>
/// A list maintaining weak reference of objects.
/// </summary>
/// <typeparam name="T">Type of items tracked by weak reference.</typeparam>
public class WeakList<T> : IWeakList<T>, IEnumerable<T>
public partial class WeakList<T> : IWeakList<T>, IEnumerable<T>
where T : class
{
private readonly List<InvalidatableWeakReference> list = new List<InvalidatableWeakReference>();
Expand All @@ -36,14 +36,25 @@ private void add(in InvalidatableWeakReference item)

public bool Remove(T item)
{
var enumerator = GetEnumeratorNoTrim();
int hashCode = EqualityComparer<T>.Default.GetHashCode(item);

while (enumerator.MoveNext())
for (int i = listStart; i < listEnd; i++)
{
if (enumerator.Current != item)
var reference = list[i].Reference;

// Check if the object is valid.
if (reference == null)
continue;

// Compare by hash code (fast).
if (list[i].ObjectHashCode != hashCode)
continue;

// Compare by object equality (slow).
if (!reference.TryGetTarget(out var target) || target != item)
continue;

RemoveAt(enumerator.CurrentOffset);
RemoveAt(i - listStart);
return true;
}

Expand All @@ -52,14 +63,13 @@ public bool Remove(T item)

public bool Remove(WeakReference<T> weakReference)
{
var enumerator = GetEnumeratorNoTrim();

while (enumerator.MoveNext())
for (int i = listStart; i < listEnd; i++)
{
if (enumerator.CurrentReference != weakReference)
// Check if the object is valid.
if (list[i].Reference != weakReference)
continue;

RemoveAt(enumerator.CurrentOffset);
RemoveAt(i - listStart);
return true;
}

Expand All @@ -84,24 +94,36 @@ public void RemoveAt(int index)

public bool Contains(T item)
{
var enumerator = GetEnumeratorNoTrim();
int hashCode = EqualityComparer<T>.Default.GetHashCode(item);

while (enumerator.MoveNext())
for (int i = listStart; i < listEnd; i++)
{
if (enumerator.Current == item)
return true;
var reference = list[i].Reference;

// Check if the object is valid.
if (reference == null)
continue;

// Compare by hash code (fast).
if (list[i].ObjectHashCode != hashCode)
continue;

// Compare by object equality (slow).
if (!reference.TryGetTarget(out var target) || target != item)
continue;

return true;
}

return false;
}

public bool Contains(WeakReference<T> weakReference)
{
var enumerator = GetEnumeratorNoTrim();

while (enumerator.MoveNext())
for (int i = listStart; i < listEnd; i++)
{
if (enumerator.CurrentReference == weakReference)
// Check if the object is valid.
if (list[i].Reference == weakReference)
return true;
}

Expand All @@ -110,7 +132,7 @@ public bool Contains(WeakReference<T> weakReference)

public void Clear() => listStart = listEnd = 0;

public Enumerator GetEnumerator()
public ValidItemsEnumerator GetEnumerator()
{
// Trim from the sides - items that have been removed.
list.RemoveRange(listEnd, list.Count - listEnd);
Expand All @@ -123,101 +145,32 @@ public Enumerator GetEnumerator()
listStart = 0;
listEnd = list.Count;

return GetEnumeratorNoTrim();
return new ValidItemsEnumerator(this);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator GetEnumeratorNoTrim() => new Enumerator(this);

IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public struct Enumerator : IEnumerator<T>
private readonly struct InvalidatableWeakReference
{
private WeakList<T> weakList;
private T currentObject;

internal Enumerator(WeakList<T> weakList)
{
this.weakList = weakList;

CurrentOffset = -1; // The first MoveNext() should bring the iterator to the start
CurrentReference = null;
currentObject = null;
}

public bool MoveNext()
{
while (true)
{
++CurrentOffset;

int index = weakList.listStart + CurrentOffset;

// Check whether we're still within the valid range of the list.
if (index >= weakList.listEnd)
return false;

var weakReference = weakList.list[index].Reference;

// Check whether the reference exists.
if (weakReference == null)
{
// If the reference doesn't exist, it must have previously been removed and can be skipped.
continue;
}

// Check whether the object can be retrieved.
if (!weakReference.TryGetTarget(out currentObject))
{
// If the object can't be retrieved, mark the reference for removal.
// The removal will occur on the _next_ enumeration (see: GetEnumerator()).
weakList.RemoveAt(CurrentOffset);
continue;
}

CurrentReference = weakReference;
return true;
}
}

public void Reset()
{
CurrentOffset = -1;
CurrentReference = null;
currentObject = null;
}
public readonly WeakReference<T>? Reference;

public readonly T Current => currentObject;

internal WeakReference<T> CurrentReference { get; private set; }

internal int CurrentOffset { get; private set; }

readonly object IEnumerator.Current => Current;

public void Dispose()
{
weakList = null;
currentObject = null;
CurrentReference = null;
}
}

internal readonly struct InvalidatableWeakReference
{
[CanBeNull]
public readonly WeakReference<T> Reference;
/// <summary>
/// Hash code of the target of <see cref="Reference"/>.
/// </summary>
public readonly int ObjectHashCode;

public InvalidatableWeakReference([CanBeNull] T reference)
: this(new WeakReference<T>(reference))
public InvalidatableWeakReference(T reference)
{
Reference = new WeakReference<T>(reference);
ObjectHashCode = EqualityComparer<T>.Default.GetHashCode(reference);
}

public InvalidatableWeakReference([CanBeNull] WeakReference<T> weakReference)
public InvalidatableWeakReference(WeakReference<T> weakReference)
{
Reference = weakReference;
ObjectHashCode = !weakReference.TryGetTarget(out var target) ? 0 : EqualityComparer<T>.Default.GetHashCode(target);
}
}
}
Expand Down
73 changes: 73 additions & 0 deletions osu.Framework/Lists/WeakList_ValidItemsEnumerator.cs
@@ -0,0 +1,73 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable enable

using System.Collections;
using System.Collections.Generic;

namespace osu.Framework.Lists
{
public partial class WeakList<T>
{
/// <summary>
/// An enumerator over only the valid items of a <see cref="WeakList{T}"/>.
/// </summary>
public struct ValidItemsEnumerator : IEnumerator<T?>
{
private readonly WeakList<T> weakList;
private int currentItemIndex;

/// <summary>
/// Creates a new <see cref="ValidItemsEnumerator"/>.
/// </summary>
/// <param name="weakList">The <see cref="WeakList{T}"/> to enumerate over.</param>
internal ValidItemsEnumerator(WeakList<T> weakList)
{
this.weakList = weakList;

currentItemIndex = weakList.listStart - 1; // The first MoveNext() should bring the iterator to the start
Current = null;
}

public bool MoveNext()
{
while (true)
{
++currentItemIndex;

// Check whether we're still within the valid range of the list.
if (currentItemIndex >= weakList.listEnd)
return false;

var weakReference = weakList.list[currentItemIndex].Reference;

// Check whether the reference exists.
if (weakReference == null || !weakReference.TryGetTarget(out var obj))
{
// If the reference doesn't exist, it must have previously been removed and can be skipped.
continue;
}

Current = obj;
return true;
}
}

public void Reset()
{
currentItemIndex = weakList.listStart - 1;
Current = null;
}

public T? Current { get; private set; }

readonly object? IEnumerator.Current => Current;

public void Dispose()
{
Current = null;
}
}
}
}

0 comments on commit 65424f4

Please sign in to comment.