fsm

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

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

Go to latest
Published: Oct 14, 2022 License: Apache-2.0 Imports: 6 Imported by: 0

README

README

Very simple finite state machine in golang.

Based, loosely on FSM from Rational Rhapsody.

Goals and Non Goals

Goals:

  • To provide useful, simple to use FSM for golang
  • To follow UML state diagram semantics as far as reasonable for the features implemented.
  • Provide concurrent and non-concurrent implementations.

Non Goals:

  • Full implementation of UML state machine specification

Usage

For detailed usage, look at the test cases and examples folder.

Self Documenting

gofsm can automatically produce PlantUML state machine diagrams. The example below will create the diagram below:

uml diagram

Examples

An example of a car park payment meter is shown below (from examples/paymentmeter):

package main

import (
	"bytes"
	"fmt"
	"os"

	fsm "github.com/johngrange/gofsm"
)

func main() {
	// Set up state machine
	type currentCoinPayment struct {
		numCoins  uint
		coinValue uint
	}
	type paymentMeter struct {
		paymentsCollected uint
		ticketsIssued     uint
		ticketCost        uint
		currentPayment    currentCoinPayment
	}

	var (
		paymentMeterSM                                                             fsm.ImmediateFSM
		idleStateBuilder, acceptingPaymentStateBuilder, printingTicketStateBuilder fsm.StateBuilder
	)

	// car park payment meter model

	paymentMeterData := &paymentMeter{
		ticketCost: 300,
	}
	stateMachineBuilder := fsm.NewFSMBuilder()
	stateMachineBuilder.SetData(paymentMeterData)

	idleStateBuilder = fsm.NewStateBuilder("idle")
	stateMachineBuilder.GetInitialState().AddTransition(idleStateBuilder)

	acceptingPaymentStateBuilder = fsm.NewStateBuilder("acceptingPayment")

	printingTicketStateBuilder = fsm.NewStateBuilder("printingTicket")

	idleStateBuilder.AddTransition(acceptingPaymentStateBuilder).SetTrigger("evInsertCoin").SetEffect(
		func(ev fsm.Event, fsmData interface{}, dispatcher fsm.Dispatcher) {
			stateData := &(fsmData).(*paymentMeter).currentPayment
			fmt.Printf("stateData: %+v\n", stateData)
			coinAmount := ev.Data().(uint)
			stateData.coinValue += coinAmount
			stateData.numCoins++
			fmt.Printf("stateData: %+v\n", stateData)
		}, "coinValue += ev.coinAmount", "numCoins++", // add labels to effect in plant uml output
	) // parameter is coin value: uint

	acceptingPaymentStateBuilder.AddTransition(acceptingPaymentStateBuilder).SetTrigger("evInsertCoin").SetEffect(
		func(ev fsm.Event, fsmData interface{}, dispatcher fsm.Dispatcher) {
			stateData := &(fsmData).(*paymentMeter).currentPayment
			fmt.Printf("stateData: %+v\n", stateData)

			coinAmount := ev.Data().(uint)
			stateData.coinValue += coinAmount
			stateData.numCoins++

			fmt.Printf("stateData: %+v\n", stateData)
		}, "coinValue += ev.coinAmount", "numCoins++", // add labels to effect in plant uml output
	) // parameter is coin value: uint

	acceptingPaymentStateBuilder.AddTransition(printingTicketStateBuilder).SetTrigger("evPrintTicket").
		SetGuard(func(fsmData, eventData interface{}) bool {
			meterData := (fsmData).(*paymentMeter)

			return meterData.currentPayment.coinValue >= meterData.ticketCost
		}, "currentPayment.coinValue >= ticketCost").
		SetEffect(func(ev fsm.Event, fsmData interface{}, dispatcher fsm.Dispatcher) {
			meter := (fsmData).(*paymentMeter)
			fmt.Printf("Printing ticket for %dp\n", meter.currentPayment.coinValue)
			meter.paymentsCollected += meter.currentPayment.coinValue
			meter.ticketsIssued++
		}, "paymentsCollected += currentPayment.coinValue", "ticketsIssued++")

	acceptingPaymentStateBuilder.OnExit(func(state fsm.State, fsmData interface{}, dispatcher fsm.Dispatcher) {
		fmt.Printf("onExit\n")
		meterData := (fsmData).(*paymentMeter)
		meterData.currentPayment.coinValue = 0
		meterData.currentPayment.numCoins = 0
	})

	printingTicketStateBuilder.AddTransition(idleStateBuilder)

	stateMachineBuilder.
		AddState(idleStateBuilder).
		AddState(acceptingPaymentStateBuilder).
		AddState(printingTicketStateBuilder)

	// replace with stateMachineBuilder.BuildThreadedFSM() for a state
	// machine that will run event management in separate go routine
	// (and therefore automatically progress through guarded transitions when data changes)

	paymentMeterSM, err := stateMachineBuilder.BuildImmediateFSM()
	if err != nil {
		fmt.Println(err)
		return
	}

	const coinAmount = 50
	const customers = 10
	const coinsPerCustomer = 6

	// Set up some tracing
	logger := fsm.NewFSMLogger()
	counter := fsm.NewStateCounter()
	paymentMeterSM.AddTracer(logger)
	paymentMeterSM.AddTracer(counter)

	// Output our state machine in plant uml format
	buf := bytes.Buffer{}

	err = fsm.RenderPlantUML(&buf, paymentMeterSM)
	if err != nil {
		fmt.Println(err)
		return
	}
	err = os.WriteFile("paymentmeter.uml", buf.Bytes(), 0600)
	if err != nil {
		fmt.Println(err)
		return
	}

	// Setup complete, run our state machine
	paymentMeterSM.Start()
	defer paymentMeterSM.Stop()

	for customer := 0; customer < customers; customer++ {
		fmt.Printf("serving customer %d\n", customer)
		fmt.Printf("state: %s\n", paymentMeterSM.CurrentState().Name()) //idle
		for coin := 0; coin < coinsPerCustomer; coin++ {
			fmt.Printf("inserting coin %d\n", coin)
			paymentMeterSM.Dispatch(fsm.NewEvent("evInsertCoin", uint(coinAmount)))
			fmt.Printf("state: %s\n", paymentMeterSM.CurrentState().Name()) //acceptingPayment
		}
		fmt.Printf("state: %s\n", paymentMeterSM.CurrentState().Name()) //acceptingPayment
		paymentMeterSM.Dispatch(fsm.NewEvent("evPrintTicket", nil))
		fmt.Printf("state: %s\n", paymentMeterSM.CurrentState().Name()) //idle
	}
	fmt.Printf("Issued %d tickets, took %d pence\n", paymentMeterData.ticketsIssued, paymentMeterData.paymentsCollected) // 10, 3000

	fmt.Println("State machine log trace")
	for _, l := range logger.Entries {
		fmt.Println(l)
	}

	fmt.Println("Counts of states visited during run")
	for state, count := range counter.StateCounts {
		fmt.Printf("%s: %d\n", state, count)
	}
	fmt.Println("Counts of events discarded during run (arrived when not in state to process them)")
	for event, count := range counter.RejectedEventCounts {
		fmt.Printf("%s: %d\n", event, count)
	}

}

