Browse Source

Added WeakSubscriptionManager

Nikita Tsukanov 10 years ago
parent
commit
64ed815f4c

+ 1 - 0
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 - 0
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 - 0
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 - 0
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));
+        }
+    }
+}