Docs Concepts

Topics

Named, typed channels for inter-module communication.

Topics

A topic is a string name paired with a Java type. Publishers push values onto it; subscribers receive every value; anything can read the latest value on demand. Topics are the only way modules communicate — no direct references, no shared mutable state.

Topic<Double> power = orchestrator.getOrCreateTopic("intake/set/power", Double.class);
orchestrator.publish("intake/set/power", 0.75);

Naming

Slash-separated paths, rooted by the producing subsystem:

g1/left_stick_y          gamepad axis (published by GamepadAdaptor)
g1/a/rising              button press edge
drive/power/fl           per-wheel power (published by the drive node)
intake/power             current intake power

The root of the path says who publishes it: g1/... is gamepad 1, drive/... is the drive subsystem. Consumers never publish to another subsystem’s topics — they publish to their own state topics and subscribe to what they need.

Typing

A topic’s type is fixed at creation and checked on every publish.

  • Primitives and wrappers are interchangeable. double.class and Double.class name the same topic type, so an annotation method declared double power binds cleanly to a Double-typed topic.
  • Compatibility uses assignability, so a Number topic accepts Integer and Double publishes, and an Object topic accepts anything.
  • Publishing an incompatible value throws IllegalArgumentException with both type names — e.g. publishing a Double to a topic created as Integer.
  • Reading with the wrong type is safe: getLatestValue returns an empty Optional rather than throwing.

Decide topic types up front. Because the first publish creates the topic from the value’s runtime type, an accidental publish("battery/volts", 12) (an int) would lock the topic to Integer and make every later Double publish throw. Declare the type with getOrCreateTopic at the top of the owning node, then publish untyped values freely.

Auto-creation

You never have to pre-create a topic — the first publish creates it from the value’s runtime type, and the first subscribe creates it from the declared handler type. Pre-creating with getOrCreateTopic is still recommended for anything shared between modules, because it pins the type at one obvious place.

GamepadAdaptor pre-creates all of its topics when attached, so you can subscribe to g1/a/rising before the first gamepad poll ever runs.

Reading the latest value

Every topic remembers its most recent value:

Optional<Double> latest = orchestrator.getLatestValue("drive/power/fl", Double.class);
double fl = latest.orElse(0.0);

Topic<Double> flTopic = orchestrator.findTopic("drive/power/fl", Double.class).orElseThrow();
double fl2 = flTopic.latestValueOr(0.0);
long nanosSinceUpdate = System.nanoTime() - flTopic.latestPublishNanos();

Fetching the latest value is the right tool inside periodic loops; subscribing is the right tool when the event matters, not the current state. See Subscriptions for the tradeoff.

Next steps