Runtime Events Intermediate
Hook into LOGO machine lifecycle events โ react when programs start or stop, and when multiblock structures are assembled or taken apart.
Do I need this page?
- Yes if your addon needs to initialize/cleanup state when a program starts or stops
- Yes if you need to react when a LOGO multiblock is built or broken
- No if your nodes are self-contained โ most simple addons don't need lifecycle hooks
Prerequisite: Core Concepts
On this page
What are Runtime Events?
Runtime events are lifecycle hooks that fire at critical moments in a LOGO machine's operation. They let addon mods:
- Initialize/clean up addon state per machine
- Log machine activity
- Sync data with external systems
- React to structure changes
| Component | Package | Purpose |
|---|---|---|
ISpcRuntimeEventListener | api.event | Interface with 4 default events to override |
SpcRuntimeEventRegistry | api.event | Where you register your listener |
Machine Lifecycle
This diagram shows when each event fires in the LOGO machine lifecycle:
ISpcRuntimeEventListener โ All 4 Events
All methods are default โ override only the ones you need.
Both parameters are always provided by the SPC runtime.
| Event Method | Parameters | When It Fires |
|---|---|---|
onProgramStart(ServerLevel level, BlockPos machinePos) |
level โ the server levelmachinePos โ processing unit anchor position |
A LOGO program begins execution on a machine |
onProgramStop(ServerLevel level, BlockPos machinePos) |
Same as above | A running program is stopped (manual stop, error, or chunk unload) |
onMultiblockAssemble(ServerLevel level, BlockPos machinePos) |
Same as above | Multiblock structure is successfully validated |
onMultiblockDisassemble(ServerLevel level, BlockPos machinePos) |
Same as above | Multiblock breaks (block removed, structure invalidated) |
Firing order guarantees
| Guarantee | Details |
|---|---|
| Assemble before Start | onMultiblockAssemble always fires before onProgramStart |
| Stop before Disassemble | If a program is running, onProgramStop fires before onMultiblockDisassemble |
| All listeners called | All registered listeners are iterated in registration order |
| Server-side only | Events only fire on the logical server (ServerLevel) |
SpcRuntimeEventRegistry
| Method | Returns | Parameters | Description |
|---|---|---|---|
register(ISpcRuntimeEventListener) |
void |
listener โ non-null |
Register a listener. Must call during mod init (before freeze). |
all() |
List<ISpcRuntimeEventListener> |
โ | Unmodifiable snapshot of all registered listeners |
freeze() |
void |
โ | Internal โ do not call |
Registration uses CopyOnWriteArrayList internally โ thread-safe even during parallel mod loading.
Use Cases for Each Event
| Event | Use Case | Example |
|---|---|---|
onProgramStart |
Initialize per-machine addon state | Allocate a rotation history buffer for trending |
onProgramStart |
Start external monitoring | Begin logging to a file or network dashboard |
onProgramStop |
Clean up per-machine state | Release buffers, flush logs, reset counters |
onProgramStop |
Safety shutdown | Set all addon actuators to safe positions |
onMultiblockAssemble |
Scan for addon modules | Find your custom blocks in the structure |
onMultiblockAssemble |
Show notifications | Log that a machine with addon modules was built |
onMultiblockDisassemble |
Invalidate cached references | Drop references to block entities in the structure |
onMultiblockDisassemble |
Emergency stop | Cut power to connected machines immediately |
Complete Example
public class MyAddonListener implements ISpcRuntimeEventListener {
private final Map<BlockPos, AddonState> machines = new HashMap<>();
@Override
public void onMultiblockAssemble(ServerLevel level, BlockPos machinePos) {
LOGGER.info("LOGO machine assembled at {}", machinePos);
}
@Override
public void onProgramStart(ServerLevel level, BlockPos machinePos) {
machines.put(machinePos, new AddonState());
LOGGER.info("Program started at {}", machinePos);
}
@Override
public void onProgramStop(ServerLevel level, BlockPos machinePos) {
AddonState state = machines.remove(machinePos);
if (state != null) state.cleanup();
LOGGER.info("Program stopped at {}", machinePos);
}
@Override
public void onMultiblockDisassemble(ServerLevel level, BlockPos machinePos) {
machines.remove(machinePos);
LOGGER.info("LOGO machine disassembled at {}", machinePos);
}
}
// In your @Mod constructor:
SpcRuntimeEventRegistry.register(new MyAddonListener());
Tips & Pitfalls
| Do | Don't |
|---|---|
| Keep event handlers fast โ they run on the server thread | Don't do heavy I/O or network calls synchronously |
Use machinePos as a key for per-machine state |
Don't store world references โ they can leak memory |
Clean up in onProgramStop and onMultiblockDisassemble |
Don't assume both fire โ blocks can break without program stop if never started |
| Register during mod init (constructor or FMLCommonSetupEvent) | Don't register after freeze โ it will throw IllegalStateException |