Page 1 of 1

statemachine.lua

Posted: Tue Nov 20, 2012 9:42 am
by conroy
I needed a state machine for my game. All the libraries I found didn't suit my needs, so I wrote my own

https://github.com/kyleconroy/lua-state-machine

Basic example creating a stoplight state machine.

Code: Select all

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'  }
}})

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'

Re: statemachine.lua

Posted: Wed Nov 21, 2012 6:08 pm
by rokit boy
pretty neat, minimal but neat.

Re: statemachine.lua

Posted: Wed Nov 21, 2012 6:53 pm
by kikito
I gave this a look and it's pretty neat. I like how the initial example is easy to grasp, but then you keep reading and reading and discovering advanced stuff if you need it.

I've got some minor syntax suggestions for you to consider:

1- Using the event name as keys of the "events" table:

So this:

Code: Select all

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'  }
  }
})
Becomes:

Code: Select all

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

2- Putting the callbacks inside the event table, and remove the "on":

Code: Select all

local fsm = machine.create({
  initial = 'green',
  events = {
    warn  = { from = 'green',  to = 'yellow', enter= function() ... end },
    panic = { from = 'yellow', to = 'red', before=function() ... end    },
    calm  = { from = 'red',    to = 'yellow', after=function() ... end },
    clear = { from = 'yellow', to = 'green', leave=function() ... end  }
  }
})
3- By the way, I'm not sure about the difference between "enter" vs "before" and "after" vs "leave". Maybe adding a phrase saying "The difference between enter and before is ..." right after explaining the callbacks would help.

4- Remove "magical" callback function names
By magical I'm referring to these. They require too much mental work IMHO:

Code: Select all

fsm.ongreen       = nil -- was this on enter, or onBefore? If I change ongreen, what happens if I invoke onentergreen?
fsm.onyellow      = nil
fsm.onred         = nil
fsm.onchangestate = function(event, from, to) print(to) end
If you transform your events into call-able tables, you can have a slightly longer names, but with no magic. I consider this better because there are no rules to remember. Let the code explains itself:

Code: Select all

fsm.green.enter       = nil
fsm.yellow.enter      = nil
fsm.red.enter         = nil
fsm.change = function(event, from, to) print(to) end -- instead of onchangestate
Nice thing: you can define the callbacks using Lua's regular function definition syntax if you want:

Code: Select all

function fsm.yellow.enter(...)
...
end
Having fsm.yellow as a table has other advantages. For example you could define fsm.yellow.draw, fsm.red.draw, and then just do fsm.current.draw().

This is all I could think for now.

Regards!

EDIT: I forgot to mention that I saw it's integrated with TravisCI! That's very cool! But out of curiosity: why "erlang"?

Re: statemachine.lua

Posted: Wed Nov 21, 2012 10:05 pm
by Lafolie
This is awesome! Great to see kikito's additions too.

Re: statemachine.lua

Posted: Thu Nov 22, 2012 8:39 am
by Robin
kikito wrote:1- Using the event name as keys of the "events" table:
I wanted to suggest that as well, but then I saw it wouldn't work:

From an example in his GitHub README:

Code: Select all

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'    },
}})
(The original had a bug: it had [] instead of {} for the sequence -- it was translated from JavaScript, which explains that.)

Anyway, it becomes really hairy if you try to use the event name as a key there.

I guess you could do

Code: Select all

local fsm = machine.create({
  initial = 'hungry',
  events = {
    eat = { name = 'eat',  from = {'hungry', 'satisfied', 'full'}, to = {'satisfied', 'full', 'sick'}},
    rest = {from = {'hungry', 'satisfied', 'full', 'sick'}, to = 'hungry'},
}})
Hm, maybe that's not even a bad idea.

Re: statemachine.lua

Posted: Fri Nov 23, 2012 6:28 am
by conroy
Yep, Robin pointed out the problem with using a table indexed by event name instead of just a table of tables.

The reason it uses the erlang type is funny. Travis CI doesn't have native support for Lua, so you have to install lua and luajit yourself. The erlang environment makes this easy to do, hence the reason for its use.

I personally like the named state transitions. While "magic" I think they are easy to understand. I think defining all the functions in the same table, per the first example, is a bit too much.

I'm pretty happy with where this is now. Some of the features (such as multiple states and async transitions) don't work yet, so I'll be adding support for those next.