committed by
GitHub
8 changed files with 442 additions and 100 deletions
@ -0,0 +1,236 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Collections.Pooled; |
|||
|
|||
namespace Avalonia.Utilities; |
|||
|
|||
internal class WeakHashList<T> where T : class |
|||
{ |
|||
private struct Key |
|||
{ |
|||
public WeakReference<T>? Weak; |
|||
public T? Strong; |
|||
public int HashCode; |
|||
|
|||
public static Key MakeStrong(T r) => new() |
|||
{ |
|||
HashCode = r.GetHashCode(), |
|||
Strong = r |
|||
}; |
|||
|
|||
public static Key MakeWeak(T r) => new() |
|||
{ |
|||
HashCode = r.GetHashCode(), |
|||
Weak = new WeakReference<T>(r) |
|||
}; |
|||
|
|||
public override int GetHashCode() => HashCode; |
|||
} |
|||
|
|||
class KeyComparer : IEqualityComparer<Key> |
|||
{ |
|||
public bool Equals(Key x, Key y) |
|||
{ |
|||
if (x.HashCode != y.HashCode) |
|||
return false; |
|||
if (x.Strong != null) |
|||
{ |
|||
if (y.Strong != null) |
|||
return x.Strong == y.Strong; |
|||
if (y.Weak == null) |
|||
return false; |
|||
return y.Weak.TryGetTarget(out var weakTarget) && weakTarget == x.Strong; |
|||
} |
|||
else if (y.Strong != null) |
|||
{ |
|||
if (x.Weak == null) |
|||
return false; |
|||
return x.Weak.TryGetTarget(out var weakTarget) && weakTarget == y.Strong; |
|||
} |
|||
else |
|||
{ |
|||
if (x.Weak == null || x.Weak.TryGetTarget(out var xTarget) == false) |
|||
return y.Weak?.TryGetTarget(out _) != true; |
|||
return y.Weak?.TryGetTarget(out var yTarget) == true && xTarget == yTarget; |
|||
} |
|||
} |
|||
|
|||
public int GetHashCode(Key obj) => obj.HashCode; |
|||
public static KeyComparer Instance = new(); |
|||
} |
|||
|
|||
Dictionary<Key, int>? _dic; |
|||
WeakReference<T>?[]? _arr; |
|||
int _arrCount; |
|||
|
|||
public bool IsEmpty => _dic == null || _dic.Count == 0; |
|||
public bool NeedCompact { get; private set; } |
|||
|
|||
public void Add(T item) |
|||
{ |
|||
if (_dic != null) |
|||
{ |
|||
var strongKey = Key.MakeStrong(item); |
|||
if (_dic.TryGetValue(strongKey, out var cnt)) |
|||
_dic[strongKey] = cnt + 1; |
|||
else |
|||
_dic[Key.MakeWeak(item)] = 1; |
|||
return; |
|||
} |
|||
|
|||
if (_arr == null) |
|||
_arr = new WeakReference<T>[8]; |
|||
|
|||
if (_arrCount < _arr.Length) |
|||
{ |
|||
_arr[_arrCount] = new WeakReference<T>(item); |
|||
_arrCount++; |
|||
return; |
|||
} |
|||
|
|||
// Check if something is dead
|
|||
for (var c = 0; c < _arrCount; c++) |
|||
{ |
|||
if (_arr[c]!.TryGetTarget(out _) == false) |
|||
{ |
|||
_arr[c] = new WeakReference<T>(item); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
_dic = new Dictionary<Key, int>(KeyComparer.Instance); |
|||
foreach (var existing in _arr) |
|||
{ |
|||
if (existing!.TryGetTarget(out var target)) |
|||
Add(target); |
|||
} |
|||
_arr = null; |
|||
|
|||
} |
|||
|
|||
public void Remove(T item) |
|||
{ |
|||
if (_arr != null) |
|||
{ |
|||
for (var c = 0; c < _arr.Length; c++) |
|||
{ |
|||
if (_arr[c]?.TryGetTarget(out var target) == true && target == item) |
|||
{ |
|||
_arr[c] = null; |
|||
Compact(); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
else if (_dic != null) |
|||
{ |
|||
var strongKey = Key.MakeStrong(item); |
|||
|
|||
if (_dic.TryGetValue(strongKey, out var cnt)) |
|||
{ |
|||
if (cnt > 1) |
|||
{ |
|||
_dic[strongKey] = cnt - 1; |
|||
return; |
|||
} |
|||
} |
|||
|
|||
_dic.Remove(strongKey); |
|||
} |
|||
} |
|||
|
|||
private void ArrCompact() |
|||
{ |
|||
if (_arr != null) |
|||
{ |
|||
int empty = -1; |
|||
for (var c = 0; c < _arrCount; c++) |
|||
{ |
|||
var r = _arr[c]; |
|||
//Mark current index as first empty
|
|||
if (r == null && empty == -1) |
|||
empty = c; |
|||
//If current element isn't null and we have an empty one
|
|||
if (r != null && empty != -1) |
|||
{ |
|||
_arr[c] = null; |
|||
_arr[empty] = r; |
|||
empty++; |
|||
} |
|||
} |
|||
|
|||
if (empty != -1) |
|||
_arrCount = empty; |
|||
} |
|||
} |
|||
|
|||
public void Compact() |
|||
{ |
|||
if (_dic != null) |
|||
{ |
|||
PooledList<Key>? toRemove = null; |
|||
foreach (var kvp in _dic) |
|||
{ |
|||
if (kvp.Key.Weak?.TryGetTarget(out _) != true) |
|||
(toRemove ??= new PooledList<Key>()).Add(kvp.Key); |
|||
} |
|||
|
|||
if (toRemove != null) |
|||
{ |
|||
foreach (var k in toRemove) |
|||
_dic.Remove(k); |
|||
toRemove.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static readonly Stack<PooledList<T>> s_listPool = new(); |
|||
|
|||
public static void ReturnToSharedPool(PooledList<T> list) |
|||
{ |
|||
list.Clear(); |
|||
s_listPool.Push(list); |
|||
} |
|||
|
|||
public PooledList<T>? GetAlive(Func<PooledList<T>>? factory = null) |
|||
{ |
|||
PooledList<T>? pooled = null; |
|||
if (_arr != null) |
|||
{ |
|||
bool needCompact = false; |
|||
for (var c = 0; c < _arrCount; c++) |
|||
{ |
|||
if (_arr[c]?.TryGetTarget(out var target) == true) |
|||
(pooled ??= factory?.Invoke() |
|||
?? (s_listPool.Count > 0 |
|||
? s_listPool.Pop() |
|||
: new PooledList<T>())).Add(target!); |
|||
else |
|||
{ |
|||
_arr[c] = null; |
|||
needCompact = true; |
|||
} |
|||
} |
|||
if(needCompact) |
|||
ArrCompact(); |
|||
return pooled; |
|||
} |
|||
if (_dic != null) |
|||
{ |
|||
|
|||
foreach (var kvp in _dic) |
|||
{ |
|||
if (kvp.Key.Weak?.TryGetTarget(out var target) == true) |
|||
(pooled ??= factory?.Invoke() |
|||
?? (s_listPool.Count > 0 |
|||
? s_listPool.Pop() |
|||
: new PooledList<T>())) |
|||
.Add(target!); |
|||
else |
|||
NeedCompact = true; |
|||
} |
|||
} |
|||
|
|||
return pooled; |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Media; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
namespace Avalonia.Benchmarks.Visuals; |
|||
|
|||
[MemoryDiagnoser] |
|||
public class VisualAffectsRenderBenchmarks |
|||
{ |
|||
private readonly TestVisual _target; |
|||
private readonly IPen _pen; |
|||
|
|||
public VisualAffectsRenderBenchmarks() |
|||
{ |
|||
_target = new TestVisual(); |
|||
_pen = new Pen(Brushes.Black); |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void SetPropertyThatAffectsRender() |
|||
{ |
|||
_target.Pen = _pen; |
|||
_target.Pen = null; |
|||
} |
|||
|
|||
private class TestVisual : Visual |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Pen"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IPen> PenProperty = |
|||
AvaloniaProperty.Register<Border, IPen>(nameof(Pen)); |
|||
|
|||
public IPen Pen |
|||
{ |
|||
get => GetValue(PenProperty); |
|||
set => SetValue(PenProperty, value); |
|||
} |
|||
|
|||
static TestVisual() |
|||
{ |
|||
AffectsRender<TestVisual>(PenProperty); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue