Also, in my experience with state machines, laying-out explicit state transition map and conditions is very useful, and you usually have to do it anyway, either in some kind of diagram, code comment or just write it down in the code, otherwise it's always wrong for anything non-trivial :)
E.g. something like this:
local state_map = {
init = function() return 'read1', 3 end,
read1 = function() return 'read2', 3 end,
read2 = function() return 'read3', 5 end,
read3 = function() return 'nextRecipe' end,
nextRecipe = function(recipe_selected)
if not recipe_selected
then return 'nextItem'
else return 'nextRecipe', 5 end end,
nextItem = function(item_selected)
if not item_selected
then return 'calculateNeeded'
else return 'read3', 5 end end,
read4 = function() return 'read5', 5 end,
read5 = function() return 'nextRecipe' end,
calculateNeeded = function() return 'wait' end,
wait = function() return 'init', 300 end,
}
local function state_next(...)
local state0 = State
State, delay = state_map[state0](...)
---- Logging state changes tend to be very useful with state machines
-- print((' -- mlc[%s] state: %s -> %s'):format(uid, state0, State))
if (delay or 0) < 1 then return main[State]() end
end
That'd also make actual methods more obvious and less boilerplatey, allow for states to change within same tick, add logging or whatever other stuff in there easily, etc.
function main.nextRecipe()
Recipe = nil
for name in pairs(Recipes[Item].recipes) do
if not Recipes[Item].recipes[k].ingredients
then Recipe = name; break end end
if not Recipe then
ItemsToPopulate[Item] = nil
return state_next()
end
out = {['green/'..Recipe]=1, ['red/signal-at']=4}
return state_next('recipe_selected')
end
Though of couse there're infinite ways to express same thing, mostly up to personal style/preferences.
Just wanted to point it out as an option and a particular way it can be written down in lua.