Processes & Commands¶
Any operation - a lerp, a transfer, an animation - follows the same lifecycle: start, run, complete. This means you can swap any operation for any other without changing the component that runs it.
Want an object to slide into place with a smooth lerp? Later decide it should snap instantly? Or animate along a curve? All three are interchangeable. The component just says "run this operation" and does not care what the operation actually does.
Instant actions work the same way. Setting a parent, firing an event, toggling a flag - these are one-shot commands that share the same lifecycle, so they can be used anywhere an operation is expected.
The Lifecycle¶
Every operation follows the same state flow:
State Diagram¶
stateDiagram-v2
[*] --> Idle
Idle --> Running : Begin()
Running --> Paused : Pause()
Paused --> Running : Unpause()
Running --> Complete : natural completion
Running --> Complete : Complete()
Paused --> Running : Begin() [resets]
Complete --> Running : Begin()
Complete --> [*]
| Action | What Happens |
|---|---|
| Begin | Starts execution. An optional callback fires when it finishes naturally. |
| Pause | Freezes execution without losing progress. |
| Unpause | Resumes from where it was paused. |
| Complete | Snaps to the final state instantly. Safe to call in any state. |
| State | Meaning |
|---|---|
| Running | Actively executing |
| Complete | Reached final state, either naturally or via Complete |
| Paused | Frozen mid-execution via Pause |
Two Kinds of Operations¶
Over-Time Processes¶
Anything that spans multiple frames - a lerp, a timed sequence, a physics-driven motion. These run until their stop condition is met or they are completed manually.
Instant Commands¶
One-shot operations that complete immediately: set a parent, apply a force impulse, fire an event, toggle a flag. Commands can be used anywhere a process is expected - they just finish right away.
When a field only accepts commands, the selection popup filters out over-time processes, preventing mismatches.
Technical API¶
IProcess¶
The core interface for multi-frame operations:
public interface IProcess
{
void Begin(Action onComplete = null);
void Pause();
void Unpause();
void Complete();
bool IsRunning { get; }
bool IsComplete { get; }
bool IsPaused { get; }
float Duration { get; }
}
ICommand¶
Extends IProcess for instant operations:
Base Classes¶
Command¶
Abstract base class for instant operations. Handles the IProcess plumbing (Begin/Complete/state tracking) so you only implement Execute():
[Serializable]
[JungleClassInfo("Logs a debug message", "Icons/log", "Debug")]
public class LogCommand : Command
{
[SerializeField] private string message;
public override void Execute()
{
Debug.Log(message);
}
}
FlexibleProcess¶
Abstract base class for over-time operations. Provides:
- Execution scheduling — Configure when
Execute()is called viaIExecutionScheduler - Execution conditions — Configure when the process should stop via
ExecutionCondition - Lifecycle hooks — Override
OnBegin()andOnCompleting()for setup and teardown
[Serializable]
[JungleClassInfo("Moves an object forward over time", "Icons/move", "Movement")]
public class MoveForward : FlexibleProcess
{
[SerializeReference] [JungleClassSelection]
private IValue<float> speed;
protected override void OnBegin() { /* setup */ }
protected override void Execute()
{
// Called each scheduled frame
target.position += target.forward * speed.Value() * Time.deltaTime;
}
protected override void OnCompleting() { /* teardown */ }
}
Execution Scheduling¶
FlexibleProcess delegates its frame timing to an IExecutionScheduler. This controls when Execute() is called:
| Scheduler | Behavior |
|---|---|
| ExecuteEveryFrame | Calls Execute() once per Update |
| ExecuteEveryFixedFrame | Calls Execute() once per FixedUpdate |
| ExecuteEveryLateFrame | Calls Execute() once per LateUpdate |
| ExecuteEveryTimeInterval | Calls Execute() at a configurable time interval |
| ExecuteEveryXFrames | Calls Execute() every N frames |
The scheduler is a [SerializeReference] field, so it is configurable per-instance in the Inspector via the class selection system.
Execution Conditions¶
Execution conditions determine when a process stops. They are the stop criteria for FlexibleProcess:
| Condition | Stops When |
|---|---|
| NeverStopExecutionCondition | Never — runs until Complete() is called externally |
| ExecuteOnceExecutionCondition | After a single Execute() call |
| DurationExecutionCondition | After a specified duration elapses |
| DistanceToTargetCondition | When a transform reaches a target distance |
| SustainedDistanceToTargetCondition | When distance is sustained for a duration |
| TransformDistanceCondition | When two transforms reach a relative distance |
| EventFiredExecutionCondition | When a specified event fires |
| WhileConditionMetExecutionCondition | While an external condition evaluates to true |
Each condition supports an invert toggle, flipping its logic (e.g., "stop when close" becomes "stop when far").
Targeted Processes¶
Some processes operate on a specific GameObject rather than implicit state. These use the targeted variants:
ITargetedProcess¶
Extends IProcess with an OverrideTarget property. The caller sets the target before calling Begin().
TargetedCommand¶
Abstract base class for targeted instant operations. Like Command, but with a GameObject OverrideTarget that the command can reference during Execute().
[Serializable]
[JungleClassInfo("Disables the target GameObject")]
public class DisableTarget : TargetedCommand
{
public override void Execute()
{
if (OverrideTarget)
OverrideTarget.SetActive(false);
}
}
Targeted processes are used extensively in Octoputs, where drag/drop operations need to act on the object being dragged.
How Octoputs Uses Processes¶
In Octoputs, processes drive the entire drag-and-drop lifecycle:
- Attach phase — A process animates the object to its attachment point
- Detach phase — A process animates the object away from its attachment point
- Transfer —
AttachableTransferProcessorchestrates moving objects between storages
Each phase is an IProcess. When a phase completes, the next one begins. This makes the animation and behavior sequence fully configurable in the Inspector without code changes.
Summary¶
flowchart TD
IP["IProcess"] --> IC["ICommand"]
IP --> FP["FlexibleProcess"]
IP --> ITP["ITargetedProcess"]
IC --> CMD["Command (base class)"]
ITP --> TC["TargetedCommand (base class)"]
FP --> ES["IExecutionScheduler"]
FP --> EC["ExecutionCondition"]
- Use ICommand / Command for instant one-shot operations
- Use FlexibleProcess for over-time operations with configurable scheduling and stop conditions
- Use ITargetedProcess / TargetedCommand when the operation needs a target GameObject
- All processes share the same Begin/Pause/Complete lifecycle, making them interchangeable