// vim: shiftwidth=4 tabstop=4 noexpandtab package main import ( "log" ) // This struct represents the statemachine. type Machine struct { name string initial string current string states StateMap api chan string state_listeners []*(chan string) hook HookFunction } type StateMap map[string]MachineState type MachineState struct { on TransitionMap } type TransitionMap map[string]MachineTransition type MachineTransition struct { to string } type HookFunction func(string, string, *Machine) // This will run the statemachine (blocking). func (m *Machine) Run() { var event string for { event = <-m.api // read api (blocking) m.processEvent(event) } } // A simple Getter to retrieve the current state. func (m Machine) GetState() string { if m.current == "" { return m.initial } return m.current } // This function blocks until the current state differs // from the 'known' state. It then returns the current // state. func (m *Machine) GetStateOnChange(known string) string { listener := make(chan string) var current string m.registerListener(&listener) defer m.deregisterListener(&listener) for { if m.GetState() != known { current = m.GetState() break } current = <-listener if current != known { break } } return current } // Use this function to send an event to the statemachine. func (m *Machine) SendEvent(event string) { m.api <- event // blocks until m.run() goroutine handles this event } func (m *Machine) registerListener(listener *(chan string)) { m.state_listeners = append(m.state_listeners, listener) } func (m *Machine) deregisterListener(listener *(chan string)) { wasRemoved := false if len(m.state_listeners) == 0 { log.Println("Not able to unregister listener from empty state_listener slice") } for index,item := range(m.state_listeners) { if item == listener { if index == len(m.state_listeners) - 1 { m.state_listeners = m.state_listeners[: len(m.state_listeners) - 1] } else { m.state_listeners[index] = m.state_listeners[len(m.state_listeners) - 1] m.state_listeners = m.state_listeners[: len(m.state_listeners) - 1] } wasRemoved = true } } if !wasRemoved { log.Println("Could not find listener in slice state_listeners") } } func (m *Machine) processEvent(event string) string { current := m.GetState() next := m.states[current].on[event].to if next != "" { // check if transition is possible log.Printf("State machine '%s' changes from '%s' to '%s'\n", m.name, current, next) // inform all state listeners for _,listener := range(m.state_listeners) { *listener <- next } m.hook(current, next, m) // execute registered callback function m.current = next // set new state return next } return current }