using System; using System.Collections.Generic; using System.Diagnostics; using Implab.Parallels; namespace Implab.Diagnostics { public class TraceRegistry: IObservable { class Subscription : IDisposable { readonly WeakReference m_registry; public Subscription(TraceRegistry registry) { m_registry = new WeakReference(registry); } public void Dispose() { TraceRegistry t; if (m_registry.TryGetTarget(out t)) t.RemoveSubscription(this); } } /// /// The global collection of available diagnostic channels /// /// public static TraceRegistry Global { get; } = new TraceRegistry(); readonly object m_lock = new object(); readonly Dictionary> m_subscriptions = new Dictionary>(); readonly SimpleAsyncQueue m_channels = new SimpleAsyncQueue(); public void Register(TraceChannel channel) { // notifications can run in parallel IObserver[] handlers = null; lock(m_lock) { m_channels.Enqueue(channel); if (m_subscriptions.Count > 0) { handlers = new IObserver[m_subscriptions.Count]; m_subscriptions.Values.CopyTo(handlers, 0); } } if (handlers != null) foreach(var h in handlers) h.OnNext(channel); } /// /// Subscribes the specified handler to notifications about new trace /// channels /// /// /// public IDisposable Subscribe(IObserver handler) { Safe.ArgumentNotNull(handler, nameof(handler)); var cookie = new Subscription(this); IEnumerable snap; // lock to ensure that no new channels will be added // while the subscription is added lock(m_lock) { m_subscriptions.Add(cookie, handler); snap = m_channels.Snapshot(); } // announce previously declared channels if required if (snap != null) { foreach(var c in snap) handler.OnNext(c); } // return the subscription return cookie; } void RemoveSubscription(object cookie) { lock(m_lock) m_subscriptions.Remove(cookie); } } }