Skip to content

Tutorial: ControllableComponent and Execution Frames

Most lifecycle-driven Jungle components (Reactor, Visualizer, StateMachine, Detector, Constrainer, ProcessComponent, AttachingAgent's holding point) inherit the same execution-frame model from ControllableComponent. Learning it once means controlling every one of them the same way.

By the end of this tutorial you'll understand how to make any of those components begin and end on:

  • A GameObject enable/disable.
  • A specific Unity callback (Start, OnDestroy).
  • An IEvent pair (begin on click, end on second click).
  • Another component's lifecycle cycle (an Attachable's Attached, an Agent's Holding).
  • A manual Begin() / End() call from code.

What's a ControllableComponent?

It's a MonoBehaviour base class that wraps any "I do work between a Begin and an End" component in a uniform lifecycle harness.

Each subclass implements two protected methods:

  • OnBegin() — start the work (subscribe, spawn, perform layout, whatever).
  • OnEnd() — stop and clean up (unsubscribe, reclaim, complete in-flight).

There is a third, optional one: OnStop(), which defaults to calling OnEnd(). It runs instead of OnEnd when the component goes away with its scene, where finishing in-flight work would read objects that are already destroyed. Override it when your component has something to unschedule. On that path Ended is not raised either: anything watching this component through a Controllable Cycle frame gets no end edge, because acting on one would finish runs and start end bodies against objects the scene is destroying. Dependents in the same scene take their own unload stop.

When Begin is called, the base raises lifecycle events, calls OnBegin, marks itself as running. When End is called, the inverse.

The clever part: who calls Begin / End is configurable per-component, via an Execution Frame.


The Execution Frame

Every ControllableComponent serializes one IExecutionFrame. The frame decides when to invoke Begin / End. Common frame types:

Frame Begins on Ends on
Mono Lifecycle Frame A chosen MonoBehaviour callback (e.g. OnEnable, Start, Awake). The matching teardown callback (OnDisable, OnDestroy).
Event Frame Any configured IEvent raises. A second IEvent raises (or never — single-shot).
Targeted Cycle Frame A cycle on another component begins (e.g. AttachingAgent's Holding). The same cycle ends.
External Frame Only when you call Begin() / End() manually. Same — fully manual.

So one swap of the Execution Frame field turns "this Reactor runs while the GameObject is active" into "this Reactor runs while the player is holding an object". No code change, no different component.


1. Default: GameObject Enable

Every freshly-added ControllableComponent ships with Mono Lifecycle Frame configured for OnEnable / OnDisable. That means:

  • The component begins when the GameObject becomes active.
  • The component ends when the GameObject becomes inactive.

90% of the time this is what you want. Done.


2. Switch to Start (One-Shot)

If your component should run a single Begin pass and never restart on re-enable, swap to Mono Lifecycle Frame with the trigger set to Start / OnDestroy.

Useful for one-time setup: a Process Component whose Value Command writes starting values into a Data Storage, then never touches them again.


3. Bind to an Event Pair

For "begin on event A, end on event B" semantics:

  1. Set the Execution Frame to Event Frame.
  2. beginEvent — drop an IEvent source in (a MouseButtonEvent, EventFromAsset, SignalEventFromHub, anything).
  3. endEvent — another IEvent, or leave it for "never ends" / "ends manually".

Example: a Constrainer that's only active during a special phase — set beginEvent to OnPhaseStart, endEvent to OnPhaseEnd.


4. Bind to Another Component's Cycle

The most powerful frame: Targeted Cycle Frame. Pick a component that exposes an ICycleProvider<T> (most Octoputs and many Jungle components do) and a cycle kind. The ControllableComponent begins when that cycle begins and ends when it ends.

Real examples:

Host of the ControllableComponent Targeted cycle Result
A Reactor on an AttachableObject The Attachable's Attached cycle Reactor runs while the object is attached, idle otherwise.
A Process Component on an AttachingAgent The Agent's Holding cycle Its Value Command holds "carry weight" values while the agent is holding.
A Process Component on a Cube The Cube's Attached cycle Its Measurement Command only measures speed and distance while attached.

The key insight: the cycle is owned by another component, but the ControllableComponent doesn't care which one — it just needs an ICycleProvider. Swap the source, swap the trigger, no code change.


5. Manual Control from Code

For full custom control, set the Execution Frame to External Frame (or some equivalent "manual" option). Then call controllable.Begin() and controllable.End() from your script.

Used when the begin/end logic is too custom for any existing frame — e.g. tied to a finite state machine you wrote yourself, or a network event you're authoritatively dispatching.


6. Lifecycle Events

Every ControllableComponent fires C# events around its lifecycle:

Event When
Began Raised as Begin fires, before the subclass's own logic runs.
Ended Raised after End has finished the subclass's own logic. Not raised when the component goes away with its scene.

Subscribe from code via += when you need to observe lifecycle externally. You can also use a Controllable Cycle execution frame on another component to begin/end in sync with any ControllableComponent.


7. Why This Matters

This pattern is the unifying abstraction in Jungle's lifecycle layer. It means:

  • You don't memorize different lifecycle semantics per component — they all behave the same.
  • "When does this run?" is always one question with one Inspector field as the answer.
  • Rebinding when is a serializable Inspector change, not a code change.

If a component inherits ControllableComponent, you can move it from "active when GameObject enabled" to "active during a specific cycle" with a single dropdown.


8. Cycle vs Targeted Cycle

Some components expose targeted cycles — cycles with a payload (the AttachableObject currently in flight, the GameObject currently being dragged). The Targeted Cycle Frame can subscribe with the payload, so the ControllableComponent receives "begin with target X" / "end with target X" hooks. Useful when the work needs to know what triggered the cycle.

Non-targeted cycles are simpler — Begin/End with no payload.


Next Steps