• Stars
    star
    341
  • Rank 123,998 (Top 3 %)
  • Language
    Lua
  • License
    MIT License
  • Created about 12 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A finite state machine lua micro framework

Lua Finite State Machine

This standalone lua module provides a finite state machine for your pleasure. Based heavily on Jake Gordon's javascript-state-machine.

Download

You can download statemachine.lua.

Alternatively:

git clone [email protected]:kyleconroy/lua-state-machine

Usage

In its simplest form, create a standalone state machine using:

local machine = require('statemachine')

local fsm = machine.create({
  initial = 'green',
  events = {
    { name = 'warn',  from = 'green',  to = 'yellow' },
    { name = 'panic', from = 'yellow', to = 'red'    },
    { name = 'calm',  from = 'red',    to = 'yellow' },
    { name = 'clear', from = 'yellow', to = 'green'  }
}})

... will create an object with a method for each event:

  • fsm:warn() - transition from 'green' to 'yellow'
  • fsm:panic() - transition from 'yellow' to 'red'
  • fsm:calm() - transition from 'red' to 'yellow'
  • fsm:clear() - transition from 'yellow' to 'green'

along with the following members:

  • fsm.current - contains the current state
  • fsm.currentTransitioningEvent - contains the current event that is in a transition.
  • fsm:is(s) - return true if state s is the current state
  • fsm:can(e) - return true if event e can be fired in the current state
  • fsm:cannot(e) - return true if event e cannot be fired in the current state

Multiple 'from' and 'to' states for a single event

If an event is allowed from multiple states, and always transitions to the same state, then simply provide an array of states in the from attribute of an event. However, if an event is allowed from multiple states, but should transition to a different state depending on the current state, then provide multiple event entries with the same name:

local machine = require('statemachine')

local fsm = machine.create({
  initial = 'hungry',
  events = {
    { name = 'eat',  from = 'hungry',                                to = 'satisfied' },
    { name = 'eat',  from = 'satisfied',                             to = 'full'      },
    { name = 'eat',  from = 'full',                                  to = 'sick'      },
    { name = 'rest', from = {'hungry', 'satisfied', 'full', 'sick'}, to = 'hungry'    },
}})

This example will create an object with 2 event methods:

  • fsm:eat()
  • fsm:rest()

The rest event will always transition to the hungry state, while the eat event will transition to a state that is dependent on the current state.

NOTE: The rest event could use a wildcard '*' for the 'from' state if it should be allowed from any current state.

NOTE: The rest event in the above example can also be specified as multiple events with the same name if you prefer the verbose approach.

Callbacks

4 callbacks are available if your state machine has methods using the following naming conventions:

  • onbeforeevent - fired before the event
  • onleavestate - fired when leaving the old state
  • onenterstate - fired when entering the new state
  • onafterevent - fired after the event

You can affect the event in 3 ways:

  • return false from an onbeforeevent handler to cancel the event.
  • return false from an onleavestate handler to cancel the event.
  • return ASYNC from an onleavestate or onenterstate handler to perform an asynchronous state transition (see next section)

For convenience, the 2 most useful callbacks can be shortened:

  • onevent - convenience shorthand for onafterevent
  • onstate - convenience shorthand for onenterstate

In addition, a generic onstatechange() callback can be used to call a single function for all state changes:

All callbacks will be passed the same arguments:

  • self
  • event name
  • from state
  • to state
  • (followed by any arguments you passed into the original event method)

Callbacks can be specified when the state machine is first created:

local machine = require('statemachine')

local fsm = machine.create({
  initial = 'green',
  events = {
    { name = 'warn',  from = 'green',  to = 'yellow' },
    { name = 'panic', from = 'yellow', to = 'red'    },
    { name = 'calm',  from = 'red',    to = 'yellow' },
    { name = 'clear', from = 'yellow', to = 'green'  }
  },
  callbacks = {
    onpanic =  function(self, event, from, to, msg) print('panic! ' .. msg)    end,
    onclear =  function(self, event, from, to, msg) print('thanks to ' .. msg) end,
    ongreen =  function(self, event, from, to)      print('green light')       end,
    onyellow = function(self, event, from, to)      print('yellow light')      end,
    onred =    function(self, event, from, to)      print('red light')         end,
  }
})

fsm:warn()
fsm:panic('killer bees')
fsm:calm()
fsm:clear('sedatives in the honey pots')
...

Additionally, they can be added and removed from the state machine at any time:

fsm.ongreen       = nil
fsm.onyellow      = nil
fsm.onred         = nil
fsm.onstatechange = function(self, event, from, to) print(to) end

or

function fsm:onstatechange(event, from, to) print(to) end

Asynchronous State Transitions

Sometimes, you need to execute some asynchronous code during a state transition and ensure the new state is not entered until your code has completed.

A good example of this is when you transition out of a menu state, perhaps you want to gradually fade the menu away, or slide it off the screen and don't want to transition to your game state until after that animation has been performed.

