3. Core Concepts
The mental model behind every Psyclone system: the PsySpec blueprint, the three component kinds, the publish/subscribe message bus, one-shot vs continuous execution, and what happens at start-up.
The PsySpec: one file describes the system
A Psyclone system is declared in a single XML file, the PsySpec, given at start-up with spec=<file>. It states which components to create — modules, whiteboards, catalogs — and how data flows between them. After start-up, the system can additionally create components dynamically that were never in the PsySpec.
The general shape of a PsySpec:
<psySpec>
<variable name="MainDir" value="./data" /> <!-- %MainDir% substitution -->
<include file="system.xml" /> <!-- splice in another file -->
<library name="MyExamples" library="Examples" />
<node name="Node1" address="otherhost" port="10000" />
<module name="..."> ... </module>
<whiteboard name="..."> ... </whiteboard>
<catalog name="..." type="..."> ... </catalog>
</psySpec>
<variable>entries define%name%text substitutions applied over the whole spec before parsing; aname=valuecommand-line argument overrides the spec default.<include>splices another XML file in place, like a C include; the full XML is parsed only after all includes and variable substitution.<library>registers a DLL/SO of crank functions.<node>entries attach other computers to the system (see Distributed Systems).
The complete element-by-element reference — with honest implemented / stub / roadmap status per attribute — is chapter 13.
Three kinds of components
| Kind | Behaviour | Typical use |
|---|---|---|
| Module | Triggers match incoming messages; a crank function processes them; posts publish results. May stay resident in its crank (continuous) or run once per trigger (one-shot). | All processing: sensor handling, analysis, decision logic, actuation. |
| Catalog | Everything a module does, plus answering synchronous queries with replies. | Data stores with a simple query language, or conduits to other systems (search services, recognition servers, other robots). |
| Whiteboard | A special catalog that stores messages only, with a sophisticated retrieval query language and visual viewing/filtering in PsyProbe. | Short-term shared memory: keep recent messages available for later retrieval. |
Every component has at least one crank. Modules declare theirs in the spec; whiteboards have a built-in crank; for catalogs the crank is the catalog type.
The publish/subscribe message bus
Components never call each other. Instead, every message carries a dot-notation type — e.g. input.audio.raw, input.audio.normalised — and components subscribe to types with <trigger> elements, exactly or with wildcards (input.audio.*, input.*.raw). A <post> publishes a message of a given type without knowing (or caring) who receives it. Messages nobody subscribes to are simply discarded (or kept in shared memory when a ttl is set).
Crucially, trigger and post names are separate from message types: crank code refers only to names (api->waitForNewMessage(100, "Input"), api->postOutputMessage("Output", msg)). Rerouting the system — say, feeding a module smoothed instead of raw frames — is a PsySpec edit only; no code changes.
input.audio.raw to every matching subscription — exact or wildcard — and skips the rest.Posting from code returns the number of messages actually delivered; negative values report failures: POST_FAILED (-1) error, POST_NOSPEC (-2) posted before any trigger arrived, POST_OUTOFCONTEXT (-3) the context changed and this crank is no longer active (see Contexts).
Alongside messages, Psyclone offers signals: a lightweight broadcast that bypasses subscription checking, used like a conductor's stick to beat time in simulations — see Messaging & Signals.
One-shot vs continuous components
A crank can run in two modes, chosen purely by how you write it:
- One-shot: the crank runs once per trigger and returns; the thread goes back to the pool. Cheap and scalable — the default choice.
- Continuous: the crank keeps its OS thread with a
while (api->shouldContinue())loop, callingwaitForNewMessage(…)with a timeout so it can also do other work every cycle. Worth it for heavy initialisation or large local persistent data — but each continuous component holds a thread, and many of them can hurt performance.
Psyclone automatically maintains a pool of spare threads either way. Details and code patterns are in Modules.
How a system starts up
- Psyclone reads the PsySpec, expands all
<include>files and%variable%substitutions, then parses the result. - Crank libraries are located and loaded; nodes and process spaces are set up; every declared component is created and its subscriptions registered.
- When everything is in place, the system posts the
Psyclone.Readymessage and printsSYSTEM READY. Any module with a trigger onPsyclone.Readystarts immediately — that is how ping-pong serves the first ball. - The system runs until a component posts
Psyclone.Shutdown(or the process is stopped), then printsSYSTEM SHUTDOWN.
Psyclone.Ready is also the name of the default context, active for as long as the system runs. Everything you declare outside an explicit <context> block lives there — a fact that becomes important in chapter 6.