//
// 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;
/// <summary>
/// An operation made from multiple operations
/// that should execute right after each other.
/// </summary>
public class CompoundOperation : ICombatOperation
{
private readonly ICombatTechnique _technique;
private readonly ICombatOperation[] _operations;
private readonly OperationQueueType _queueType;
private ICombatOperation? _currentOperation;
private CancellationTokenSource? _ct;
private Task<Result>? _compoundOperation;
private bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="CompoundOperation"/> class.
/// </summary>
/// <param name="technique">The combat technique used for calling HandleWaiting.</param>
/// <param name="queueType">The queue type.</param>
/// <param name="operations">The operations to execute.</param>
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;
}
/// <inheritdoc />
public void Dispose()
{
foreach (var operation in _operations)
{
operation.Dispose();
}
}
/// <inheritdoc />
public OperationQueueType QueueType => _queueType;
/// <inheritdoc />
public bool MayBeCancelled => _currentOperation?.MayBeCancelled ?? true;
/// <inheritdoc />
public Task<Result> 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());
}
/// <inheritdoc />
public async Task<Result> 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;
}
}
/// <inheritdoc />
public bool IsExecuting()
=> _compoundOperation is not null && !IsFinished();
/// <inheritdoc />
public bool IsFinished()
=> _compoundOperation?.IsCompleted ?? false;
/// <inheritdoc />
public Result CanBeUsed(ICombatState combatState)
=> _operations[0].CanBeUsed(combatState);
/// <inheritdoc />
public void Cancel()
{
if (_disposed)
{
return;
}
_disposed = true;
_currentOperation?.Cancel();
_ct?.Cancel();
}
private async Task<Result> 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();
}
}