Trace level messages
Collection change events are all deferred, and replaced with reset if it makes sense.
This commit is contained in:
@@ -77,7 +77,7 @@ namespace Torch.Server.Managers
|
|||||||
if (evm is T m)
|
if (evm is T m)
|
||||||
{
|
{
|
||||||
var result = _factory(m);
|
var result = _factory(m);
|
||||||
_log.Debug($"Model factory {_factory.Method} created {result} for {evm}");
|
_log.Trace($"Model factory {_factory.Method} created {result} for {evm}");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -224,7 +224,7 @@ namespace Torch.Server.Managers
|
|||||||
if (factory.Method.GetParameters()[0].ParameterType.IsInstanceOfType(model) &&
|
if (factory.Method.GetParameters()[0].ParameterType.IsInstanceOfType(model) &&
|
||||||
factory.DynamicInvoke(model) is Control result)
|
factory.DynamicInvoke(model) is Control result)
|
||||||
{
|
{
|
||||||
_log.Debug($"Control factory {factory.Method} created {result}");
|
_log.Trace($"Control factory {factory.Method} created {result}");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
_log.Warn($"No control created for {model}");
|
_log.Warn($"No control created for {model}");
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -16,7 +17,8 @@ namespace Torch.Collections
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TC">Collection type</typeparam>
|
/// <typeparam name="TC">Collection type</typeparam>
|
||||||
/// <typeparam name="TV">Value type</typeparam>
|
/// <typeparam name="TV">Value type</typeparam>
|
||||||
public abstract class MtObservableCollection<TC, TV> : INotifyPropertyChanged, INotifyCollectionChanged, IEnumerable<TV> where TC : class, ICollection<TV>
|
public abstract class MtObservableCollection<TC, TV> : INotifyPropertyChanged, INotifyCollectionChanged,
|
||||||
|
IEnumerable<TV>, ICollection where TC : class, ICollection<TV>
|
||||||
{
|
{
|
||||||
protected readonly ReaderWriterLockSlim Lock;
|
protected readonly ReaderWriterLockSlim Lock;
|
||||||
protected readonly TC Backing;
|
protected readonly TC Backing;
|
||||||
@@ -30,6 +32,13 @@ namespace Torch.Collections
|
|||||||
Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||||
_version = 0;
|
_version = 0;
|
||||||
_threadViews = new ThreadLocal<ThreadView>(() => new ThreadView(this));
|
_threadViews = new ThreadLocal<ThreadView>(() => new ThreadView(this));
|
||||||
|
_deferredSnapshot = new DeferredUpdateToken(this);
|
||||||
|
_flushEventQueue = new Timer(FlushCollectionEventQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
~MtObservableCollection()
|
||||||
|
{
|
||||||
|
_flushEventQueue.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -53,6 +62,7 @@ namespace Torch.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region ICollection
|
#region ICollection
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Add(TV item)
|
public void Add(TV item)
|
||||||
{
|
{
|
||||||
@@ -61,7 +71,8 @@ namespace Torch.Collections
|
|||||||
Backing.Add(item);
|
Backing.Add(item);
|
||||||
MarkSnapshotsDirty();
|
MarkSnapshotsDirty();
|
||||||
OnPropertyChanged(nameof(Count));
|
OnPropertyChanged(nameof(Count));
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, Backing.Count - 1));
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item,
|
||||||
|
Backing.Count - 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +118,8 @@ namespace Torch.Collections
|
|||||||
|
|
||||||
OnPropertyChanged(nameof(Count));
|
OnPropertyChanged(nameof(Count));
|
||||||
OnCollectionChanged(oldIndex.HasValue
|
OnCollectionChanged(oldIndex.HasValue
|
||||||
? new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, oldIndex.Value)
|
? new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item,
|
||||||
|
oldIndex.Value)
|
||||||
: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -126,11 +138,13 @@ namespace Torch.Collections
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsReadOnly => Backing.IsReadOnly;
|
public bool IsReadOnly => Backing.IsReadOnly;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Event Wrappers
|
#region Event Wrappers
|
||||||
private readonly WeakReference<DeferredUpdateToken> _deferredSnapshot = new WeakReference<DeferredUpdateToken>(null);
|
|
||||||
private bool _deferredSnapshotTaken = false;
|
private readonly DeferredUpdateToken _deferredSnapshot;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposable that stops update signals and signals a full refresh when disposed.
|
/// Disposable that stops update signals and signals a full refresh when disposed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -138,13 +152,8 @@ namespace Torch.Collections
|
|||||||
{
|
{
|
||||||
using (Lock.WriteUsing())
|
using (Lock.WriteUsing())
|
||||||
{
|
{
|
||||||
if (_deferredSnapshotTaken)
|
_deferredSnapshot.Enter();
|
||||||
return new DummyToken();
|
return _deferredSnapshot;
|
||||||
DeferredUpdateToken token;
|
|
||||||
if (!_deferredSnapshot.TryGetTarget(out token))
|
|
||||||
_deferredSnapshot.SetTarget(token = new DeferredUpdateToken());
|
|
||||||
token.SetCollection(this);
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,53 +166,81 @@ namespace Torch.Collections
|
|||||||
|
|
||||||
private class DeferredUpdateToken : IDisposable
|
private class DeferredUpdateToken : IDisposable
|
||||||
{
|
{
|
||||||
private MtObservableCollection<TC, TV> _collection;
|
private readonly MtObservableCollection<TC, TV> _collection;
|
||||||
|
private int _depth;
|
||||||
|
|
||||||
internal void SetCollection(MtObservableCollection<TC, TV> c)
|
internal DeferredUpdateToken(MtObservableCollection<TC, TV> c)
|
||||||
{
|
{
|
||||||
c._deferredSnapshotTaken = true;
|
|
||||||
_collection = c;
|
_collection = c;
|
||||||
c.NotificationsEnabled = false;
|
}
|
||||||
|
|
||||||
|
internal void Enter()
|
||||||
|
{
|
||||||
|
if (Interlocked.Increment(ref _depth) == 1)
|
||||||
|
{
|
||||||
|
_collection.NotificationsEnabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
using (_collection.Lock.WriteUsing())
|
if (Interlocked.Decrement(ref _depth) == 0)
|
||||||
{
|
using (_collection.Lock.WriteUsing())
|
||||||
_collection.NotificationsEnabled = true;
|
{
|
||||||
_collection.OnPropertyChanged(nameof(Count));
|
_collection.NotificationsEnabled = true;
|
||||||
_collection.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
_collection.OnPropertyChanged(nameof(Count));
|
||||||
_collection._deferredSnapshotTaken = false;
|
_collection.OnCollectionChanged(
|
||||||
}
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void OnPropertyChanged(string propName)
|
protected void OnPropertyChanged(string propName)
|
||||||
{
|
{
|
||||||
NotifyEvent(this, new PropertyChangedEventArgs(propName));
|
if (!NotificationsEnabled)
|
||||||
|
return;
|
||||||
|
_propertyChangedEvent.Raise(this, new PropertyChangedEventArgs(propName));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
NotifyEvent(this, e);
|
if (!NotificationsEnabled)
|
||||||
OnPropertyChanged("Item[]");
|
return;
|
||||||
|
_collectionEventQueue.Enqueue(e);
|
||||||
|
// In half a second, flush the events
|
||||||
|
_flushEventQueue.Change(500, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void NotifyEvent(object sender, PropertyChangedEventArgs args)
|
private readonly Timer _flushEventQueue;
|
||||||
|
|
||||||
|
private readonly Queue<NotifyCollectionChangedEventArgs> _collectionEventQueue =
|
||||||
|
new Queue<NotifyCollectionChangedEventArgs>();
|
||||||
|
|
||||||
|
private void FlushCollectionEventQueue(object data)
|
||||||
{
|
{
|
||||||
if (NotificationsEnabled)
|
bool reset = _collectionEventQueue.Count >= 2;
|
||||||
_propertyChangedEvent.Raise(sender, args);
|
var itemsChanged = false;
|
||||||
|
while (_collectionEventQueue.TryDequeue(out NotifyCollectionChangedEventArgs e))
|
||||||
|
if (!reset)
|
||||||
|
{
|
||||||
|
_collectionChangedEvent.Raise(this, e);
|
||||||
|
itemsChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reset)
|
||||||
|
{
|
||||||
|
_collectionChangedEvent.Raise(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
|
itemsChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemsChanged)
|
||||||
|
OnPropertyChanged("Item[]");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void NotifyEvent(object sender, NotifyCollectionChangedEventArgs args)
|
private readonly MtObservableEvent<PropertyChangedEventArgs, PropertyChangedEventHandler> _propertyChangedEvent
|
||||||
{
|
=
|
||||||
if (NotificationsEnabled)
|
|
||||||
_collectionChangedEvent.Raise(sender, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly MtObservableEvent<PropertyChangedEventArgs, PropertyChangedEventHandler> _propertyChangedEvent =
|
|
||||||
new MtObservableEvent<PropertyChangedEventArgs, PropertyChangedEventHandler>();
|
new MtObservableEvent<PropertyChangedEventArgs, PropertyChangedEventHandler>();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event PropertyChangedEventHandler PropertyChanged
|
public event PropertyChangedEventHandler PropertyChanged
|
||||||
{
|
{
|
||||||
@@ -219,8 +256,10 @@ namespace Torch.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly MtObservableEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler> _collectionChangedEvent =
|
private readonly MtObservableEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler>
|
||||||
new MtObservableEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler>();
|
_collectionChangedEvent =
|
||||||
|
new MtObservableEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler>();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event NotifyCollectionChangedEventHandler CollectionChanged
|
public event NotifyCollectionChangedEventHandler CollectionChanged
|
||||||
{
|
{
|
||||||
@@ -235,6 +274,7 @@ namespace Torch.Collections
|
|||||||
OnPropertyChanged(nameof(IsObserved));
|
OnPropertyChanged(nameof(IsObserved));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -243,6 +283,7 @@ namespace Torch.Collections
|
|||||||
public bool IsObserved => _collectionChangedEvent.IsObserved || _propertyChangedEvent.IsObserved;
|
public bool IsObserved => _collectionChangedEvent.IsObserved || _propertyChangedEvent.IsObserved;
|
||||||
|
|
||||||
#region Enumeration
|
#region Enumeration
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages a snapshot to a collection and dispatches enumerators from that snapshot.
|
/// Manages a snapshot to a collection and dispatches enumerators from that snapshot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -250,10 +291,12 @@ namespace Torch.Collections
|
|||||||
{
|
{
|
||||||
private readonly MtObservableCollection<TC, TV> _owner;
|
private readonly MtObservableCollection<TC, TV> _owner;
|
||||||
private readonly WeakReference<List<TV>> _snapshot;
|
private readonly WeakReference<List<TV>> _snapshot;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="MtObservableCollection{TC,TV}._version"/> of the <see cref="_snapshot"/>
|
/// The <see cref="MtObservableCollection{TC,TV}._version"/> of the <see cref="_snapshot"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int _snapshotVersion;
|
private int _snapshotVersion;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Number of strong references to the value pointed to be <see cref="_snapshot"/>
|
/// Number of strong references to the value pointed to be <see cref="_snapshot"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -358,6 +401,28 @@ namespace Torch.Collections
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void ICollection.CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
using (Lock.ReadUsing())
|
||||||
|
{
|
||||||
|
int i = index;
|
||||||
|
foreach (TV value in Backing)
|
||||||
|
{
|
||||||
|
if (i >= array.Length)
|
||||||
|
break;
|
||||||
|
array.SetValue(value, i++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
object ICollection.SyncRoot => this;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool ICollection.IsSynchronized => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -12,7 +13,7 @@ namespace Torch.Collections
|
|||||||
/// Multithread safe, observable list
|
/// Multithread safe, observable list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Value type</typeparam>
|
/// <typeparam name="T">Value type</typeparam>
|
||||||
public class MtObservableList<T> : MtObservableCollection<IList<T>, T>, IList<T>
|
public class MtObservableList<T> : MtObservableCollection<IList<T>, T>, IList<T>, IList
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the MtObservableList class that is empty and has the default initial capacity.
|
/// Initializes a new instance of the MtObservableList class that is empty and has the default initial capacity.
|
||||||
@@ -56,7 +57,8 @@ namespace Torch.Collections
|
|||||||
Backing.Insert(index, item);
|
Backing.Insert(index, item);
|
||||||
MarkSnapshotsDirty();
|
MarkSnapshotsDirty();
|
||||||
OnPropertyChanged(nameof(Count));
|
OnPropertyChanged(nameof(Count));
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
|
OnCollectionChanged(
|
||||||
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +71,8 @@ namespace Torch.Collections
|
|||||||
Backing.RemoveAt(index);
|
Backing.RemoveAt(index);
|
||||||
MarkSnapshotsDirty();
|
MarkSnapshotsDirty();
|
||||||
OnPropertyChanged(nameof(Count));
|
OnPropertyChanged(nameof(Count));
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, index));
|
OnCollectionChanged(
|
||||||
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +91,8 @@ namespace Torch.Collections
|
|||||||
T old = Backing[index];
|
T old = Backing[index];
|
||||||
Backing[index] = value;
|
Backing[index] = value;
|
||||||
MarkSnapshotsDirty();
|
MarkSnapshotsDirty();
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, old, index));
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
|
||||||
|
value, old, index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,5 +125,51 @@ namespace Torch.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IList.Add(object value)
|
||||||
|
{
|
||||||
|
if (value is T t)
|
||||||
|
using (Lock.WriteUsing())
|
||||||
|
{
|
||||||
|
int index = Backing.Count;
|
||||||
|
Backing.Add(t);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IList.Contains(object value)
|
||||||
|
{
|
||||||
|
return value is T t && Contains(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
int IList.IndexOf(object value)
|
||||||
|
{
|
||||||
|
return value is T t ? IndexOf(t) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList.Insert(int index, object value)
|
||||||
|
{
|
||||||
|
Insert(index, (T) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList.Remove(object value)
|
||||||
|
{
|
||||||
|
if (value is T t)
|
||||||
|
base.Remove(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
object IList.this[int index]
|
||||||
|
{
|
||||||
|
get => this[index];
|
||||||
|
set => this[index] = (T) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool IList.IsFixedSize => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user