Parallel

Runs commands in parallel (sub-sequence). This is not true concurrency, but rather, each part of each command is run in the order that they are in the constructor. (1’s initialise is run, then 2’s initialise, etc, etc.). It finishes when all commands it contains finish.

new Parallel(
	new Lambda("1"),
	new Lambda("2")
	/*, varargs commands*/
);

The s-expression representation of this command is:

(parallel (
	1
	2))

The Parallel.addCommands() method is non-mutating, and returns a new Parallel.

A command can be chained into a Parallel via the Command.with() method, this code snippet is the same as above:

new Lambda("1")
	.with(
		new Lambda("2")
		/*, varag commands*/
	);

Sequential

Runs commands in sequence. (1 is run until it finishes, then 2, etc, etc.). It finishes when all commands it contains finish.

new Sequential(
	new Lambda("1"),
	new Lambda("2")
	/*, varargs commands*/
);

The s-expression representation of this command is:

(sequential (
	1
	2))

The Sequential.addCommands() method is non-mutating, and returns a new Sequential.

A command can be chained into a Sequential via the Command.then() method, this code snippet is the same as above:

new Lambda("1")
	.then(
		new Lambda("2")
		/*, varag commands*/
	);

Race / Deadline

Runs commands the same as Parallel, but Race finishes as soon as any command finishes, and Deadline finishes when the deadline command finishes. This early end does not interrupt the rest of the unfinished commands.

Mercurial only has one class for both of these: Race.

// race
new Race(
	null,
	new Lambda("1"),
	new Lambda("2")
);
// deadline
new Race(
	new Lambda("deadline"),
	new Lambda("1"),
	new Lambda("2")
);

The s-expression representation of this command is:

(race (
	1
	2))

(race deadline (
	1
	2))

deadline is omitted if it is null.

The Race.addCommands() and Race.setDeadline() methods are non-mutating, and return a new Race.

A command can be chained into a Race via the Command.raceWith() method, this code snippet is the same as above:

new Lambda("1")
	.raceWith(
		new Lambda("2")
		/*, varag commands*/
	);

A command can be chained into a Race as the deadline via the Command.asDeadline() method, this code snippet is the same as above:

new Lambda("deadline")
	.asDeadline(
		new Lambda("1"),
		new Lambda("2")
		/*, varag commands*/
	);

Wait

Wait waits its argument in seconds. This example waits 1 second:

new Wait(1.0);

The s-expression representation of this command is:

(wait 1.0)

A command can be chained into a Race with a Wait as the deadline via the Command.timeout(duration) method, these two are equivalent:

new Lambda("1").timeout(1.0);

new Race(new Wait(1.0), new Lambda("1"));

IfElse

If else takes a BooleanSupplier and a command to run if true and a command to run if false. When the command is initialised it runs the supplier and uses the result to decide if to run trueCommand or falseCommand.

new IfElse(
	() -> true,
	new Lambda("1"),
	new Lambda("2")
);

This example will always run 1, because it always returns true.

The s-expression representation of this command is:

(? true 1 2)

StateMachine

StateMachine is a Finite State Machine (FSM) command. It makes it easy to assemble a state machine where each state is a command. It finishes if the command for the current state is finished, and there isn’t a state change underway.

We’ll use this enum for the example:

enum States {
	START,
	MIDDLE,
	END;
}
StateMachine<States> selectionCommand = new StateMachine<>(States.START)
	// withState is a non-mutating method
	// it takes a state,
	// and a function that takes in a RefCell to the state along with an
	// escaped name that matches the name of the state
	.withState(States.START, (RefCell<States> state, String name) ->
		// we can use our own name, or use the name from the lambda to use the name of the state
		new Lambda("demo")
			// to change the state, we can enter a new state in the cell
			// if the new state isn't different, then nothing will happen
			// but if you set the state to something different, then back to the
			// previous state, then the current command will be cancelled and restarted
			.addEnd((interrupted) -> state.accept(States.MIDDLE))
	)
	.withState(States.MIDDLE, (state, name) ->
		new Lambda(name)
			.addEnd(interrupted -> {
				if (interrupted) {
					state.accept(States.END);
				}
				else {
					state.accept(States.START);
				}
			})
	)
	.withState(States.END, (state, name) -> new Lambda(name));

// we can also set the state externally
selectionCommand.setState(States.START);

StateMachine can work with any state type, read more about RefCell in Util.

Its important to note that StateMachine.withState() is non-mutating.

Additionally, you must ensure that the StateMachine never has a state that doesn’t exist while scheduled, as this will cause it to throw an error.

The name that is passed to the withState callback is a preescaped name that matches the name of the state.

The s-expression representation of this command is:

(state-machine START (
	(START demo)
	MIDDLE
	END))

StateMachine displays each state-command pair as (state command) by default, however, if state and command have the same name, then it just becomes state, which we can see above.

Advancing

Advancing is more of a utility for commands bound to user input.

Advancing moves forwards through the list whenever it is schedule()d again. This means that if it is bound to a button or similar, it makes it easy to write some command toggle or state switcher.

In this manner, it acts a bit like Sequential but does not advance by itself. At the same time, it is a bit like a rudimentary StateMachine.

Advancing advancing = new Advancing(
	new Lambda("1"),
	new Lambda("2")
	/*, varag commands*/
);

Advancing wraps around, and can be manually advanced like so:

advancing.advance();

So if you advance at command ‘2’, then it wraps back to ‘1’.

Additionally, manual advancement needs a warning, manual advancement is a ‘request’ for advancement, rather than immediate advancement. This means that the advancement needs to be processed by the advancing command itself before Advancing.advance() will have affect again.

The s-expression representation of this command is:

(advancing (
	1
	2))