Timeline: use ObservableCollection instead of DynamicData

This commit is contained in:
2023-08-14 17:15:03 +02:00
parent 8aa8d83598
commit 4381f1b47a
11 changed files with 49 additions and 191 deletions

View File

@@ -1,8 +1,8 @@
using FileTime.Core.Models; using System.Collections.ObjectModel;
namespace FileTime.App.Core.ViewModels.Timeline; namespace FileTime.App.Core.ViewModels.Timeline;
public interface IParallelCommandsViewModel public interface IParallelCommandsViewModel
{ {
BindedCollection<ICommandTimeStateViewModel> Commands { get; } ObservableCollection<ICommandTimeStateViewModel> Commands { get; }
} }

View File

@@ -1,8 +1,8 @@
using FileTime.Core.Models; using System.Collections.ObjectModel;
namespace FileTime.App.Core.ViewModels.Timeline; namespace FileTime.App.Core.ViewModels.Timeline;
public interface ITimelineViewModel public interface ITimelineViewModel
{ {
BindedCollection<IParallelCommandsViewModel> ParallelCommandsGroups { get; } ObservableCollection<IParallelCommandsViewModel> ParallelCommandsGroups { get; }
} }

View File

@@ -1,19 +1,21 @@
using DynamicData.Alias; using System.Collections.ObjectModel;
using FileTime.Core.Extensions;
using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using ObservableComputations;
namespace FileTime.App.Core.ViewModels.Timeline; namespace FileTime.App.Core.ViewModels.Timeline;
public class ParallelCommandsViewModel : IParallelCommandsViewModel public class ParallelCommandsViewModel : IParallelCommandsViewModel, IDisposable
{ {
public BindedCollection<ICommandTimeStateViewModel> Commands { get; } private readonly OcConsumer _ocConsumer = new();
public ObservableCollection<ICommandTimeStateViewModel> Commands { get; }
public ParallelCommandsViewModel(ParallelCommands parallelCommands) public ParallelCommandsViewModel(ParallelCommands parallelCommands)
{ {
Commands = parallelCommands Commands = parallelCommands
.Commands .Commands
.Select(c => new CommandTimeStateViewModel(c) as ICommandTimeStateViewModel) .Selecting(c => new CommandTimeStateViewModel(c) as ICommandTimeStateViewModel)
.ToBindedCollection(); .For(_ocConsumer);
} }
public void Dispose() => _ocConsumer.Dispose();
} }

View File

@@ -1,20 +1,22 @@
using DynamicData.Alias; using System.Collections.ObjectModel;
using FileTime.Core.Extensions;
using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using ObservableComputations;
namespace FileTime.App.Core.ViewModels.Timeline; namespace FileTime.App.Core.ViewModels.Timeline;
public class TimelineViewModel : ITimelineViewModel public class TimelineViewModel : ITimelineViewModel, IDisposable
{ {
public BindedCollection<IParallelCommandsViewModel> ParallelCommandsGroups { get; } private readonly OcConsumer _ocConsumer = new();
public ObservableCollection<IParallelCommandsViewModel> ParallelCommandsGroups { get; }
public TimelineViewModel(ICommandScheduler commandScheduler) public TimelineViewModel(ICommandScheduler commandScheduler)
{ {
ParallelCommandsGroups = ParallelCommandsGroups =
commandScheduler commandScheduler
.CommandsToRun .CommandsToRun
.Select(p => new ParallelCommandsViewModel(p) as IParallelCommandsViewModel) .Selecting(p => new ParallelCommandsViewModel(p) as IParallelCommandsViewModel)
.ToBindedCollection(); .For(_ocConsumer);
} }
public void Dispose() => _ocConsumer.Dispose();
} }

View File

@@ -15,5 +15,7 @@ public interface ITheme
IColor? MarkedSelectedItemBackgroundColor { get; } IColor? MarkedSelectedItemBackgroundColor { get; }
IColor? SelectedItemColor { get; } IColor? SelectedItemColor { get; }
IColor? SelectedTabBackgroundColor { get; } IColor? SelectedTabBackgroundColor { get; }
IColor? WarningForegroundColor { get; }
IColor? ErrorForegroundColor { get; }
ListViewItemTheme ListViewItemTheme { get; } ListViewItemTheme ListViewItemTheme { get; }
} }

