about sequencers

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
spir
Citizen
Posts: 76
Joined: Wed Oct 17, 2012 1:12 pm

about sequencers

Post by spir »

Hello,

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)
Each PH is a phase, is associated with an action, or set of actions, and has one or more entry condition(s) C. Thingies in parens will be dealt with below.
* 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
Ok. A few additions:
* 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
... la vita e estrany ...
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: about sequencers

Post by kikito »

I realised how trivial it is w/o any "skeleton', and how such a framework would rather make things heavy rather than be helpful
I'm all for not overengineering things, but on this particular case, I'd say you are sacrificing too much clarity in order to get a too small speed increase.

The problem is that your code doesn't look like the diagram you drew (it looks like "lots of ifs"). Trying to debug that code in 6 months will be very difficult, even for you. And it is a relatively simple diagram. When you add more rules, it will quickly become unwieldy.

Thus, unless this is the only diagram you plan to do, I urge you to reconsider and work on that library you talked about. Something that makes your code look like a diagram.
When I write def I mean function.
spir
Citizen
Posts: 76
Joined: Wed Oct 17, 2012 1:12 pm

Re: about sequencers

Post by spir »

kikito wrote:
I realised how trivial it is w/o any "skeleton', and how such a framework would rather make things heavy rather than be helpful
I'm all for not overengineering things, but on this particular case, I'd say you are sacrificing too much clarity in order to get a too small speed increase.

The problem is that your code doesn't look like the diagram you drew (it looks like "lots of ifs"). Trying to debug that code in 6 months will be very difficult, even for you. And it is a relatively simple diagram. When you add more rules, it will quickly become unwieldy.

Thus, unless this is the only diagram you plan to do, I urge you to reconsider and work on that library you talked about. Something that makes your code look like a diagram.
I think I understand what you mean, and would probably share your views if I did not know from actual experience how easy this "paradigm" is, in practice, and with very complicated logic. Look, when there is a bug, what we get concretely (in hardware or software output) is that the process is blocked at some stage, does not reach another stage, does not launch an action -- or such things.

Then, all you need to do is search for the given phase(s), et voilà. Its input/output conditions are obvious (simple bit logic), as well as its actions. A prerequisite is that you get an idea of the actual ids / var names of phases or states in software, for which design documents would help, at best with such "dynamic logic" shemata... (in real automatics, you get at least hardware plans, from which such information can be found easily, at least from physical/logical harware I/O ports).
Probably I explained badly (I did my best, though :( ). Really, the trick of having just one bit for each step, and build the logic around them is surprisingly easy and powerful. (The only thing, maybe: just try next time you need such a kind of logic. Just write a little schema and each stage a name for a boolean var.) Also, in non-hardware-related programming, we are so used that very complex paradigms are more or less completely useless to simplify our life, to help found nicer ways in the labyrinth of imaginary worlds ;) ... But when get a concrete world to control things are different: there are natural laws, and hardware is reliable.

Denis
... la vita e estrany ...
User avatar
verilog
Citizen
Posts: 97
Joined: Thu Nov 03, 2011 3:15 am
Contact:

Re: about sequencers

Post by verilog »

Hi spir,
I'm intrigued, can you share a little bit more about what are you working on? PLC simulation, maybe?

Your love code seems to be a direct transliteration of your GRAFCET, I mean, each node of the tree results directly in the evaluation of a condition. This is fairly straightforward, but could be difficult to maintain in the long term, also, could be a little bit unoptimized for PC architectures.

I'm sure you've realized this before, but simulating low-level software (or hardware) with high-level programing (or scripting) languages is not easy, especially if your operations are bit-level related. What I’m trying to say is that, a relatively simple operation in a dedicated architecture (e.g. PLC) could be translated as an unnecessary difficult series of instructions in a general-purpose architecture (e.g. PC).

Maybe you can re-work your code translation to take advantages of the general-purpose architecture. In this case, you could represent your GRAFCET as a transition matrix. A transition matrix describes the necessary conditions to reach a certain state in a state machine, this kind of data structure is easily handled in Lua, and maybe it could be useful for you.

