statemachine

package module
v0.0.0-...-b27cddb Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2019 License: Apache-2.0 Imports: 7 Imported by: 1

README

StateMachine

StateMachine supports creating productive State Machines In Go

GoDoc

Introduction

TLDR Turnstile Example

Turnstile StateMachine Example Diagram

State machines provide an alternative way of thinking about how we code any job/process/workflow.

Using a state machine for an object, that reacts to events differently based on its current state, reduces the amount of boilerplate and duct-taping you have to introduce to your code.

StateMachine package provides a feature complete implementation of finite-state machines in Go.

What Is A Finite-State Machine Even?

A finite-state machine (FSM) is an abstract machine that can be in exactly one of a finite number of states at any given time. The FSM can change from one state to another in response to some external inputs; the change from one state to another is called a transition. An FSM is defined by a list of its states, its initial state, and the conditions for each transition.

Wikipedia

Further Reading

Installation

Run this in your project directory:

go get -u https://github.com/Gurpartap/statemachine-go

Import StateMachine with this line in your Go code:

import "github.com/Gurpartap/statemachine-go"

Usage

Project Goals

A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system.

John Gall (1975)

StateMachine is simple in its specification, DSL, and internal implementation. And it works. There are no plans to introduce advanced FSM features such as regions, submachines, history based transitions, join, fork, etc., unless there's a simple way to do so without affecting the rest of the implementation. Well, submachines have already been implemented (partially and is in flux).

Performance is generally a significant factor when considering the use of a third party package. However, an API that I can actually code and design in my mind, ahead of using it, is just as important to me.

StateMachine's API design and developer productivity take precedence over its benchmark numbers (especially when compared to a bare metal switch statement based state machine implementation, which may not take you as far).

For this, StateMachine provides a DSL using its builder objects. These builders compute and validate the state definitions, and then inject the result (states, events, transitions, callbacks, etc.) into the state machine during its initialization. Subsequently, these builders are free to be garbage collected.

Moreover, the state machinery is not dependent on these DSL builders. State machines may also be initialized from directly allocating definition structs, or even parsing them from JSON, along with pre-registered callback references.

StateMachine definitions comprise of the following basic components:

These, and some additional components are covered below, along with their example usage code.

Adding a state machine is as simple as embedding statemachine.Machine in your struct, defining states and events, along with their transitions.

type Process struct {
    statemachine.Machine

    // or

    Machine statemachine.Machine
}

func NewProcess() *Process {
    process := &Process{}

    process.Machine = statemachine.NewMachine()
    process.Machine.Build(func(m statemachine.MachineBuilder) {
        // ...
    })

    // or

    process.Machine = statemachine.BuildNewMachine(func(m statemachine.MachineBuilder) {
        // ...
    })

    return process
}

States, events, and transitions are defined using a DSL composed of "builders", including statemachine.MachineBuilder and statemachine.EventBuilder. These builders provide a clean and type-safe DSL for writing the specification of how the state machine functions.

The subsequent examples are a close port of my experience with using the state_machines Ruby gem, from which StateMachine Go package's DSL is highly inspired.

System Process StateMachine Example Diagram

The example represented in the diagram above is implemented in examples/cognizant/process.go.

If, instead of the builders DSL, you would rather want to specify the StateMachine directly using definition structs, take a look at the ExampleMachineDef test function. The same may also be imported from JSON or HCL.

States and Initial State

Possible states in the state machine may be manually defined, along with the initial state. However, states are also inferred from event transition definitions.

Initial state is set during the initialization of the state machine, and is required to be defined in the builder.

process.Machine.Build(func(m statemachine.MachineBuilder) {
    m.States("unmonitored", "running", "stopped")
    m.States("starting", "stopping", "restarting")

    // Initial state must be defined.
    m.InitialState("unmonitored")
})

Events

Events act as a virtual function which when fired, trigger a state transition.

process.Machine.Build(func(m statemachine.MachineBuilder) {
    m.Event("monitor", ... )

    m.Event("start", ... )

    m.Event("stop", ... )

    m.Event("restart", ... )

    m.Event("unmonitor", ... )

    m.Event("tick", ... )
})

Timed Events

Currently there one one timed event available:

TimedEvery(duration time.Duration)

Makes the event fire automatically at every specified duration.

