using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Unity.CodeEditor.Utils
{
///
/// WeakEventManager base class. Inspired by the WPF WeakEventManager class and the code in
/// https://social.msdn.microsoft.com/Forums/silverlight/en-US/34d85c3f-52ea-4adc-bb32-8297f5549042/command-binding-memory-leak?forum=silverlightbugs
///
/// Copied here from ReactiveUI due to bugs in its design (singleton instance for multiple events).
///
/// The type of the event source.
/// The type of the event handler.
/// The type of the event arguments.
internal abstract class WeakEventManagerBase
where TEventManager : WeakEventManagerBase, new()
{
// ReSharper disable once StaticMemberInGenericType
private static readonly object StaticSource = new object();
///
/// Mapping between the target of the delegate (for example a Button) and the handler (EventHandler).
/// Windows Phone needs this, otherwise the event handler gets garbage collected.
///
private readonly ConditionalWeakTable> _targetToEventHandler = new ConditionalWeakTable>();
///
/// Mapping from the source of the event to the list of handlers. This is a CWT to ensure it does not leak the source of the event.
///
private readonly ConditionalWeakTable _sourceToWeakHandlers = new ConditionalWeakTable();
private static readonly Lazy CurrentLazy = new Lazy(() => new TEventManager());
private static TEventManager Current => CurrentLazy.Value;
///
/// Adds a weak reference to the handler and associates it with the source.
///
/// The source.
/// The handler.
internal static void AddHandler(TEventSource source, TEventHandler handler)
{
Current.PrivateAddHandler(source, handler);
}
///
/// Removes the association between the source and the handler.
///
/// The source.
/// The handler.
internal static void RemoveHandler(TEventSource source, TEventHandler handler)
{
Current.PrivateRemoveHandler(source, handler);
}
///
/// Delivers the event to the handlers registered for the source.
///
/// The sender.
/// The instance containing the event data.
protected static void DeliverEvent(object sender, TEventArgs args)
{
Current.PrivateDeliverEvent(sender, args);
}
///
/// Override this method to attach to an event.
///
/// The source.
protected abstract void StartListening(TEventSource source);
///
/// Override this method to detach from an event.
///
/// The source.
protected abstract void StopListening(TEventSource source);
protected void PrivateAddHandler(TEventSource source, TEventHandler handler)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (handler == null) throw new ArgumentNullException(nameof(handler));
if (!typeof(TEventHandler).GetTypeInfo().IsSubclassOf(typeof(Delegate)))
{
throw new ArgumentException("Handler must be Delegate type");
}
AddWeakHandler(source, handler);
AddTargetHandler(handler);
}
private void AddWeakHandler(TEventSource source, TEventHandler handler)
{
if (_sourceToWeakHandlers.TryGetValue(source, out var weakHandlers))
{
// clone list if we are currently delivering an event
if (weakHandlers.IsDeliverActive)
{
weakHandlers = weakHandlers.Clone();
_sourceToWeakHandlers.Remove(source);
_sourceToWeakHandlers.Add(source, weakHandlers);
}
weakHandlers.AddWeakHandler(source, handler);
}
else
{
weakHandlers = new WeakHandlerList();
weakHandlers.AddWeakHandler(source, handler);
_sourceToWeakHandlers.Add(source, weakHandlers);
StartListening(source);
}
Purge(source);
}
private void AddTargetHandler(TEventHandler handler)
{
var @delegate = handler as Delegate;
var key = @delegate?.Target ?? StaticSource;
if (_targetToEventHandler.TryGetValue(key, out var delegates))
{
delegates.Add(@delegate);
}
else
{
delegates = new List { @delegate };
_targetToEventHandler.Add(key, delegates);
}
}
protected void PrivateRemoveHandler(TEventSource source, TEventHandler handler)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (handler == null) throw new ArgumentNullException(nameof(handler));
if (!typeof(TEventHandler).GetTypeInfo().IsSubclassOf(typeof(Delegate)))
{
throw new ArgumentException("handler must be Delegate type");
}
RemoveWeakHandler(source, handler);
RemoveTargetHandler(handler);
}
private void RemoveWeakHandler(TEventSource source, TEventHandler handler)
{
if (_sourceToWeakHandlers.TryGetValue(source, out var weakHandlers))
{
// clone list if we are currently delivering an event
if (weakHandlers.IsDeliverActive)
{
weakHandlers = weakHandlers.Clone();
_sourceToWeakHandlers.Remove(source);
_sourceToWeakHandlers.Add(source, weakHandlers);
}
if (weakHandlers.RemoveWeakHandler(source, handler) && weakHandlers.Count == 0)
{
_sourceToWeakHandlers.Remove(source);
StopListening(source);
}
}
}
private void RemoveTargetHandler(TEventHandler handler)
{
var @delegate = handler as Delegate;
var key = @delegate?.Target ?? StaticSource;
if (_targetToEventHandler.TryGetValue(key, out var delegates))
{
delegates.Remove(@delegate);
if (delegates.Count == 0)
{
_targetToEventHandler.Remove(key);
}
}
}
private void PrivateDeliverEvent(object sender, TEventArgs args)
{
var source = sender ?? StaticSource;
var hasStaleEntries = false;
if (_sourceToWeakHandlers.TryGetValue(source, out var weakHandlers))
{
using (weakHandlers.DeliverActive())
{
hasStaleEntries = weakHandlers.DeliverEvent(source, args);
}
}
if (hasStaleEntries)
{
Purge(source);
}
}
private void Purge(object source)
{
if (_sourceToWeakHandlers.TryGetValue(source, out var weakHandlers))
{
if (weakHandlers.IsDeliverActive)
{
weakHandlers = weakHandlers.Clone();
_sourceToWeakHandlers.Remove(source);
_sourceToWeakHandlers.Add(source, weakHandlers);
}
else
{
weakHandlers.Purge();
}
}
}
internal class WeakHandler
{
private readonly WeakReference _source;
private readonly WeakReference _originalHandler;
internal bool IsActive => _source != null && _source.IsAlive && _originalHandler != null && _originalHandler.IsAlive;
internal TEventHandler Handler
{
get
{
if (_originalHandler == null)
{
return default(TEventHandler);
}
return (TEventHandler)_originalHandler.Target;
}
}
internal WeakHandler(object source, TEventHandler originalHandler)
{
_source = new WeakReference(source);
_originalHandler = new WeakReference(originalHandler);
}
internal bool Matches(object o, TEventHandler handler)
{
return _source != null &&
ReferenceEquals(_source.Target, o) &&
_originalHandler != null &&
(ReferenceEquals(_originalHandler.Target, handler) ||
_originalHandler.Target is TEventHandler &&
handler is TEventHandler &&
handler is Delegate del && _originalHandler.Target is Delegate origDel && Equals(del.Target, origDel.Target));
}
}
internal class WeakHandlerList
{
private int _deliveries;
private readonly List _handlers;
internal WeakHandlerList()
{
_handlers = new List();
}
internal void AddWeakHandler(TEventSource source, TEventHandler handler)
{
var handlerSink = new WeakHandler(source, handler);
_handlers.Add(handlerSink);
}
internal bool RemoveWeakHandler(TEventSource source, TEventHandler handler)
{
foreach (var weakHandler in _handlers)
{
if (weakHandler.Matches(source, handler))
{
return _handlers.Remove(weakHandler);
}
}
return false;
}
internal WeakHandlerList Clone()
{
var newList = new WeakHandlerList();
newList._handlers.AddRange(_handlers.Where(h => h.IsActive));
return newList;
}
internal int Count => _handlers.Count;
internal bool IsDeliverActive => _deliveries > 0;
internal IDisposable DeliverActive()
{
Interlocked.Increment(ref _deliveries);
return new Disposable(() => Interlocked.Decrement(ref _deliveries));
}
// ReSharper disable once MemberHidesStaticFromOuterClass
internal virtual bool DeliverEvent(object sender, TEventArgs args)
{
var hasStaleEntries = false;
foreach (var handler in _handlers)
{
if (handler.IsActive)
{
var @delegate = handler.Handler as Delegate;
@delegate?.DynamicInvoke(sender, args);
}
else
{
hasStaleEntries = true;
}
}
return hasStaleEntries;
}
internal void Purge()
{
for (var i = _handlers.Count - 1; i >= 0; i--)
{
if (!_handlers[i].IsActive)
{
_handlers.RemoveAt(i);
}
}
}
}
}
internal sealed class Disposable : IDisposable
{
private volatile Action _dispose;
internal Disposable(Action dispose)
{
_dispose = dispose;
}
internal bool IsDisposed => _dispose == null;
public void Dispose()
{
Interlocked.Exchange(ref _dispose, null)?.Invoke();
}
}
}