~ruther/NosSmooth

ref: 977eadfb13d751caeb07fccc6c7664b95e6e44df NosSmooth/Extensions/NosSmooth.Extensions.Combat/Operations/CompoundOperation.cs -rw-r--r-- 4.5 KiB
977eadfb — František Boháček feat(combat): allow cancelling operations 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//
//  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 CancellationTokenSource? _ct;
    private Task<Result>? _compoundOperation;

    /// <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 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);

    private async Task<Result> UseAsync(ICombatState combatState, CancellationToken ct)
    {
        foreach (var operation in _operations)
        {
            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();
    }
}
Do not follow this link