What do you think about the pros/cons of events + state machine concept?
I worked at a place that used QP/C to make an impressive hard-real-time embedded system. The shoebox-sized device had three distinct boards, each serving a separate function and having a dissimilar processor: ATmega, ARM7TDMI and a TI DSP. Each board had serial connections to the other two boards (triangle topology) and conveyed the QP Events as packetized messages. This was effectively a distributed system at the embedded scale.
I believe QP is excellent for creating event-based, deeply embedded systems (no MMU and small memories). However, I find the PSICC2 book is very difficult to follow. It doesn't take the reader down a smooth street to understanding. Instead, it jerks him to and fro and expects him to learn from the hard knocks and find the nuggets of information despite the concussions. Thankfully, I had coworkers to help me learn The Way.
One downside of the QP methodology is that the programmer is forced to write code that executes quickly and returns. Indeed QP's Run-To-Completion paradigm means its thread-switching granularity is directly tied to the thread's longest execution path. Imposing code that executes quickly is, of course, a great thing for an embedded system. However, it can also have a downside because any code that takes significant time must now be refactored in a fashion that kills readability of the code. For example, instead of iterating in a for loop thousands of times, you perform the body once, pass an event to yourself to perform it again and then keep a counter for when to quit. QP-novices can hardly tell it's a loop at all. It looks more like an event-based goto. That said,
I very much want to either wrap QP/C[++] or better, create a nim-source equivalent.
And I very much want to create (or re-use if one already exists) a DSL for hierarchical state machines (HSM). I believe there is a good chance the DSL could neatly impose the syntax and semantics needed (as explained in PSICC2) to successfully implement an HSM. After all, Nim's use of whitespace-indented untyped blocks naturally creates hierarchy.
And I very much want to create (or re-use if one already exists) a DSL for hierarchical state machines (HSM). I believe there is a good chance the DSL could neatly impose the syntax and semantics needed (as explained in PSICC2) to successfully implement an HSM. After all, Nim's use of whitespace-indented untyped blocks naturally creates hierarchy.
I'm working on an implementation in Nim that is modeled closely on SCXML, though there's no XML involved (nor UML for that matter), with some differences aimed at leveraging Nim's static type system, e.g. enum is used for states and events instead of strings:
https://github.com/michaelsbradleyjr/snarts/tree/algos
Ongoing work for the initial implementation is on the algos branch. I've mostly wrapped up work on the DSL front-end and tests for it. I'm now working on the validator/compiler, and I have some ideas for making it easy (declarative) to invoke an external "service" in the same thread or a thread in a pool.
It would be nice to figure out a way to hook it into the XState Visualizer, or at least leverage some of their ideas and open source code for it, though that might be easier said than done. To be clear, while I'm not un-interested in having snarts usable with Nim's JS backend, for now I'm primarily aiming to support the C backend.
It'd be really cool to see something similar for Nim! Especially if it could embed logic proofs or invariants like Ada.
Note that it'd be possible to use Nim async to create a similar style setup too. Async functions are turned into iterator functions which are simple state machines storing the current function state. It's convenient as there's already a lot of knowledge / expertise around this sort of code.
Though you'd need to have a priority queue based executor on the async-loop and change it to not rely on network based events but instead interrupts. I suspect signals and interrupts would work fine for that.
And I very much want to create (or re-use if one already exists) a DSL for hierarchical state machines (HSM).
A very primitive macro: https://github.com/planetis-m/macromachining
Glad to see there is interest in Samek's work.
Reading his book was a revelation to me. It was the first time I had seen a framework described that could manage complex systems with rather simple ideas. No threading or operating system required. It was a tough read for me because I have hard time reading C and have a harder time reading C macros. When I got stuck on the dispatch function, I started an implementation in Nim.
I think the difficulty in a framework like QL (Quantum Leaps) is that modifying code takes a lot of work, because there is a lot of bookkeeping to be maitained.
I think that is why there are so many graphical code generators in this space.
Nim is an excellent match to implement Samek's framework.
A clarification for those that haven't read Samek's book. It is much much more than state machines.
It is is an event based framework that allows the creation of independent Active Objects that can send messages(events) to each other.
Each Active Ojbject has local data, a message que, and a Hiearcheal Finite State Machine. The framework takes events from an eventpool and passes the events to any AO that subscribes to them.
Events can be created by timers, interrupts, and within the execution of the HFSMs.
If feels like mini Erlang on a 16-bit microcontroller.
So, I've been working on this in fits/starts. My focus is a system for bare metal embedded devices with statically allocated memory
The good: It works on a PC and it works on an embedded device. I can do automatic testing of the target from the host.
The bad: It's over 2000 lines on Nim macros. Yeah, so I thought trying to read C macros was difficult so I went neck deep into Nim macros.
The ugly: I'm using Nim as a code generator?? 1) The dsl creates an Event type and then many downstream types are built on top of this. This was tying me in knots when compiling. I tried messing with generics but no luck. 2) I was constantly dumping macros to understand what was going on. A lot of code gets generated. Need to be able to review what is happening, what better documentation than nim code. Is this a ridiculous concept, anybody else doing code generation?
Here is what the DSL looks like
import ../core/[eventbuild, eventutil]
event RefreshDisplay: nil
event Keypress:
keyint: uint8
event TimeOut: nil
event TimeDelay: nil
event DownTick: nil
event UpTick: nil
event RxDataReady:
bytes: uint16
event TxDataReady:
txbytes: uint8
event TxComplete: nil
event BlueButton: nil
event EncoderButton: nil
event EncoderUp:
pulsedown: uint8
event EncoderDown:
pulseup: uint8
event MenuTimeout: nil
event EncoderUpdate: nil
# These event are only valid on host, include on target in order to synch
# ordinals of events signals are used in serialize/deserializing
event StartTest: nil
event CancelTest: nil
event DataTest: nil
event StartRecipe : nil
event CancelRecipe : nil
event EvPumpAOn : nil
event EvPumpAOff : nil
event EvSolenoidAOn : nil
event EvSolenoidBOn : nil
event Done:
philo_done: int
event Hungry:
philo_hungry: int
event Eat:
philo_eat: int
event PhiloTimeOut: nil
build()
calling build at the end will generate this Nim code.
The Event type is the heart of the framework.
import ../core/eventutil
type
Signals* = enum
handled, ignored, enter, exit, init, transition, super, RefreshDisplay,
Keypress, TimeOut, TimeDelay, DownTick, UpTick, RxDataReady, TxDataReady,
TxComplete, BlueButton, EncoderButton, EncoderUp, EncoderDown, MenuTimeout,
EncoderUpdate, StartTest, CancelTest, DataTest, StartRecipe, CancelRecipe,
EvPumpAOn, EvPumpAOff, EvSolenoidAOn, EvSolenoidBOn, Done, Hungry, Eat,
PhiloTimeOut
UserSignals* = range[RefreshDisplay .. PhiloTimeOut]
type
Event* = object
trace*: uint8
case signal*: Signals
of ignored:
nil
of Done:
philo_done*: int
of EncoderDown:
pulseup*: uint8
of EncoderUp:
pulsedown*: uint8
of RxDataReady:
bytes*: uint16
of Eat:
philo_eat*: int
of Keypress:
keyint*: uint8
of TxDataReady:
txbytes*: uint8
of Hungry:
philo_hungry*: int
else:
nil
type
EventPoolItem* = object
nogc*: bool
event*: Event
refCtr*: int8
id*: uint8
next*: uint8
proc deserializeEvent*(buf: pointer): Event =
var ctr = 3
let sigid = cast[ptr uint8](cast[int](buf) + 1)[]
let mySig = Signals(sigid)
let traceid = cast[ptr uint8](cast[ByteAddress](buf) + 2)[]
case mySig
of ignored:
result = Event(trace: traceid, signal: mySig)
of Hungry:
var philo_hungry: int
readDataLE(buf, philo_hungry)
result = Event(signal: mySig, trace: traceid, philo_hungry: philo_hungry)
of TxDataReady:
var txbytes: uint8
readDataLE(buf, txbytes)
result = Event(signal: mySig, trace: traceid, txbytes: txbytes)
of Keypress:
var keyint: uint8
readDataLE(buf, keyint)
result = Event(signal: mySig, trace: traceid, keyint: keyint)
of Eat:
var philo_eat: int
readDataLE(buf, philo_eat)
result = Event(signal: mySig, trace: traceid, philo_eat: philo_eat)
of RxDataReady:
var bytes: uint16
readDataLE(buf, bytes)
result = Event(signal: mySig, trace: traceid, bytes: bytes)
of EncoderUp:
var pulsedown: uint8
readDataLE(buf, pulsedown)
result = Event(signal: mySig, trace: traceid, pulsedown: pulsedown)
of EncoderDown:
var pulseup: uint8
readDataLE(buf, pulseup)
result = Event(signal: mySig, trace: traceid, pulseup: pulseup)
of Done:
var philo_done: int
readDataLE(buf, philo_done)
result = Event(signal: mySig, trace: traceid, philo_done: philo_done)
else:
result = Event(trace: traceid, signal: mySig)
proc serializeEvent*(evt: Event; bytePtr: ptr byte): uint8 =
let cmdId = 2.uint8
let mySig: uint8 = ord(evt.signal).uint8
var ctr: uint8 = 3
let sigPos = cast[ByteAddress](bytePtr) + 1
bytePtr[] = cmdId
cast[ptr byte](sigPos)[] = mySig
cast[ptr byte](sigPos + 1)[] = evt.trace
case evt.signal
of ignored:
result = ctr
of Hungry:
writeDataLE(bytePtr, evt.philo_hungry)
result = ctr
of TxDataReady:
writeDataLE(bytePtr, evt.txbytes)
result = ctr
of Keypress:
writeDataLE(bytePtr, evt.keyint)
result = ctr
of Eat:
writeDataLE(bytePtr, evt.philo_eat)
result = ctr
of RxDataReady:
writeDataLE(bytePtr, evt.bytes)
result = ctr
of EncoderUp:
writeDataLE(bytePtr, evt.pulsedown)
result = ctr
of EncoderDown:
writeDataLE(bytePtr, evt.pulseup)
result = ctr
of Done:
writeDataLE(bytePtr, evt.philo_done)
result = ctr
else:
result = ctr
include ../core/imports # Note: this is an include NOT import
import ../core/machinebuild
forward:
import recipe, philoglobal, timers
priorities : {noao, TxMgr, Smachine, RecipeMgr, Tabletop, Philosopher, Philosopher2, Philosopher3, Philosopher4, Philosopher5}
filename tasker
buildMachine RecipeMgr:
priority: RecipeMgr
state: (active, terminal)
state: (idle, active)
state: (error, active)
state: (done, active)
state: (wait, active)
state: (actions, active)
state: (getnext, active)
state: (checkseq, active)
state: (checkpar, active)
buildMachine TxMgr:
priority: TxMgr
state: (active, terminalx)
state: (send, active)
state: (busy, active)
txbytesWaiting: uint16
txbytesWriting: uint16
txctr: byte
txBuf : array[128, byte]
buildMachine Smachine:
topstate: terminalx
priority: Smachine
state: (s, terminalx)
state: (s1, s)
state: (s11, s1)
state: (s2, s)
state: (s21, s2)
state: (s211, s21)
foo: int
buildMachine Philosopher:
topstate: terminal
priority: Philosopher
state: (philo, terminal)
state: (thinking, philo)
state: (hungry, philo)
state: (eating, philo)
philo: int
phtime: Timer
buildMachine Philosopher2:
copy: Philosopher
buildMachine Philosopher3:
copy: Philosopher
buildMachine Philosopher4:
copy: Philosopher
buildMachine Philosopher5:
copy: Philosopher
buildMachine Tabletop:
topstate: terminal
priority: Tabletop
state: (tabletop, terminal)
state: (serving, tabletop)
state: (exit, tabletop)
fork: array[num_philo, forkstate]
ishungry: array[num_philo, hungrystate]
build()
And it generates the code below. Matrices have been truncated
include ../core/imports
import ../core/global
import
recipe, philoglobal, timers
type
AllMachines* {.pure.} = enum
RecipeMgr, TxMgr, Smachine, Philosopher, Philosopher2, Philosopher3,
Philosopher4, Philosopher5, Tabletop
type
ao_priority = enum
noao_pri, TxMgr_pri, Smachine_pri, RecipeMgr_pri, Tabletop_pri,
Philosopher_pri, Philosopher2_pri, Philosopher3_pri, Philosopher4_pri,
Philosopher5_pri
const
MaxStatesAll* = 9
MaxDepthAll* = 4
NumStatesAll* = 10
type
RootTablePaired = array[1 .. MaxStatesAll,
array[1 .. MaxDepthAll, HandlerFuncIdx]]
TransTablePaired = array[1 .. MaxStatesAll, array[1 .. MaxStatesAll,
array[1 .. MaxDepthAll, HandlerFuncIdx]]]
type
FsmTR_Data* = object
exit_statepaths_p*: TransTablePaired
entry_statepaths_p*: TransTablePaired
entry_paths_p*: TransTableShort
root_table_p*: RootTablePaired
FsmTR_DataPtr = ptr FsmTR_Data
FsmPtr = ptr Fsm
AO* = object
fsmp*: FsmPtr
mesgQ*: RingBuffer[mesgQLength, ptr EventPoolItem]
priority*: uint8
Fsm* = object
target_state*: HandlerFuncIdx
current_state*: HandlerFuncIdx
priority*: int
machinecopy*: AllMachines
history*: HandlerFuncIdx
name*: string
trans_data*: FsmTR_DataPtr
initializer*: initFunc
handler_functions*: FunctTableArrayNano
case machine*: AllMachines
of TxMgr:
txbytesWaiting*: uint16
txbytesWriting*: uint16
txctr*: byte
txBuf*: array[128, byte]
of Smachine:
foo*: int
of Philosopher:
philo*: int
phtime*: Timer
of Tabletop:
fork*: array[num_philo, forkstate]
ishungry*: array[num_philo, hungrystate]
else:
nil
HandlerFunctNano = proc (fsm: var Fsm; event: var Event): Signals {.nimcall.}
initFunc = proc (): (HandlerFuncIdx, Signals)
FunctTableArrayNano = array[NumStatesAll, HandlerFunctNano]
proc copyTransitionTable(trans: TransTable): TransTablePaired {.compileTime.} =
for x in 1 .. MaxStatesAll:
for y in 1 .. MaxStatesAll:
for z in 1 .. MaxDepthAll:
result[x][y][z] = uint8(trans[x][y][z])
proc copyRootTable(root: RootTable): RootTablePaired =
for x in 1 .. MaxStatesAll:
for z in 1 .. MaxDepthAll:
result[x][z] = uint8(root[x][z])
const
RecipeMgrTRData = FsmTR_Data(root_table_p: copyRootTable([[1, 0, 0, 0, 0, 0],
[2, 1, 0, 0, 0, 0], [3, 1, 0, 0, 0, 0], [4, 1, 0, 0, 0, 0],
[5, 1, 0, 0, 0, 0], [6, 1, 0, 0, 0, 0], [7, 1, 0, 0, 0, 0],
[8, 1, 0, 0, 0, 0], [9, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]), entry_statepaths_p: copyTransitionTable([[
[0, 0, 0, 0, 0, 0], [2, 0, 0, 0, 0, 0], [3, 0, 0, 0, 0, 0],
[4, 0, 0, 0, 0, 0], [5, 0, 0, 0, 0, 0], [6, 0, 0, 0, 0, 0],
[7, 0, 0, 0, 0, 0], [8, 0, 0, 0, 0, 0], [9, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[3, 0, 0, 0, 0, 0], [4, 0, 0, 0, 0, 0],
[5, 0, 0, 0, 0, 0], [6, 0, 0, 0, 0, 0],
[7, 0, 0, 0, 0, 0], [8, 0, 0, 0, 0, 0],
[9, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]], [
[0, 0, 0, 0, 0, 0], [2, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[4, 0, 0, 0, 0, 0], [5, 0, 0, 0, 0, 0], [6, 0, 0, 0, 0, 0],
[7, 0, 0, 0, 0, 0], [8, 0, 0, 0, 0, 0], [9, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0, 0], [2, 0, 0, 0, 0, 0],
[3, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[5, 0, 0, 0, 0, 0], [6, 0, 0, 0, 0, 0],
...
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]]]))
var TxMgr* = Fsm(machine: AllMachines.TxMgr, priority: 1)
TxMgr.trans_data = unsafeAddr(TxMgrTRData)
const
SmachineTRData = FsmTR_Data(root_table_p: copyRootTable([[1, 0, 0, 0, 0, 0],
[2, 1, 0, 0, 0, 0], [3, 2, 1, 0, 0, 0], [4, 1, 0, 0, 0, 0],
[5, 4, 1, 0, 0, 0], [6, 5, 4, 1, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]), entry_statepaths_p: copyTransitionTable([[
[0, 0, 0, 0, 0, 0], [2, 0, 0, 0, 0, 0], [2, 3, 0, 0, 0, 0],
[4, 0, 0, 0, 0, 0], [4, 5, 0, 0, 0, 0], [4, 5, 6, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[3, 0, 0, 0, 0, 0], [4, 0, 0, 0, 0, 0],
[4, 5, 0, 0, 0, 0], [4, 5, 6, 0, 0, 0],
...
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]], [
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]]]))
var Smachine* = Fsm(machine: AllMachines.Smachine, priority: 2)
Smachine.trans_data = unsafeAddr(SmachineTRData)
const
PhilosopherTRData = FsmTR_Data(root_table_p: copyRootTable([
[1, 0, 0, 0, 0, 0], [2, 1, 0, 0, 0, 0], [3, 1, 0, 0, 0, 0],
[4, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]]), entry_statepaths_p: copyTransitionTable([[
[0, 0, 0, 0, 0, 0], [2, 0, 0, 0, 0, 0], [3, 0, 0, 0, 0, 0],
[4, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[3, 0, 0, 0, 0, 0], [4, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
..
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]]]))
var Philosopher* = Fsm(machine: AllMachines.Philosopher, priority: 5)
Philosopher.trans_data = unsafeAddr(PhilosopherTRData)
const
TabletopTRData = FsmTR_Data(root_table_p: copyRootTable([[1, 0, 0, 0, 0, 0],
[2, 1, 0, 0, 0, 0], [3, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]), entry_statepaths_p: copyTransitionTable([[
[0, 0, 0, 0, 0, 0], [2, 0, 0, 0, 0, 0], [3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[3, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]]]))
var Tabletop* = Fsm(machine: AllMachines.Tabletop, priority: 4)
Tabletop.trans_data = unsafeAddr(TabletopTRData)
const
numAO = 9
var activeObjects*: array[1 .. numAO, AO]
activeObjects[ord(RecipeMgr_pri)] = AO(priority: ord(RecipeMgr_pri),
fsmp: addr(RecipeMgr))
activeObjects[ord(RecipeMgr_pri)].mesgQ.initRingBuffer
activeObjects[ord(TxMgr_pri)] = AO(priority: ord(TxMgr_pri), fsmp: addr(TxMgr))
activeObjects[ord(TxMgr_pri)].mesgQ.initRingBuffer
activeObjects[ord(Smachine_pri)] = AO(priority: ord(Smachine_pri),
fsmp: addr(Smachine))
activeObjects[ord(Smachine_pri)].mesgQ.initRingBuffer
activeObjects[ord(Philosopher_pri)] = AO(priority: ord(Philosopher_pri),
fsmp: addr(Philosopher))
activeObjects[ord(Philosopher_pri)].mesgQ.initRingBuffer
var Philosopher2*: Fsm = Philosopher
Philosopher2.priority = ord(Philosopher2_pri)
activeObjects[ord(Philosopher2_pri)] = AO(priority: ord(Philosopher2_pri),
fsmp: addr(Philosopher2))
activeObjects[ord(Philosopher2_pri)].mesgQ.initRingBuffer
var Philosopher3*: Fsm = Philosopher
Philosopher3.priority = ord(Philosopher3_pri)
activeObjects[ord(Philosopher3_pri)] = AO(priority: ord(Philosopher3_pri),
fsmp: addr(Philosopher3))
activeObjects[ord(Philosopher3_pri)].mesgQ.initRingBuffer
var Philosopher4*: Fsm = Philosopher
Philosopher4.priority = ord(Philosopher4_pri)
activeObjects[ord(Philosopher4_pri)] = AO(priority: ord(Philosopher4_pri),
fsmp: addr(Philosopher4))
activeObjects[ord(Philosopher4_pri)].mesgQ.initRingBuffer
var Philosopher5*: Fsm = Philosopher
Philosopher5.priority = ord(Philosopher5_pri)
activeObjects[ord(Philosopher5_pri)] = AO(priority: ord(Philosopher5_pri),
fsmp: addr(Philosopher5))
activeObjects[ord(Philosopher5_pri)].mesgQ.initRingBuffer
activeObjects[ord(Tabletop_pri)] = AO(priority: ord(Tabletop_pri),
fsmp: addr(Tabletop))
activeObjects[ord(Tabletop_pri)].mesgQ.initRingBuffer
include ../core/ao
include ../core/dispatch
3) After that more DSLs to describe the behaviours of the HFSMs This is the Machine Samek describes in 4.2
machine: Active Ojects defined in previous dsl state: maps to the state defined in previous dsl rct: maps to the event coming in
include ../core/imports
import ../core/machineprocs
forward:
import smachinestatesImp
import taskerVarType
export taskerVarType
import taskerglobal
filename smachinefsm
machine(Smachine)
state(s)
rct(init):
logtrans "s-INIT"
nextstate(Smachine, s1)
rct(enter):
logtrans "s-ENTER"
return handled
rct(exit):
logtrans "s-EXIT"
return handled
rct Keypress:
if (evt.keyint == ord('i').byte) and fsm.foo == 1:
logtrans "s-I"
fsm.foo = 0
return handled
elif evt.keyint == ord('e').byte:
logtrans "s-E"
nextstate(Smachine, s11)
elif evt.keyint == ord('w').byte:
logtrans "got w"
return handled
elif evt.keyint == ord('v').byte:
logtrans "got v write DMA"
return handled
else:
return super
state s1
rct init:
logtrans "s1-INIT"
nextstate(Smachine, s11)
rct enter:
#discard
logtrans "s1-ENTER"
rct exit:
discard
logtrans "s1-EXIT"
rct Keypress:
if evt.keyint == ord('i').byte: #keyToint(Key.I)
logtrans "s-I"
discard
elif evt.keyint == ord('c').byte: # keyToint(Key.C):
logtrans "s1-C"
nextstate(Smachine, s2)
elif evt.keyint == ord('f').byte: #keyToint(Key.F):
logtrans "s1-F"
nextstate(Smachine, s211)
elif evt.keyint == ord('a').byte: #keyToint(Key.A):
logtrans "s1-A"
nextstate(Smachine, s1)
elif evt.keyint == charAscii('b'): #keyToint(Key.B):
logtrans "s1-B"
nextstate(Smachine, s11)
elif evt.keyint == charAscii('d') and fsm.foo == 0:
logtrans "s1-D"
fsm.foo = 1
nextstate(Smachine, s)
else:
return super
state s11
rct enter:
logtrans "s11-ENTER"
#discard
rct exit:
logtrans "s11-EXIT"
#discard
rct Keypress:
if evt.keyint == charAscii('g'): #keyToint(Key.G):
#logtrans "s11-G"
trace(SMachineT, "got G")
nextstate(Smachine, s211)
elif evt.keyint == charAscii('h'): #keyToint(Key.H):
logtrans "s11-H"
nextstate(Smachine, s)
elif evt.keyint == charAscii('q'): #keyToint(Key.Q):
logtrans "s11-Q"
nextstate(Smachine, s11)
elif evt.keyint == charAscii('d') and fsm.foo == 1:
logtrans "s11-D"
fsm.foo = 0
nextstate(Smachine, s1)
else:
return super
state s2
rct init:
logtrans "s2-INIT"
nextstate(Smachine, s211)
rct enter:
#discard
logtrans "s2-ENTER"
rct exit:
discard
logtrans "s2-EXIT"
rct Keypress:
if evt.keyint == charAscii('i') and fsm.foo == 0:
logtrans "s2-I"
fsm.foo = 1
return handled
elif evt.keyint == charAscii('f'): #keyToint(Key.F):
logtrans "s2-F"
nextstate(Smachine, s11)
elif evt.keyint == charAscii('c'): #keyToint(Key.C):
logtrans "s2-C"
nextstate(Smachine, s1)
else:
return super
state s21
rct init:
logtrans "s21-INIT"
nextstate(Smachine, s211)
rct enter:
#discard
logtrans "s21-ENTER"
rct exit:
#discard
logtrans "s21-EXIT"
rct Keypress:
if evt.keyint == charAscii('a'): #keyToint(Key.A):
trace(SmachineT, "s21-A")
logtrans "s21-A"
nextstate(Smachine, s21)
elif evt.keyint == charAscii('g'): #keyToint(Key.G):
logtrans "s21-G"
trace(SMachineT, "s21-G")
nextstate(Smachine, s1)
elif evt.keyint == charAscii('b'): # keyToint(Key.B):
#logtrans "s21-B"
nextstate(Smachine, s211)
else:
return super
state s211
rct enter:
logtrans "s211-ENTER"
#discard
rct exit:
#discard
logtrans "s211-EXIT"
rct Keypress:
if evt.keyint == charAscii('d'): #keyToint(Key.D):
logtrans "s211-D"
nextstate(Smachine, s21)
elif evt.keyint == charAscii('h'): #keyToint(Key.H):
logtrans "s211-H"
nextstate(Smachine, s)
else:
return super
initializer(Smachine, s2):
subscribe(Smachine, Keypress)
Smachine.foo = 0
build()
And it generates state handler functions for each state and puts them in the handler_functions array.
include ../core/imports
import
smachinestatesImp
import
taskerVarType
export
taskerVarType
import
taskerglobal
proc initializeSmachine(): (HandlerFuncIdx, Signals) =
subscribe(Smachine, Keypress)
Smachine.foo = 0
return (uint8(ord(s2)), transition)
proc handlerSmachine_s21(fsm: var Fsm; evt: var Event): Signals =
case evt.signal
of ignored:
return ignored
of exit:
logtrans "s21-EXIT"
of init:
logtrans "s21-INIT"
nextstate(Smachine, s211)
of enter:
logtrans "s21-ENTER"
of Keypress:
if evt.keyint == charAscii('a'):
trace(SmachineT, "s21-A")
logtrans "s21-A"
nextstate(Smachine, s21)
elif evt.keyint == charAscii('g'):
logtrans "s21-G"
trace(SMachineT, "s21-G")
nextstate(Smachine, s1)
elif evt.keyint == charAscii('b'):
nextstate(Smachine, s211)
else:
return super
else:
return super
proc handlerSmachine_s1(fsm: var Fsm; evt: var Event): Signals =
case evt.signal
of ignored:
return ignored
of exit:
discard
logtrans "s1-EXIT"
of init:
logtrans "s1-INIT"
nextstate(Smachine, s11)
of enter:
logtrans "s1-ENTER"
of Keypress:
if evt.keyint == ord('i').byte:
logtrans "s-I"
discard
elif evt.keyint == ord('c').byte:
logtrans "s1-C"
nextstate(Smachine, s2)
elif evt.keyint == ord('f').byte:
logtrans "s1-F"
nextstate(Smachine, s211)
elif evt.keyint == ord('a').byte:
logtrans "s1-A"
nextstate(Smachine, s1)
elif evt.keyint == charAscii('b'):
logtrans "s1-B"
nextstate(Smachine, s11)
elif evt.keyint == charAscii('d') and fsm.foo == 0:
logtrans "s1-D"
fsm.foo = 1
nextstate(Smachine, s)
else:
return super
else:
return super
proc handlerSmachine_s11(fsm: var Fsm; evt: var Event): Signals =
case evt.signal
of ignored:
return ignored
of exit:
logtrans "s11-EXIT"
of enter:
logtrans "s11-ENTER"
of Keypress:
if evt.keyint == charAscii('g'):
trace(SMachineT, "got G")
nextstate(Smachine, s211)
elif evt.keyint == charAscii('h'):
logtrans "s11-H"
nextstate(Smachine, s)
elif evt.keyint == charAscii('q'):
logtrans "s11-Q"
nextstate(Smachine, s11)
elif evt.keyint == charAscii('d') and fsm.foo == 1:
logtrans "s11-D"
fsm.foo = 0
nextstate(Smachine, s1)
else:
return super
else:
return super
proc handlerSmachine_s211(fsm: var Fsm; evt: var Event): Signals =
case evt.signal
of ignored:
return ignored
of exit:
logtrans "s211-EXIT"
of enter:
logtrans "s211-ENTER"
of Keypress:
if evt.keyint == charAscii('d'):
logtrans "s211-D"
nextstate(Smachine, s21)
elif evt.keyint == charAscii('h'):
logtrans "s211-H"
nextstate(Smachine, s)
else:
return super
else:
return super
proc handlerSmachine_s2(fsm: var Fsm; evt: var Event): Signals =
case evt.signal
of ignored:
return ignored
of exit:
discard
logtrans "s2-EXIT"
of init:
logtrans "s2-INIT"
nextstate(Smachine, s211)
of enter:
logtrans "s2-ENTER"
of Keypress:
if evt.keyint == charAscii('i') and fsm.foo == 0:
logtrans "s2-I"
fsm.foo = 1
return handled
elif evt.keyint == charAscii('f'):
logtrans "s2-F"
nextstate(Smachine, s11)
elif evt.keyint == charAscii('c'):
logtrans "s2-C"
nextstate(Smachine, s1)
else:
return super
else:
return super
proc handlerSmachine_s(fsm: var Fsm; evt: var Event): Signals =
case evt.signal
of ignored:
return ignored
of exit:
logtrans "s-EXIT"
return handled
of init:
logtrans "s-INIT"
nextstate(Smachine, s1)
of enter:
logtrans "s-ENTER"
return handled
of Keypress:
if (evt.keyint == ord('i').byte) and fsm.foo == 1:
logtrans "s-I"
fsm.foo = 0
return handled
elif evt.keyint == ord('e').byte:
logtrans "s-E"
nextstate(Smachine, s11)
elif evt.keyint == ord('w').byte:
logtrans "got w"
return handled
elif evt.keyint == ord('v').byte:
logtrans "got v write DMA"
return handled
else:
return super
else:
return super
Smachine.handler_functions[ord(SmachineStates.s21)] = handlerSmachine_s21
Smachine.handler_functions[ord(SmachineStates.s1)] = handlerSmachine_s1
Smachine.handler_functions[ord(SmachineStates.s11)] = handlerSmachine_s11
Smachine.handler_functions[ord(SmachineStates.s211)] = handlerSmachine_s211
Smachine.handler_functions[ord(SmachineStates.s2)] = handlerSmachine_s2
Smachine.handler_functions[ord(SmachineStates.s)] = handlerSmachine_s
Smachine.initializer = initializeSmachine
Smachine.machinecopy = AllMachines.Smachine
So there are four compilation steps.
A cfg file helps to automate all the compilation steps.
Other Nim benefits
So to summarize, I don't think wrapping C is the way to go. More power in using the Nim compiler.
Show me a repo? Ok will set something up.
Show me a repo?
Show me a repo :-)