-
Notifications
You must be signed in to change notification settings - Fork 129
Expand file tree
/
Copy pathStateMachine.cs
More file actions
252 lines (221 loc) · 11.5 KB
/
StateMachine.cs
File metadata and controls
252 lines (221 loc) · 11.5 KB
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
//-------------------------------------------------------------------------------
// <copyright file="StateMachine.cs" company="Appccelerate">
// Copyright (c) 2008-2019 Appccelerate
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
//-------------------------------------------------------------------------------
namespace Appccelerate.StateMachine.AsyncMachine
{
using System;
using System.Threading.Tasks;
using Events;
using Infrastructure;
using States;
using Transitions;
/// <summary>
/// Base implementation of a state machine.
/// </summary>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <typeparam name="TEvent">The type of the event.</typeparam>
public class StateMachine<TState, TEvent> :
INotifier<TState, TEvent>
where TState : IComparable
where TEvent : IComparable
{
private readonly IFactory<TState, TEvent> factory;
private readonly IStateLogic<TState, TEvent> stateLogic;
/// <summary>
/// Initializes a new instance of the <see cref="StateMachine{TState,TEvent}"/> class.
/// </summary>
/// <param name="factory">The factory used to create internal instances.</param>
/// <param name="stateLogic">The state logic used to handle state changes.</param>
public StateMachine(IFactory<TState, TEvent> factory, IStateLogic<TState, TEvent> stateLogic)
{
this.factory = factory;
this.stateLogic = stateLogic;
}
/// <summary>
/// Occurs when no transition could be executed.
/// </summary>
public event EventHandler<TransitionEventArgs<TState, TEvent>> TransitionDeclined;
/// <summary>
/// Occurs when an exception was thrown inside a transition of the state machine.
/// </summary>
public event EventHandler<TransitionExceptionEventArgs<TState, TEvent>> TransitionExceptionThrown;
/// <summary>
/// Occurs when a transition begins.
/// </summary>
public event EventHandler<TransitionEventArgs<TState, TEvent>> TransitionBegin;
/// <summary>
/// Occurs when a transition completed.
/// </summary>
public event EventHandler<TransitionCompletedEventArgs<TState, TEvent>> TransitionCompleted;
private static async Task SwitchStateTo(
IStateDefinition<TState, TEvent> newState,
StateContainer<TState, TEvent> stateContainer,
IStateMachineInformation<TState, TEvent> stateMachineInformation)
{
var oldState = stateContainer.CurrentState.ExtractOr(null);
stateContainer.CurrentState = Initializable<IStateDefinition<TState, TEvent>>.Initialized(newState);
await stateContainer
.ForEach(extension =>
extension.SwitchedState(stateMachineInformation, oldState, newState))
.ConfigureAwait(false);
}
/// <summary>
/// Enters the initial state as specified with <paramref name="initialState"/>.
/// </summary>
/// <param name="stateContainer">Contains all mutable state of of the state machine.</param>
/// <param name="stateMachineInformation">The state machine information.</param>
/// <param name="stateDefinitions">The definitions for all states of this state Machine.</param>
/// <param name="initialState">The initial state the state machine should enter.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task EnterInitialState(
StateContainer<TState, TEvent> stateContainer,
IStateMachineInformation<TState, TEvent> stateMachineInformation,
IStateDefinitionDictionary<TState, TEvent> stateDefinitions,
TState initialState)
{
await stateContainer.ForEach(extension => extension.EnteringInitialState(stateMachineInformation, initialState))
.ConfigureAwait(false);
var context = this.factory.CreateTransitionContext(null, new Missable<TEvent>(), Missing.Value, this);
await this.EnterInitialState(context, stateContainer, stateMachineInformation, stateDefinitions, initialState)
.ConfigureAwait(false);
await stateContainer.ForEach(extension => extension.EnteredInitialState(stateMachineInformation, initialState, context))
.ConfigureAwait(false);
}
/// <summary>
/// Fires the specified event.
/// </summary>
/// <param name="eventId">The event.</param>
/// <param name="eventArgument">The event argument.</param>
/// <param name="stateContainer">Contains all mutable state of of the state machine.</param>
/// <param name="stateMachineInformation">The state machine information.</param>
/// <param name="stateDefinitions">The definitions for all states of this state Machine.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task Fire(
TEvent eventId,
object? eventArgument,
StateContainer<TState, TEvent> stateContainer,
IStateMachineInformation<TState, TEvent> stateMachineInformation,
IStateDefinitionDictionary<TState, TEvent> stateDefinitions)
{
CheckThatStateMachineHasEnteredInitialState(stateContainer);
await stateContainer.ForEach(extension => extension.FiringEvent(stateMachineInformation, ref eventId, ref eventArgument))
.ConfigureAwait(false);
var currentState = stateContainer.CurrentState.ExtractOrThrow();
var context = this.factory.CreateTransitionContext(currentState, new Missable<TEvent>(eventId), eventArgument, this);
var result = await this.stateLogic.Fire(currentState, context, stateContainer)
.ConfigureAwait(false);
if (!(result is FiredTransitionResult<TState> firedTransitionResult))
{
this.OnTransitionDeclined(context);
return;
}
var newState = stateDefinitions[firedTransitionResult.NewState];
await SwitchStateTo(newState, stateContainer, stateMachineInformation)
.ConfigureAwait(false);
await stateContainer.ForEach(extension => extension.FiredEvent(stateMachineInformation, context))
.ConfigureAwait(false);
this.OnTransitionCompleted(context, stateMachineInformation);
}
public void OnExceptionThrown(ITransitionContext<TState, TEvent> context, Exception exception)
{
RethrowExceptionIfNoHandlerRegistered(exception, this.TransitionExceptionThrown);
this.RaiseEvent(this.TransitionExceptionThrown, new TransitionExceptionEventArgs<TState, TEvent>(context, exception), context, false);
}
/// <summary>
/// Fires the <see cref="TransitionBegin"/> event.
/// </summary>
/// <param name="transitionContext">The transition context.</param>
public void OnTransitionBegin(ITransitionContext<TState, TEvent> transitionContext)
{
this.RaiseEvent(this.TransitionBegin, new TransitionEventArgs<TState, TEvent>(transitionContext), transitionContext, true);
}
// ReSharper disable once UnusedParameter.Local
private static void RethrowExceptionIfNoHandlerRegistered<T>(Exception exception, EventHandler<T> exceptionHandler)
where T : EventArgs
{
if (exceptionHandler == null)
{
throw new StateMachineException("No exception listener is registered. Exception: ", exception);
}
}
/// <summary>
/// Fires the <see cref="TransitionDeclined"/> event.
/// </summary>
/// <param name="transitionContext">The transition event context.</param>
private void OnTransitionDeclined(ITransitionContext<TState, TEvent> transitionContext)
{
this.RaiseEvent(this.TransitionDeclined, new TransitionEventArgs<TState, TEvent>(transitionContext), transitionContext, true);
}
/// <summary>
/// Fires the <see cref="TransitionCompleted"/> event.
/// </summary>
/// <param name="transitionContext">The transition event context.</param>
/// <param name="stateMachineInformation">The state machine information.</param>
private void OnTransitionCompleted(ITransitionContext<TState, TEvent> transitionContext, IStateMachineInformation<TState, TEvent> stateMachineInformation)
{
this.RaiseEvent(
this.TransitionCompleted,
new TransitionCompletedEventArgs<TState, TEvent>(
stateMachineInformation.CurrentStateId.ExtractOrThrow(),
transitionContext),
transitionContext,
true);
}
private async Task EnterInitialState(
ITransitionContext<TState, TEvent> context,
StateContainer<TState, TEvent> stateContainer,
IStateMachineInformation<TState, TEvent> stateMachineInformation,
IStateDefinitionDictionary<TState, TEvent> stateDefinitions,
TState initialStateId)
{
var initialState = stateDefinitions[initialStateId];
var initializer = this.factory.CreateStateMachineInitializer(initialState, context);
var newStateId = await initializer.EnterInitialState(this.stateLogic, stateContainer).
ConfigureAwait(false);
var newStateDefinition = stateDefinitions[newStateId];
await SwitchStateTo(newStateDefinition, stateContainer, stateMachineInformation)
.ConfigureAwait(false);
}
private void RaiseEvent<T>(EventHandler<T> eventHandler, T arguments, ITransitionContext<TState, TEvent> context, bool raiseEventOnException)
where T : EventArgs
{
try
{
if (eventHandler == null)
{
return;
}
eventHandler(this, arguments);
}
catch (Exception e)
{
if (!raiseEventOnException)
{
throw;
}
((INotifier<TState, TEvent>)this).OnExceptionThrown(context, e);
}
}
private static void CheckThatStateMachineHasEnteredInitialState(StateContainer<TState, TEvent> stateContainer)
{
if (!stateContainer.CurrentState.IsInitialized)
{
throw new InvalidOperationException(ExceptionMessages.StateMachineHasNotYetEnteredInitialState);
}
}
}
}