View File

@@ -1,27 +0,0 @@
using DynamicData;
using FileTime.Core.Models;
namespace FileTime.Core.Extensions;
public static class BindedCollectionExtensions
{
public static BindedCollection<T> ToBindedCollection<T>(this IObservable<IChangeSet<T>> source)
{
return new BindedCollection<T>(source);
}
public static BindedCollection<T> ToBindedCollection<T>(this IObservable<IObservable<IChangeSet<T>>?> source)
{
return new BindedCollection<T>(source);
}
public static BindedCollection<T, TKey> ToBindedCollection<T, TKey>(this IObservable<IChangeSet<T, TKey>> source) where TKey : notnull
{
return new BindedCollection<T, TKey>(source);
}
public static BindedCollection<T, TKey> ToBindedCollection<T, TKey>(this IObservable<IObservable<IChangeSet<T, TKey>>?> source) where TKey : notnull
{
return new BindedCollection<T, TKey>(source);
}
}

View File

@@ -1,106 +0,0 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using DynamicData;
using PropertyChanged.SourceGenerator;
namespace FileTime.Core.Models;
public partial class BindedCollection<T> : IDisposable, INotifyPropertyChanged
{
private readonly IDisposable? _disposable;
private IDisposable? _innerDisposable;
[Notify] private ReadOnlyObservableCollection<T>? _collection;
public BindedCollection()
{
}
public BindedCollection(IObservable<IChangeSet<T>> dynamicList)
{
_disposable = dynamicList
.Bind(out var collection)
.DisposeMany()
.Subscribe();
_collection = collection;
}
public BindedCollection(IObservable<IObservable<IChangeSet<T>>?> dynamicListSource)
{
_disposable = dynamicListSource.Subscribe(dynamicList =>
{
_innerDisposable?.Dispose();
if (dynamicList is not null)
{
_innerDisposable = dynamicList
.Bind(out var collection)
.DisposeMany()
.Subscribe();
Collection = collection;
}
else
{
Collection = null;
}
});
}
public void Dispose()
{
_disposable?.Dispose();
_innerDisposable?.Dispose();
GC.SuppressFinalize(this);
}
}
public partial class BindedCollection<T, TKey> : IDisposable, INotifyPropertyChanged where TKey : notnull
{
private readonly IDisposable? _disposable;
private IDisposable? _innerDisposable;
[Notify] private ReadOnlyObservableCollection<T>? _collection;
public BindedCollection()
{
}
public BindedCollection(IObservable<IChangeSet<T, TKey>> dynamicList)
{
_disposable = dynamicList
.Bind(out var collection)
.DisposeMany()
.Subscribe();
_collection = collection;
}
public BindedCollection(IObservable<IObservable<IChangeSet<T, TKey>>?> dynamicListSource)
{
_disposable = dynamicListSource.Subscribe(dynamicList =>
{
_innerDisposable?.Dispose();
if (dynamicList is not null)
{
_innerDisposable = dynamicList
.Bind(out var collection)
.DisposeMany()
.Subscribe();
Collection = collection;
}
else
{
Collection = null;
}
});
}
public void Dispose()
{
_disposable?.Dispose();
_innerDisposable?.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -1,4 +1,4 @@
using DynamicData; using System.Collections.ObjectModel;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -8,7 +8,7 @@ public interface ICommandScheduler
{ {
Task AddCommand(ICommand command, int? batchId = null, bool toNewBatch = false); Task AddCommand(ICommand command, int? batchId = null, bool toNewBatch = false);
IObservable<FullName> ContainerToRefresh { get; } IObservable<FullName> ContainerToRefresh { get; }
IObservable<IChangeSet<ParallelCommands>> CommandsToRun { get; } ReadOnlyObservableCollection<ParallelCommands> CommandsToRun { get; }
bool IsRunningEnabled { get; } bool IsRunningEnabled { get; }
void RefreshContainer(FullName container); void RefreshContainer(FullName container);
Task SetRunningEnabledAsync(bool value); Task SetRunningEnabledAsync(bool value);

View File

@@ -1,21 +1,17 @@
using DynamicData; using System.Collections.ObjectModel;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Extensions;
using FileTime.Core.Models;
namespace FileTime.Core.Timeline; namespace FileTime.Core.Timeline;
public class ParallelCommands : IDisposable public class ParallelCommands
{ {
private static ushort _idCounter; private static ushort _idCounter;
private readonly SourceList<CommandTimeState> _commands; private readonly ObservableCollection<CommandTimeState> _commands;
private PointInTime? _startTime; private PointInTime? _startTime;
public ushort Id { get; } public ushort Id { get; }
public IObservable<IChangeSet<CommandTimeState>> Commands { get; } public ReadOnlyObservableCollection<CommandTimeState> Commands { get; }
public BindedCollection<CommandTimeState> CommandsCollection { get; }
public int CommandCount => _commands.Count; public int CommandCount => _commands.Count;
public PointInTime? Result { get; private set; } public PointInTime? Result { get; private set; }
@@ -37,10 +33,8 @@ public class ParallelCommands : IDisposable
{ {
Id = _idCounter++; Id = _idCounter++;
_commands = new SourceList<CommandTimeState>(); _commands = new ObservableCollection<CommandTimeState>(commands);
_commands.Edit((innerList) => innerList.AddRange(commands)); Commands = new(_commands);
Commands = _commands.Connect();
CommandsCollection = Commands.ToBindedCollection();
Result = result; Result = result;
} }
@@ -85,7 +79,7 @@ public class ParallelCommands : IDisposable
public async Task RemoveCommand(ICommand command) public async Task RemoveCommand(ICommand command)
{ {
var commandTimeState = _commands.Items.First(c => c.Command == command); var commandTimeState = _commands.First(c => c.Command == command);
_commands.Remove(commandTimeState); _commands.Remove(commandTimeState);
await RefreshResult(); await RefreshResult();
} }
@@ -93,7 +87,7 @@ public class ParallelCommands : IDisposable
public async Task<PointInTime?> RefreshResult() public async Task<PointInTime?> RefreshResult()
{ {
var result = StartTime; var result = StartTime;
foreach (var commandTimeState in _commands.Items) foreach (var commandTimeState in _commands)
{ {
await commandTimeState.UpdateStateAsync(result); await commandTimeState.UpdateStateAsync(result);
if (result != null) if (result != null)
@@ -113,9 +107,4 @@ public class ParallelCommands : IDisposable
Result = result; Result = result;
return Result; return Result;
} }
public void Dispose()
{
CommandsCollection.Dispose();
}
} }

View File

@@ -1,6 +1,6 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using DynamicData;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -8,7 +8,7 @@ namespace FileTime.Core.Timeline;
public class CommandScheduler : ICommandScheduler public class CommandScheduler : ICommandScheduler
{ {
private readonly SourceList<ParallelCommands> _commandsToRun = new(); private readonly ObservableCollection<ParallelCommands> _commandsToRun = new();
private readonly List<ICommandExecutor> _commandExecutors = new(); private readonly List<ICommandExecutor> _commandExecutors = new();
private readonly Subject<FullName> _containerToRefresh = new(); private readonly Subject<FullName> _containerToRefresh = new();
@@ -29,11 +29,11 @@ public class CommandScheduler : ICommandScheduler
} }
} }
public IObservable<IChangeSet<ParallelCommands>> CommandsToRun { get; } public ReadOnlyObservableCollection<ParallelCommands> CommandsToRun { get; }
public CommandScheduler(ILocalCommandExecutor localExecutor) public CommandScheduler(ILocalCommandExecutor localExecutor)
{ {
CommandsToRun = _commandsToRun.Connect(); CommandsToRun = new(_commandsToRun);
ContainerToRefresh = _containerToRefresh.AsObservable(); ContainerToRefresh = _containerToRefresh.AsObservable();
@@ -55,16 +55,16 @@ public class CommandScheduler : ICommandScheduler
} }
else if (toNewBatch) else if (toNewBatch)
{ {
batchToAdd = new ParallelCommands(_commandsToRun.Items.Last().Result); batchToAdd = new ParallelCommands(_commandsToRun.Last().Result);
_commandsToRun.Add(batchToAdd); _commandsToRun.Add(batchToAdd);
} }
else if (batchId != null && _commandsToRun.Items.First(b => b.Id == batchId) is { } parallelCommands) else if (batchId != null && _commandsToRun.First(b => b.Id == batchId) is { } parallelCommands)
{ {
batchToAdd = parallelCommands; batchToAdd = parallelCommands;
} }
else else
{ {
batchToAdd = _commandsToRun.Items.First(); batchToAdd = _commandsToRun.First();
} }
await batchToAdd.AddCommand(command); await batchToAdd.AddCommand(command);
@@ -88,7 +88,7 @@ public class CommandScheduler : ICommandScheduler
{ {
if (!_isRunningEnabled) return; if (!_isRunningEnabled) return;
var commandsToExecute = _commandsToRun.Items.FirstOrDefault()?.CommandsCollection.Collection; var commandsToExecute = _commandsToRun.FirstOrDefault()?.Commands;
if (commandsToExecute is null || commandsToExecute.All(c => c.ExecutionState != ExecutionState.Initializing && c.ExecutionState != ExecutionState.Waiting)) return; if (commandsToExecute is null || commandsToExecute.All(c => c.ExecutionState != ExecutionState.Initializing && c.ExecutionState != ExecutionState.Waiting)) return;
foreach (var commandToExecute in commandsToExecute) foreach (var commandToExecute in commandsToExecute)
@@ -113,17 +113,14 @@ public class CommandScheduler : ICommandScheduler
return _commandExecutors[0]; return _commandExecutors[0];
} }
private async void ExecutorOnCommandFinished(object? sender, ICommand command) private async void ExecutorOnCommandFinished(object? sender, ICommand command) =>
{
await RunWithLockAsync(async () => await RunWithLockAsync(async () =>
{ {
var firstCommandBlock = _commandsToRun var firstCommandBlock = _commandsToRun
.Items
.FirstOrDefault(); .FirstOrDefault();
var state = firstCommandBlock var state = firstCommandBlock
?.CommandsCollection ?.Commands
.Collection .FirstOrDefault(c => c.Command == command);
?.FirstOrDefault(c => c.Command == command);
if (state is null) return; if (state is null) return;
@@ -137,13 +134,12 @@ public class CommandScheduler : ICommandScheduler
} }
} }
}); });
}
private async Task RefreshCommands() private async Task RefreshCommands()
{ {
var currentTime = PointInTime.CreateEmpty(); var currentTime = PointInTime.CreateEmpty();
foreach (var batch in _commandsToRun.Items) foreach (var batch in _commandsToRun)
{ {
currentTime = await batch.SetStartTimeAsync(currentTime); currentTime = await batch.SetStartTimeAsync(currentTime);
} }

View File

@@ -321,7 +321,7 @@
</ia:DataTriggerBehavior> </ia:DataTriggerBehavior>
</i:Interaction.Behaviors> </i:Interaction.Behaviors>
<ItemsControl <ItemsControl
ItemsSource="{Binding TimelineViewModel.ParallelCommandsGroups.Collection}" ItemsSource="{Binding TimelineViewModel.ParallelCommandsGroups}"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
x:Name="CommandGroups"> x:Name="CommandGroups">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
@@ -333,7 +333,7 @@
<DataTemplate> <DataTemplate>
<Grid ColumnDefinitions="300,10" VerticalAlignment="Stretch"> <Grid ColumnDefinitions="300,10" VerticalAlignment="Stretch">
<ScrollViewer> <ScrollViewer>
<ItemsControl ItemsSource="{Binding Commands.Collection}"> <ItemsControl ItemsSource="{Binding Commands}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Border <Border