MT observable collections

(UI sync fix)
This commit is contained in:
Westin Miller
2017-10-27 17:52:27 -07:00
parent fdc20d4e9d
commit 6f5142393b
20 changed files with 860 additions and 390 deletions

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
<variable name="logContent" value="${message:withException=true}"/>

View File

@@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using NLog;
using Sandbox.Engine.Utils;
using Torch.Collections;
using VRage.Game;
using VRage.Game.ModAPI;
@@ -58,7 +59,7 @@ namespace Torch.Server.ViewModels
private SessionSettingsViewModel _sessionSettings;
public SessionSettingsViewModel SessionSettings { get => _sessionSettings; set { _sessionSettings = value; OnPropertyChanged(); } }
public ObservableList<string> WorldPaths { get; } = new ObservableList<string>();
public MtObservableList<string> WorldPaths { get; } = new MtObservableList<string>();
private string _administrators;
public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } }
private string _banned;

View File

@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Sandbox.Game.Entities.Cube;
using Sandbox.ModAPI;
using Sandbox.ModAPI.Interfaces;
using Torch.Collections;
using Torch.Server.ViewModels.Entities;
namespace Torch.Server.ViewModels.Blocks
@@ -15,7 +16,7 @@ namespace Torch.Server.ViewModels.Blocks
public class BlockViewModel : EntityViewModel
{
public IMyTerminalBlock Block { get; }
public ObservableList<PropertyViewModel> Properties { get; } = new ObservableList<PropertyViewModel>();
public MtObservableList<PropertyViewModel> Properties { get; } = new MtObservableList<PropertyViewModel>();
public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}";

View File

