129 lines
2.5 KiB
Go
129 lines
2.5 KiB
Go
package statemachine
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
Running StateType = "running"
|
|
Suspended StateType = "suspended"
|
|
|
|
Halt EventType = "halt"
|
|
Resume EventType = "resume"
|
|
)
|
|
|
|
type Suspendable interface {
|
|
Halt()
|
|
Resume()
|
|
}
|
|
|
|
type HaltAction struct{}
|
|
|
|
func (a *HaltAction) Execute(ctx EventContext) EventType {
|
|
s, ok := ctx.(*Suspender)
|
|
if !ok {
|
|
fmt.Println("unable to halt, event context is not Suspendable")
|
|
return NoOp
|
|
}
|
|
s.target.Halt()
|
|
return NoOp
|
|
}
|
|
|
|
type ResumeAction struct{}
|
|
|
|
func (a *ResumeAction) Execute(ctx EventContext) EventType {
|
|
s, ok := ctx.(*Suspender)
|
|
if !ok {
|
|
fmt.Println("unable to resume, event context is not Suspendable")
|
|
return NoOp
|
|
}
|
|
s.target.Resume()
|
|
return NoOp
|
|
}
|
|
|
|
type Suspender struct {
|
|
StateMachine
|
|
target Suspendable
|
|
log LogFn
|
|
}
|
|
|
|
type LogFn func(fmt string, args ...interface{})
|
|
|
|
func NewSuspender(target Suspendable, log LogFn) *Suspender {
|
|
return &Suspender{
|
|
target: target,
|
|
log: log,
|
|
StateMachine: StateMachine{
|
|
Current: Running,
|
|
States: States{
|
|
Running: State{
|
|
Action: &ResumeAction{},
|
|
Events: Events{
|
|
Halt: Suspended,
|
|
},
|
|
},
|
|
|
|
Suspended: State{
|
|
Action: &HaltAction{},
|
|
Events: Events{
|
|
Resume: Running,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (s *Suspender) RunEvents(eventSpec string) {
|
|
s.log("running event spec: %s", eventSpec)
|
|
for _, et := range parseEventSpec(eventSpec, s.log) {
|
|
if et.delay != 0 {
|
|
//s.log("waiting %s", et.delay.String())
|
|
time.Sleep(et.delay)
|
|
continue
|
|
}
|
|
if et.event == "" {
|
|
s.log("ignoring empty event")
|
|
continue
|
|
}
|
|
s.log("sending event %s", et.event)
|
|
err := s.SendEvent(et.event, s)
|
|
if err != nil {
|
|
s.log("error sending event %s: %s", et.event, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
type eventTiming struct {
|
|
delay time.Duration
|
|
event EventType
|
|
}
|
|
|
|
func parseEventSpec(spec string, log LogFn) []eventTiming {
|
|
fields := strings.Split(spec, "->")
|
|
out := make([]eventTiming, 0, len(fields))
|
|
for _, f := range fields {
|
|
f = strings.TrimSpace(f)
|
|
words := strings.Split(f, " ")
|
|
|
|
// TODO: try to implement a "waiting" state instead of special casing like this
|
|
if words[0] == "wait" {
|
|
if len(words) != 2 {
|
|
log("expected 'wait' to be followed by duration, e.g. 'wait 30s'. ignoring.")
|
|
continue
|
|
}
|
|
d, err := time.ParseDuration(words[1])
|
|
if err != nil {
|
|
log("bad argument for 'wait': %s", err)
|
|
continue
|
|
}
|
|
out = append(out, eventTiming{delay: d})
|
|
} else {
|
|
out = append(out, eventTiming{event: EventType(words[0])})
|
|
}
|
|
}
|
|
return out
|
|
}
|