Skip to content

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:

public interface ICommand : IProcess
{
    void Execute();
}

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 via IExecutionScheduler
  • Execution conditions — Configure when the process should stop via ExecutionCondition
  • Lifecycle hooks — Override OnBegin() and OnCompleting() 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
  • TransferAttachableTransferProcess orchestrates 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