process.Machine.Build(func(m statemachine.MachineBuilder) {
	m.Event("tick", func(e statemachine.EventBuilder) {
		e.TimedEvery(1 * time.Second)
		// e.Transition().From(...).To(...)
	}

	// or

	m.Event("tick").
		TimedEvery(1 * time.Second).
		// e.Transition().From(...).To(...)
}

Choice

Choice assists in choosing event transition(s) based on a boolean condition.

Note that Choice is not executed if the event also specifies transitions of its own.

The example below runs the tick event every second, and decides the state to transition to based on based on whether the process is currently running on the system or not, as long as we're also not set to SkipTicks (for start, stop, and restart grace times).

process.Machine.Build(func(m statemachine.MachineBuilder) {
	// the nested way:

	m.Event("tick", func(e statemachine.EventBuilder) {
		e.Choice(&process.IsProcessRunning, func(c statemachine.ChoiceBuilder) {
			c.Unless(process.SkipTick)

			c.OnTrue(func(e statemachine.EventBuilder) {
				// e.Transition().From(...).To(...)
			})

			c.OnFalse(func(e statemachine.EventBuilder) {
				// e.Transition().From(...).To(...)
			})
		})
	})

	// preferred alternative syntax:

	m.Event("tick").
		TimedEvery(1 * time.Second).
		Choice(&process.IsProcessRunning).Label("isRunning"). // Label helps with diagrams and debugging
		Unless(process.SkipTick). // TODO: move this to SkipUntil
		OnTrue(func(e statemachine.EventBuilder) {
			e.Transition().From("starting").To("running")
			e.Transition().From("restarting").To("running")
			e.Transition().From("stopping").To("running")
			e.Transition().From("stopped").To("running")
		}).
		OnFalse(func(e statemachine.EventBuilder) {
			e.Transition().From("starting").To("stopped")
			e.Transition().From("restarting").To("stopped")
			e.Transition().From("running").To("stopped")
			e.Transition().From("stopping").To("stopped")
			e.Transition().From("stopped").To("starting").
				If(&process.ShouldAutoStart).Label("shouldAutoStart")
			})
}

Transitions

Transitions represent the change in state when an event is fired.

Note that .From(states ...string) accepts variadic args.

process.Machine.Build(func(m statemachine.MachineBuilder) {
    m.Event("monitor", func(e statemachine.EventBuilder) {
        e.Transition().From("unmonitored").To("stopped")
    })

    m.Event("start", func(e statemachine.EventBuilder) {
        // from either of the defined states
        e.Transition().From("unmonitored", "stopped").To("starting")
    })

    m.Event("stop", func(e statemachine.EventBuilder) {
        e.Transition().From("running").To("stopping")
    })

    m.Event("restart", func(e statemachine.EventBuilder) {
        e.Transition().From("running", "stopped").To("restarting")
    })

    m.Event("unmonitor", func(e statemachine.EventBuilder) {
        e.Transition().FromAny().To("unmonitored")
    })

    m.Event("tick", func(e statemachine.EventBuilder) {
        // ...
    })
})

Transition Guards (Conditions)

Transition Guards are conditional callbacks which expect a boolean return value, implying whether or not the transition in context should occur.

type TransitionGuardFnBuilder interface {
    If(guardFunc ...TransitionGuardFunc)
    Unless(guardFunc ...TransitionGuardFunc)
}

Valid TransitionGuardFunc signatures:

*bool
func() bool
func(transition statemachine.Transition) bool
// Assuming process.IsProcessRunning is a bool variable, and
// process.GetIsProcessRunning is a func returning a bool value.
m.Event("tick", func(e statemachine.EventBuilder) {
    // If guard
    e.Transition().From("starting").To("running").If(&process.IsProcessRunning)

    // Unless guard
    e.Transition().From("starting").To("stopped").Unless(process.GetIsProcessRunning)

    // ...

    e.Transition().From("stopped").To("starting").If(func(t statemachine.Transition) bool {
        return process.ShouldAutoStart && !process.GetIsProcessRunning()
    })

    // or

    e.Transition().From("stopped").To("starting").
        If(&process.ShouldAutoStart).
        AndUnless(&process.IsProcessRunning)

    // ...
})

Transition Callbacks

Transition Callback methods are called before, around, or after a transition.

Before Transition

Before Transition callbacks do not act as a conditional, and a bool return value will not impact the transition.

Valid TransitionCallbackFunc signatures:

func()
func(m statemachine.Machine)
func(t statemachine.Transition)
func(m statemachine.Machine, t statemachine.Transition)
process.Machine.Build(func(m statemachine.MachineBuilder) {
    // ...

    m.BeforeTransition().FromAny().To("stopping").Do(func() {
        process.ShouldAutoStart = false
    })

    // ...
}
Around Transition

Around Transition's callback provides a next func as input, which must be called inside the callback. (TODO: Missing to call the method will trigger a runtime failure with an appropriately described error.)

Valid TransitionCallbackFunc signatures:

func(next func())
func(m statemachine.Machine, next func())
func(t statemachine.Transition, next func())
func(m statemachine.Machine, t statemachine.Transition, next func())
process.Machine.Build(func(m statemachine.MachineBuilder) {
    // ...

    m.
        AroundTransition().
        From("starting", "restarting").
        To("running").
        Do(func(next func()) {
            start := time.Now()

            // it'll trigger a failure if next is not called
            next()

            elapsed = time.Since(start)

            log.Printf("it took %s to [re]start the process.\n", elapsed)
        })

    // ...
})
After Transition

After Transition callback is called when the state has successfully transitioned.

Valid TransitionCallbackFunc signatures:

func()
func(m statemachine.Machine)
func(t statemachine.Transition)
func(m statemachine.Machine, t statemachine.Transition)
process.Machine.Build(func(m statemachine.MachineBuilder) {
    // ...

    // notify system admin
    m.AfterTransition().From("running").ToAny().Do(process.DialHome)

    // log all transitions
    m.
        AfterTransition().
        Any().
        Do(func(t statemachine.Transition) {
            log.Printf("State changed from '%s' to '%s'.\n", t.From(), t.To())
        })

    // ...
})

Event Callbacks

There is only one Event Callback method, which is called after an event fails to transition the state.

After Failure

After Failure callback is called when there's an error with event firing.

Valid TransitionCallbackFunc signatures:

func()
func(err error)
func(m statemachine.Machine, err error)
func(t statemachine.Event, err error)
func(m statemachine.Machine, t statemachine.Event, err error)
process.Machine.Build(func(m statemachine.MachineBuilder) {
    // ...

    m.AfterFailure().OnAnyEvent().
        Do(func(e statemachine.Event, err error) {
            log.Printf(
                "could not transition with event='%s' err=%+v\n",
                e.Event(),
                err
            )
        })

    // ...
})

Matchers

Event Transition Matchers

These may map from one or more from states to exactly one to state.

type TransitionBuilder interface {
    From(states ...string) TransitionFromBuilder
    FromAny() TransitionFromBuilder
    FromAnyExcept(states ...string) TransitionFromBuilder
}

type TransitionFromBuilder interface {
    ExceptFrom(states ...string) TransitionExceptFromBuilder
    To(state string) TransitionToBuilder
}

type TransitionExceptFromBuilder interface {
    To(state string) TransitionToBuilder
}

Examples:

e.Transition().From("first_gear").To("second_gear")

e.Transition().From("first_gear", "second_gear", "third_gear").To("stalled")

allGears := vehicle.GetAllGearStates()
e.Transition().From(allGears...).ExceptFrom("neutral_gear").To("stalled")

e.Transition().FromAny().To("stalled")

e.Transition().FromAnyExcept("neutral_gear").To("stalled")
Transition Callback Matchers

These may map from one or more from states to one or more to states.

type TransitionCallbackBuilder interface {
    From(states ...string) TransitionCallbackFromBuilder
    FromAny() TransitionCallbackFromBuilder
    FromAnyExcept(states ...string) TransitionCallbackFromBuilder
}

type TransitionCallbackFromBuilder interface {
    ExceptFrom(states ...string) TransitionCallbackExceptFromBuilder
    To(states ...string) TransitionCallbackToBuilder
    ToSame() TransitionCallbackToBuilder
    ToAny() TransitionCallbackToBuilder
    ToAnyExcept(states ...string) TransitionCallbackToBuilder
}

type TransitionCallbackExceptFromBuilder interface {
    To(states ...string) TransitionCallbackToBuilder
    ToSame() TransitionCallbackToBuilder
    ToAny() TransitionCallbackToBuilder
    ToAnyExcept(states ...string) TransitionCallbackToBuilder
}

Examples:

m.BeforeTransition().From("idle").ToAny().Do(someFunc)

m.AroundTransition().From("state_x").ToAnyExcept("state_y").Do(someFunc)

m.AfterTransition().Any().Do(someFunc)
// ...is same as:
m.AfterTransition().FromAny().ToAny().Do(someFunc)
Event Callback Matchers

These may match on one or more events.

type EventCallbackBuilder interface {
	On(events ...string) EventCallbackOnBuilder
	OnAnyEvent() EventCallbackOnBuilder
	OnAnyEventExcept(events ...string) EventCallbackOnBuilder
}

type EventCallbackOnBuilder interface {
	Do(callbackFunc EventCallbackFunc) EventCallbackOnBuilder
}

Examples:

m.AfterFailure().OnAnyEventExcept("event_z").Do(someFunc)
Callback Functions

Any callback function's arguments (and return types) are dynamically set based on what types are defined (dependency injection). Setting any unavailable arg or return type will cause a panic during initialization.

For example, if your BeforeTransition() callback does not need access to the statemachine.Transition variable, you may just define the callback with a blank function signature: func(), instead of func(t statemachine.Transition). Similarly, for an AfterFailure() callback you can use func(err error), or func(e statemachine.Event, err error), or even just func() .

About

Copyright 2017 Gurpartap Singh

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.

Documentation

Overview

Package statemachine provides a convenient implementation of a Finite-state Machine (FSM) in Go. Learn more about FSM on Wikipedia:

https://en.wikipedia.org/wiki/Finite-state_machine

Let's begin with an object that can suitably adopt state machine behaviour:

type Turnstile struct {
    // Embed the state machine into our turnstile.
    statemachine.Machine

    // DisplayMsg stores the text that is displayed to the user.
    // Contains either "Pay" or "Go".
    DisplayMsg string
}

t := &Turnstile{}
t.DisplayMsg = "Pay"

Here we're embedding `statemachine.Machine` into our Turnstile struct. Embedding, although not necessary, is a suitable way to apply the state machine behaviour to the struct itself. Turnstile's DisplayMsg variable stores the text that is displayed to the user, and may store either "Pay" or "Go", depending on the state.

Now let's set our state machine's definitions utilizing `MachineBuilder`:

t.BuildNewMachine(func(m statemachine.MachineBuilder) {
    // States may be pre-defined here.
    m.States("locked", "unlocked")

    // Initial State is required to start the state machine.
    // Setting initial state does not invoke any callbacks.
    m.InitialState("locked")

    // Events and their transition(s).
    m.Event("insertCoin", func(e statemachine.EventBuilder) {
        e.Transition().From("locked").To("unlocked")
    })
    m.Event("turn", func(e statemachine.EventBuilder) {
        e.Transition().From("unlocked").To("locked")
    })

    // Transition callbacks.
    m.AfterTransition().From("locked").To("unlocked").Do(func() {
        t.DisplayMsg = "Go"
    })
    m.AfterTransition().From("unlocked").To("locked").Do(func() {
        t.DisplayMsg = "Pay"
    })
})

Now that our turnstile is ready, let's take it for a spin:

t.StartMachine()
fmt.Println(t.State(), t.DisplayMsg) // => locked, Pay

err := t.Fire("turn")
fmt.Println(err)                     // => no matching transition for the event
fmt.Println(t.State(), t.DisplayMsg) // => locked, Pay

t.Fire("insertCoin")
fmt.Println(t.State(), t.DisplayMsg) // => unlocked, Go

t.Fire("turn")
fmt.Println(t.State(), t.DisplayMsg) // => locked, Pay

t.StopMachine()
Example (SystemProcess)
package main

import (
	"log"

	"github.com/Gurpartap/statemachine-go"
)

var processStates = []string{
	"unmonitored", "running", "stopped",
	"starting", "stopping", "restarting",
}

type ExampleProcess struct {
	statemachine.Machine

	IsAutoStartOn    bool
	IsProcessRunning bool
}

func (p *ExampleProcess) GetIsAutoStartOn() bool {
	return p.IsAutoStartOn
}

func (p *ExampleProcess) GetIsProcessRunning() bool {
	return p.IsProcessRunning
}

func (ExampleProcess) Start()   {}
func (ExampleProcess) Stop()    {}
func (ExampleProcess) Restart() {}

func (ExampleProcess) NotifyTriggers(transition statemachine.Transition)   {}
func (ExampleProcess) RecordTransition(transition statemachine.Transition) {}

func (ExampleProcess) LogFailure(err error) {
	log.Println(err)
}

func main() {
	process := &ExampleProcess{}

	process.Machine = statemachine.BuildNewMachine(func(machineBuilder statemachine.MachineBuilder) {
		machineBuilder.States(processStates...)
		machineBuilder.InitialState("unmonitored")

		machineBuilder.Event("tick", func(eventBuilder statemachine.EventBuilder) {
			eventBuilder.Transition().From("starting").To("running").If(process.GetIsProcessRunning)
			eventBuilder.Transition().From("starting").To("stopped").Unless(process.GetIsProcessRunning)

			// The process failed to die after entering the stopping state.
			// Change the state to reflect reality.
			eventBuilder.Transition().From("running").To("stopped").Unless(process.GetIsProcessRunning)

			eventBuilder.Transition().From("stopping").To("running").If(process.GetIsProcessRunning)
			eventBuilder.Transition().From("stopping").To("stopped").Unless(process.GetIsProcessRunning)

			eventBuilder.Transition().From("stopped").To("running").If(process.GetIsProcessRunning)
			eventBuilder.Transition().From("stopped").To("starting").If(func(transition statemachine.Transition) bool {
				return process.GetIsAutoStartOn() && !process.GetIsProcessRunning()
			})

			eventBuilder.Transition().From("restarting").To("running").If(process.GetIsProcessRunning)
			eventBuilder.Transition().From("restarting").To("stopped").Unless(process.GetIsProcessRunning)
		})

		machineBuilder.Event("monitor", func(eventBuilder statemachine.EventBuilder) {
			eventBuilder.Transition().From("unmonitored").To("stopped")
		})

		machineBuilder.Event("start", func(eventBuilder statemachine.EventBuilder) {
			eventBuilder.Transition().From("unmonitored", "stopped").To("starting")
		})

		machineBuilder.Event("stop", func(eventBuilder statemachine.EventBuilder) {
			eventBuilder.Transition().From("running").To("stopping")
		})

		machineBuilder.Event("restart", func(eventBuilder statemachine.EventBuilder) {
			eventBuilder.Transition().From("running", "stopped").To("restarting")
		})

		machineBuilder.Event("unmonitor", func(eventBuilder statemachine.EventBuilder) {
			eventBuilder.Transition().FromAny().To("unmonitored")
		})

		machineBuilder.BeforeTransition().To("starting").Do(func() { process.IsAutoStartOn = true })
		machineBuilder.AfterTransition().To("starting").Do(func() { process.Start() })

		machineBuilder.BeforeTransition().To("stopping").Do(func() { process.IsAutoStartOn = false })
		machineBuilder.AfterTransition().To("stopping").Do(func() { process.Stop() })

		machineBuilder.BeforeTransition().To("restarting").Do(func() { process.IsAutoStartOn = true })
		machineBuilder.AfterTransition().To("restarting").Do(func() { process.Restart() })

		machineBuilder.BeforeTransition().To("unmonitored").Do(func() { process.IsAutoStartOn = false })

		machineBuilder.BeforeTransition().Any().Do(process.NotifyTriggers)
		machineBuilder.AfterTransition().Any().Do(process.RecordTransition)

		machineBuilder.AfterFailure().OnAnyEvent().Do(process.LogFailure)
	})
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoMatchingTransition = errors.New("no matching transition")
View Source
var ErrStateTypeNotSupported = errors.New("state type not supported")
View Source
var ErrTransitionNotAllowed = errors.New("transition not allowed")

Functions

This section is empty.

Types

type ChoiceBuilder

type ChoiceBuilder interface {
	Label(label string) ChoiceBuilder
	Unless(guard TransitionGuard) ChoiceBuilder
	OnTrue(eventBuilderFn func(eventBuilder EventBuilder)) ChoiceTrueBuilder
	OnFalse(eventBuilderFn func(eventBuilder EventBuilder)) ChoiceFalseBuilder
}

ChoiceBuilder provides the ability to define the conditions and result handling of the choice definition.

type ChoiceCondition

type ChoiceCondition interface{}

ChoiceCondition may accept Transition object as input, and it must return a bool type.

Valid ChoiceCondition types:

 bool
	func() bool
	func(transition statemachine.Transition) bool

type ChoiceConditionDef

type ChoiceConditionDef struct {
	Label          string          `json:",omitempty" hcl:"label" hcle:"omitempty"`
	RegisteredFunc string          `json:",omitempty" hcl:"registered_func" hcle:"omitempty"`
	Condition      ChoiceCondition `json:"-" hcle:"omit"`
}

type ChoiceDef

type ChoiceDef struct {
	Condition   *ChoiceConditionDef `json:",omitempty" hcl:"condition" hcle:"omitempty"`
	UnlessGuard *TransitionGuardDef `json:",omitempty" hcl:"unless_condition" hcle:"omitempty"`
	OnTrue      *EventDef           `json:",omitempty" hcl:"on_true" hcle:"omitempty"`
	OnFalse     *EventDef           `json:",omitempty" hcl:"on_false" hcle:"omitempty"`
}

func (*ChoiceDef) SetCondition

func (def *ChoiceDef) SetCondition(condition ChoiceCondition)

func (*ChoiceDef) SetLabel

func (def *ChoiceDef) SetLabel(label string)

func (*ChoiceDef) SetOnFalse

func (def *ChoiceDef) SetOnFalse(eventBuilderFn func(eventBuilder EventBuilder))

func (*ChoiceDef) SetOnTrue

func (def *ChoiceDef) SetOnTrue(eventBuilderFn func(eventBuilder EventBuilder))

func (*ChoiceDef) SetUnlessGuard

func (def *ChoiceDef) SetUnlessGuard(guard TransitionGuard)

type ChoiceFalseBuilder

type ChoiceFalseBuilder interface {
	OnTrue(eventBuilderFn func(eventBuilder EventBuilder))
}

ChoiceFalseBuilder inherits from ChoiceBuilder

type ChoiceTrueBuilder

type ChoiceTrueBuilder interface {
	OnFalse(eventBuilderFn func(eventBuilder EventBuilder))
}

ChoiceTrueBuilder inherits from ChoiceBuilder

type Event

type Event interface {
	Event() string
}

Event provides methods for accessing useful information about the active event.

type EventBuildable

type EventBuildable interface {
	SetEventDef(event string, def *EventDef)
}

EventBuildable implementation is able to consume the result of building features from EventBuilder. EventBuildable is oblivious to Event or it's implementation.

type EventBuilder

type EventBuilder interface {
	TimedEvery(duration time.Duration) EventBuilder

	// TODO: Assert that Choice(...) and Transition() are not used together.
	Choice(condition ChoiceCondition) ChoiceBuilder

	// Transition begins the transition builder, accepting states and guards.
	Transition() TransitionBuilder

	// Build plugs the collected feature definitions into any object
	// that understands them (implements dsl.EventBuildable). Use this method
	// if you're not using MachineBuilder.Event() to define the event.
	Build(event EventBuildable)
}

EventBuilder provides the ability to define an event along with its transitions and their guards. EventBuilder is oblivious to Event or it's implementation.

func NewEventBuilder

func NewEventBuilder(name string) EventBuilder

NewEventBuilder returns a zero-valued instance of eventBuilder, which implements EventBuilder.

Example
p := &ExampleProcess{}
p.Machine = statemachine.NewMachine()

machineBuilder := statemachine.NewMachineBuilder()
machineBuilder.States(processStates...)
machineBuilder.InitialState("unmonitored")

eventBuilder := statemachine.NewEventBuilder("monitor")
eventBuilder.Transition().From("unmonitored").To("stopped")
eventBuilder.Build(machineBuilder)

machineBuilder.Build(p.Machine)

fmt.Println(p.Machine.GetState())

if err := p.Machine.Fire("monitor"); err != nil {
	fmt.Println(err)
}

fmt.Println(p.Machine.GetState())

if err := p.Machine.Fire("monitor"); err != nil {
	fmt.Println(err)
}
Output:

unmonitored
stopped
no matching transition

type EventCallbackBuilder

type EventCallbackBuilder interface {
	On(events ...string) EventCallbackOnBuilder
	OnAnyEvent() EventCallbackOnBuilder
	OnAnyEventExcept(events ...string) EventCallbackOnBuilder
}

EventCallbackBuilder provides the ability to define the `on` event(s) of the event callback matcher.

type EventCallbackDef

type EventCallbackDef struct {
	On       []string                `json:",omitempty" hcl:"on" hcle:"omitempty"`
	ExceptOn []string                `json:",omitempty" hcl:"except_on" hcle:"omitempty"`
	Do       []*EventCallbackFuncDef `json:",omitempty" hcl:"do" hcle:"omitempty"`
	// contains filtered or unexported fields
}

func (*EventCallbackDef) AddCallbackFunc

func (s *EventCallbackDef) AddCallbackFunc(callbackFunc EventCallbackFunc)

func (*EventCallbackDef) MatchesEvent

func (s *EventCallbackDef) MatchesEvent(event string) bool

func (*EventCallbackDef) SetOn

func (s *EventCallbackDef) SetOn(events ...string)

func (*EventCallbackDef) SetOnAnyEventExcept

func (s *EventCallbackDef) SetOnAnyEventExcept(exceptEvents ...string)

type EventCallbackFunc

type EventCallbackFunc interface{}

EventCallbackFunc is a func with dynamic args. Any callback func of this type may accept a Transition object as input. Return values will be ignored.

For AfterFailure callback, it must accept an `error` type arg:

func(err error)
func(transition eventmachine.Transition, err error)

type EventCallbackFuncDef

type EventCallbackFuncDef struct {
	RegisteredFunc string            `json:",omitempty" hcl:"registered_func" hcle:"omitempty"`
	Func           EventCallbackFunc `json:"-" hcle:"omit"`
}

type EventCallbackOnBuilder

type EventCallbackOnBuilder interface {
	Do(callbackFunc EventCallbackFunc) EventCallbackOnBuilder
}

EventCallbackOnBuilder inherits from EventCallbackBuilder (or EventCallbackExceptFromBuilder) and provides the ability to define the transition callback func.

type EventDef

type EventDef struct {
	// Name        string
	TimedEvery  time.Duration    `json:",omitempty" hcl:"timed_every" hcle:"omitempty"`
	Choice      *ChoiceDef       `json:",omitempty" hcl:"choice" hcle:"omitempty"`
	Transitions []*TransitionDef `json:",omitempty" hcl:"transitions" hcle:"omitempty"`
}

func (*EventDef) AddTransition

func (def *EventDef) AddTransition(transitionDef *TransitionDef)

func (*EventDef) SetChoice

func (def *EventDef) SetChoice(choiceDef *ChoiceDef)

func (*EventDef) SetEvery

func (def *EventDef) SetEvery(duration time.Duration)

type Machine

type Machine interface {
	Build(machineBuilderFn func(machineBuilder MachineBuilder))
	SetMachineDef(def *MachineDef)

	GetStateMap() StateMap

	GetState() string
	SetCurrentState(state interface{}) error
	IsState(state string) bool

	Submachine(idPath ...string) (Machine, error)

	Fire(event string) error

	Send(signal Message) error
}

Machine provides a public interface to the state machine implementation. It provides methods to build and access features of the state machine.

func BuildNewMachine

func BuildNewMachine(machineBuilderFn func(machineBuilder MachineBuilder)) Machine

BuildNewMachine creates a zero-valued instance of machine, and builds it using the passed machineBuilderFn arg.

Example
p := &ExampleProcess{}

p.Machine = statemachine.BuildNewMachine(func(m statemachine.MachineBuilder) {
	m.States(processStates...)
	m.InitialState("unmonitored")

	// ...
})

fmt.Println(p.Machine.GetState())
Output:

unmonitored

func NewMachine

func NewMachine() Machine

NewMachine returns a zero-valued instance of machine, which implements Machine.

Example
p := &ExampleProcess{}

p.Machine = statemachine.NewMachine()
p.Machine.Build(func(m statemachine.MachineBuilder) {
	m.States(processStates...)
	m.InitialState("unmonitored")

	// ...
})

fmt.Println(p.Machine.GetState())
Output:

unmonitored

type MachineBuildable

type MachineBuildable interface {
	SetMachineDef(def *MachineDef)
}

MachineBuildable implementation is able to consume the result of building features from dsl.MachineBuilder. MachineBuildable is oblivious to Machine or it's implementation.

type MachineBuilder

type MachineBuilder interface {
	// Build plugs the collected feature definitions into any object
	// that understands them (implements MachineBuildable). Use this method
	// if you're not using Machine.Build() to define the state machine.
	Build(machine MachineBuildable)

	SetEventDef(event string, def *EventDef)

	ID(id string)

	// States pre-defines the set of known states. This is optional because
	// all known states will be identified from other definitions.
	States(states ...string)

	// InitialState defines the state that the machine initializes with.
	// Initial state must be defined for every state machine.
	InitialState(state string)

	Submachine(state string, submachineBuilderFn func(submachineBuilder MachineBuilder))

	// Event provides the ability to define possible transitions for an event.
	Event(name string, eventBuilderFn ...func(eventBuilder EventBuilder)) EventBuilder

	BeforeTransition() TransitionCallbackBuilder
	AroundTransition() TransitionCallbackBuilder
	AfterTransition() TransitionCallbackBuilder
	AfterFailure() EventCallbackBuilder
}

MachineBuilder provides the ability to define all features of the state machine, including states, events, event transitions, and transition callbacks. MachineBuilder is oblivious to Machine or it's implementation.

func NewMachineBuilder

func NewMachineBuilder() MachineBuilder

NewMachineBuilder returns a zero-valued instance of machineBuilder, which implements MachineBuilder.

Example
machineBuilder := statemachine.NewMachineBuilder()
machineBuilder.States(processStates...)
machineBuilder.InitialState("unmonitored")

machineBuilder.Event("monitor", func(e statemachine.EventBuilder) {
	e.Transition().From("unmonitored").To("stopped")
})

machineBuilder.Event("unmonitor", func(e statemachine.EventBuilder) {
	e.Transition().FromAny().To("unmonitored")
})

p := &ExampleProcess{}
p.Machine = statemachine.NewMachine()

machineBuilder.Build(p.Machine)

fmt.Println(p.Machine.GetState())

if err := p.Machine.Fire("monitor"); err != nil {
	fmt.Println(err)
}

fmt.Println(p.Machine.GetState())

if err := p.Machine.Fire("unmonitor"); err != nil {
	fmt.Println(err)
}

fmt.Println(p.Machine.GetState())
Output:

unmonitored
stopped
unmonitored

type MachineDef

type MachineDef struct {
	ID           string                   `json:"id,omitempty" hcl:"id" hcle:"omitempty"`
	States       []string                 `hcl:"states"`
	InitialState string                   `hcl:"initial_state"`
	Events       map[string]*EventDef     `json:",omitempty" hcl:"event" hcle:"omitempty"`
	Submachines  map[string][]*MachineDef `json:",omitempty" hcl:"submachine" hcle:"omitempty"`

	BeforeCallbacks []*TransitionCallbackDef `json:",omitempty" hcl:"before_callbacks" hcle:"omitempty"`
	AroundCallbacks []*TransitionCallbackDef `json:",omitempty" hcl:"around_callbacks" hcle:"omitempty"`
	AfterCallbacks  []*TransitionCallbackDef `json:",omitempty" hcl:"after_callbacks" hcle:"omitempty"`

	FailureCallbacks []*EventCallbackDef `json:",omitempty" hcl:"failure_callbacks" hcle:"omitempty"`
}
Example
// statemachine.RegisterFunc("after-callback-1", func() {
// 	fmt.Printf("after callback\n")
// })

machineDef := &statemachine.MachineDef{
	States:       processStates,
	InitialState: "unmonitored",
	Submachines: map[string][]*statemachine.MachineDef{
		"running": {
			{
				InitialState: "pending",
				AfterCallbacks: []*statemachine.TransitionCallbackDef{
					{To: []string{"success"}, ExitToState: "stopped"},
					{To: []string{"failure"}, ExitToState: "restarting"},
				},
			},
		},
	},
	Events: map[string]*statemachine.EventDef{
		"monitor": {
			Transitions: []*statemachine.TransitionDef{{From: []string{"unmonitored"}, To: "stopped"}},
		},
		"unmonitor": {
			Transitions: []*statemachine.TransitionDef{{To: "unmonitored"}},
		},
	},
	AfterCallbacks: []*statemachine.TransitionCallbackDef{
		{
			Do: []*statemachine.TransitionCallbackFuncDef{
				// {
				// 	RegisteredFunc: "after-callback-1",
				// },
				{
					Func: func() {
						fmt.Printf("after callback\n")
					},
				},
			},
		},
	},
}

p := &ExampleProcess{}
p.Machine = statemachine.NewMachine()
p.Machine.SetMachineDef(machineDef)

fmt.Println(p.Machine.GetState())

if err := p.Machine.Fire("monitor"); err != nil {
	fmt.Println(err)
}

fmt.Println(p.Machine.GetState())

if err := p.Machine.Fire("unmonitor"); err != nil {
	fmt.Println(err)
}

fmt.Println(p.Machine.GetState())
Output:

unmonitored
after callback
stopped
after callback
unmonitored

func NewMachineDef

func NewMachineDef() *MachineDef

func (*MachineDef) AddAfterCallback

func (def *MachineDef) AddAfterCallback(CallbackDef *TransitionCallbackDef)

func (*MachineDef) AddAroundCallback

func (def *MachineDef) AddAroundCallback(CallbackDef *TransitionCallbackDef)

func (*MachineDef) AddBeforeCallback

func (def *MachineDef) AddBeforeCallback(CallbackDef *TransitionCallbackDef)

func (*MachineDef) AddEvent

func (def *MachineDef) AddEvent(event string, eventDef *EventDef)

func (*MachineDef) AddFailureCallback

func (def *MachineDef) AddFailureCallback(CallbackDef *EventCallbackDef)

func (*MachineDef) SetID

func (def *MachineDef) SetID(id string)

func (*MachineDef) SetInitialState

func (def *MachineDef) SetInitialState(state string)

func (*MachineDef) SetStates

func (def *MachineDef) SetStates(states ...string)

func (*MachineDef) SetSubmachine

func (def *MachineDef) SetSubmachine(state string, submachine *MachineDef)

type Message

type Message interface {
	Value() interface{}
}

type OverrideState

type OverrideState struct {
	State interface{}
}

func (OverrideState) Value

func (e OverrideState) Value() interface{}

type StateMap

type StateMap map[string]interface{}

type Transition

type Transition interface {
	From() string
	To() string
}

Transition provides methods for accessing useful information about the active transition.

type TransitionAndGuardBuilder

type TransitionAndGuardBuilder interface {
	Label(label string) TransitionAndGuardBuilder
	AndIf(guards ...TransitionGuard) TransitionAndGuardBuilder
	AndUnless(guards ...TransitionGuard) TransitionAndGuardBuilder
}

TransitionAndGuardBuilder inherits from TransitionToBuilder and provides the ability to define additional guard condition funcs for the transition.

type TransitionBuilder

type TransitionBuilder interface {
	From(states ...string) TransitionFromBuilder
	FromAny() TransitionFromBuilder
	FromAnyExcept(states ...string) TransitionFromBuilder
}

TransitionBuilder provides the ability to define the `from` state(s) of the transition matcher.

type TransitionCallbackBuilder

type TransitionCallbackBuilder interface {
	From(states ...string) TransitionCallbackFromBuilder
	FromAny() TransitionCallbackFromBuilder
	FromAnyExcept(states ...string) TransitionCallbackFromBuilder
	To(states ...string) TransitionCallbackToBuilder
	ToAnyExcept(states ...string) TransitionCallbackToBuilder
	Any() TransitionCallbackToBuilder
}

TransitionCallbackBuilder provides the ability to define the `from` state(s) of the transition callback matcher.

type TransitionCallbackDef

type TransitionCallbackDef struct {
	From        []string                     `json:",omitempty" hcl:"from" hcle:"omitempty"`
	ExceptFrom  []string                     `json:",omitempty" hcl:"except_from" hcle:"omitempty"`
	To          []string                     `json:",omitempty" hcl:"to" hcle:"omitempty"`
	ExceptTo    []string                     `json:",omitempty" hcl:"except_to" hcle:"omitempty"`
	Do          []*TransitionCallbackFuncDef `json:",omitempty" hcl:"do" hcle:"omitempty"`
	ExitToState string                       `json:",omitempty" hcl:"exit_to_state" hcle:"omitempty"`
	// contains filtered or unexported fields
}

func (*TransitionCallbackDef) AddCallbackFunc

func (s *TransitionCallbackDef) AddCallbackFunc(callbackFuncs ...TransitionCallbackFunc)

func (*TransitionCallbackDef) Matches

func (s *TransitionCallbackDef) Matches(from, to string) bool

func (*TransitionCallbackDef) SetExitToState

func (s *TransitionCallbackDef) SetExitToState(supermachineState string)

func (*TransitionCallbackDef) SetFrom

func (s *TransitionCallbackDef) SetFrom(states ...string)

func (*TransitionCallbackDef) SetFromAnyExcept

func (s *TransitionCallbackDef) SetFromAnyExcept(exceptStates ...string)

func (*TransitionCallbackDef) SetSame

func (s *TransitionCallbackDef) SetSame()

func (*TransitionCallbackDef) SetTo

func (s *TransitionCallbackDef) SetTo(states ...string)

func (*TransitionCallbackDef) SetToAnyExcept

func (s *TransitionCallbackDef) SetToAnyExcept(exceptStates ...string)

type TransitionCallbackDoBuilder

type TransitionCallbackDoBuilder interface {
	Label(label string) TransitionCallbackToBuilder
}

type TransitionCallbackExceptFromBuilder

type TransitionCallbackExceptFromBuilder interface {
	To(states ...string) TransitionCallbackToBuilder
	ToSame() TransitionCallbackToBuilder
	ToAny() TransitionCallbackToBuilder
	ToAnyExcept(states ...string) TransitionCallbackToBuilder
	ToAnyExceptSame() TransitionCallbackToBuilder
}

TransitionCallbackExceptFromBuilder inherits `from` states from TransitionCallbackBuilder and provides the ability to define the `to` states of the transition callback matcher.

type TransitionCallbackFromBuilder

type TransitionCallbackFromBuilder interface {
	ExceptFrom(states ...string) TransitionCallbackExceptFromBuilder
	To(states ...string) TransitionCallbackToBuilder
	ToSame() TransitionCallbackToBuilder
	ToAny() TransitionCallbackToBuilder
	ToAnyExcept(states ...string) TransitionCallbackToBuilder
	ToAnyExceptSame() TransitionCallbackToBuilder
}

TransitionCallbackFromBuilder inherits `from` states from TransitionCallbackBuilder and provides the ability to define the `except from` and `to` states of the transition callback matcher.

type TransitionCallbackFunc

type TransitionCallbackFunc interface{}

TransitionCallbackFunc is a func with dynamic args. Any callback func of this type may accept a Machine and/or Transition object as inputs. Return values will be ignored.

For BeforeTransition and AfterTransition:

func()
func(machine statemachine.Machine)
func(transition statemachine.Transition)
func(machine statemachine.Machine, transition statemachine.Transition)

For AroundTransition callback, it must accept a `func()` type arg. The callback must call `next()` to continue the transition.

func(next func())
func(machine statemachine.Machine, next func())
func(transition statemachine.Transition, next func())
func(machine statemachine.Machine, transition statemachine.Transition, next func())

For AfterFailure callback, it must accept an `error` type arg:

func(err error)
func(machine statemachine.Machine, err error)
func(transition statemachine.Transition, err error)
func(machine statemachine.Machine, transition statemachine.Transition, err error)

TODO: perhaps support a Service interface struct, with methods to listen

for state changes. a service may be useful to implement [interruptible]
long-running callbacks. example: a download in a download manager.

type TransitionCallbackFuncDef

type TransitionCallbackFuncDef struct {
	Label          string                 `json:",omitempty" hcl:"label" hcle:"omitempty"`
	RegisteredFunc string                 `json:",omitempty" hcl:"registered_func" hcle:"omitempty"`
	Func           TransitionCallbackFunc `json:"-" hcle:"omit"`
}

type TransitionCallbackToBuilder

type TransitionCallbackToBuilder interface {
	ExitToState(supermachineState string)
	Do(callbackFuncs ...TransitionCallbackFunc) TransitionCallbackDoBuilder
}

TransitionCallbackToBuilder inherits from TransitionCallbackBuilder (or TransitionCallbackExceptFromBuilder) and provides the ability to define the transition callback func.

type TransitionDef

type TransitionDef struct {
	From         []string              `json:",omitempty" hcl:"from" hcle:"omitempty"`
	ExceptFrom   []string              `json:",omitempty" hcl:"except_from" hcle:"omitempty"`
	To           string                `hcl:"to"`
	IfGuards     []*TransitionGuardDef `json:",omitempty" hcl:"if_guard" hcle:"omitempty"`
	UnlessGuards []*TransitionGuardDef `json:",omitempty" hcl:"unless_guard" hcle:"omitempty"`
}

func (*TransitionDef) AddIfGuard

func (def *TransitionDef) AddIfGuard(guards ...TransitionGuard)

func (*TransitionDef) AddUnlessGuard

func (def *TransitionDef) AddUnlessGuard(guards ...TransitionGuard)

func (*TransitionDef) IsAllowed

func (def *TransitionDef) IsAllowed(fromState string, machine Machine) bool

func (*TransitionDef) Matches

func (def *TransitionDef) Matches(matchFrom string) bool

func (*TransitionDef) SetFrom

func (def *TransitionDef) SetFrom(states ...string)

func (*TransitionDef) SetFromAnyExcept

func (def *TransitionDef) SetFromAnyExcept(exceptStates ...string)

func (*TransitionDef) SetTo

func (def *TransitionDef) SetTo(state string)

type TransitionExceptFromBuilder

type TransitionExceptFromBuilder interface {
	To(state string) TransitionToBuilder
}

TransitionExceptFromBuilder inherits from TransitionFromBuilder and provides the ability to define the `to` state of the transition matcher.

type TransitionFromBuilder

type TransitionFromBuilder interface {
	ExceptFrom(states ...string) TransitionExceptFromBuilder
	To(state string) TransitionToBuilder
}

TransitionFromBuilder inherits `from` states from TransitionBuilder and provides the ability to define the `to` state as well as the `except from` states of the transition matcher.

type TransitionGuard

type TransitionGuard interface{}

TransitionGuard may accept Transition object as input, and it must return a bool type.

Valid TransitionGuard types:

 bool
	func() bool
	func(transition statemachine.Transition) bool

type TransitionGuardDef

type TransitionGuardDef struct {
	Label          string          `json:",omitempty" hcl:"label" hcle:"omitempty"`
	RegisteredFunc string          `json:",omitempty" hcl:"registered_func" hcle:"omitempty"`
	Guard          TransitionGuard `json:"-" hcle:"omit"`
}

type TransitionToBuilder

type TransitionToBuilder interface {
	If(guards ...TransitionGuard) TransitionAndGuardBuilder
	Unless(guards ...TransitionGuard) TransitionAndGuardBuilder
}

TransitionToBuilder inherits from TransitionFromBuilder (or TransitionExceptFromBuilder) and provides the ability to define the guard condition funcs for the transition.

type TriggerEvent

type TriggerEvent struct {
	Event string
}

func (TriggerEvent) Value

func (e TriggerEvent) Value() interface{}

Directories

Path Synopsis
examples
hcl
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL