@@ -65,6 +65,14 @@ type StateMachine[D any] struct {
65
65
// that the handler is of type StateCallHandler[D, M, R].
66
66
stateCallHandlers map [gen.Atom ]map [string ]any
67
67
68
+ // eventHandlers maps events to the handler for the event.
69
+ // Key: Event name (gen.Atom) - The name of the event
70
+ // Value: The event handler (any). There is a compile-time guarantee that
71
+ // the handler is of type EventHandler[D, E]
72
+ eventHandlers map [gen.Event ]any
73
+
74
+ // Callback that is invoked immediately after every state change. If no
75
+ // callback is registered stateEnterCallback is nil.
68
76
stateEnterCallback StateEnterCallback [D ]
69
77
}
70
78
@@ -79,13 +87,21 @@ type StateMessageHandler[D any, M any] func(gen.Atom, D, M, gen.Process) (gen.At
79
87
// R is the type of the result value.
80
88
type StateCallHandler [D any , M any , R any ] func (gen.Atom , D , M , gen.Process ) (gen.Atom , D , R , error )
81
89
90
+ // Type alias for event handler callbacks.
91
+ // D is the type of the data associated with the StateMachine.
92
+ // E is the type of the event.
93
+ type EventHandler [D any , E any ] func (gen.Atom , D , E , gen.Process ) (gen.Atom , D , error )
94
+
95
+ // Type alias for StateEnter callback.
96
+ // D is the type of the data associated with the StateMachine.
82
97
type StateEnterCallback [D any ] func (gen.Atom , gen.Atom , D , gen.Process ) (gen.Atom , D , error )
83
98
84
99
type StateMachineSpec [D any ] struct {
85
100
initialState gen.Atom
86
101
data D
87
102
stateMessageHandlers map [gen.Atom ]map [string ]any
88
103
stateCallHandlers map [gen.Atom ]map [string ]any
104
+ eventHandlers map [gen.Event ]any
89
105
stateEnterCallback StateEnterCallback [D ]
90
106
}
91
107
@@ -96,6 +112,7 @@ func NewStateMachineSpec[D any](initialState gen.Atom, options ...Option[D]) Sta
96
112
initialState : initialState ,
97
113
stateMessageHandlers : make (map [gen.Atom ]map [string ]any ),
98
114
stateCallHandlers : make (map [gen.Atom ]map [string ]any ),
115
+ eventHandlers : make (map [gen.Event ]any ),
99
116
}
100
117
for _ , opt := range options {
101
118
opt (& spec )
@@ -135,6 +152,12 @@ func WithStateEnterCallback[D any](callback StateEnterCallback[D]) Option[D] {
135
152
}
136
153
}
137
154
155
+ func WithEventHandler [D any , E any ](event gen.Event , handler EventHandler [D , E ]) Option [D ] {
156
+ return func (s * StateMachineSpec [D ]) {
157
+ s .eventHandlers [event ] = handler
158
+ }
159
+ }
160
+
138
161
func (s * StateMachine [D ]) CurrentState () gen.Atom {
139
162
return s .currentState
140
163
}
@@ -165,6 +188,8 @@ func (s *StateMachine[D]) SetData(data D) {
165
188
s .data = data
166
189
}
167
190
191
+ type startMonitoringEvents struct {}
192
+
168
193
//
169
194
// ProcessBehavior implementation
170
195
//
@@ -200,8 +225,14 @@ func (sm *StateMachine[D]) ProcessInit(process gen.Process, args ...any) (rr err
200
225
sm .data = spec .data
201
226
sm .stateMessageHandlers = spec .stateMessageHandlers
202
227
sm .stateCallHandlers = spec .stateCallHandlers
228
+ sm .eventHandlers = spec .eventHandlers
203
229
sm .stateEnterCallback = spec .stateEnterCallback
204
230
231
+ // if we have event handlers we need to start listening for events
232
+ if len (sm .eventHandlers ) > 0 {
233
+ sm .Send (sm .PID (), startMonitoringEvents {})
234
+ }
235
+
205
236
return nil
206
237
}
207
238
@@ -263,20 +294,33 @@ func (sm *StateMachine[D]) ProcessRun() (rr error) {
263
294
264
295
switch message .Type {
265
296
case gen .MailboxMessageTypeRegular :
266
- // check if there is a handler for the message in the current state
267
- messageType := typeName (message )
268
- handler , ok := sm .lookupMessageHandler (messageType )
269
- if ok == false {
270
- return fmt .Errorf ("No handler for message %s in state %s" , messageType , sm .currentState )
297
+ switch message .Message .(type ) {
298
+ case startMonitoringEvents :
299
+ // start monitoring
300
+ for event := range sm .eventHandlers {
301
+ if _ , err := sm .MonitorEvent (event ); err != nil {
302
+ panic (fmt .Sprintf ("Error monitoring event: %v." , err ))
303
+ }
304
+ }
305
+ sm .Log ().Info ("StateMachine %s is now monitoring events" , sm .PID )
306
+ return nil
307
+
308
+ default :
309
+ // check if there is a handler for the message in the current state
310
+ messageType := reflect .TypeOf (message .Message ).String ()
311
+ handler , ok := sm .lookupMessageHandler (messageType )
312
+ if ok == false {
313
+ return fmt .Errorf ("No handler for message %s in state %s" , messageType , sm .currentState )
314
+ }
315
+ return sm .invokeMessageHandler (handler , message )
271
316
}
272
- return sm .invokeMessageHandler (handler , message )
273
317
274
318
case gen .MailboxMessageTypeRequest :
275
319
var reason error
276
320
var result any
277
321
278
322
// check if there is a handler for the call in the current state
279
- messageType := typeName (message )
323
+ messageType := reflect . TypeOf (message . Message ). String ( )
280
324
handler , ok := sm .lookupCallHandler (messageType )
281
325
if ok == false {
282
326
return fmt .Errorf ("No handler for message %s in state %s" , messageType , sm .currentState )
@@ -294,9 +338,12 @@ func (sm *StateMachine[D]) ProcessRun() (rr error) {
294
338
sm .SendResponse (message .From , message .Ref , result )
295
339
296
340
case gen .MailboxMessageTypeEvent :
297
- if reason := sm .behavior .HandleEvent (message .Message .(gen.MessageEvent )); reason != nil {
298
- return reason
341
+ event := message .Message .(gen.MessageEvent )
342
+ handler , exists := sm .eventHandlers [event .Event ]
343
+ if exists == false {
344
+ return fmt .Errorf ("No handler for event %v" , event )
299
345
}
346
+ return sm .invokeEventHandler (handler , & event )
300
347
301
348
case gen .MailboxMessageTypeExit :
302
349
switch exit := message .Message .(type ) {
@@ -358,10 +405,6 @@ func (s *StateMachine[D]) Terminate(reason error) {}
358
405
// Internals
359
406
//
360
407
361
- func typeName (message * gen.MailboxMessage ) string {
362
- return reflect .TypeOf (message .Message ).String ()
363
- }
364
-
365
408
func (sm * StateMachine [D ]) lookupMessageHandler (messageType string ) (any , bool ) {
366
409
if stateMessageHandlers , exists := sm .stateMessageHandlers [sm .currentState ]; exists == true {
367
410
if callback , exists := stateMessageHandlers [messageType ]; exists == true {
@@ -382,7 +425,7 @@ func (sm *StateMachine[D]) invokeMessageHandler(handler any, message *gen.Mailbo
382
425
383
426
if len (results ) != 3 {
384
427
sm .Log ().Panic ("StateMachine terminated. Panic reason: unexpected " +
385
- "error when invoking call handler for %v " , typeName (message ))
428
+ "error when invoking call handler for %s " , reflect . TypeOf (message . Message ))
386
429
return gen .TerminateReasonPanic
387
430
}
388
431
if ! results [2 ].IsNil () {
@@ -419,12 +462,12 @@ func (sm *StateMachine[D]) invokeCallHandler(handler any, message *gen.MailboxMe
419
462
420
463
if len (results ) != 4 {
421
464
sm .Log ().Panic ("StateMachine terminated. Panic reason: unexpected " +
422
- "error when invoking call handler for %v " , typeName (message ))
465
+ "error when invoking call handler for %s " , reflect . TypeOf (message . Message ))
423
466
return nil , gen .TerminateReasonPanic
424
467
}
425
468
426
469
if ! results [3 ].IsNil () {
427
- err := results [1 ].Interface ().(error )
470
+ err := results [3 ].Interface ().(error )
428
471
return nil , err
429
472
}
430
473
@@ -439,3 +482,33 @@ func (sm *StateMachine[D]) invokeCallHandler(handler any, message *gen.MailboxMe
439
482
440
483
return result , nil
441
484
}
485
+
486
+ func (sm * StateMachine [D ]) invokeEventHandler (handler any , message * gen.MessageEvent ) error {
487
+ callbackValue := reflect .ValueOf (handler )
488
+ stateValue := reflect .ValueOf (sm .currentState )
489
+ dataValue := reflect .ValueOf (sm .Data ())
490
+ msgValue := reflect .ValueOf (message .Message )
491
+ procValue := reflect .ValueOf (sm )
492
+
493
+ results := callbackValue .Call ([]reflect.Value {stateValue , dataValue , msgValue , procValue })
494
+
495
+ if len (results ) != 3 {
496
+ sm .Log ().Panic ("StateMachine terminated. Panic reason: unexpected " +
497
+ "error when invoking call handler for %s" , reflect .TypeOf (message .Message ))
498
+ return gen .TerminateReasonPanic
499
+ }
500
+
501
+ if ! results [2 ].IsNil () {
502
+ err := results [2 ].Interface ().(error )
503
+ return err
504
+ }
505
+
506
+ setDataMethod := reflect .ValueOf (sm ).MethodByName ("SetData" )
507
+ setDataMethod .Call ([]reflect.Value {results [1 ]})
508
+ // It is important that we set the state last as this can potentially trigger
509
+ // a state enter callback
510
+ setCurrentStateMethod := reflect .ValueOf (sm ).MethodByName ("SetCurrentState" )
511
+ setCurrentStateMethod .Call ([]reflect.Value {results [0 ]})
512
+
513
+ return nil
514
+ }
0 commit comments