Move TrackedQueue to LibationUiBase

This commit is contained in:
Mbucari 2023-03-01 09:33:17 -07:00
parent 2a59329350
commit df224cc7f3
4 changed files with 76 additions and 295 deletions

View File

@ -1,4 +1,5 @@
using ApplicationServices;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading;
using DataLayer;
@ -16,9 +17,8 @@ namespace LibationAvalonia.ViewModels
public class ProcessQueueViewModel : ViewModelBase, ILogForm
{
public ObservableCollection<LogEntry> LogEntries { get; } = new();
public TrackedQueue<ProcessBookViewModel> Items { get; } = new();
private TrackedQueue<ProcessBookViewModel> Queue => Items;
public AvaloniaList<ProcessBookViewModel> Items { get; } = new();
public TrackedQueue<ProcessBookViewModel> Queue { get; }
public ProcessBookViewModel SelectedItem { get; set; }
public Task QueueRunner { get; private set; }
public bool Running => !QueueRunner?.IsCompleted ?? false;
@ -28,6 +28,7 @@ namespace LibationAvalonia.ViewModels
public ProcessQueueViewModel()
{
Logger = LogMe.RegisterForm(this);
Queue = new(Items);
Queue.QueuededCountChanged += Queue_QueuededCountChanged;
Queue.CompletedCountChanged += Queue_CompletedCountChanged;
@ -88,19 +89,19 @@ namespace LibationAvalonia.ViewModels
public decimal SpeedLimitIncrement { get; private set; }
private void Queue_CompletedCountChanged(object sender, int e)
private async void Queue_CompletedCountChanged(object sender, int e)
{
int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.FailedRetry or ProcessBookResult.ValidationFail);
int completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success);
ErrorCount = errCount;
CompletedCount = completeCount;
Dispatcher.UIThread.Post(() => this.RaisePropertyChanged(nameof(Progress)));
await Dispatcher.UIThread.InvokeAsync(() => this.RaisePropertyChanged(nameof(Progress)));
}
private void Queue_QueuededCountChanged(object sender, int cueCount)
private async void Queue_QueuededCountChanged(object sender, int cueCount)
{
QueuedCount = cueCount;
Dispatcher.UIThread.Post(() => this.RaisePropertyChanged(nameof(Progress)));
await Dispatcher.UIThread.InvokeAsync(() => this.RaisePropertyChanged(nameof(Progress)));
}
public void WriteLine(string text)

View File

@ -15,7 +15,7 @@ namespace LibationAvalonia.Views
{
public partial class ProcessQueueControl : UserControl
{
private TrackedQueue<ProcessBookViewModel> Queue => _viewModel.Items;
private TrackedQueue<ProcessBookViewModel> Queue => _viewModel.Queue;
private ProcessQueueViewModel _viewModel => DataContext as ProcessQueueViewModel;
public ProcessQueueControl()
@ -76,14 +76,14 @@ namespace LibationAvalonia.Views
},
};
vm.Items.Enqueue(testList);
vm.Items.MoveNext();
vm.Items.MoveNext();
vm.Items.MoveNext();
vm.Items.MoveNext();
vm.Items.MoveNext();
vm.Items.MoveNext();
vm.Items.MoveNext();
vm.Queue.Enqueue(testList);
vm.Queue.MoveNext();
vm.Queue.MoveNext();
vm.Queue.MoveNext();
vm.Queue.MoveNext();
vm.Queue.MoveNext();
vm.Queue.MoveNext();
vm.Queue.MoveNext();
return;
}
#endregion

View File

