~ruther/NosSmooth

16cca6fa15fb0c13e0fee4d9665e7c9adc4c2c3c — Rutherther 2 years ago 7a30c95
feat(core): add support for timeout moving to errors
M Core/NosSmooth.Core/Contracts/ContractBuilder.cs => Core/NosSmooth.Core/Contracts/ContractBuilder.cs +16 -3
@@ 29,7 29,7 @@ public class ContractBuilder<TData, TState, TError>
    private readonly TState _defaultState;

    private readonly Dictionary<TState, DefaultContract<TData, TState, TError>.StateActionAsync> _actions;
    private readonly Dictionary<TState, (TimeSpan, TState)> _timeouts;
    private readonly Dictionary<TState, (TimeSpan, TState?, TError?)> _timeouts;

    private TState? _fillAtState;
    private DefaultContract<TData, TState, TError>.FillDataAsync? _fillData;


@@ 44,7 44,7 @@ public class ContractBuilder<TData, TState, TError>
        _contractor = contractor;
        _defaultState = defaultState;
        _actions = new Dictionary<TState, DefaultContract<TData, TState, TError>.StateActionAsync>();
        _timeouts = new Dictionary<TState, (TimeSpan, TState)>();
        _timeouts = new Dictionary<TState, (TimeSpan, TState?, TError?)>();
    }

    /// <summary>


@@ 56,7 56,20 @@ public class ContractBuilder<TData, TState, TError>
    /// <returns>The updated builder.</returns>
    public ContractBuilder<TData, TState, TError> SetTimeout(TState state, TimeSpan timeout, TState nextState)
    {
        _timeouts[state] = (timeout, nextState);
        _timeouts[state] = (timeout, nextState, null);
        return this;
    }

    /// <summary>
    /// Sets timeout of the given state.
    /// </summary>
    /// <param name="state">The state to set timeout for.</param>
    /// <param name="timeout">The timeout span.</param>
    /// <param name="error">The error to set.</param>
    /// <returns>The updated builder.</returns>
    public ContractBuilder<TData, TState, TError> SetTimeout(TState state, TimeSpan timeout, TError error)
    {
        _timeouts[state] = (timeout, null, error);
        return this;
    }


M Core/NosSmooth.Core/Contracts/DefaultContract.cs => Core/NosSmooth.Core/Contracts/DefaultContract.cs +40 -7
@@ 44,7 44,7 @@ public class DefaultContract<TData, TState, TError> : IContract<TData, TState>

    private readonly SemaphoreSlim _semaphore;

    private readonly IDictionary<TState, (TimeSpan, TState)> _timeouts;
    private readonly IDictionary<TState, (TimeSpan, TState?, TError?)> _timeouts;
    private readonly IDictionary<TState, StateActionAsync> _actions;
    private readonly Contractor _contractor;
    private readonly TState _defaultState;


@@ 75,7 75,7 @@ public class DefaultContract<TData, TState, TError> : IContract<TData, TState>
        TState fillAtState,
        FillDataAsync fillData,
        IDictionary<TState, StateActionAsync> actions,
        IDictionary<TState, (TimeSpan Timeout, TState NextState)> timeouts
        IDictionary<TState, (TimeSpan Timeout, TState? NextState, TError? Error)> timeouts
    )
    {
        _semaphore = new SemaphoreSlim(1, 1);


@@ 139,8 139,16 @@ public class DefaultContract<TData, TState, TError> : IContract<TData, TState>
        if (resultData.Error is not null)
        {
            _error = resultData.Error;
            _waitCancellationSource?.Cancel();
            return ContractUpdateResponse.Interested;
            try
            {
                _waitCancellationSource?.Cancel();
            }
            catch
            {
                // ignored
            }

            return ContractUpdateResponse.InterestedAndUnregister;
        }

        if (resultData.NextState is null)


@@ 170,6 178,7 @@ public class DefaultContract<TData, TState, TError> : IContract<TData, TState>
            {
                _error = error;
                _waitCancellationSource?.Cancel();
                return new ContractError<TError>(error.Value);
            }

            if (state is not null)


@@ 199,6 208,16 @@ public class DefaultContract<TData, TState, TError> : IContract<TData, TState>
            );
        }

        if (_error is not null)
        {
            return new ContractError<TError>(_error.Value);
        }

        if (_resultError is not null)
        {
            return Result<TData>.FromError(_resultError.Value);
        }

        if (CurrentState.CompareTo(state) >= 0)
        { // already reached.
            return Data;


@@ 234,6 253,8 @@ public class DefaultContract<TData, TState, TError> : IContract<TData, TState>
            {
                Unregister();
            }

            _waitCancellationSource?.Dispose();
        }

        if (ct.IsCancellationRequested)


@@ 259,6 280,10 @@ public class DefaultContract<TData, TState, TError> : IContract<TData, TState>
        return Data;
    }

    /// <inheritdoc />
    public bool HasReachedState(TState state)
        => _error is not null || _resultError is not null || CurrentState.CompareTo(state) >= 0;

    private async Task<Result<ContractUpdateResponse>> SetupNewState<TAny>(TAny data, CancellationToken ct)
    {
        if (_fillAtState.CompareTo(CurrentState) == 0)


@@ 304,7 329,7 @@ public class DefaultContract<TData, TState, TError> : IContract<TData, TState>
        if (_timeouts.ContainsKey(CurrentState))
        {
            var currentState = CurrentState;
            var (timeout, state) = _timeouts[CurrentState];
            var (timeout, state, error) = _timeouts[CurrentState];

            Task.Run
            (


@@ 314,8 339,16 @@ public class DefaultContract<TData, TState, TError> : IContract<TData, TState>

                    if (CurrentState.CompareTo(currentState) == 0)
                    {
                        await SetCurrentState(state);
                        await SetupNewState<int?>(null!, default);
                        if (state is not null)
                        {
                            await SetCurrentState(state.Value);
                            await SetupNewState<int?>(null!, default);
                        }
                        else if (error is not null)
                        {
                            _error = error;
                            _waitCancellationSource?.Cancel();
                        }
                    }
                }
            );

M Core/NosSmooth.Core/Contracts/IContract.cs => Core/NosSmooth.Core/Contracts/IContract.cs +7 -0
@@ 116,4 116,11 @@ public interface IContract<TData, TState> : IContract
    /// <returns>The data of the contract or an error.</returns>
    /// <exception cref="InvalidOperationError">Thrown in case the given state cannot fill the data.</exception>
    public Task<Result<TData>> WaitForAsync(TState state, bool unregisterAfter = true, CancellationToken ct = default);

    /// <summary>
    /// Gets whether the given state has been reached already.
    /// </summary>
    /// <param name="state">The state to check has been reached.</param>
    /// <returns>True in case the given state has been reached or there was an error.</returns>
    public bool HasReachedState(TState state);
}
\ No newline at end of file

Do not follow this link