MT observable collections
(UI sync fix)
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
|
||||||
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
|
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
|
||||||
<variable name="logContent" value="${message:withException=true}"/>
|
<variable name="logContent" value="${message:withException=true}"/>
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Sandbox.Engine.Utils;
|
using Sandbox.Engine.Utils;
|
||||||
|
using Torch.Collections;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ namespace Torch.Server.ViewModels
|
|||||||
private SessionSettingsViewModel _sessionSettings;
|
private SessionSettingsViewModel _sessionSettings;
|
||||||
public SessionSettingsViewModel SessionSettings { get => _sessionSettings; set { _sessionSettings = value; OnPropertyChanged(); } }
|
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;
|
private string _administrators;
|
||||||
public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } }
|
public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } }
|
||||||
private string _banned;
|
private string _banned;
|
||||||
|
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
|||||||
using Sandbox.Game.Entities.Cube;
|
using Sandbox.Game.Entities.Cube;
|
||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
using Sandbox.ModAPI.Interfaces;
|
using Sandbox.ModAPI.Interfaces;
|
||||||
|
using Torch.Collections;
|
||||||
using Torch.Server.ViewModels.Entities;
|
using Torch.Server.ViewModels.Entities;
|
||||||
|
|
||||||
namespace Torch.Server.ViewModels.Blocks
|
namespace Torch.Server.ViewModels.Blocks
|
||||||
@@ -15,7 +16,7 @@ namespace Torch.Server.ViewModels.Blocks
|
|||||||
public class BlockViewModel : EntityViewModel
|
public class BlockViewModel : EntityViewModel
|
||||||
{
|
{
|
||||||
public IMyTerminalBlock Block { get; }
|
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}";
|
public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}";
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Sandbox.Game.Entities;
|
using Sandbox.Game.Entities;
|
||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
|
using Torch.Collections;
|
||||||
using Torch.Server.ViewModels.Blocks;
|
using Torch.Server.ViewModels.Blocks;
|
||||||
|
|
||||||
namespace Torch.Server.ViewModels.Entities
|
namespace Torch.Server.ViewModels.Entities
|
||||||
@@ -9,7 +10,7 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
public class GridViewModel : EntityViewModel, ILazyLoad
|
public class GridViewModel : EntityViewModel, ILazyLoad
|
||||||
{
|
{
|
||||||
private MyCubeGrid Grid => (MyCubeGrid)Entity;
|
private MyCubeGrid Grid => (MyCubeGrid)Entity;
|
||||||
public ObservableList<BlockViewModel> Blocks { get; } = new ObservableList<BlockViewModel>();
|
public MtObservableList<BlockViewModel> Blocks { get; } = new MtObservableList<BlockViewModel>();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string DescriptiveName { get; }
|
public string DescriptiveName { get; }
|
||||||
@@ -34,7 +35,7 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
{
|
{
|
||||||
var block = obj.FatBlock as IMyTerminalBlock;
|
var block = obj.FatBlock as IMyTerminalBlock;
|
||||||
if (block != null)
|
if (block != null)
|
||||||
Blocks.Insert(new BlockViewModel(block, Tree), b => b.Name);
|
Blocks.Add(new BlockViewModel(block, Tree));
|
||||||
|
|
||||||
OnPropertyChanged(nameof(Name));
|
OnPropertyChanged(nameof(Name));
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ using Sandbox.Game.Entities;
|
|||||||
using VRage.Game.Entity;
|
using VRage.Game.Entity;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Torch.Collections;
|
||||||
|
|
||||||
namespace Torch.Server.ViewModels.Entities
|
namespace Torch.Server.ViewModels.Entities
|
||||||
{
|
{
|
||||||
@@ -15,7 +16,7 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
|
|
||||||
public override bool CanStop => false;
|
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()
|
public async Task UpdateAttachedGrids()
|
||||||
{
|
{
|
||||||
|
@@ -11,16 +11,17 @@ using VRage.Game.ModAPI;
|
|||||||
using VRage.ModAPI;
|
using VRage.ModAPI;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using Torch.Collections;
|
||||||
|
|
||||||
namespace Torch.Server.ViewModels
|
namespace Torch.Server.ViewModels
|
||||||
{
|
{
|
||||||
public class EntityTreeViewModel : ViewModel
|
public class EntityTreeViewModel : ViewModel
|
||||||
{
|
{
|
||||||
//TODO: these should be sorted sets for speed
|
//TODO: these should be sorted sets for speed
|
||||||
public ObservableList<GridViewModel> Grids { get; set; } = new ObservableList<GridViewModel>();
|
public MtObservableList<GridViewModel> Grids { get; set; } = new MtObservableList<GridViewModel>();
|
||||||
public ObservableList<CharacterViewModel> Characters { get; set; } = new ObservableList<CharacterViewModel>();
|
public MtObservableList<CharacterViewModel> Characters { get; set; } = new MtObservableList<CharacterViewModel>();
|
||||||
public ObservableList<EntityViewModel> FloatingObjects { get; set; } = new ObservableList<EntityViewModel>();
|
public MtObservableList<EntityViewModel> FloatingObjects { get; set; } = new MtObservableList<EntityViewModel>();
|
||||||
public ObservableList<VoxelMapViewModel> VoxelMaps { get; set; } = new ObservableList<VoxelMapViewModel>();
|
public MtObservableList<VoxelMapViewModel> VoxelMaps { get; set; } = new MtObservableList<VoxelMapViewModel>();
|
||||||
public Dispatcher ControlDispatcher => _control.Dispatcher;
|
public Dispatcher ControlDispatcher => _control.Dispatcher;
|
||||||
|
|
||||||
private EntityViewModel _currentEntity;
|
private EntityViewModel _currentEntity;
|
||||||
@@ -29,7 +30,7 @@ namespace Torch.Server.ViewModels
|
|||||||
public EntityViewModel CurrentEntity
|
public EntityViewModel CurrentEntity
|
||||||
{
|
{
|
||||||
get => _currentEntity;
|
get => _currentEntity;
|
||||||
set { _currentEntity = value; OnPropertyChanged(); }
|
set { _currentEntity = value; OnPropertyChanged(nameof(CurrentEntity)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityTreeViewModel(UserControl control)
|
public EntityTreeViewModel(UserControl control)
|
||||||
@@ -67,16 +68,16 @@ namespace Torch.Server.ViewModels
|
|||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case MyCubeGrid grid:
|
case MyCubeGrid grid:
|
||||||
Grids.Insert(new GridViewModel(grid, this), g => g.Name);
|
Grids.Add(new GridViewModel(grid, this));
|
||||||
break;
|
break;
|
||||||
case MyCharacter character:
|
case MyCharacter character:
|
||||||
Characters.Insert(new CharacterViewModel(character, this), c => c.Name);
|
Characters.Add(new CharacterViewModel(character, this));
|
||||||
break;
|
break;
|
||||||
case MyFloatingObject floating:
|
case MyFloatingObject floating:
|
||||||
FloatingObjects.Insert(new FloatingObjectViewModel(floating, this), f => f.Name);
|
FloatingObjects.Add(new FloatingObjectViewModel(floating, this));
|
||||||
break;
|
break;
|
||||||
case MyVoxelBase voxel:
|
case MyVoxelBase voxel:
|
||||||
VoxelMaps.Insert(new VoxelMapViewModel(voxel, this), v => v.Name);
|
VoxelMaps.Add(new VoxelMapViewModel(voxel, this));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,18 +6,19 @@ using System.Threading.Tasks;
|
|||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.API.Plugins;
|
using Torch.API.Plugins;
|
||||||
|
using Torch.Collections;
|
||||||
|
|
||||||
namespace Torch.Server.ViewModels
|
namespace Torch.Server.ViewModels
|
||||||
{
|
{
|
||||||
public class PluginManagerViewModel : ViewModel
|
public class PluginManagerViewModel : ViewModel
|
||||||
{
|
{
|
||||||
public ObservableList<PluginViewModel> Plugins { get; } = new ObservableList<PluginViewModel>();
|
public MtObservableList<PluginViewModel> Plugins { get; } = new MtObservableList<PluginViewModel>();
|
||||||
|
|
||||||
private PluginViewModel _selectedPlugin;
|
private PluginViewModel _selectedPlugin;
|
||||||
public PluginViewModel SelectedPlugin
|
public PluginViewModel SelectedPlugin
|
||||||
{
|
{
|
||||||
get => _selectedPlugin;
|
get => _selectedPlugin;
|
||||||
set { _selectedPlugin = value; OnPropertyChanged(); }
|
set { _selectedPlugin = value; OnPropertyChanged(nameof(SelectedPlugin)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public PluginManagerViewModel() { }
|
public PluginManagerViewModel() { }
|
||||||
|
@@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SharpDX.Toolkit.Collections;
|
using SharpDX.Toolkit.Collections;
|
||||||
|
using Torch.Collections;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
using VRage.Library.Utils;
|
using VRage.Library.Utils;
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ namespace Torch.Server.ViewModels
|
|||||||
BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value));
|
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
|
#region Multipliers
|
||||||
|
|
||||||
|
@@ -1,77 +1,358 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using Torch.Utils;
|
||||||
|
|
||||||
namespace Torch
|
namespace Torch.Collections
|
||||||
{
|
{
|
||||||
[Obsolete("Use ObservableList<T>.")]
|
/// <summary>
|
||||||
public class MTObservableCollection<T> : ObservableCollection<T>
|
/// 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;
|
Backing = backing;
|
||||||
if (collectionChanged != null)
|
// recursion so the events can read snapshots.
|
||||||
foreach (var del in collectionChanged.GetInvocationList())
|
Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||||
{
|
_version = 0;
|
||||||
var nh = (NotifyCollectionChangedEventHandler)del;
|
_threadViews = new ThreadLocal<ThreadView>(() => new ThreadView(this));
|
||||||
var dispObj = nh.Target as DispatcherObject;
|
}
|
||||||
|
|
||||||
var dispatcher = dispObj?.Dispatcher;
|
/// <summary>
|
||||||
if (dispatcher != null && !dispatcher.CheckAccess())
|
/// Takes a snapshot of this collection. Note: This call is only done when a read lock is acquired.
|
||||||
{
|
/// </summary>
|
||||||
dispatcher.BeginInvoke(
|
/// <param name="old">Collection to clear and reuse, or null if none</param>
|
||||||
(Action)(() => nh.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
|
/// <returns>The snapshot</returns>
|
||||||
DispatcherPriority.DataBind);
|
protected abstract TC Snapshot(TC old);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
171
Torch/Collections/MtObservableDictionary.cs
Normal file
171
Torch/Collections/MtObservableDictionary.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
Torch/Collections/MtObservableList.cs
Normal file
128
Torch/Collections/MtObservableList.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
36
Torch/Collections/TransformComparer.cs
Normal file
36
Torch/Collections/TransformComparer.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
Torch/Collections/TransformEnumerator.cs
Normal file
55
Torch/Collections/TransformEnumerator.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -14,7 +14,7 @@ using VRage.Utils;
|
|||||||
namespace Torch.Managers
|
namespace Torch.Managers
|
||||||
{
|
{
|
||||||
[PatchShim]
|
[PatchShim]
|
||||||
internal class KeenLogManager
|
internal static class KeenLogPatch
|
||||||
{
|
{
|
||||||
private static readonly Logger _log = LogManager.GetLogger("Keen");
|
private static readonly Logger _log = LogManager.GetLogger("Keen");
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
private static MethodInfo Method(string name)
|
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")]
|
[ReflectedMethod(Name = "GetThreadId")]
|
@@ -48,7 +48,7 @@ namespace Torch.Managers
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event Action<IPlayer> PlayerLeft;
|
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
|
#pragma warning disable 649
|
||||||
[ReflectedGetter(Name = "m_players")]
|
[ReflectedGetter(Name = "m_players")]
|
||||||
|
@@ -27,7 +27,7 @@ namespace Torch.Managers
|
|||||||
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
|
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
|
||||||
private const string MANIFEST_NAME = "manifest.xml";
|
private const string MANIFEST_NAME = "manifest.xml";
|
||||||
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
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
|
#pragma warning disable 649
|
||||||
[Dependency]
|
[Dependency]
|
||||||
private ITorchSessionManager _sessionManager;
|
private ITorchSessionManager _sessionManager;
|
||||||
|
@@ -154,7 +154,11 @@
|
|||||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||||
<Link>Properties\AssemblyVersion.cs</Link>
|
<Link>Properties\AssemblyVersion.cs</Link>
|
||||||
</Compile>
|
</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="Event\EventShimAttribute.cs" />
|
||||||
<Compile Include="Managers\ChatManager\ChatManagerClient.cs" />
|
<Compile Include="Managers\ChatManager\ChatManagerClient.cs" />
|
||||||
<Compile Include="Managers\ChatManager\ChatManagerServer.cs" />
|
<Compile Include="Managers\ChatManager\ChatManagerServer.cs" />
|
||||||
@@ -164,7 +168,7 @@
|
|||||||
<Compile Include="Event\EventList.cs" />
|
<Compile Include="Event\EventList.cs" />
|
||||||
<Compile Include="Event\EventManager.cs" />
|
<Compile Include="Event\EventManager.cs" />
|
||||||
<Compile Include="Event\IEventList.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\AssemblyMemory.cs" />
|
||||||
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
|
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
|
||||||
<Compile Include="Managers\PatchManager\EmitExtensions.cs" />
|
<Compile Include="Managers\PatchManager\EmitExtensions.cs" />
|
||||||
@@ -190,7 +194,6 @@
|
|||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="SaveGameStatus.cs" />
|
<Compile Include="SaveGameStatus.cs" />
|
||||||
<Compile Include="Collections\KeyTree.cs" />
|
<Compile Include="Collections\KeyTree.cs" />
|
||||||
<Compile Include="Collections\ObservableDictionary.cs" />
|
|
||||||
<Compile Include="Collections\RollingAverage.cs" />
|
<Compile Include="Collections\RollingAverage.cs" />
|
||||||
<Compile Include="CommandLine.cs" />
|
<Compile Include="CommandLine.cs" />
|
||||||
<Compile Include="Commands\CategoryAttribute.cs" />
|
<Compile Include="Commands\CategoryAttribute.cs" />
|
||||||
@@ -215,6 +218,7 @@
|
|||||||
<Compile Include="Utils\Reflection.cs" />
|
<Compile Include="Utils\Reflection.cs" />
|
||||||
<Compile Include="Managers\ScriptingManager.cs" />
|
<Compile Include="Managers\ScriptingManager.cs" />
|
||||||
<Compile Include="Utils\StringUtils.cs" />
|
<Compile Include="Utils\StringUtils.cs" />
|
||||||
|
<Compile Include="Utils\SynchronizationExtensions.cs" />
|
||||||
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
||||||
<Compile Include="Utils\ReflectedManager.cs" />
|
<Compile Include="Utils\ReflectedManager.cs" />
|
||||||
<Compile Include="Session\TorchSessionManager.cs" />
|
<Compile Include="Session\TorchSessionManager.cs" />
|
||||||
@@ -224,7 +228,6 @@
|
|||||||
<Compile Include="Session\TorchSession.cs" />
|
<Compile Include="Session\TorchSession.cs" />
|
||||||
<Compile Include="Utils\TorchLauncher.cs" />
|
<Compile Include="Utils\TorchLauncher.cs" />
|
||||||
<Compile Include="ViewModels\ModViewModel.cs" />
|
<Compile Include="ViewModels\ModViewModel.cs" />
|
||||||
<Compile Include="Collections\MTObservableCollection.cs" />
|
|
||||||
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
||||||
<Compile Include="SteamHelper.cs" />
|
<Compile Include="SteamHelper.cs" />
|
||||||
<Compile Include="Extensions\StringExtensions.cs" />
|
<Compile Include="Extensions\StringExtensions.cs" />
|
||||||
|
96
Torch/Utils/SynchronizationExtensions.cs
Normal file
96
Torch/Utils/SynchronizationExtensions.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user