diff --git a/Torch.Server/Managers/EntityControlManager.cs b/Torch.Server/Managers/EntityControlManager.cs
index 5f43884..65d1e8f 100644
--- a/Torch.Server/Managers/EntityControlManager.cs
+++ b/Torch.Server/Managers/EntityControlManager.cs
@@ -77,7 +77,7 @@ namespace Torch.Server.Managers
if (evm is T 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 null;
@@ -224,7 +224,7 @@ namespace Torch.Server.Managers
if (factory.Method.GetParameters()[0].ParameterType.IsInstanceOfType(model) &&
factory.DynamicInvoke(model) is Control result)
{
- _log.Debug($"Control factory {factory.Method} created {result}");
+ _log.Trace($"Control factory {factory.Method} created {result}");
return result;
}
_log.Warn($"No control created for {model}");
diff --git a/Torch/Collections/MTObservableCollection.cs b/Torch/Collections/MTObservableCollection.cs
index dec4b60..3096bea 100644
--- a/Torch/Collections/MTObservableCollection.cs
+++ b/Torch/Collections/MTObservableCollection.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
@@ -16,7 +17,8 @@ namespace Torch.Collections
///
/// Collection type
/// Value type
- public abstract class MtObservableCollection : INotifyPropertyChanged, INotifyCollectionChanged, IEnumerable where TC : class, ICollection
+ public abstract class MtObservableCollection : INotifyPropertyChanged, INotifyCollectionChanged,
+ IEnumerable, ICollection where TC : class, ICollection
{
protected readonly ReaderWriterLockSlim Lock;
protected readonly TC Backing;
@@ -30,6 +32,13 @@ namespace Torch.Collections
Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
_version = 0;
_threadViews = new ThreadLocal(() => new ThreadView(this));
+ _deferredSnapshot = new DeferredUpdateToken(this);
+ _flushEventQueue = new Timer(FlushCollectionEventQueue);
+ }
+
+ ~MtObservableCollection()
+ {
+ _flushEventQueue.Dispose();
}
///
@@ -53,6 +62,7 @@ namespace Torch.Collections
}
#region ICollection
+
///
public void Add(TV item)
{
@@ -61,7 +71,8 @@ namespace Torch.Collections
Backing.Add(item);
MarkSnapshotsDirty();
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));
OnCollectionChanged(oldIndex.HasValue
- ? new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, oldIndex.Value)
+ ? new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item,
+ oldIndex.Value)
: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
return true;
}
@@ -126,11 +138,13 @@ namespace Torch.Collections
///
public bool IsReadOnly => Backing.IsReadOnly;
+
#endregion
#region Event Wrappers
- private readonly WeakReference _deferredSnapshot = new WeakReference(null);
- private bool _deferredSnapshotTaken = false;
+
+ private readonly DeferredUpdateToken _deferredSnapshot;
+
///
/// Disposable that stops update signals and signals a full refresh when disposed.
///
@@ -138,13 +152,8 @@ namespace Torch.Collections
{
using (Lock.WriteUsing())
{
- if (_deferredSnapshotTaken)
- return new DummyToken();
- DeferredUpdateToken token;
- if (!_deferredSnapshot.TryGetTarget(out token))
- _deferredSnapshot.SetTarget(token = new DeferredUpdateToken());
- token.SetCollection(this);
- return token;
+ _deferredSnapshot.Enter();
+ return _deferredSnapshot;
}
}
@@ -157,53 +166,81 @@ namespace Torch.Collections
private class DeferredUpdateToken : IDisposable
{
- private MtObservableCollection _collection;
+ private readonly MtObservableCollection _collection;
+ private int _depth;
- internal void SetCollection(MtObservableCollection c)
+ internal DeferredUpdateToken(MtObservableCollection c)
{
- c._deferredSnapshotTaken = true;
_collection = c;
- c.NotificationsEnabled = false;
+ }
+
+ internal void Enter()
+ {
+ if (Interlocked.Increment(ref _depth) == 1)
+ {
+ _collection.NotificationsEnabled = false;
+ }
}
public void Dispose()
{
- using (_collection.Lock.WriteUsing())
- {
- _collection.NotificationsEnabled = true;
- _collection.OnPropertyChanged(nameof(Count));
- _collection.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
- _collection._deferredSnapshotTaken = false;
- }
-
+ if (Interlocked.Decrement(ref _depth) == 0)
+ using (_collection.Lock.WriteUsing())
+ {
+ _collection.NotificationsEnabled = true;
+ _collection.OnPropertyChanged(nameof(Count));
+ _collection.OnCollectionChanged(
+ new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
}
}
protected void OnPropertyChanged(string propName)
{
- NotifyEvent(this, new PropertyChangedEventArgs(propName));
+ if (!NotificationsEnabled)
+ return;
+ _propertyChangedEvent.Raise(this, new PropertyChangedEventArgs(propName));
}
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
- NotifyEvent(this, e);
- OnPropertyChanged("Item[]");
+ if (!NotificationsEnabled)
+ 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 _collectionEventQueue =
+ new Queue();
+
+ private void FlushCollectionEventQueue(object data)
{
- if (NotificationsEnabled)
- _propertyChangedEvent.Raise(sender, args);
+ bool reset = _collectionEventQueue.Count >= 2;
+ 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)
- {
- if (NotificationsEnabled)
- _collectionChangedEvent.Raise(sender, args);
- }
-
- private readonly MtObservableEvent _propertyChangedEvent =
+ private readonly MtObservableEvent _propertyChangedEvent
+ =
new MtObservableEvent();
+
///
public event PropertyChangedEventHandler PropertyChanged
{
@@ -219,8 +256,10 @@ namespace Torch.Collections
}
}
- private readonly MtObservableEvent _collectionChangedEvent =
- new MtObservableEvent();
+ private readonly MtObservableEvent
+ _collectionChangedEvent =
+ new MtObservableEvent();
+
///
public event NotifyCollectionChangedEventHandler CollectionChanged
{
@@ -235,14 +274,16 @@ namespace Torch.Collections
OnPropertyChanged(nameof(IsObserved));
}
}
+
#endregion
-
+
///
/// Is this collection observed by any listeners.
///
public bool IsObserved => _collectionChangedEvent.IsObserved || _propertyChangedEvent.IsObserved;
#region Enumeration
+
///
/// Manages a snapshot to a collection and dispatches enumerators from that snapshot.
///
@@ -250,10 +291,12 @@ namespace Torch.Collections
{
private readonly MtObservableCollection _owner;
private readonly WeakReference> _snapshot;
+
///
/// The of the
///
private int _snapshotVersion;
+
///
/// Number of strong references to the value pointed to be
///
@@ -358,6 +401,28 @@ namespace Torch.Collections
///
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
#endregion
+
+ ///
+ 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++);
+ }
+ }
+ }
+
+ ///
+ object ICollection.SyncRoot => this;
+
+ ///
+ bool ICollection.IsSynchronized => true;
}
-}
+}
\ No newline at end of file
diff --git a/Torch/Collections/MtObservableList.cs b/Torch/Collections/MtObservableList.cs
index b290d03..af21eb0 100644
--- a/Torch/Collections/MtObservableList.cs
+++ b/Torch/Collections/MtObservableList.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
@@ -12,7 +13,7 @@ namespace Torch.Collections
/// Multithread safe, observable list
///
/// Value type
- public class MtObservableList : MtObservableCollection, T>, IList
+ public class MtObservableList : MtObservableCollection, T>, IList, IList
{
///
/// 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);
MarkSnapshotsDirty();
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);
MarkSnapshotsDirty();
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];
Backing[index] = value;
MarkSnapshotsDirty();
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, old, index));
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
+ value, old, index));
}
}
}
@@ -121,5 +125,51 @@ namespace Torch.Collections
}
}
}
+
+ ///
+ 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;
+ }
+
+ ///
+ void IList.Insert(int index, object value)
+ {
+ Insert(index, (T) value);
+ }
+
+ ///
+ void IList.Remove(object value)
+ {
+ if (value is T t)
+ base.Remove(t);
+ }
+
+ ///
+ object IList.this[int index]
+ {
+ get => this[index];
+ set => this[index] = (T) value;
+ }
+
+ ///
+ bool IList.IsFixedSize => false;
}
-}
+}
\ No newline at end of file