Overview
The Event System is what elevates ProDex from a process-flow simulator to a system that can model sophisticated operational logic. It’s built on three primitives:- Topics — pub/sub messaging between components
- State Variables — mutable runtime state that lives on the model
- Event Hooks — actions that fire on lifecycle events
Topics
A Topic is a named channel components can publish to and subscribe to. When a component publishes a message on a topic, any component subscribed to that topic receives the message and can react. Topics enable coordination that isn’t expressible through flow connections alone — one component can trigger behavior in another without being directly wired to it. Example — kanban signal: when a downstream buffer drops below a threshold, publish areplenish message on a topic. An upstream Source subscribed to that topic reacts by releasing more material.
State Variables
A State Variable is a named, mutable value that lives on the model (not on any individual entity). State variables persist across entities — they retain their value as entities flow through the model, and they can be read from any expression. Use state variables for anything that represents the state of the system rather than the state of a single entity: current shift, machine uptime counters, inventory levels, demand signals, rolling averages.State Variables vs. Entity Attributes
- Entity attribute — belongs to a specific entity and moves with it through the flow. Defined on the entity type.
- State Variable — belongs to the model and persists independently of entities. Defined at the model level.
Event Hooks
An Event Hook is a rule that fires an action when a lifecycle event occurs on a component. Hooks connect lifecycle events to actions — the event determines when the hook fires, the action determines what it does.Canonical Events
These are the lifecycle events components emit. Each event is only valid on specific component types, and each runs its condition and action expressions in a specific context.| Event | Fires On | Context |
|---|---|---|
on_entity_created | Source | single-entity (the newly created entity) |
on_entity_created | Combiner (output), Separator (output), Transformer (output) | single-entity (the output entity) |
on_entity_entered | BufferNode, Station | single-entity |
on_entity_exited | BufferNode, Station | single-entity |
on_process_started | Process | single-entity |
on_process_completed | Process | single-entity |
on_entity_routed | Router | single-entity |
on_entity_consumed | Combiner, Separator, Transformer | single-entity (the input being consumed) |
on_batch_assembled | Combiner | multi-entity (all inputs in the batch) |
on_batch_created | Separator | multi-entity (all outputs being produced) |
on_slot_freed | Resource | no-entity |
on_slot_working | Resource | no-entity |
on_entity_terminated | Sink | single-entity |
on_entity_entered/on_entity_exitedonly fire on BufferNode and Station, not on every component that has entities passing through. Processes don’t emit them — useon_process_started/on_process_completedinstead. Routers don’t emit them — useon_entity_routed.on_slot_freedandon_slot_workingare on Resources, not Stations. Stations have entity enter/exit events; Resources have slot events.on_batch_createdis a Separator event (one input becomes many outputs — multi-entity context over the outputs). Combiner’s output event ison_entity_created, in single-entity context.
Context Rules
The event’s context determines what identifiers your hook’s condition and action expressions can reference:- No-entity context —
SIM_TIME, component-query functions, constants, state variables, andSELF(the component). No entity attributes. - Single-entity context — everything above plus entity attributes by bare name (
priority,weight) andENTITY_TYPE,ENTITY_AGE. - Multi-entity context — no-entity identifiers plus aggregation functions over the set of entities (
SUM(weight),ANY(priority > 5)).
Action Types
When a hook fires, it executes an action. The valid actions:assign— write to a State Variable. Example:assign(current_shift, "night")orassign(wip_count, wip_count + 1).emit— publish a message to a Topic. Example:emit("replenish"). Triggers any subscribed components to react.release_entity— release an entity on demand. Valid on Sources (releases a new entity) and BufferNodes (releases the next queued entity from a paused or hold-mode buffer). Used for pull-based patterns where release is driven by an event, not by arrival logic or buffer state alone.pause— pause a component. The component stops accepting new entities and stops processing until resumed.resume— resume a paused component.set_capacity— change the capacity of a Resource at runtime. Used for shift-driven capacity patterns. (Not valid on Stations — to change station behavior via events, usepause/resumeor model it as resource capacity.)
Cycle Detection
The Event System detects and rejects cycles and self-loops at validation time. Specifically:- A hook that listens for an event on component A and fires an action that triggers another event on component A, creating a loop, is rejected.
- A topic that both publishes from and subscribes to the same component in a way that creates an infinite loop is rejected.
Scheduled Actions
A Schedule can also fire actions at specific simulation times, independent of any component lifecycle event. Scheduled actions are more restricted than Event Hook actions:- Only
assignandemitare valid as scheduled actions. - Component-bound actions (
pause,resume,set_capacity,release_entity) are not available in schedules. Use a scheduledemitto a topic, then attach an Event Hook that subscribes to the topic and performs the component-bound action.
Common Patterns
Kanban / Pull-Based Production. Downstream components signal upstream sources when they need more material. Combine a State Variable (current WIP) with an Event Hook (publish to a topic when WIP drops below a threshold) and a subscription on the Source with arelease_entity action.
Backpressure. When a downstream buffer is full, pause or slow upstream arrivals. An Event Hook watches buffer level changes and updates a state variable that the Source’s arrival-rate expression reads.
Shift-Driven Capacity Changes. Resources change capacity by time of day. A scheduled emit("shift_start_night") fires at the shift boundary; an Event Hook on the resource subscribes to that topic and runs set_capacity(resource, night_capacity).
Cross-Component Coordination. Two distant components that aren’t connected by flow can still coordinate through a shared topic. One publishes, the other reacts.
When to Reach For the Event System
Most simple models don’t need events — a Source, a few Processes, a Sink, and you’re running. Reach for events when:- You need behavior that spans multiple components without direct flow
- You need the model to react to operational rules (shift schedules, priority changes, demand surges)
- You’re modeling lean or pull systems where downstream pulls from upstream
- You need to track and react to cross-cutting metrics (rolling WIP, utilization)