Cheers! :crazy:
spir
Citizen
Posts: 76
Joined: Wed Oct 17, 2012 1:12 pm

Re: about sequencers

Post by spir »

verilog wrote:Hi spir,
I'm intrigued, can you share a little bit more about what are you working on? PLC simulation, maybe?

Your love code seems to be a direct transliteration of your GRAFCET, I mean, each node of the tree results directly in the evaluation of a condition. This is fairly straightforward, but could be difficult to maintain in the long term, also, could be a little bit unoptimized for PC architectures.

I'm sure you've realized this before, but simulating low-level software (or hardware) with high-level programing (or scripting) languages is not easy, especially if your operations are bit-level related. What I’m trying to say is that, a relatively simple operation in a dedicated architecture (e.g. PLC) could be translated as an unnecessary difficult series of instructions in a general-purpose architecture (e.g. PC).

Maybe you can re-work your code translation to take advantages of the general-purpose architecture. In this case, you could represent your GRAFCET as a transition matrix. A transition matrix describes the necessary conditions to reach a certain state in a state machine, this kind of data structure is easily handled in Lua, and maybe it could be useful for you.

Cheers! :crazy:
All right, very interesting! About the translation low-level -> high-level, I guess things are not such simple. The high-level-ness of our language expresses and is made for so-to-say "off-time" actions and computations. While the meaning and purpose of models like grafcet are made for controlling processes which live in a timely manner (either realtime, originally, or any kind of simulated time-line in a "virtual world"); this time is not controlled by the controller, but instead controls it ;) or rather the controller has too react to events happening out of its control (the controller is the slave ;))
[There are also paradigms which try to address such problems and views, such reactive [functional] programming, but they are very abstract, and afaik mostly academic.]

Still, as you say, there are high-level structures we can take profit of. About transition matrix, I now realise I'm also re-inventing (low-level) state machines, kind of, and very primitive, by implementing the control bits (states, conditions) as flags on the relevant "objects" (the ones whose actions set or depend on the state denoted by a flag). This is a primitive way of trying make a separation of concerns by not having the objects directly controlling each other (which ends up making spaghetti code, and worse, dynamic spaghettis).
You ask for an example, so here is one control func trying to use this principle:

Code: Select all

booa.control = function (booa, dt)
   -- potential mode changes launched by auto_tempo tick:
   -- pass into modes prelude, auto, postlude
   if booa.auto_tempo.tick then
      -- end game ?
      if booa.mode == "postlude" then
         note "\n   < < <   t h a t ' s   a l l ,   f o l k s !   > > >\n"
         love.event.quit()
      -- pass in postlude mode ?
      elseif #gloopies == 0 then
         booa.mode = "postlude"
      -- pass in auto mode ?
      else
         booa.mode = "auto"
         booa:auto_start()
      end
   end
   
   -- pass into manual mode ?
   if booa.mode == "auto" and booa.arrow then
      booa.mode = "manu"
   end
   
   -- case manu mode: move via arrow ?
   if booa.mode == "manu" and booa.arrow then
      booa:manu_move()
      booa.auto_tempo:start()
   end
   
   -- case auto mode: tick for move ?
   if booa.mode == "auto" and booa.move_tempo.tick then
      booa:auto_move()
   end
   
   -- gloop ?
   local gl = map.matrix[booa.i][booa.j].gloopie
   if gl then
      -- eaten !!!
      map.matrix[booa.i][booa.j].gloopie = nil
      gloopies:kill(gl.numero)
      notef("--> %g gloopies", #gloopies)
      -- gloop mode
      booa.mode = "gloop"
      booa.auto_tempo:start()
   end
end
[Don't laugh at the vocabulary, it's part of the game!]

I realise I'm unclear, maybe we should open a topic on code organisation on both the static (state, syncrony, model) and dynamic (events, diachrony, control) perspectives.

Denis
... la vita e estrany ...
Post Reply

Who is online

Users browsing this forum: Google [Bot] and 151 guests