Documentation

Index

Constants

View Source
const (
	InitialStateName = "initial"
	FinalStateName   = "FinalState"
)
View Source
const InitialFinalStateSymbol = "[*]"

Variables

This section is empty.

Functions

func RenderPlantUML

func RenderPlantUML(w io.Writer, stateMachine FSM) error

Types

type Action

type Action func(state State, fsmData interface{}, dispatcher Dispatcher)

type Dispatcher

type Dispatcher interface {
	Dispatch(Event)
}

type Event

type Event interface {
	Name() string
	Data() interface{}
	Labels() []string
}

func NewEvent

func NewEvent(name string, data interface{}, labels ...string) Event

type FSM

type FSM interface {
	Dispatcher
	Visitable
	Observable

	CurrentState() State
	Start()
	Stop()
	GetData() interface{}
	GetDispatcher() Dispatcher
}

type ImmediateFSM

type ImmediateFSM interface {
	FSM
	Tick() // Manually check for and progress state changes that are not event driven
}

type LogEntry

type LogEntry struct {
	When    time.Time
	Message string
}

type Logger

type Logger struct {
	Entries  []LogEntry
	Detailed bool
}

func NewFSMLogger

func NewFSMLogger() *Logger

func (*Logger) Fprint

func (l *Logger) Fprint(w io.Writer) error