@@ -2,6 +2,7 @@
using System.Linq;
using Sandbox.Game.Entities;
using Sandbox.ModAPI;
using Torch.Collections;
using Torch.Server.ViewModels.Blocks;
namespace Torch.Server.ViewModels.Entities
@@ -9,7 +10,7 @@ namespace Torch.Server.ViewModels.Entities
public class GridViewModel : EntityViewModel, ILazyLoad
{
private MyCubeGrid Grid => (MyCubeGrid)Entity;
public ObservableList<BlockViewModel> Blocks { get; } = new ObservableList<BlockViewModel>();
public MtObservableList<BlockViewModel> Blocks { get; } = new MtObservableList<BlockViewModel>();
/// <inheritdoc />
public string DescriptiveName { get; }
@@ -34,7 +35,7 @@ namespace Torch.Server.ViewModels.Entities
{
var block = obj.FatBlock as IMyTerminalBlock;
if (block != null)
Blocks.Insert(new BlockViewModel(block, Tree), b => b.Name);
Blocks.Add(new BlockViewModel(block, Tree));
OnPropertyChanged(nameof(Name));
}

View File

@@ -4,6 +4,7 @@ using Sandbox.Game.Entities;
using VRage.Game.Entity;
using VRage.Game.ModAPI;
using System.Threading.Tasks;
using Torch.Collections;
namespace Torch.Server.ViewModels.Entities
{
@@ -15,7 +16,7 @@ namespace Torch.Server.ViewModels.Entities
public override bool CanStop => false;
public ObservableList<GridViewModel> AttachedGrids { get; } = new ObservableList<GridViewModel>();
public MtObservableList<GridViewModel> AttachedGrids { get; } = new MtObservableList<GridViewModel>();
public async Task UpdateAttachedGrids()
{

View File

@@ -11,16 +11,17 @@ using VRage.Game.ModAPI;
using VRage.ModAPI;
using System.Windows.Threading;
using NLog;
using Torch.Collections;
namespace Torch.Server.ViewModels
{
public class EntityTreeViewModel : ViewModel
{
//TODO: these should be sorted sets for speed
public ObservableList<GridViewModel> Grids { get; set; } = new ObservableList<GridViewModel>();
public ObservableList<CharacterViewModel> Characters { get; set; } = new ObservableList<CharacterViewModel>();
public ObservableList<EntityViewModel> FloatingObjects { get; set; } = new ObservableList<EntityViewModel>();
public ObservableList<VoxelMapViewModel> VoxelMaps { get; set; } = new ObservableList<VoxelMapViewModel>();
public MtObservableList<GridViewModel> Grids { get; set; } = new MtObservableList<GridViewModel>();
public MtObservableList<CharacterViewModel> Characters { get; set; } = new MtObservableList<CharacterViewModel>();
public MtObservableList<EntityViewModel> FloatingObjects { get; set; } = new MtObservableList<EntityViewModel>();
public MtObservableList<VoxelMapViewModel> VoxelMaps { get; set; } = new MtObservableList<VoxelMapViewModel>();
public Dispatcher ControlDispatcher => _control.Dispatcher;
private EntityViewModel _currentEntity;
@@ -29,7 +30,7 @@ namespace Torch.Server.ViewModels
public EntityViewModel CurrentEntity
{
get => _currentEntity;
set { _currentEntity = value; OnPropertyChanged(); }
set { _currentEntity = value; OnPropertyChanged(nameof(CurrentEntity)); }
}
public EntityTreeViewModel(UserControl control)
@@ -67,16 +68,16 @@ namespace Torch.Server.ViewModels
switch (obj)
{
case MyCubeGrid grid:
Grids.Insert(new GridViewModel(grid, this), g => g.Name);
Grids.Add(new GridViewModel(grid, this));
break;
case MyCharacter character:
Characters.Insert(new CharacterViewModel(character, this), c => c.Name);
Characters.Add(new CharacterViewModel(character, this));
break;
case MyFloatingObject floating:
FloatingObjects.Insert(new FloatingObjectViewModel(floating, this), f => f.Name);
FloatingObjects.Add(new FloatingObjectViewModel(floating, this));
break;
case MyVoxelBase voxel:
VoxelMaps.Insert(new VoxelMapViewModel(voxel, this), v => v.Name);
VoxelMaps.Add(new VoxelMapViewModel(voxel, this));
break;
}
}

View File

@@ -6,18 +6,19 @@ using System.Threading.Tasks;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Plugins;
using Torch.Collections;
namespace Torch.Server.ViewModels
{
public class PluginManagerViewModel : ViewModel
{
public ObservableList<PluginViewModel> Plugins { get; } = new ObservableList<PluginViewModel>();
public MtObservableList<PluginViewModel> Plugins { get; } = new MtObservableList<PluginViewModel>();
private PluginViewModel _selectedPlugin;
public PluginViewModel SelectedPlugin
{
get => _selectedPlugin;
set { _selectedPlugin = value; OnPropertyChanged(); }
set { _selectedPlugin = value; OnPropertyChanged(nameof(SelectedPlugin)); }
}
public PluginManagerViewModel() { }

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpDX.Toolkit.Collections;
using Torch.Collections;
using VRage.Game;
using VRage.Library.Utils;
@@ -35,7 +36,7 @@ namespace Torch.Server.ViewModels
BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value));
}
public ObservableList<BlockLimitViewModel> BlockLimits { get; } = new ObservableList<BlockLimitViewModel>();
public MtObservableList<BlockLimitViewModel> BlockLimits { get; } = new MtObservableList<BlockLimitViewModel>();
#region Multipliers

View File

@@ -1,77 +1,358 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using Torch.Utils;
namespace Torch
namespace Torch.Collections
{
[Obsolete("Use ObservableList<T>.")]
public class MTObservableCollection<T> : ObservableCollection<T>
/// <summary>
/// Multithread safe, observable collection
/// </summary>
/// <typeparam name="TC">Collection type</typeparam>
/// <typeparam name="TV">Value type</typeparam>
public abstract class MtObservableCollection<TC, TV> : INotifyPropertyChanged, INotifyCollectionChanged, IEnumerable<TV> where TC : class, ICollection<TV>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected readonly ReaderWriterLockSlim Lock;
protected readonly TC Backing;
private int _version;
private readonly ThreadLocal<ThreadView> _threadViews;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
protected MtObservableCollection(TC backing)
{
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
if (collectionChanged != null)
foreach (var del in collectionChanged.GetInvocationList())
{
var nh = (NotifyCollectionChangedEventHandler)del;
var dispObj = nh.Target as DispatcherObject;
Backing = backing;
// recursion so the events can read snapshots.
Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
_version = 0;
_threadViews = new ThreadLocal<ThreadView>(() => new ThreadView(this));
}
var dispatcher = dispObj?.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => nh.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
/// <summary>
/// Takes a snapshot of this collection. Note: This call is only done when a read lock is acquired.
/// </summary>
/// <param name="old">Collection to clear and reuse, or null if none</param>
/// <returns>The snapshot</returns>
protected abstract TC Snapshot(TC old);
nh.Invoke(this, e);
/// <summary>
/// Marks all snapshots taken of this collection as dirty.
/// </summary>
protected void MarkSnapshotsDirty()
{
_version++;
}
#region ICollection
/// <inheritdoc/>
public void Add(TV item)
{
using(Lock.WriteUsing())
{
Backing.Add(item);
MarkSnapshotsDirty();
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, Backing.Count - 1));
}
}
/// <inheritdoc/>
public void Clear()
{
using(Lock.WriteUsing())
{
Backing.Clear();
MarkSnapshotsDirty();
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
/// <inheritdoc/>
public bool Contains(TV item)
{
using (Lock.ReadUsing())
return Backing.Contains(item);
}
/// <inheritdoc/>
public void CopyTo(TV[] array, int arrayIndex)
{
using (Lock.ReadUsing())
Backing.CopyTo(array, arrayIndex);
}
/// <inheritdoc/>
public bool Remove(TV item)
{
using(Lock.UpgradableReadUsing()) {
int? oldIndex = (Backing as IList<TV>)?.IndexOf(item);
if (oldIndex == -1)
return false;
using(Lock.WriteUsing()) {
if (!Backing.Remove(item))
return false;
MarkSnapshotsDirty();
OnPropertyChanged(nameof(Count));
OnCollectionChanged(oldIndex != null
? new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, oldIndex)
: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
return true;
}
}
public void Insert<TKey>(T item, Func<T, TKey> selector, IComparer<TKey> comparer)
{
var key = selector(item);
for (var i = 0; i < Count; i++)
{
var key2 = selector(Items[i]);
if (comparer.Compare(key, key2) < 1)
continue;
Insert(i + 1, item);
return;
}
Add(item);
}
public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null)
{
List<T> sortedItems;
if (comparer != null)
sortedItems = Items.OrderBy(selector, comparer).ToList();
else
sortedItems = Items.OrderBy(selector).ToList();
Items.Clear();
foreach (var item in sortedItems)
Add(item);
}
public void RemoveWhere(Func<T, bool> condition)
{
for (var i = Items.Count - 1; i > 0; i--)
{
if (condition(Items[i]))
RemoveAt(i);
}
}
/// <inheritdoc/>
public int Count
{
get
{
using (Lock.ReadUsing())
return Backing.Count;
}
}
/// <inheritdoc/>
public bool IsReadOnly => Backing.IsReadOnly;
#endregion
#region Event Wrappers
protected void OnPropertyChanged(string propName)
{
NotifyEvent(this, new PropertyChangedEventArgs(propName));
}
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyEvent(this, e);
}
protected void NotifyEvent(object sender, PropertyChangedEventArgs args)
{
_propertyChangedEvent.Raise(sender, args);
}
protected void NotifyEvent(object sender, NotifyCollectionChangedEventArgs args)
{
_collectionChangedEvent.Raise(sender, args);
}
private readonly DispatcherEvent<PropertyChangedEventArgs, PropertyChangedEventHandler> _propertyChangedEvent =
new DispatcherEvent<PropertyChangedEventArgs, PropertyChangedEventHandler>();
/// <inheritdoc/>
public event PropertyChangedEventHandler PropertyChanged
{
add => _propertyChangedEvent.Add(value);
remove => _propertyChangedEvent.Remove(value);
}
private readonly DispatcherEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler> _collectionChangedEvent =
new DispatcherEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler>();
/// <inheritdoc/>
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => _collectionChangedEvent.Add(value);
remove => _collectionChangedEvent.Remove(value);
}
/// <summary>
/// Event that invokes handlers registered by dispatchers on dispatchers.
/// </summary>
/// <typeparam name="TEvtArgs">Event argument type</typeparam>
/// <typeparam name="TEvtHandle">Event handler delegate type</typeparam>
private sealed class DispatcherEvent<TEvtArgs, TEvtHandle> where TEvtArgs : EventArgs
{
private delegate void DelInvokeHandler(TEvtHandle handler, object sender, TEvtArgs args);
private static readonly DelInvokeHandler _invokeDirectly;
static DispatcherEvent()
{
MethodInfo invoke = typeof(TEvtHandle).GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
Debug.Assert(invoke != null, "No invoke method on handler type");
_invokeDirectly = (DelInvokeHandler)Delegate.CreateDelegate(typeof(DelInvokeHandler), invoke);
}
private static Dispatcher CurrentDispatcher => Dispatcher.FromThread(Thread.CurrentThread);
private event EventHandler<TEvtArgs> _event;
internal void Raise(object sender, TEvtArgs args)
{
_event?.Invoke(sender, args);
}
internal void Add(TEvtHandle evt)
{
if (evt == null)
return;
_event += new DispatcherDelegate(evt).Invoke;
}
internal void Remove(TEvtHandle evt)
{
if (_event == null || evt == null)
return;
Delegate[] invokeList = _event.GetInvocationList();
for (int i = invokeList.Length - 1; i >= 0; i--)
{
var wrapper = (DispatcherDelegate)invokeList[i].Target;
Debug.Assert(wrapper._dispatcher == CurrentDispatcher, "Adding and removing should be done from the same dispatcher");
if (wrapper._delegate.Equals(evt))
{
_event -= wrapper.Invoke;
return;
}
}
}
private struct DispatcherDelegate
{
internal readonly Dispatcher _dispatcher;
internal readonly TEvtHandle _delegate;
internal DispatcherDelegate(TEvtHandle del)
{
_dispatcher = CurrentDispatcher;
_delegate = del;
}
public void Invoke(object sender, TEvtArgs args)
{
if (_dispatcher == null || _dispatcher == CurrentDispatcher)
_invokeDirectly(_delegate, sender, args);
else
// (Delegate) (object) == dual cast so that the compiler likes it
_dispatcher.BeginInvoke((Delegate)(object)_delegate, DispatcherPriority.DataBind, sender, args);
}
}
}
#endregion
#region Enumeration
/// <summary>
/// Manages a snapshot to a collection and dispatches enumerators from that snapshot.
/// </summary>
private sealed class ThreadView
{
private readonly MtObservableCollection<TC, TV> _owner;
private readonly WeakReference<TC> _snapshot;
/// <summary>
/// The <see cref="MtObservableCollection{TC,TV}._version"/> of the <see cref="_snapshot"/>
/// </summary>
private int _snapshotVersion;
/// <summary>
/// Number of strong references to the value pointed to be <see cref="_snapshot"/>
/// </summary>
private int _snapshotRefCount;
internal ThreadView(MtObservableCollection<TC, TV> owner)
{
_owner = owner;
_snapshot = new WeakReference<TC>(null);
_snapshotVersion = 0;
_snapshotRefCount = 0;
}
private TC GetSnapshot()
{
// reading the version number + snapshots
using (_owner.Lock.ReadUsing())
{
if (!_snapshot.TryGetTarget(out TC currentSnapshot) || _snapshotVersion != _owner._version)
{
// Update the snapshot, using the old one if it isn't referenced.
currentSnapshot = _owner.Snapshot(_snapshotRefCount == 0 ? currentSnapshot : null);
_snapshotVersion = _owner._version;
_snapshotRefCount = 0;
_snapshot.SetTarget(currentSnapshot);
}
return currentSnapshot;
}
}
/// <summary>
/// Borrows a snapshot from a <see cref="ThreadView"/> and provides an enumerator.
/// Once <see cref="Dispose"/> is called the read lock is released.
/// </summary>
internal sealed class Enumerator : IEnumerator<TV>
{
private readonly IEnumerator<TV> _backing;
private readonly ThreadView _owner;
private bool _disposed;
internal Enumerator(ThreadView owner)
{
_owner = owner;
// Lock required since destructors run MT
lock (_owner)
{
_owner._snapshotRefCount++;
_backing = owner.GetSnapshot().GetEnumerator();
}
_disposed = false;
}
~Enumerator()
{
// Lock required since destructors run MT
if (!_disposed && _owner != null)
lock (_owner)
Dispose();
}
public void Dispose()
{
// safe deref so finalizer can clean up
_backing?.Dispose();
_owner._snapshotRefCount--;
_disposed = true;
}
public bool MoveNext()
{
if (_disposed)
throw new ObjectDisposedException(nameof(Enumerator));
return _backing.MoveNext();
}
public void Reset()
{
if (_disposed)
throw new ObjectDisposedException(nameof(Enumerator));
_backing.Reset();
}
public TV Current
{
get
{
if (_disposed)
throw new ObjectDisposedException(nameof(Enumerator));
return _backing.Current;
}
}
object IEnumerator.Current => Current;
}
}
/// <inheritdoc/>
public IEnumerator<TV> GetEnumerator()
{
return new ThreadView.Enumerator(_threadViews.Value);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#endregion
}
}

View File

@@ -0,0 +1,171 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.Utils;
namespace Torch.Collections
{
/// <summary>
/// Multithread safe observable dictionary
/// </summary>
/// <typeparam name="TK">Key type</typeparam>
/// <typeparam name="TV">Value type</typeparam>
public class MtObservableDictionary<TK, TV> : MtObservableCollection<IDictionary<TK, TV>, KeyValuePair<TK, TV>>, IDictionary<TK, TV>
{
/// <summary>
/// Creates an empty observable dictionary
/// </summary>
public MtObservableDictionary() : base(new Dictionary<TK, TV>())
{
Keys = new ProxyCollection<TK>(this, Backing.Keys, (x) => x.Key);
Values = new ProxyCollection<TV>(this, Backing.Values, (x) => x.Value);
}
protected override IDictionary<TK, TV> Snapshot(IDictionary<TK, TV> old)
{
if (old == null)
return new Dictionary<TK, TV>(Backing);
old.Clear();
foreach (KeyValuePair<TK, TV> k in Backing)
old.Add(k);
return old;
}
/// <inheritdoc/>
public bool ContainsKey(TK key)
{
using (Lock.ReadUsing())
return Backing.ContainsKey(key);
}
/// <inheritdoc/>
public void Add(TK key, TV value)
{
Add(new KeyValuePair<TK, TV>(key, value));
}
/// <inheritdoc/>
public bool Remove(TK key)
{
return TryGetValue(key, out TV result) && Remove(new KeyValuePair<TK, TV>(key, result));
}
/// <inheritdoc/>
public bool TryGetValue(TK key, out TV value)
{
using (Lock.ReadUsing())
return Backing.TryGetValue(key, out value);
}
/// <inheritdoc/>
public TV this[TK key]
{
get
{
using (Lock.ReadUsing())
return Backing[key];
}
set
{
using (Lock.WriteUsing())
{
var oldKv = new KeyValuePair<TK, TV>(key, Backing[key]);
var newKv = new KeyValuePair<TK, TV>(key, value);
Backing[key] = value;
MarkSnapshotsDirty();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newKv, oldKv));
}
}
}
/// <inheritdoc/>
public ICollection<TK> Keys { get; }
/// <inheritdoc/>
public ICollection<TV> Values { get; }
internal void RaiseFullReset()
{
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private class ProxyCollection<TP> : ICollection<TP>
{
private readonly MtObservableDictionary<TK, TV> _owner;
private readonly ICollection<TP> _backing;
private readonly Func<KeyValuePair<TK, TV>, TP> _selector;
internal ProxyCollection(MtObservableDictionary<TK, TV> owner, ICollection<TP> backing, Func<KeyValuePair<TK, TV>, TP> selector)
{
_owner = owner;
_backing = backing;
_selector = selector;
}
/// <inheritdoc/>
public IEnumerator<TP> GetEnumerator() => new TransformEnumerator<KeyValuePair<TK, TV>, TP>(_owner.GetEnumerator(), _selector);
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc/>
public void Add(TP item)
{
using (_owner.Lock.WriteUsing())
{
_backing.Add(item);
_owner.RaiseFullReset();
}
}
/// <inheritdoc/>
public void Clear()
{
_owner.Clear();
}
/// <inheritdoc/>
public bool Contains(TP item)
{
using (_owner.Lock.ReadUsing())
return _backing.Contains(item);
}
/// <inheritdoc/>
public void CopyTo(TP[] array, int arrayIndex)
{
using (_owner.Lock.ReadUsing())
_backing.CopyTo(array, arrayIndex);
}
/// <inheritdoc/>
public bool Remove(TP item)
{
using (_owner.Lock.WriteUsing())
{
if (!_backing.Remove(item))
return false;
_owner.RaiseFullReset();
return true;
}
}
/// <inheritdoc/>
public int Count
{
get
{
using (_owner.Lock.ReadUsing())
return _backing.Count;
}
}
public bool IsReadOnly => _backing.IsReadOnly;
}
}
}

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.Utils;
namespace Torch.Collections
{
/// <summary>
/// Multithread safe, observable list
/// </summary>
/// <typeparam name="T">Value type</typeparam>
public class MtObservableList<T> : MtObservableCollection<IList<T>, T>, IList<T>
{
/// <summary>
/// Initializes a new instance of the MtObservableList class that is empty and has the default initial capacity.
/// </summary>
public MtObservableList() : base(new List<T>())
{
}
/// <summary>
/// Initializes a new instance of the MtObservableList class that is empty and has the specified initial capacity.
/// </summary>
/// <param name="capacity"></param>
public MtObservableList(int capacity) : base(new List<T>(capacity))
{
}
protected override IList<T> Snapshot(IList<T> old)
{
if (old == null)
{
var list = new List<T>(Backing);
return list;
}
old.Clear();
if (old is List<T> tmp)
tmp.AddRange(Backing);
else
foreach (T k in Backing)
old.Add(k);
return old;
}
/// <inheritdoc/>
public int IndexOf(T item)
{
using (Lock.ReadUsing())
return Backing.IndexOf(item);
}
/// <inheritdoc/>
public void Insert(int index, T item)
{
using(Lock.WriteUsing())
{
Backing.Insert(index, item);
MarkSnapshotsDirty();
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
}
/// <inheritdoc/>
public void RemoveAt(int index)
{
using (Lock.WriteUsing())
{
T old = Backing[index];
Backing.RemoveAt(index);
MarkSnapshotsDirty();
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, index));
}
}
/// <inheritdoc/>
public T this[int index]
{
get
{
using(Lock.ReadUsing())
return Backing[index];
}
set
{
using(Lock.ReadUsing()) {
T old = Backing[index];
Backing[index] = value;
MarkSnapshotsDirty();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, old, index));
}
}
}
/// <inheritdoc/>
public void RemoveWhere(Func<T, bool> predicate)
{
for (int i = Count - 1; i >= 0; i--)
if (predicate(this[i]))
RemoveAt(i);
}
/// <summary>
/// Sorts the list using the given selector and comparer./>
/// </summary>
public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null)
{
using (Lock.ReadUsing()) {
comparer = comparer ?? Comparer<TKey>.Default;
if (Backing is List<T> lst)
lst.Sort(new TransformComparer<T, TKey>(selector, comparer));
else
{
List<T> sortedItems = Backing.OrderBy(selector, comparer).ToList();
Backing.Clear();
foreach (T v in sortedItems)
Backing.Add(v);
}
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}
}

