//
// CompoundOperation.cs
//
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Diagnostics;
using NosSmooth.Extensions.Combat.Techniques;
using Remora.Results;
namespace NosSmooth.Extensions.Combat.Operations;
///
/// An operation made from multiple operations
/// that should execute right after each other.
///
public class CompoundOperation : ICombatOperation
{
private readonly ICombatTechnique _technique;
private readonly ICombatOperation[] _operations;
private readonly OperationQueueType _queueType;
private ICombatOperation? _currentOperation;
private CancellationTokenSource? _ct;
private Task? _compoundOperation;
private bool _disposed;
///
/// Initializes a new instance of the class.
///
/// The combat technique used for calling HandleWaiting.
/// The queue type.
/// The operations to execute.
public CompoundOperation
(ICombatTechnique technique, OperationQueueType queueType = OperationQueueType.TotalControl, params ICombatOperation[] operations)
{
if (operations.Length == 0)
{
throw new ArgumentNullException(nameof(operations), "The compound operation needs at least one operation.");
}
_technique = technique;
_operations = operations;
_queueType = queueType;
}
///
public void Dispose()
{
foreach (var operation in _operations)
{
operation.Dispose();
}
}
///
public OperationQueueType QueueType => _queueType;
///
public bool MayBeCancelled => _currentOperation?.MayBeCancelled ?? true;
///
public Task BeginExecution(ICombatState combatState, CancellationToken ct = default)
{
if (_compoundOperation is not null)
{
return Task.FromResult(Result.FromSuccess());
}
_ct = new CancellationTokenSource();
_compoundOperation = Task.Run
(
() => UseAsync(combatState, _ct.Token),
_ct.Token
);
return Task.FromResult(Result.FromSuccess());
}
///
public async Task WaitForFinishedAsync(ICombatState combatState, CancellationToken ct = default)
{
if (IsFinished())
{
return Result.FromSuccess();
}
await BeginExecution(combatState, ct);
if (_compoundOperation is null)
{
throw new UnreachableException();
}
try
{
return await _compoundOperation;
}
catch (OperationCanceledException)
{
return Result.FromSuccess();
}
catch (Exception e)
{
return e;
}
}
///
public bool IsExecuting()
=> _compoundOperation is not null && !IsFinished();
///
public bool IsFinished()
=> _compoundOperation?.IsCompleted ?? false;
///
public Result CanBeUsed(ICombatState combatState)
=> _operations[0].CanBeUsed(combatState);
///
public void Cancel()
{
if (_disposed)
{
return;
}
_disposed = true;
_currentOperation?.Cancel();
_ct?.Cancel();
}
private async Task UseAsync(ICombatState combatState, CancellationToken ct)
{
foreach (var operation in _operations)
{
_currentOperation = operation;
CanBeUsedResponse canBeUsed = CanBeUsedResponse.MustWait;
while (canBeUsed != CanBeUsedResponse.CanBeUsed)
{
var canBeUsedResult = operation.CanBeUsed(combatState);
if (canBeUsedResult is { IsSuccess: false, Error: not CannotBeUsedError })
{
return canBeUsedResult;
}
var error = canBeUsedResult.Error as CannotBeUsedError;
canBeUsed = error?.Response ?? CanBeUsedResponse.CanBeUsed;
if (canBeUsed != CanBeUsedResponse.CanBeUsed)
{
_technique.HandleWaiting(QueueType, combatState, this, error!);
}
await Task.Delay(10, ct);
}
var result = await operation.BeginExecution(combatState, ct);
if (!result.IsSuccess)
{
return result;
}
result = await operation.WaitForFinishedAsync(combatState, ct);
if (!result.IsSuccess)
{
return result;
}
}
return Result.FromSuccess();
}
}