You can now return ASYNC from your onleavestate and/or onenterstate handlers and the state machine will be 'put on hold' until you are ready to trigger the transition using the new transition(eventName) method.

If another event is triggered during a state machine transition, the event will be triggered relative to the state the machine was transitioning to or from. Any calls to transition with the cancelled async event name will be invalidated.

During a state change, asyncState will transition from NONE to [event]WaitingOnLeave to [event]WaitingOnEnter, looping back to NONE. If the state machine is put on hold, asyncState will pause depending on which handler you returned ASYNC from.

Example of asynchronous transitions:

local machine = require('statemachine')
local manager = require('SceneManager')

local fsm = machine.create({

  initial = 'menu',

  events = {
    { name = 'play', from = 'menu', to = 'game' },
    { name = 'quit', from = 'game', to = 'menu' }
  },

  callbacks = {

    onentermenu = function() manager.switch('menu') end,
    onentergame = function() manager.switch('game') end,

    onleavemenu = function(fsm, name, from, to)
      manager.fade('fast', function()
        fsm:transition(name)
      end)
      return fsm.ASYNC -- tell machine to defer next state until we call transition (in fadeOut callback above)
    end,

    onleavegame = function(fsm, name, from, to)
      manager.slide('slow', function()
        fsm:transition(name)
      end)
      return fsm.ASYNC -- tell machine to defer next state until we call transition (in slideDown callback above)
    end,
  }
})

If you decide to cancel the async event, you can call fsm.cancelTransition(eventName)

Initialization Options

How the state machine should initialize can depend on your application requirements, so the library provides a number of simple options.

By default, if you dont specify any initial state, the state machine will be in the 'none' state and you would need to provide an event to take it out of this state:

local machine = require('statemachine')

local fsm = machine.create({
  events = {
    { name = 'startup', from = 'none',  to = 'green' },
    { name = 'panic',   from = 'green', to = 'red'   },
    { name = 'calm',    from = 'red',   to = 'green' },
}})

print(fsm.current) -- "none"
fsm:startup()
print(fsm.current) -- "green"

If you specify the name of your initial event (as in all the earlier examples), then an implicit startup event will be created for you and fired when the state machine is constructed.

local machine = require('statemachine')

local fsm = machine.create({
  inital = 'green',
  events = {
    { name = 'panic',   from = 'green', to = 'red'   },
    { name = 'calm',    from = 'red',   to = 'green' },
}})
print(fsm.current) -- "green"

More Repositories

1

sqlc

Generate type-safe code from SQL
Go
8,446
star
2

pgoutput

Postgres logical replication in Go
Go
99
star
3

statemachine

Simple finite-state machines in Python
Python
37
star
4

grain

The Entire History of You
Go
35
star
5

apple-stock

A quick and dirty project to figure out how much an Apple product would be worth in Apple stock today
Python
34
star
6

packer-templates

Ubuntu packer templates
24
star
7

deckbrew

Magic: The Gathering API
Go
18
star
8

scrapebook

Liberating your data from Facebook in one simple step
Python
17
star
9

timesuck

Privately track your computer usage
JavaScript
17
star
10

gitlicense

REST API for determining what license a repo has
Python
13
star
11

safari-monkey

Turn user scripts into Safari extensions
Python
13
star
12

areyouresponsive

A small site that make developing responsive websites easier
Python
12
star
13

prefab

a basic configuration manager
Go
11
star
14

sqlc-playground

JavaScript
9
star
15

vogeltron

Reddit bot for updating /r/SFGiants sidebar
Python
9
star
16

cgox

Cross-compilation for cgo
Go
8
star
17

grpc-on-heroku

A simple GRPC service running on Heroku.
JavaScript
7
star
18

golite

Port the SQLite parser to Go
Go
7
star
19

python-twilio2

The next generation Twilio helper library
Python
7
star
20

handlehelp

Figure out if a handle is available on a website
JavaScript
7
star
21

sqlc-tour

Go
6
star
22

jqueried

Safari Extension for adding jQuery to the active web page
JavaScript
6
star
23

paper

A dependency-free cilent for the Dropbox Paper API
Go
6
star
24

stashboard2

A Stashboard rewrite using Django
Python
6
star
25

slackmoji

Slackup your back emoji
Go
6
star
26

dead-batteries

How many packages will PEP 594 break?
Go
6
star
27

sqlparse

Go
6
star
28

rollcall

An attendance taking application for the iPhone and iPod Touch
Objective-C
5
star
29

sqlc-dev

Website for sqlc
CSS
5
star
30

hello-cgo

A simple "Hello World" application using cgo
C
5
star
31

circle-of-parity

College Football + Science = AWESOME
JavaScript
4
star
32

brushwagg

Scrape MTG data from magiccards.info
Python
4
star
33

dockerboxes

Vagrantfiles for Docker VMs
Ruby
4
star
34

songkick