View File

@@ -1,151 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace Torch.Collections
{
[Serializable]
public class ObservableDictionary<TKey, TValue> : ViewModel, IDictionary<TKey, TValue>
{
private IDictionary<TKey, TValue> _internalDict;
public ObservableDictionary()
{
_internalDict = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
_internalDict = new Dictionary<TKey, TValue>(dictionary);
}
/// <summary>
/// Create a <see cref="ObservableDictionary{TKey,TValue}"/> using the given dictionary by reference. The original dictionary should not be used after calling this.
/// </summary>
public static ObservableDictionary<TKey, TValue> ByReference(IDictionary<TKey, TValue> dictionary)
{
return new ObservableDictionary<TKey, TValue>
{
_internalDict = dictionary
};
}
/// <inheritdoc />
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _internalDict.GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_internalDict).GetEnumerator();
}
/// <inheritdoc />
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
/// <inheritdoc />
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
/// <inheritdoc />
public void Clear()
{
_internalDict.Clear();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
OnPropertyChanged(nameof(Count));
}
/// <inheritdoc />
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _internalDict.Contains(item);
}
/// <inheritdoc />
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
foreach (var kv in _internalDict)
{
array[arrayIndex] = kv;
arrayIndex++;
}
}
/// <inheritdoc />
public int Count => _internalDict.Count;
/// <inheritdoc />
public bool IsReadOnly => false;
/// <inheritdoc />
public bool ContainsKey(TKey key)
{
return _internalDict.ContainsKey(key);
}
/// <inheritdoc />
public void Add(TKey key, TValue value)
{
_internalDict.Add(key, value);
var kv = new KeyValuePair<TKey, TValue>(key, value);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, kv));
OnPropertyChanged(nameof(Count));
}
/// <inheritdoc />
public bool Remove(TKey key)
{
if (!_internalDict.ContainsKey(key))
return false;
var kv = new KeyValuePair<TKey, TValue>(key, this[key]);
if (!_internalDict.Remove(key))
return false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, kv));
OnPropertyChanged(nameof(Count));
return true;
}
/// <inheritdoc />
public bool TryGetValue(TKey key, out TValue value)
{
return _internalDict.TryGetValue(key, out value);
}
/// <inheritdoc />
public TValue this[TKey key]
{
get => _internalDict[key];
set
{
var oldKv = new KeyValuePair<TKey, TValue>(key, _internalDict[key]);
var newKv = new KeyValuePair<TKey, TValue>(key, value);
_internalDict[key] = value;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newKv, oldKv));
}
}
/// <inheritdoc />
public ICollection<TKey> Keys => _internalDict.Keys;
/// <inheritdoc />
public ICollection<TValue> Values => _internalDict.Values;
}
}

