Browse Source

Added WeakSubscriptionManager

pull/321/head
Nikita Tsukanov 11 years ago
parent
commit
64ed815f4c
  1. 1
      src/Perspex.Base/Perspex.Base.csproj
  2. 126
      src/Perspex.Base/Utilities/WeakSubscriptionManager.cs
  3. 1
      tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
  4. 70
      tests/Perspex.Base.UnitTests/WeakSubscriptionManagerTests.cs

1
src/Perspex.Base/Perspex.Base.csproj

@ -82,6 +82,7 @@
<Compile Include="Threading\SingleThreadDispatcher.cs" />
<Compile Include="Utilities\MathUtilities.cs" />
<Compile Include="Utilities\TypeUtilities.cs" />
<Compile Include="Utilities\WeakSubscriptionManager.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Reactive.Core">

126
src/Perspex.Base/Utilities/WeakSubscriptionManager.cs

@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Perspex.Utilities
{
public static class WeakSubscriptionManager
{
static class SubscriptionTypeStorage<T>
{
public static readonly ConditionalWeakTable<object, SubscriptionDic<T>> Subscribers
= new ConditionalWeakTable<object, SubscriptionDic<T>>();
}
class SubscriptionDic<T> : Dictionary<string, Subscription<T>>
{
}
static readonly Dictionary<Type, Dictionary<string, EventInfo>> Accessors
= new Dictionary<Type, Dictionary<string, EventInfo>>();
class Subscription<T>
{
WeakReference<IWeakSubscriber<T>>[] _data = new WeakReference<IWeakSubscriber<T>>[16];
int _count = 0;
readonly EventInfo _info;
readonly SubscriptionDic<T> _sdic;
private readonly object _target;
private readonly string _eventName;
private readonly EventHandler<T> _delegate;
public Subscription(SubscriptionDic<T> sdic, object target, string eventName)
{
_sdic = sdic;
_target = target;
_eventName = eventName;
var t = target.GetType();
Dictionary<string, EventInfo> evDic;
if (!Accessors.TryGetValue(t, out evDic))
Accessors[t] = evDic = new Dictionary<string, EventInfo>();
if (!evDic.TryGetValue(eventName, out _info))
evDic[eventName] = _info = t.GetRuntimeEvent(eventName);
_info.AddEventHandler(target, _delegate = OnEvent);
}
void Destroy()
{
_info.RemoveEventHandler(_target, _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++;
}
void Compact()
{
int empty = -1;
for (int c = 1; 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];
IWeakSubscriber<T> sub;
if (r.TryGetTarget(out sub))
sub.OnEvent(sender, eventArgs);
else
needCompact = true;
}
if (needCompact)
Compact();
}
}
public static void Subscribe<T>(object target, string eventName, IWeakSubscriber<T> subscriber)
{
var dic = SubscriptionTypeStorage<T>.Subscribers.GetOrCreateValue(target);
Subscription<T> sub;
if (!dic.TryGetValue(eventName, out sub))
dic[eventName] = sub = new Subscription<T>(dic, target, eventName);
sub.Add(new WeakReference<IWeakSubscriber<T>>(subscriber));
}
}
public interface IWeakSubscriber<T>
{
void OnEvent(object sender, T ev);
}
}

1
tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj

@ -85,6 +85,7 @@
<Compile Include="PerspexPropertyTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PriorityValueTests.cs" />
<Compile Include="WeakSubscriptionManagerTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />

70
tests/Perspex.Base.UnitTests/WeakSubscriptionManagerTests.cs

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Perspex.Utilities;
using Xunit;
namespace Perspex.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…
Cancel
Save