committed by
GitHub
23 changed files with 2 additions and 703 deletions
@ -1,193 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Reflection; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
|
|
||||
namespace Avalonia.Utilities |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Manages subscriptions to events using weak listeners.
|
|
||||
/// </summary>
|
|
||||
public static class WeakSubscriptionManager |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Subscribes to an event on an object using a weak subscription.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TTarget">The type of the target.</typeparam>
|
|
||||
/// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
|
|
||||
/// <param name="target">The event source.</param>
|
|
||||
/// <param name="eventName">The name of the event.</param>
|
|
||||
/// <param name="subscriber">The subscriber.</param>
|
|
||||
[Obsolete("Use WeakEvent")] |
|
||||
public static void Subscribe<TTarget, TEventArgs>(TTarget target, string eventName, IWeakSubscriber<TEventArgs> subscriber) |
|
||||
where TEventArgs : EventArgs |
|
||||
{ |
|
||||
_ = target ?? throw new ArgumentNullException(nameof(target)); |
|
||||
|
|
||||
var dic = SubscriptionTypeStorage<TEventArgs>.Subscribers.GetOrCreateValue(target); |
|
||||
|
|
||||
if (!dic.TryGetValue(eventName, out var sub)) |
|
||||
{ |
|
||||
dic[eventName] = sub = new Subscription<TEventArgs>(dic, typeof(TTarget), target, eventName); |
|
||||
} |
|
||||
|
|
||||
sub.Add(new WeakReference<IWeakSubscriber<TEventArgs>>(subscriber)); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Unsubscribes from an event.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The type of the event arguments.</typeparam>
|
|
||||
/// <param name="target">The event source.</param>
|
|
||||
/// <param name="eventName">The name of the event.</param>
|
|
||||
/// <param name="subscriber">The subscriber.</param>
|
|
||||
public static void Unsubscribe<T>(object target, string eventName, IWeakSubscriber<T> subscriber) |
|
||||
where T : EventArgs |
|
||||
{ |
|
||||
if (SubscriptionTypeStorage<T>.Subscribers.TryGetValue(target, out var dic)) |
|
||||
{ |
|
||||
if (dic.TryGetValue(eventName, out var sub)) |
|
||||
{ |
|
||||
sub.Remove(subscriber); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static class SubscriptionTypeStorage<T> |
|
||||
where T : EventArgs |
|
||||
{ |
|
||||
public static readonly ConditionalWeakTable<object, SubscriptionDic<T>> Subscribers |
|
||||
= new ConditionalWeakTable<object, SubscriptionDic<T>>(); |
|
||||
} |
|
||||
|
|
||||
private class SubscriptionDic<T> : Dictionary<string, Subscription<T>> |
|
||||
where T : EventArgs |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
private static readonly Dictionary<Type, Dictionary<string, EventInfo>> Accessors |
|
||||
= new Dictionary<Type, Dictionary<string, EventInfo>>(); |
|
||||
|
|
||||
private class Subscription<T> where T : EventArgs |
|
||||
{ |
|
||||
private readonly EventInfo _info; |
|
||||
private readonly SubscriptionDic<T> _sdic; |
|
||||
private readonly object _target; |
|
||||
private readonly string _eventName; |
|
||||
private readonly Delegate _delegate; |
|
||||
|
|
||||
private WeakReference<IWeakSubscriber<T>>?[] _data = new WeakReference<IWeakSubscriber<T>>?[16]; |
|
||||
private int _count = 0; |
|
||||
|
|
||||
public Subscription(SubscriptionDic<T> sdic, Type targetType, object target, string eventName) |
|
||||
{ |
|
||||
_sdic = sdic; |
|
||||
_target = target; |
|
||||
_eventName = eventName; |
|
||||
if (!Accessors.TryGetValue(targetType, out var evDic)) |
|
||||
Accessors[targetType] = evDic = new Dictionary<string, EventInfo>(); |
|
||||
|
|
||||
if (evDic.TryGetValue(eventName, out var info)) |
|
||||
{ |
|
||||
_info = info; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
var ev = targetType.GetRuntimeEvents().FirstOrDefault(x => x.Name == eventName); |
|
||||
|
|
||||
if (ev == null) |
|
||||
{ |
|
||||
throw new ArgumentException( |
|
||||
$"The event {eventName} was not found on {target.GetType()}."); |
|
||||
} |
|
||||
|
|
||||
evDic[eventName] = _info = ev; |
|
||||
} |
|
||||
|
|
||||
var del = new Action<object, T>(OnEvent); |
|
||||
_delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target); |
|
||||
_info.AddMethod!.Invoke(target, new[] { _delegate }); |
|
||||
} |
|
||||
|
|
||||
void Destroy() |
|
||||
{ |
|
||||
_info.RemoveMethod!.Invoke(_target, new[] { _delegate }); |
|
||||
_sdic.Remove(_eventName); |
|
||||
} |
|
||||
|
|
||||
public void Add(WeakReference<IWeakSubscriber<T>> s) |
|
||||
{ |
|
||||
if (_count == _data.Length) |
|
||||
{ |
|
||||
//Extend capacity
|
|
||||
var ndata = new WeakReference<IWeakSubscriber<T>>?[_data.Length*2]; |
|
||||
Array.Copy(_data, ndata, _data.Length); |
|
||||
_data = ndata; |
|
||||
} |
|
||||
_data[_count] = s!; |
|
||||
_count++; |
|
||||
} |
|
||||
|
|
||||
public void Remove(IWeakSubscriber<T> s) |
|
||||
{ |
|
||||
var removed = false; |
|
||||
|
|
||||
for (int c = 0; c < _count; ++c) |
|
||||
{ |
|
||||
var reference = _data[c]; |
|
||||
IWeakSubscriber<T>? instance; |
|
||||
|
|
||||
if (reference != null && reference.TryGetTarget(out instance) && instance == s) |
|
||||
{ |
|
||||
_data[c] = null; |
|
||||
removed = true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (removed) |
|
||||
{ |
|
||||
Compact(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void Compact() |
|
||||
{ |
|
||||
int empty = -1; |
|
||||
for (int c = 0; c < _count; c++) |
|
||||
{ |
|
||||
var r = _data[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) |
|
||||
{ |
|
||||
_data[c] = null; |
|
||||
_data[empty] = r; |
|
||||
empty++; |
|
||||
} |
|
||||
} |
|
||||
if (empty != -1) |
|
||||
_count = empty; |
|
||||
if (_count == 0) |
|
||||
Destroy(); |
|
||||
} |
|
||||
|
|
||||
void OnEvent(object sender, T eventArgs) |
|
||||
{ |
|
||||
var needCompact = false; |
|
||||
for(var c=0; c<_count; c++) |
|
||||
{ |
|
||||
var r = _data[c]; |
|
||||
if (r?.TryGetTarget(out var sub) == true) |
|
||||
sub!.OnEvent(sender, eventArgs); |
|
||||
else |
|
||||
needCompact = true; |
|
||||
} |
|
||||
if (needCompact) |
|
||||
Compact(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,19 +0,0 @@ |
|||||
using System; |
|
||||
|
|
||||
namespace Avalonia.VisualTree |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Interface for controls that host their own separate visual tree, such as popups.
|
|
||||
/// </summary>
|
|
||||
[Obsolete] |
|
||||
public interface IVisualTreeHost |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Gets the root of the hosted visual tree.
|
|
||||
/// </summary>
|
|
||||
/// <value>
|
|
||||
/// The root of the hosted visual tree.
|
|
||||
/// </value>
|
|
||||
IVisual? Root { get; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,155 +0,0 @@ |
|||||
using System.Linq; |
|
||||
using Avalonia.Collections; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Avalonia.Base.UnitTests.Collections |
|
||||
{ |
|
||||
public class AvaloniaListExtenionsTests |
|
||||
{ |
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||
[Fact] |
|
||||
public void CreateDerivedList_Creates_Initial_Items() |
|
||||
{ |
|
||||
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 }); |
|
||||
|
|
||||
var target = source.CreateDerivedList(x => new Wrapper(x)); |
|
||||
var result = target.Select(x => x.Value).ToList(); |
|
||||
|
|
||||
Assert.Equal(source, result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void CreateDerivedList_Handles_Add() |
|
||||
{ |
|
||||
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 }); |
|
||||
var target = source.CreateDerivedList(x => new Wrapper(x)); |
|
||||
|
|
||||
source.Add(4); |
|
||||
|
|
||||
var result = target.Select(x => x.Value).ToList(); |
|
||||
|
|
||||
Assert.Equal(source, result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void CreateDerivedList_Handles_Insert() |
|
||||
{ |
|
||||
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 }); |
|
||||
var target = source.CreateDerivedList(x => new Wrapper(x)); |
|
||||
|
|
||||
source.Insert(1, 4); |
|
||||
|
|
||||
var result = target.Select(x => x.Value).ToList(); |
|
||||
|
|
||||
Assert.Equal(source, result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void CreateDerivedList_Handles_Remove() |
|
||||
{ |
|
||||
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 }); |
|
||||
var target = source.CreateDerivedList(x => new Wrapper(x)); |
|
||||
|
|
||||
source.Remove(2); |
|
||||
|
|
||||
var result = target.Select(x => x.Value).ToList(); |
|
||||
|
|
||||
Assert.Equal(source, result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void CreateDerivedList_Handles_RemoveRange() |
|
||||
{ |
|
||||
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 }); |
|
||||
var target = source.CreateDerivedList(x => new Wrapper(x)); |
|
||||
|
|
||||
source.RemoveRange(1, 2); |
|
||||
|
|
||||
var result = target.Select(x => x.Value).ToList(); |
|
||||
|
|
||||
Assert.Equal(source, result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void CreateDerivedList_Handles_Move() |
|
||||
{ |
|
||||
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 }); |
|
||||
var target = source.CreateDerivedList(x => new Wrapper(x)); |
|
||||
|
|
||||
source.Move(2, 0); |
|
||||
|
|
||||
var result = target.Select(x => x.Value).ToList(); |
|
||||
|
|
||||
Assert.Equal(source, result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData(0, 2, 3)] |
|
||||
[InlineData(0, 2, 4)] |
|
||||
[InlineData(0, 2, 5)] |
|
||||
[InlineData(0, 4, 4)] |
|
||||
[InlineData(1, 2, 0)] |
|
||||
[InlineData(1, 2, 4)] |
|
||||
[InlineData(1, 2, 5)] |
|
||||
[InlineData(1, 4, 0)] |
|
||||
[InlineData(2, 2, 0)] |
|
||||
[InlineData(2, 2, 1)] |
|
||||
[InlineData(2, 2, 3)] |
|
||||
[InlineData(2, 2, 4)] |
|
||||
[InlineData(2, 2, 5)] |
|
||||
[InlineData(4, 2, 0)] |
|
||||
[InlineData(4, 2, 1)] |
|
||||
[InlineData(4, 2, 3)] |
|
||||
[InlineData(5, 1, 0)] |
|
||||
[InlineData(5, 1, 3)] |
|
||||
public void CreateDerivedList_Handles_MoveRange(int oldIndex, int count, int newIndex) |
|
||||
{ |
|
||||
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3, 4, 5 }); |
|
||||
var target = source.CreateDerivedList(x => new Wrapper(x)); |
|
||||
|
|
||||
source.MoveRange(oldIndex, count, newIndex); |
|
||||
|
|
||||
var result = target.Select(x => x.Value).ToList(); |
|
||||
|
|
||||
Assert.Equal(source, result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void CreateDerivedList_Handles_Replace() |
|
||||
{ |
|
||||
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 }); |
|
||||
var target = source.CreateDerivedList(x => new Wrapper(x)); |
|
||||
|
|
||||
source[1] = 4; |
|
||||
|
|
||||
var result = target.Select(x => x.Value).ToList(); |
|
||||
|
|
||||
Assert.Equal(source, result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void CreateDerivedList_Handles_Clear() |
|
||||
{ |
|
||||
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 }); |
|
||||
var target = source.CreateDerivedList(x => new Wrapper(x)); |
|
||||
|
|
||||
source.Clear(); |
|
||||
|
|
||||
var result = target.Select(x => x.Value).ToList(); |
|
||||
|
|
||||
Assert.Equal(source, result); |
|
||||
} |
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
|
||||
|
|
||||
|
|
||||
private class Wrapper |
|
||||
{ |
|
||||
public Wrapper(int value) |
|
||||
{ |
|
||||
Value = value; |
|
||||
} |
|
||||
|
|
||||
public int Value { get; } |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,70 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
using Avalonia.Utilities; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Avalonia.Base.UnitTests |
|
||||
{ |
|
||||
public class WeakSubscriptionManagerTests |
|
||||
{ |
|
||||
class EventSource |
|
||||
{ |
|
||||
public event EventHandler<EventArgs> Event; |
|
||||
|
|
||||
public void Fire() |
|
||||
{ |
|
||||
Event?.Invoke(this, new EventArgs()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class Subscriber : IWeakSubscriber<EventArgs> |
|
||||
{ |
|
||||
private readonly Action _onEvent; |
|
||||
|
|
||||
public Subscriber(Action onEvent) |
|
||||
{ |
|
||||
_onEvent = onEvent; |
|
||||
} |
|
||||
|
|
||||
public void OnEvent(object sender, EventArgs ev) |
|
||||
{ |
|
||||
_onEvent?.Invoke(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void EventShoudBePassedToSubscriber() |
|
||||
{ |
|
||||
bool handled = false; |
|
||||
var subscriber = new Subscriber(() => handled = true); |
|
||||
var source = new EventSource(); |
|
||||
WeakSubscriptionManager.Subscribe(source, "Event", subscriber); |
|
||||
source.Fire(); |
|
||||
Assert.True(handled); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
[Fact] |
|
||||
public void EventHandlerShouldNotBeKeptAlive() |
|
||||
{ |
|
||||
bool handled = false; |
|
||||
var source = new EventSource(); |
|
||||
AddSubscriber(source, "Event", () => handled = true); |
|
||||
for (int c = 0; c < 10; c++) |
|
||||
{ |
|
||||
GC.Collect(); |
|
||||
GC.Collect(3, GCCollectionMode.Forced, true); |
|
||||
} |
|
||||
source.Fire(); |
|
||||
Assert.False(handled); |
|
||||
} |
|
||||
|
|
||||
private void AddSubscriber(EventSource source, string name, Action func) |
|
||||
{ |
|
||||
WeakSubscriptionManager.Subscribe(source, name, new Subscriber(func)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue