States, Modules and Events

BattleScript is an event-driven language. A BattleCore device runs a finite state machine, and your script registers logic that runs in response to events. That logic is organised in three nested layers:

  • state — a mode the device can be in (for example startup, in-game, dead).
  • module — a source of events within that state (for example the FSM, a trigger input, the IR receiver).
  • event — a specific event from that module (for example entering the state, a button going down, a hit being received).

When an event occurs, the runtime looks for a matching state moduleevent handler for the current state. If no handler matches, the event is ignored.

Basic structure

Each layer takes an expression — most commonly an enum member, but a string literal works too. The event handler is a function-like block that receives the event object; by convention it is typed EventData and named evt.

state StateEnum.IN_GAME {
	module ModuleEnum.FSM {
		event EventEnum.ENTER (EventData evt) => {
			std::console.log("Entered the IN_GAME state");
			battlecore::screens.setScreen("IN_GAME");
		}
	}

	module ModuleEnum.INPUT_TRIGGER {
		event EventEnum.DOWN (EventData evt) => {
			if (battlecore::weapon.getCanFire()) {
				battlecore::weapon.fire();
			}
		}
	}
}

A single state may contain many module blocks, and a single module may contain many event handlers.

The event object

Every handler receives an evt object describing the event that fired. It always has exactly these four fields:

  • evt.stateName — the state the device was in when the event fired.
  • evt.moduleName — the module that emitted it.
  • evt.eventName — the event name.
  • evt.data — the event-specific payload, for example evt.data.playerId or evt.data.hitValue on an IR hit.

Everything the emitting module sends lives under evt.data — including the state transition details on FSM events, which are evt.data.oldState and evt.data.newState (not evt.oldState). See the Device Events reference for the payload of every event a BattleCore device emits.

EventDatais a placeholder type name meaning "an event payload of some sort". It is not a built-in type today, and the parameter may be omitted entirely when a handler does not need the payload. Per-event strict types are planned for a future release so that handler bodies can be checked against the event they are bound to.

state StateEnum.IN_GAME {
	module ModuleEnum.IR {
		event EventEnum.IN (EventData evt) => {
			// Ignore hits from our own team in a team-based game
			if (battlecore::game.getIsTeamBased() == true && evt.data.teamId == battlecore::player.getTeamId()) {
				return;
			}

			if (evt.data.bulletTypeId == 0) {
				battlecore::player.takeDamage(evt.data.hitValue);
			}
		}
	}
}

The wildcard catch-all

The "_" wildcard matches any state, module or event. It is handy for logging or for fallback behaviour that should run regardless of the current state.

state "_" {
	module "_" {
		event "_" (EventData evt) => {
			std::console.log("Event fired:", evt.stateName, evt.moduleName, evt.eventName);
		}
	}
}

The wildcard can be used at any single layer too — for example a specific state with a module "_" to handle every module within that state.

Changing state

The current state is driven by the device firmware via the battlecore::fsm module. Read it with battlecore::fsm.getState() and change it with battlecore::fsm.setState(...). Changing state causes the FSM to emit EXIT on the old state and ENTER on the new one.

state StateEnum.STARTUP {
	module ModuleEnum.FSM {
		event EventEnum.ENTER (EventData evt) => {
			std::console.log("Startup complete, entering HOME");
			battlecore::fsm.setState(StateEnum.HOME);
		}
	}
}

See the battlecore::fsm module for the full state-machine API.

Related Topics

  • Enums — define the state, module and event names.
  • Timers — schedule work with after and every inside handlers.
  • Interfaces — describe the shape of event payloads.
  • BattleCore Device API — the modules that emit events and act on them.