View File

@@ -1,155 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Threading;
namespace Torch
{
/// <summary>
/// An observable version of <see cref="List{T}"/>.
/// </summary>
public class ObservableList<T> : ViewModel, IList<T>
{
private List<T> _internalList = new List<T>();
/// <inheritdoc />
public void Clear()
{
_internalList.Clear();
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <inheritdoc />
public bool Contains(T item)
{
return _internalList.Contains(item);
}
/// <inheritdoc />
public void CopyTo(T[] array, int arrayIndex)
{
_internalList.CopyTo(array, arrayIndex);
}
/// <inheritdoc />
public bool Remove(T item)
{
var oldIndex = _internalList.IndexOf(item);
if (!_internalList.Remove(item))
return false;
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, oldIndex));
return true;
}
/// <inheritdoc />
public int Count => _internalList.Count;
/// <inheritdoc />
public bool IsReadOnly => false;
/// <inheritdoc />
public void Add(T item)
{
_internalList.Add(item);
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, Count - 1));
}
/// <inheritdoc />
public int IndexOf(T item) => _internalList.IndexOf(item);
/// <inheritdoc />
public void Insert(int index, T item)
{
_internalList.Insert(index, item);
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
/// <summary>
/// Inserts an item in order based on the provided selector and comparer. This will only work properly on a pre-sorted list.
/// </summary>
public void Insert<TKey>(T item, Func<T, TKey> selector, IComparer<TKey> comparer = null)
{
comparer = comparer ?? Comparer<TKey>.Default;
var key1 = selector(item);
for (var i = 0; i < _internalList.Count; i++)
{
var key2 = selector(_internalList[i]);
if (comparer.Compare(key1, key2) < 1)
{
Insert(i, item);
return;
}
}
Add(item);
}
/// <inheritdoc />
public void RemoveAt(int index)
{
var old = this[index];
_internalList.RemoveAt(index);
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, index));
}
public T this[int index]
{
get => _internalList[index];
set
{
var old = _internalList[index];
if (old.Equals(value))
return;
_internalList[index] = value;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, old, index));
}
}
/// <summary>
/// Sorts the list using the given selector and comparer./>
/// </summary>
public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null)
{
comparer = comparer ?? Comparer<TKey>.Default;
var sortedItems = _internalList.OrderBy(selector, comparer).ToList();
_internalList = sortedItems;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary>
/// Removes all items that satisfy the given condition.
/// </summary>
public void RemoveWhere(Func<T, bool> condition)
{
for (var i = Count - 1; i > 0; i--)
{
if (condition?.Invoke(this[i]) ?? false)
RemoveAt(i);
}
}
/// <inheritdoc />
public IEnumerator<T> GetEnumerator()
{
return _internalList.GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_internalList).GetEnumerator();
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Collections
{
/// <summary>
/// Comparer that uses a delegate to select the key to compare on.
/// </summary>
/// <typeparam name="TIn">Input to this comparer</typeparam>
/// <typeparam name="TCompare">Type of comparison key</typeparam>
public class TransformComparer<TIn, TCompare> : IComparer<TIn>
{
private readonly IComparer<TCompare> _comparer;
private readonly Func<TIn, TCompare> _selector;
/// <summary>
/// Creates a new transforming comparer that uses the given key selector, and the given key comparer.
/// </summary>
/// <param name="transform">Key selector</param>
/// <param name="comparer">Key comparer</param>
public TransformComparer(Func<TIn, TCompare> transform, IComparer<TCompare> comparer = null)
{
_selector = transform;
_comparer = comparer ?? Comparer<TCompare>.Default;
}
/// <inheritdoc/>
public int Compare(TIn x, TIn y)
{
return _comparer.Compare(_selector(x), _selector(y));
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Collections
{
/// <summary>
/// Enumerator that transforms from one enumeration into another.
/// </summary>
/// <typeparam name="TIn">Input type</typeparam>
/// <typeparam name="TOut">Output type</typeparam>
public class TransformEnumerator<TIn,TOut> : IEnumerator<TOut>
{
private readonly IEnumerator<TIn> _input;
private readonly Func<TIn, TOut> _transform;
/// <summary>
/// Creates a new transform enumerator with the given transform function
/// </summary>
/// <param name="input">Input to proxy enumerator</param>
/// <param name="transform">Transform function</param>
public TransformEnumerator(IEnumerator<TIn> input, Func<TIn, TOut> transform)
{
_input = input;
_transform = transform;
}
/// <inheritdoc/>
public void Dispose()
{
_input.Dispose();
}
/// <inheritdoc/>
public bool MoveNext()
{
return _input.MoveNext();
}
/// <inheritdoc/>
public void Reset()
{
_input.Reset();
}
/// <inheritdoc/>
public TOut Current => _transform(_input.Current);
/// <inheritdoc/>
object IEnumerator.Current => Current;
}
}

View File

@@ -14,7 +14,7 @@ using VRage.Utils;
namespace Torch.Managers
{
[PatchShim]
internal class KeenLogManager
internal static class KeenLogPatch
{
private static readonly Logger _log = LogManager.GetLogger("Keen");
@@ -63,7 +63,7 @@ namespace Torch.Managers
private static MethodInfo Method(string name)
{
return typeof(KeenLogManager).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
return typeof(KeenLogPatch).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
}
[ReflectedMethod(Name = "GetThreadId")]

View File

@@ -48,7 +48,7 @@ namespace Torch.Managers
/// <inheritdoc />
public event Action<IPlayer> PlayerLeft;
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
public MtObservableDictionary<ulong, PlayerViewModel> Players { get; } = new MtObservableDictionary<ulong, PlayerViewModel>();
#pragma warning disable 649
[ReflectedGetter(Name = "m_players")]

View File

@@ -27,7 +27,7 @@ namespace Torch.Managers
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
private const string MANIFEST_NAME = "manifest.xml";
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
private readonly ObservableDictionary<Guid, ITorchPlugin> _plugins = new ObservableDictionary<Guid, ITorchPlugin>();
private readonly MtObservableDictionary<Guid, ITorchPlugin> _plugins = new MtObservableDictionary<Guid, ITorchPlugin>();
#pragma warning disable 649
[Dependency]
private ITorchSessionManager _sessionManager;

View File

@@ -154,7 +154,11 @@
<Compile Include="..\Versioning\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="Collections\ObservableList.cs" />
<Compile Include="Collections\MtObservableCollection.cs" />
<Compile Include="Collections\MtObservableDictionary.cs" />
<Compile Include="Collections\MtObservableList.cs" />
<Compile Include="Collections\TransformComparer.cs" />
<Compile Include="Collections\TransformEnumerator.cs" />
<Compile Include="Event\EventShimAttribute.cs" />
<Compile Include="Managers\ChatManager\ChatManagerClient.cs" />
<Compile Include="Managers\ChatManager\ChatManagerServer.cs" />
@@ -164,7 +168,7 @@
<Compile Include="Event\EventList.cs" />
<Compile Include="Event\EventManager.cs" />
<Compile Include="Event\IEventList.cs" />
<Compile Include="Managers\KeenLogManager.cs" />
<Compile Include="Managers\KeenLogPatch.cs" />
<Compile Include="Managers\PatchManager\AssemblyMemory.cs" />
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
<Compile Include="Managers\PatchManager\EmitExtensions.cs" />
@@ -190,7 +194,6 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SaveGameStatus.cs" />
<Compile Include="Collections\KeyTree.cs" />
<Compile Include="Collections\ObservableDictionary.cs" />
<Compile Include="Collections\RollingAverage.cs" />
<Compile Include="CommandLine.cs" />
<Compile Include="Commands\CategoryAttribute.cs" />
@@ -215,6 +218,7 @@
<Compile Include="Utils\Reflection.cs" />
<Compile Include="Managers\ScriptingManager.cs" />
<Compile Include="Utils\StringUtils.cs" />
<Compile Include="Utils\SynchronizationExtensions.cs" />
<Compile Include="Utils\TorchAssemblyResolver.cs" />
<Compile Include="Utils\ReflectedManager.cs" />
<Compile Include="Session\TorchSessionManager.cs" />
@@ -224,7 +228,6 @@
<Compile Include="Session\TorchSession.cs" />
<Compile Include="Utils\TorchLauncher.cs" />
<Compile Include="ViewModels\ModViewModel.cs" />
<Compile Include="Collections\MTObservableCollection.cs" />
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
<Compile Include="SteamHelper.cs" />
<Compile Include="Extensions\StringExtensions.cs" />

View File

@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Torch.Utils
{
/// <summary>
/// Extension functions related to synchronization
/// </summary>
public static class SynchronizationExtensions
{
/// <summary>
/// Acquires a RAII view of the lock in read mode.
/// </summary>
/// <param name="lck">Lock</param>
/// <returns>RAII token</returns>
public static IDisposable ReadUsing(this ReaderWriterLockSlim lck)
{
return new ReaderWriterLockSlimReadToken(lck);
}
/// <summary>
/// Acquires a RAII view of the lock in upgradable read mode.
/// </summary>
/// <param name="lck">Lock</param>
/// <returns>RAII token</returns>
public static IDisposable UpgradableReadUsing(this ReaderWriterLockSlim lck)
{
return new ReaderWriterLockSlimUpgradableToken(lck);
}
/// <summary>
/// Acquires a RAII view of the lock in write mode.
/// </summary>
/// <param name="lck">Lock</param>
/// <returns>RAII token</returns>
public static IDisposable WriteUsing(this ReaderWriterLockSlim lck)
{
return new ReaderWriterLockSlimWriteToken(lck);
}
#region Support Structs
private struct ReaderWriterLockSlimUpgradableToken : IDisposable
{
private ReaderWriterLockSlim _lock;
public ReaderWriterLockSlimUpgradableToken(ReaderWriterLockSlim lc)
{
_lock = lc;
lc.EnterUpgradeableReadLock();
}
public void Dispose()
{
_lock.ExitUpgradeableReadLock();
}
}
private struct ReaderWriterLockSlimWriteToken : IDisposable
{
private ReaderWriterLockSlim _lock;
public ReaderWriterLockSlimWriteToken(ReaderWriterLockSlim lc)
{
_lock = lc;
lc.EnterWriteLock();
}
public void Dispose()
{
_lock.ExitWriteLock();
}
}
private struct ReaderWriterLockSlimReadToken : IDisposable
{
private ReaderWriterLockSlim _lock;
public ReaderWriterLockSlimReadToken(ReaderWriterLockSlim lc)
{
_lock = lc;
lc.EnterReadLock();
}
public void Dispose()
{
_lock.ExitReadLock();
}
}
#endregion
}
}