func (*Logger) OnEntry

func (l *Logger) OnEntry(state State, fsmData interface{})

func (*Logger) OnExit

func (l *Logger) OnExit(state State, fsmData interface{})

func (*Logger) OnRejectedEvent

func (l *Logger) OnRejectedEvent(ev Event, state State, fsmData interface{})

func (*Logger) OnTransition

func (l *Logger) OnTransition(ev Event, sourceState, targetState State, fsmData interface{})

type Observable

type Observable interface {
	AddTracer(Tracer)
}

type State

type State interface {
	Name() string
	Transitions() []Transition
	StateLabels() []string
	EntryLabels() []string
	ExitLabels() []string
	// contains filtered or unexported methods
}

type StateBuilder

type StateBuilder interface {
	AddTransition(target StateBuilder, labels ...string) TransitionBuilder
	OnEntry(action Action, labels ...string) StateBuilder
	OnExit(action Action, labels ...string) StateBuilder
	// contains filtered or unexported methods
}

func NewStateBuilder

func NewStateBuilder(name string, labels ...string) StateBuilder

type StateCounter

type StateCounter struct {
	StateCounts         map[string]uint64
	RejectedEventCounts map[string]uint64
}

func NewStateCounter

func NewStateCounter() *StateCounter

func (*StateCounter) OnEntry

func (s *StateCounter) OnEntry(state State, fsmData interface{})

func (*StateCounter) OnExit

func (s *StateCounter) OnExit(state State, fsmData interface{})

func (*StateCounter) OnRejectedEvent

func (s *StateCounter) OnRejectedEvent(ev Event, state State, fsmData interface{})

func (*StateCounter) OnTransition

func (s *StateCounter) OnTransition(ev Event, sourceState, targetState State, fsmData interface{})

type StateMachineBuilder

type StateMachineBuilder interface {
	AddState(StateBuilder) StateMachineBuilder
	NewState(name string, labels ...string) StateBuilder
	AddTracer(Tracer) StateMachineBuilder
	AddFinalState() StateBuilder
	GetInitialState() StateBuilder
	GetFinalState() StateBuilder
	BuildImmediateFSM() (ImmediateFSM, error)
	BuildThreadedFSM() (FSM, error)
	SetData(data interface{}) StateMachineBuilder
}

func NewFSMBuilder

func NewFSMBuilder() StateMachineBuilder

type TraceEntry

type TraceEntry struct {
	TransitionTime           time.Time
	EventName                string
	SourceState, TargetState string
}

type Tracer

type Tracer interface {
	OnEntry(state State, fsmData interface{})
	OnExit(state State, fsmData interface{})
	OnTransition(ev Event, sourceState, targetState State, fsmData interface{})
	OnRejectedEvent(ev Event, state State, fmsData interface{})
}

type Transition

type Transition interface {
	Source() State
	Target() State
	IsLocal() bool
	TriggerLabels() []string
	GuardLabels() []string
	EffectLabels() []string
	EventName() string
	TriggerType() TriggerType
	TimerDuration() time.Duration
	// contains filtered or unexported methods
}

type TransitionBuilder

type TransitionBuilder interface {
	SetEventTrigger(eventName string, labels ...string) TransitionBuilder
	SetTimedTrigger(delay time.Duration, labels ...string) TransitionBuilder
	SetGuard(guard TransitionGuard, labels ...string) TransitionBuilder
	SetEffect(efffect TransitionEffect, labels ...string) TransitionBuilder
	Source() StateBuilder
	Target() StateBuilder
	TriggerType() TriggerType
	// contains filtered or unexported methods
}

type TransitionEffect

type TransitionEffect func(ev Event, fsmData interface{}, dispatcher Dispatcher)

type TransitionGuard

type TransitionGuard func(fsmData, eventData interface{}) bool

type TriggerType

type TriggerType uint8
const (
	NoTrigger TriggerType = iota
	EventTrigger
	TimerTrigger
)

type Visitable

type Visitable interface {
	Visit(Visitor)
}

type Visitor

type Visitor interface {
	VisitState(state State)
	VisitTransition(transition Transition)
}

Directories

Path Synopsis
examples
paymentmeter command

Jump to

Keyboard shortcuts

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