Songkick API Go client
Go
3
star
35

movie-addict

Track the amount of IMDB Top 250 movies you have seen. For Facebook
PHP
3
star
36

learning-go

Follow my path to go enlightenment
Go
3
star
37

conkerchrome

Turn Google Chrome into Conkeror
JavaScript
3
star
38

dropwizard-helloworld

Super basic dropwizard service
Java
3
star
39

clocktower

Travel back in time with the Wayback Machine
Python
3
star
40

pypirss

Subscribe to projects. Stay up to date
Python
3
star
41

keep-the-change

Turn spare change into savings
Go
3
star
42

speakers

Call to Speakers: Find Your Voice
Python
3
star
43

starfighter

Fly around and blast through asteroids in this OpenGL C++ game
C
3
star
44

blobstore

Store and retrieve binary blobs from memory, the filesystem, S3, and more.
Go
3
star
45

go-wasm-plugins

Go
2
star
46

pb

Go tools for interacting with proto files
Go
2
star
47

rottenrepos

repository and source code analysis
Go
2
star
48

fieldmarshal

Easy marshaling, based on Golang structs
Python
2
star
49

bin

My collection of small scripts I use daily
Python
2
star
50

setup-sqlc

GitHub Action to install sqlc
TypeScript
2
star
51

grpc-rest-proxy

Proof-of-concept, work-in-progres, GRPC-proxy-for-rest
Go
2
star
52

sqlc-kotlin-runtime

Kotlin
2
star
53

quivr

Flickr + Hypermedia API = Crazy Delicious
Python
2
star
54

karmabot

Your new best friend on Reddit
CoffeeScript
2
star
55

seamstress

Simple configuration management for Ubuntu 10.04 (soon to be 12.04). Built on top of Fabric
Python
2
star
56

posterchild

An HTTP console for Chrome
JavaScript
2
star
57

popfly

Easily manage development machines on EC2
Python
2
star
58

homebrew-sqlc

Ruby
2
star
59

Installorama

A simple way to install Mac applications
Objective-C
2
star
60

archive

Go
2
star
61

sentinelpub

Publish EventBrite data into the Sentinel
JavaScript
2
star
62

integrity

Subresource integrity: Generate and verify integrity digests
Go
2
star
63

pycraig

A library for searching and retrieving data from craigslist
Python
1
star
64

gtlds

List of all generic top level domains
1
star
65

webtest

A fork of webtest with a cleaner API
Python
1
star
66

concentrate

A simple program for managing /etc/hosts entries
Go
1
star
67

dothingstellpeople

A community for doing.
Ruby
1
star
68

comstock

Logs have to get to the Comstock Lode
Go
1
star
69

golang

Experiments in Go
Go
1
star
70

dataflakes

Cereal nutritional fact visualization written in Tornado and Protovis
Python
1
star
71

stripe-checkout

Simple website with custom Stripe Checkout integration
1
star
72

congressbot

Python
1
star
73

vim

My .vimrc
Vim Script
1
star
74

bitewise

A food census
Python
1
star
75

salesman

Command-line link checker in Python
Python
1
star
76

heroku-buildpack-grpctools

Buildpack for gRPC tooling
Shell
1
star
77

tf2toolbox

Backpack utility tools for Team Fortress 2
JavaScript
1
star
78

protobuf-docker-library

Contains dockerfiles for protobufs in different languages
Protocol Buffer
1
star
79

sqlc-gen-greeter

Rust
1
star
80

mobi

A collection of mobi books I've made
1
star
81

metrics

Metrics service for my personal website. Using Graphite and StatsD
Python
1
star
82

responsive-redesign

A set of user styles to make popular websites responsive
CSS
1
star
83

less.core

A standard library for less.js
CSS
1
star
84

kandr

A journey across the ANSI C with Kernighan and Ritchie
C
1
star
85

random-letter

Stupid simple app for random letters
CSS
1
star
86

hn-comments

An analysis of 10000 Hacker News comments
1
star
87

exportable

A guide for exporting and archiving your data
Ruby
1
star
88

headofstate

Simple finite state machines in Python
Python
1
star
89

antlrs

Various generated lexers and parsers
Go
1
star
90

questions

A list of commonly used security questions
1
star
91

binscripts

A collection of my bin scripts
Python
1
star
92

golds

Cross-platform game development with SDL and Go
Go
1
star
93

hubot-reddit

Because this must be a good idea
CoffeeScript
1
star
94

datakick-android

Android client for Datakick
Java
1
star
95

extendplate

Template extending for Go's html/template package
Go
1
star
96

madlibs

Mad Libs with a Twilio Twist
Python
1
star
97

githubsocket

Github Hooks for your browser
Go
1
star
98

sleepercell

Sleep Tracking, written in Django
Python
1
star
99

pullup

Resolve pull requests cleanly for git-svn
Python
1
star
100

html

HTML Turing Machine Language (HTML)
Ruby
1
star