I had the need for a sequencer for a little project and was thinking about making a prototype for that, to be reused, when I realised how trivial it is w/o any "skeleton', and how such a framework would rather make things heavy rather than be helpful, mainly because elements (actions, conditions) would have to be put in functions, while they're typically trivial expressions and statements.
So, stop. But remains the logic. In automation, people use(d) to deal with a way of representing simple & complex processes of (real, hardware) machines, which is great & easy, called GRAFCET (or based on it, see wikipedia: sequential function chart).
I thought I'd share a bit about that.It's exraordinayly simple compared to its power. All is based on having simple bits represent step / phases / states of a procedure. If it's well known in the löve community, please just tell me & ignore.
Let us say you need to represent and control a process looking like that:
Code: Select all
PH0 (init)
|
- C1
|
PH1
|
-------------------
| |
- C2 - C3
| |
PH2 PH3 (single-shot)
| |
- C4 - C5
| |
-------------------
|
PH4 (blood springs on tempo)
|
- C6
|
===================
| |
PH5 PH6
| |
===================
|
- C7
|
PH7 (death after delay)
* Simple horizontal rules represent alternative branches: they have different entry and ougoing conditions.
* Double horizontal rules represent parallel branches: they have same entry and ougoing condition.
Here is how I would code that:
Code: Select all
-- phase flags
local seq = { -- give meaningful names...
ph0 = false ,
ph1 = false ,
ph2 = false ,
ph3 = false ,
ph4 = false ,
ph5 = false ,
ph6 = false ,
ph7 = false ,
}
-- init: to be called by love.load, or whenever the seq must start
seq.start = function (seq) seq.ph0 = true end
-- logic: to be called by love.update (possibly only when runs)
-- use actual conditions in place of cn
seq.logic == function(seq, dt)
if seq.ph0 and c1 then seq.ph1 = true end
if seq.ph1 and c2 then seq.ph2 = true end
if seq.ph1 and c3 then seq.ph3 = true end
if seq.ph2 and c4 then seq.ph4 = true end
if seq.ph3 and c5 then seq.ph4 = true end
if seq.ph4 and c6 then seq.ph5 = true ; seq.ph6 = true end
if (seq.ph5 or seq.ph6) and c7 then seq.ph7 = true end
end
-- actions: to be called by love.update (ditto)
seq.logic == function(seq, dt)
if seq.ph1 then action_1() end
if seq.ph2 then action_2() end
if seq.ph3 then action_3() ; c4 = true end
if seq.ph4 then if tempo then action_1() end end
if seq.ph5 then action_5() end
if seq.ph7 then action_6() end
if seq.ph6 if delay then then action_7() end end
end
* Ordinary actions are constant, like drawing or moving or flowing, then neey to be called every cycle.
* What is not represented here: when an action is finished, it must set true its outgoing condition, so that the sequencer moves on one step.
* Action 3 is single shot, single call, thus it's outgoing condition can be reset at once.
* Action 4 is repetitive, based on a tempo-timer (tick...tick...tick...).
* Action 7 is delayed, based on a delay-timer (tick once)..
(Now, it's possible to make a simple associative array of phase-flags and actions, have the sequencer just activate actions on phases, and hide the dynamics in actions. But as long as possible it is better to clearly expose the logic all together, I guess.)
Very complicated machines are programmed that way (in all kinds of industries).
Hope was clear and may be helpful.
Denis