@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace LibationAvalonia.ViewModels
namespace LibationUiBase
{
public enum QueuePosition
{
@ -33,7 +32,7 @@ namespace LibationAvalonia.ViewModels
* and is stored in ObservableCollection.Items. When the primary list changes, the
* secondary list is cleared and reset to match the primary.
*/
public class TrackedQueue<T> : ObservableCollection<T> where T : class
public class TrackedQueue<T> where T : class
{
public event EventHandler<int> CompletedCountChanged;
public event EventHandler<int> QueuededCountChanged;
@ -47,6 +46,60 @@ namespace LibationAvalonia.ViewModels
private readonly List<T> _completed = new();
private readonly object lockObject = new();
private readonly ICollection<T> _underlyingList;
public TrackedQueue(ICollection<T> underlyingList = null)
{
_underlyingList = underlyingList;
}
public T this[int index]
{
get
{
lock (lockObject)
{
if (index < _completed.Count)
return _completed[index];
index -= _completed.Count;
if (index == 0 && Current != null) return Current;
if (Current != null) index--;
if (index < _queued.Count) return _queued.ElementAt(index);
throw new IndexOutOfRangeException();
}
}
}
public int Count
{
get
{
lock (lockObject)
{
return _queued.Count + _completed.Count + (Current == null ? 0 : 1);
}
}
}
public int IndexOf(T item)
{
lock (lockObject)
{
if (_completed.Contains(item))
return _completed.IndexOf(item);
if (Current == item) return _completed.Count;
if (_queued.Contains(item))
return _queued.IndexOf(item) + (Current is null ? 0 : 1);
return -1;
}
}
public bool RemoveQueued(T item)
{
bool itemsRemoved;
@ -188,29 +241,6 @@ namespace LibationAvalonia.ViewModels
}
}
public bool TryPeek(out T item)
{
lock (lockObject)
{
if (_queued.Count == 0)
{
item = null;
return false;
}
item = _queued[0];
return true;
}
}
public T Peek()
{
lock (lockObject)
{
if (_queued.Count == 0) throw new InvalidOperationException("Queue empty");
return _queued.Count > 0 ? _queued[0] : default;
}
}
public void Enqueue(IEnumerable<T> item)
{
int queueCount;
@ -220,15 +250,15 @@ namespace LibationAvalonia.ViewModels
queueCount = _queued.Count;
}
foreach (var i in item)
base.Add(i);
_underlyingList?.Add(i);
QueuededCountChanged?.Invoke(this, queueCount);
}
private void RebuildSecondary()
{
base.ClearItems();
_underlyingList?.Clear();
foreach (var item in GetAllItems())
base.Add(item);
_underlyingList?.Add(item);
}
public IEnumerable<T> GetAllItems()

View File

@ -1,250 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace LibationWinForms.ProcessQueue
{
public enum QueuePosition
{
Fisrt,
OneUp,
OneDown,
Last
}
/*
* This data structure is like lifting a metal chain one link at a time.
* Each time you grab and lift a new link (MoveNext call):
*
* 1) you're holding a new link in your hand (Current)
* 2) the remaining chain to be lifted shortens by 1 link (Queued)
* 3) the pile of chain at your feet grows by 1 link (Completed)
*
* The index is the link position from the first link you lifted to the
* last one in the chain.
*/
public class TrackedQueue<T> where T : class
{
public event EventHandler<int> CompletedCountChanged;
public event EventHandler<int> QueuededCountChanged;
public T Current { get; private set; }
public IReadOnlyList<T> Queued => _queued;
public IReadOnlyList<T> Completed => _completed;
private readonly List<T> _queued = new();
private readonly List<T> _completed = new();
private readonly object lockObject = new();
public T this[int index]
{
get
{
lock (lockObject)
{
if (index < _completed.Count)
return _completed[index];
index -= _completed.Count;
if (index == 0 && Current != null) return Current;
if (Current != null) index--;
if (index < _queued.Count) return _queued.ElementAt(index);
throw new IndexOutOfRangeException();
}
}
}
public int Count
{
get
{
lock (lockObject)
{
return _queued.Count + _completed.Count + (Current == null ? 0 : 1);
}
}
}
public int IndexOf(T item)
{
lock (lockObject)
{
if (_completed.Contains(item))
return _completed.IndexOf(item);
if (Current == item) return _completed.Count;
if (_queued.Contains(item))
return _queued.IndexOf(item) + (Current is null ? 0 : 1);
return -1;
}
}
public bool RemoveQueued(T item)
{
bool itemsRemoved;
int queuedCount;
lock (lockObject)
{
itemsRemoved = _queued.Remove(item);
queuedCount = _queued.Count;
}
if (itemsRemoved)
QueuededCountChanged?.Invoke(this, queuedCount);
return itemsRemoved;
}
public void ClearCurrent()
{
lock(lockObject)
Current = null;
}
public bool RemoveCompleted(T item)
{
bool itemsRemoved;
int completedCount;
lock (lockObject)
{
itemsRemoved = _completed.Remove(item);
completedCount = _completed.Count;
}
if (itemsRemoved)
CompletedCountChanged?.Invoke(this, completedCount);
return itemsRemoved;
}
public void ClearQueue()
{
lock (lockObject)
_queued.Clear();
QueuededCountChanged?.Invoke(this, 0);
}
public void ClearCompleted()
{
lock (lockObject)
_completed.Clear();
CompletedCountChanged?.Invoke(this, 0);
}
public bool Any(Func<T, bool> predicate)
{
lock (lockObject)
{
return (Current != null && predicate(Current)) || _completed.Any(predicate) || _queued.Any(predicate);
}
}
public void MoveQueuePosition(T item, QueuePosition requestedPosition)
{
lock (lockObject)
{
if (_queued.Count == 0 || !_queued.Contains(item)) return;
if ((requestedPosition == QueuePosition.Fisrt || requestedPosition == QueuePosition.OneUp) && _queued[0] == item)
return;
if ((requestedPosition == QueuePosition.Last || requestedPosition == QueuePosition.OneDown) && _queued[^1] == item)
return;
int queueIndex = _queued.IndexOf(item);
if (requestedPosition == QueuePosition.OneUp)
{
_queued.RemoveAt(queueIndex);
_queued.Insert(queueIndex - 1, item);
}
else if (requestedPosition == QueuePosition.OneDown)
{
_queued.RemoveAt(queueIndex);
_queued.Insert(queueIndex + 1, item);
}
else if (requestedPosition == QueuePosition.Fisrt)
{
_queued.RemoveAt(queueIndex);
_queued.Insert(0, item);
}
else
{
_queued.RemoveAt(queueIndex);
_queued.Insert(_queued.Count, item);
}
}
}
public bool MoveNext()
{
int completedCount = 0, queuedCount = 0;
bool completedChanged = false;
try
{
lock (lockObject)
{
if (Current != null)
{
_completed.Add(Current);
completedCount = _completed.Count;
completedChanged = true;
}
if (_queued.Count == 0)
{
Current = null;
return false;
}
Current = _queued[0];
_queued.RemoveAt(0);
queuedCount = _queued.Count;
return true;
}
}
finally
{
if (completedChanged)
CompletedCountChanged?.Invoke(this, completedCount);
QueuededCountChanged?.Invoke(this, queuedCount);
}
}
public bool TryPeek(out T item)
{
lock (lockObject)
{
if (_queued.Count == 0)
{
item = null;
return false;
}
item = _queued[0];
return true;
}
}
public T Peek()
{
lock (lockObject)
{
if (_queued.Count == 0) throw new InvalidOperationException("Queue empty");
return _queued.Count > 0 ? _queued[0] : default;
}
}
public void Enqueue(IEnumerable<T> item)
{
int queueCount;
lock (lockObject)
{
_queued.AddRange(item);
queueCount = _queued.Count;
}
QueuededCountChanged?.Invoke(this, queueCount);
}
}
}