Sequence
A Sequence node ticks all underlying nodes as long as they return Success.
Otherwise, when a child returns failure the sequence is aborted.
In the language, the tree definitions and lambda invocations of this element are marked with the key word sequence.
impl store(key:string, value:string); // store a string value to a key in blackboard
root main {
sequence {
store("a","1") // first tick and proceed if succeeded
store("b","2") // sec tick and proceed if succeeded
store("c","3") // thrd tick and finish if succeeded
}
}
with a graph representation
strict digraph {
1[label="root
main ",shape=rect,color=black]
1 -> 2
2[label="sequence",shape=rect,color=darkred]
2 -> 3
2 -> 4
2 -> 5
3[label="store (key=a,default=1)",shape=component,color=green]
4[label="store (key=b,default=2)",shape=component,color=green]
5[label="store (key=c,default=3)",shape=component,color=green]
}
Common behaviour
- When it gets the first
tickit switches to staterunning - When a child returns
successit proceeds to the next one and ticks it- if this is a final child, it returns
success
- if this is a final child, it returns
- If a child returns
running, the node returnsrunningas well - If a child returns
failure, the node returnsfailureas well - When a node is restarted or halted, the process starts from the beginning (see memory sequence for an exception)
Intention
Often, it is used as a straight chain of instructions
// if the definition has only one child
// (root has only one child) '{''}' can be omitted
root main sequence {
validate_env()
perform_action()
finish_and_save()
}
Subtypes
There are 2 subtypes that bring a few subtleties to the common process.
Memory Sequence
This sequence defines in the language with the keyword m_sequence and has the following peculiarity:
The sequence memorizes the children that have succeeded and skips them next time.
root main {
retry(5) m_sequence {
store("key",1) // returns success
perform_action() // returns failure
finish_and_save()
}
}
The node perform_action returns failure and the decorator retry restarts sequence.
The main difference with a sequence is an execution starts from the node perform_action skipping the node store.
This memory persists even if the m_sequence is halted by a reactive flow node. The memory will only be reset once the final action has returned success.
That is, if finish_and_save returns success, the next iteration will start with store again.
Reactive Sequence
This sequence defines in the language with the keyword r_sequence and has the following peculiarity:
The sequence restarts all children if they return either failure or running.
root main {
r_sequence {
store("key",1) // returns success
perform_action() // returns running
finish_and_save()
}
}
The node perform_action returns running and the whole sequence returns running
but on the next tick it starts from the node store again.
r_sequence will halt the running child to allow a graceful shutdown if a prior child changes from success to failure or running. In the above example, if store returned failure on the second tick then perform_action would be halted before r_sequence returned failure itself.
If store returned running on the second tick then perform_action would also be halted, before potentially being restarted if store then returned success again. Limiting the r_sequence node to a single running child avoids undefined behaviour in nodes that assume they are being run synchronously.
Halting must be performed as quickly as possible. Note that currently only build-in flow, built-in decorator and sync action nodes are halted, async and remote actions are not.