Skip to content

Tutorial: ControllableComponent and Execution Frames

Most lifecycle-driven Jungle components — Reactor, Setter, Measurer, Visualizer, LayoutComponent, StateMachine, Detector, Constrainer, Transformer, ProcessComponent, AttachingAgent's holding point — all 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).

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 initialization Setters: "write these starting values into the DataStorage, then never touch 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 Setter on an AttachingAgent The Agent's Holding cycle Setter writes "carry weight" values while the agent is holding.
A Measurer on a Cube The Cube's Attached cycle Speed / distance only measured 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 public events around its lifecycle:

Event When
OnBeginning About to begin (pre-OnBegin).
OnBegan Begin complete.
OnEnding About to end.
OnEnded End complete.

Subscribe to these from other systems (UnityEvents in the Inspector, or += from code) when you need to observe lifecycle externally.


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