TimeTravel
This commit is contained in:
27
src/Core/FileTime.Core/Timeline/CommandTimeState.cs
Normal file
27
src/Core/FileTime.Core/Timeline/CommandTimeState.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using FileTime.Core.Command;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class CommandTimeState
|
||||
{
|
||||
public ICommand Command { get; }
|
||||
public CanCommandRun CanRun { get; private set; } = CanCommandRun.False;
|
||||
public bool ForceRun { get; set; }
|
||||
public TimeProvider? TimeProvider { get; private set; }
|
||||
|
||||
public CommandTimeState(ICommand command, PointInTime? startTime)
|
||||
{
|
||||
Command = command;
|
||||
UpdateState(startTime).Wait();
|
||||
}
|
||||
|
||||
public async Task UpdateState(PointInTime? startPoint)
|
||||
{
|
||||
CanRun = startPoint == null ? CanCommandRun.False : await Command.CanRun(startPoint);
|
||||
if (startPoint != null)
|
||||
{
|
||||
TimeProvider = startPoint.Provider as TimeProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ContainerSnapshot
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
31
src/Core/FileTime.Core/Timeline/Difference.cs
Normal file
31
src/Core/FileTime.Core/Timeline/Difference.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class Difference
|
||||
{
|
||||
public DifferenceItemType Type { get; }
|
||||
public string Name { get; }
|
||||
public AbsolutePath AbsolutePath { get; }
|
||||
public DifferenceActionType Action { get; }
|
||||
|
||||
public Difference(DifferenceItemType type, DifferenceActionType action, AbsolutePath absolutePath)
|
||||
{
|
||||
Type = type;
|
||||
AbsolutePath = absolutePath;
|
||||
Action = action;
|
||||
|
||||
Name = absolutePath.GetName();
|
||||
}
|
||||
|
||||
public Difference WithVirtualContentProvider(IContentProvider? virtualContentProvider)
|
||||
{
|
||||
return new Difference(
|
||||
Type,
|
||||
Action,
|
||||
new AbsolutePath(AbsolutePath.ContentProvider, AbsolutePath.Path, virtualContentProvider)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Core/FileTime.Core/Timeline/DifferenceActionType.cs
Normal file
8
src/Core/FileTime.Core/Timeline/DifferenceActionType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public enum DifferenceActionType
|
||||
{
|
||||
Create,
|
||||
Delete
|
||||
}
|
||||
}
|
||||
9
src/Core/FileTime.Core/Timeline/DifferenceItemType.cs
Normal file
9
src/Core/FileTime.Core/Timeline/DifferenceItemType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public enum DifferenceItemType
|
||||
{
|
||||
Container,
|
||||
Element,
|
||||
Unknown
|
||||
}
|
||||
}
|
||||
89
src/Core/FileTime.Core/Timeline/ParallelCommands.cs
Normal file
89
src/Core/FileTime.Core/Timeline/ParallelCommands.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using FileTime.Core.Command;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ParallelCommands
|
||||
{
|
||||
private static ushort _idCounter;
|
||||
public List<CommandTimeState> _commands;
|
||||
public ushort Id { get; }
|
||||
public IReadOnlyList<CommandTimeState> Commands { get; }
|
||||
public PointInTime? Result { get; private set; }
|
||||
|
||||
public ParallelCommands(PointInTime? result)
|
||||
: this(new List<CommandTimeState>(), result) { }
|
||||
|
||||
private ParallelCommands(List<CommandTimeState> commands, PointInTime? result)
|
||||
{
|
||||
Id = _idCounter++;
|
||||
|
||||
_commands = commands;
|
||||
Commands = _commands.AsReadOnly();
|
||||
|
||||
Result = result;
|
||||
}
|
||||
|
||||
public static async Task<ParallelCommands> Create(PointInTime? startTime, IEnumerable<ICommand> commands)
|
||||
{
|
||||
var commandStates = new List<CommandTimeState>();
|
||||
var currentTime = startTime;
|
||||
foreach (var command in commands)
|
||||
{
|
||||
CommandTimeState commandTimeState = new(command, currentTime);
|
||||
if (currentTime != null)
|
||||
{
|
||||
var canRun = await command.CanRun(currentTime);
|
||||
if (canRun == CanCommandRun.True)
|
||||
{
|
||||
currentTime = await command.SimulateCommand(currentTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
commandStates.Add(commandTimeState);
|
||||
}
|
||||
|
||||
return new ParallelCommands(commandStates, currentTime);
|
||||
}
|
||||
|
||||
public async Task AddCommand(ICommand command)
|
||||
{
|
||||
_commands.Add(new CommandTimeState(command, Result));
|
||||
if (Result != null)
|
||||
{
|
||||
Result = await command.SimulateCommand(Result);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PointInTime?> RefreshResult(PointInTime? startPoint)
|
||||
{
|
||||
var result = startPoint;
|
||||
foreach (var command in _commands)
|
||||
{
|
||||
await command.UpdateState(result);
|
||||
if (result != null)
|
||||
{
|
||||
var canRun = await command.Command.CanRun(result);
|
||||
if (canRun == CanCommandRun.True || (canRun == CanCommandRun.Forceable && command.ForceRun))
|
||||
{
|
||||
result = await command.Command.SimulateCommand(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result = result;
|
||||
return Result;
|
||||
}
|
||||
|
||||
public void RemoveAt(int number) => _commands.RemoveAt(number);
|
||||
|
||||
internal void Remove(CommandTimeState command) => _commands.Remove(command);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,40 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class PointInTime
|
||||
public sealed class PointInTime
|
||||
{
|
||||
private readonly Dictionary<IContentProvider, RootSnapshot> snapshots = new();
|
||||
private readonly List<Difference> _differences;
|
||||
|
||||
public IReadOnlyDictionary<IContentProvider, RootSnapshot> Snapshots => new Lazy<IReadOnlyDictionary<IContentProvider, RootSnapshot>>(() => new ReadOnlyDictionary<IContentProvider, RootSnapshot>(snapshots)).Value;
|
||||
public IReadOnlyList<Difference> Differences { get; }
|
||||
|
||||
public IContentProvider? Provider { get; }
|
||||
|
||||
private PointInTime() : this(new List<Difference>(), null) { }
|
||||
|
||||
private PointInTime(IEnumerable<Difference> differences, IContentProvider? provider)
|
||||
{
|
||||
_differences = new List<Difference>(differences);
|
||||
Differences = _differences.AsReadOnly();
|
||||
Provider = provider;
|
||||
}
|
||||
|
||||
private PointInTime(PointInTime previous, IEnumerable<Difference> differences, IContentProvider provider)
|
||||
: this(MergeDifferences(previous.Differences, differences, provider), provider) { }
|
||||
|
||||
public PointInTime WithDifferences(IEnumerable<Difference> differences) => new(this, differences, new TimeProvider(this));
|
||||
|
||||
private static List<Difference> MergeDifferences(IEnumerable<Difference> previouses, IEnumerable<Difference> differences, IContentProvider virtualProvider)
|
||||
{
|
||||
var merged = new List<Difference>();
|
||||
|
||||
merged.AddRange(previouses.Select(p => p.WithVirtualContentProvider(virtualProvider)));
|
||||
merged.AddRange(differences.Select(d => d.WithVirtualContentProvider(virtualProvider)));
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
public static PointInTime CreateEmpty(IContentProvider? parentProvder = null) =>
|
||||
parentProvder == null ? new PointInTime() : new PointInTime(new List<Difference>(), parentProvder);
|
||||
}
|
||||
}
|
||||
18
src/Core/FileTime.Core/Timeline/ReadOnlyCommandTimeState.cs
Normal file
18
src/Core/FileTime.Core/Timeline/ReadOnlyCommandTimeState.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using FileTime.Core.Command;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ReadOnlyCommandTimeState
|
||||
{
|
||||
public CanCommandRun CanRun { get; }
|
||||
public bool ForceRun { get; }
|
||||
public ICommand Command { get; }
|
||||
|
||||
public ReadOnlyCommandTimeState(CommandTimeState commandTimeState)
|
||||
{
|
||||
CanRun = commandTimeState.CanRun;
|
||||
ForceRun = commandTimeState.ForceRun;
|
||||
Command = commandTimeState.Command;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs
Normal file
11
src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ReadOnlyParallelCommands
|
||||
{
|
||||
public IReadOnlyList<ReadOnlyCommandTimeState> Commands { get; }
|
||||
public ReadOnlyParallelCommands(ParallelCommands parallelCommands)
|
||||
{
|
||||
Commands = parallelCommands.Commands.Select(c => new ReadOnlyCommandTimeState(c)).ToList().AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class RootSnapshot
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
121
src/Core/FileTime.Core/Timeline/TimeContainer.cs
Normal file
121
src/Core/FileTime.Core/Timeline/TimeContainer.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeContainer : IContainer
|
||||
{
|
||||
private readonly IContainer? _parent;
|
||||
private readonly PointInTime _pointInTime;
|
||||
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new AsyncEventHandler();
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => true;
|
||||
|
||||
public bool CanRename => true;
|
||||
|
||||
public IContentProvider Provider { get; }
|
||||
public IContentProvider VirtualProvider { get; }
|
||||
|
||||
public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
|
||||
{
|
||||
_parent = parent;
|
||||
_pointInTime = pointInTime;
|
||||
|
||||
Name = name;
|
||||
Provider = contentProvider;
|
||||
VirtualProvider = virtualContentProvider;
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
}
|
||||
|
||||
public async Task<IContainer> Clone() => new TimeContainer(Name, await _parent!.Clone(), Provider, VirtualProvider, _pointInTime);
|
||||
|
||||
public Task<IContainer> CreateContainer(string name) => Task.FromResult((IContainer)new TimeContainer(name, this, Provider, VirtualProvider, _pointInTime));
|
||||
|
||||
public Task<IElement> CreateElement(string name) => Task.FromResult((IElement)new TimeElement(name, this, Provider, VirtualProvider));
|
||||
|
||||
public Task Delete() => Task.CompletedTask;
|
||||
|
||||
public async Task<IItem?> GetByPath(string path)
|
||||
{
|
||||
var paths = path.Split(Constants.SeparatorChar);
|
||||
|
||||
var item = (await GetItems())!.FirstOrDefault(i => i.Name == paths[0]);
|
||||
|
||||
if (paths.Length == 1)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) =>
|
||||
Task.FromResult(
|
||||
(IReadOnlyList<IContainer>?)_pointInTime
|
||||
.Differences
|
||||
.Where(d =>
|
||||
d.Type == DifferenceItemType.Container
|
||||
&& GetParentPath(d.AbsolutePath.Path) == FullName)
|
||||
.Select(MapContainer)
|
||||
.ToList()
|
||||
.AsReadOnly()
|
||||
);
|
||||
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) =>
|
||||
Task.FromResult(
|
||||
(IReadOnlyList<IElement>?)_pointInTime
|
||||
.Differences
|
||||
.Where(d =>
|
||||
d.Type == DifferenceItemType.Element
|
||||
&& GetParentPath(d.AbsolutePath.Path) == FullName)
|
||||
.Select(MapElement)
|
||||
.ToList()
|
||||
.AsReadOnly()
|
||||
);
|
||||
|
||||
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
var containers = (await GetContainers(token))!;
|
||||
var elements = (await GetElements(token))!;
|
||||
|
||||
return containers.Cast<IItem>().Concat(elements).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
public async Task<bool> IsExists(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false;
|
||||
|
||||
public async Task Refresh() => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
|
||||
public Task Rename(string newName) => Task.CompletedTask;
|
||||
|
||||
private static string GetParentPath(string path) => string.Join(Constants.SeparatorChar, path.Split(Constants.SeparatorChar).Take(-1));
|
||||
|
||||
private IContainer MapContainer(Difference containerDiff)
|
||||
{
|
||||
if (containerDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{nameof(containerDiff)}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Container}.");
|
||||
return new TimeContainer(containerDiff.Name, this, Provider, containerDiff.AbsolutePath.VirtualContentProvider ?? containerDiff.AbsolutePath.ContentProvider, _pointInTime);
|
||||
}
|
||||
|
||||
private IElement MapElement(Difference elementDiff)
|
||||
{
|
||||
if (elementDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{elementDiff}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Element}.");
|
||||
return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Core/FileTime.Core/Timeline/TimeElement.cs
Normal file
42
src/Core/FileTime.Core/Timeline/TimeElement.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeElement : IElement
|
||||
{
|
||||
private readonly IContainer _parent;
|
||||
public TimeElement(string name, TimeContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
Name = name;
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
Provider = contentProvider;
|
||||
VirtualProvider = virtualContentProvider;
|
||||
}
|
||||
|
||||
public bool IsSpecial => false;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => true;
|
||||
|
||||
public bool CanRename => true;
|
||||
|
||||
public IContentProvider Provider { get; }
|
||||
public IContentProvider VirtualProvider { get; }
|
||||
|
||||
public Task Delete() => Task.CompletedTask;
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
public string GetPrimaryAttributeText() => "";
|
||||
|
||||
public Task Rename(string newName) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
89
src/Core/FileTime.Core/Timeline/TimeProvider.cs
Normal file
89
src/Core/FileTime.Core/Timeline/TimeProvider.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeProvider : IContentProvider
|
||||
{
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
public string Name => "time";
|
||||
|
||||
public string? FullName => null;
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => false;
|
||||
|
||||
public bool CanRename => false;
|
||||
|
||||
public IContentProvider Provider => this;
|
||||
|
||||
private readonly PointInTime _pointInTime;
|
||||
|
||||
public TimeProvider(PointInTime pointInTime)
|
||||
{
|
||||
_pointInTime = pointInTime;
|
||||
}
|
||||
|
||||
public bool CanHandlePath(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||
|
||||
public Task<IContainer> CreateContainer(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IElement> CreateElement(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Delete() => throw new NotSupportedException();
|
||||
|
||||
public Task<IItem?> GetByPath(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => null;
|
||||
|
||||
public Task<IReadOnlyList<IContainer>> GetRootContainers(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> IsExists(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Refresh() => Task.CompletedTask;
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
|
||||
public void SetParent(IContainer container) { }
|
||||
}
|
||||
}
|
||||
217
src/Core/FileTime.Core/Timeline/TimeRunner.cs
Normal file
217
src/Core/FileTime.Core/Timeline/TimeRunner.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeRunner
|
||||
{
|
||||
private readonly CommandExecutor _commandExecutor;
|
||||
private readonly List<ParallelCommands> _commandsToRun = new();
|
||||
private readonly object _guard = new();
|
||||
|
||||
private bool _resourceIsInUse;
|
||||
private readonly List<Thread> _commandRunners = new();
|
||||
private bool _enableRunning = true;
|
||||
|
||||
public bool EnableRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
bool result = true;
|
||||
RunWithLock(() => result = _enableRunning);
|
||||
return result;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
RunWithLock(() => _enableRunning = value);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ReadOnlyParallelCommands> ParallelCommands { get; private set; } = new List<ReadOnlyParallelCommands>().AsReadOnly();
|
||||
|
||||
public AsyncEventHandler<AbsolutePath> RefreshContainer { get; } = new AsyncEventHandler<AbsolutePath>();
|
||||
|
||||
public event EventHandler? CommandsChanged;
|
||||
|
||||
public TimeRunner(CommandExecutor commandExecutor)
|
||||
{
|
||||
_commandExecutor = commandExecutor;
|
||||
}
|
||||
|
||||
public async Task AddCommand(ICommand command, ParallelCommands? batch = null, bool toNewBatch = false)
|
||||
{
|
||||
await RunWithLockAsync(async () =>
|
||||
{
|
||||
ParallelCommands batchToAdd;
|
||||
|
||||
if (_commandsToRun.Count == 0)
|
||||
{
|
||||
batchToAdd = new ParallelCommands(PointInTime.CreateEmpty());
|
||||
_commandsToRun.Add(batchToAdd);
|
||||
}
|
||||
else if (toNewBatch)
|
||||
{
|
||||
batchToAdd = new ParallelCommands(_commandsToRun.Last().Result);
|
||||
_commandsToRun.Add(batchToAdd);
|
||||
}
|
||||
else if (batch != null && _commandsToRun.Contains(batch))
|
||||
{
|
||||
batchToAdd = batch;
|
||||
}
|
||||
else
|
||||
{
|
||||
batchToAdd = _commandsToRun[0];
|
||||
}
|
||||
await batchToAdd.AddCommand(command);
|
||||
|
||||
await RefreshCommands();
|
||||
|
||||
if (_commandRunners.Count == 0)
|
||||
{
|
||||
StartCommandRunner();
|
||||
}
|
||||
});
|
||||
|
||||
UpdateReadOnlyCommands();
|
||||
}
|
||||
|
||||
public async Task TryStartCommandRunner()
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
if (_commandRunners.Count == 0 && _commandsToRun.Count > 0)
|
||||
{
|
||||
StartCommandRunner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void StartCommandRunner()
|
||||
{
|
||||
if (_enableRunning)
|
||||
{
|
||||
RunCommands();
|
||||
}
|
||||
}
|
||||
|
||||
private void RunCommands()
|
||||
{
|
||||
if (_commandsToRun.Count > 0)
|
||||
{
|
||||
foreach (var command in _commandsToRun[0].Commands)
|
||||
{
|
||||
if (command.CanRun == CanCommandRun.True || (command.CanRun == CanCommandRun.Forceable && command.ForceRun))
|
||||
{
|
||||
var thread = new Thread(new ParameterizedThreadStart(RunCommand));
|
||||
thread.Start(command);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RunCommand(object? arg)
|
||||
{
|
||||
CommandTimeState? commandToRun = null;
|
||||
try
|
||||
{
|
||||
if (arg is CommandTimeState commandToRun2)
|
||||
{
|
||||
commandToRun = commandToRun2;
|
||||
_commandExecutor.ExecuteCommandAsync(commandToRun.Command, this).Wait();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeCommandThread(Thread.CurrentThread, commandToRun).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisposeCommandThread(Thread thread, CommandTimeState? command)
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
if (command != null)
|
||||
{
|
||||
_commandsToRun[0].Remove(command);
|
||||
}
|
||||
|
||||
_commandRunners.Remove(thread);
|
||||
});
|
||||
await UpdateReadOnlyCommands();
|
||||
|
||||
await TryStartCommandRunner();
|
||||
}
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
await RunWithLockAsync(async () =>
|
||||
{
|
||||
await RefreshCommands(PointInTime.CreateEmpty());
|
||||
});
|
||||
await UpdateReadOnlyCommands();
|
||||
|
||||
}
|
||||
|
||||
private async Task RefreshCommands(PointInTime? fullStartTime = null)
|
||||
{
|
||||
var curretnTime = fullStartTime ?? _commandsToRun[0].Result;
|
||||
var startIndex = fullStartTime == null ? 1 : 0;
|
||||
|
||||
for (var i = startIndex; i < _commandsToRun.Count; i++)
|
||||
{
|
||||
curretnTime = await _commandsToRun[i].RefreshResult(curretnTime);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateReadOnlyCommands()
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
ParallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly();
|
||||
});
|
||||
CommandsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task RunWithLockAsync(Action action)
|
||||
{
|
||||
await RunWithLockAsync(() => { action(); return Task.CompletedTask; });
|
||||
}
|
||||
|
||||
private async Task RunWithLockAsync(Func<Task> func)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
lock (_guard)
|
||||
{
|
||||
if (!_resourceIsInUse)
|
||||
{
|
||||
_resourceIsInUse = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await func();
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_guard)
|
||||
{
|
||||
_resourceIsInUse = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RunWithLock(Action action) => RunWithLockAsync(action).Wait();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user