Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Decorators

A Decorator is a specialized single-child node that wraps a child node and modifies its execution behavior or transforms its return state (Success, Failure, or Running).

Every decorator accepts exactly one child node (which can be a single action, a sub-tree, or a block wrapped in { }).


Built-In Decorator Reference

DecoratorParametersDefaultDescription
inverterNoneInverts child Success to Failure, and Failure to Success. (Running is untouched).
force_successNoneAlways returns Success regardless of whether the child succeeds or fails.
force_failNoneAlways returns Failure regardless of child outcome.
repeat(N)count: number0 (Infinite)Repeats execution of the child $N$ times. If count is 0, repeats infinitely.
retry(N)attempts: number0 (Infinite)Re-ticks the child up to $N$ times if it returns Failure. If attempts is 0, retries indefinitely.
timeout(ms)limit: number1000Limits child execution time in milliseconds. Halts child and returns Failure if exceeded.
delay(ms)wait: number0Delays the initial execution of the child for the specified duration in milliseconds.

Placement of Decorators

Decorators in Forester can be placed in two main ways:

1. In Tree / Root Definitions (Header Decorators)

Decorators can be placed directly in the declaration header of a root node or sub-tree definition:

// Root node wrapped in a repeat decorator
root main_fixed repeat(5) {
    execute_cycle()
}

// Sub-tree definition wrapped with a retry decorator
sequence retryable_task retry(3) {
    perform_step()
}

2. At Invocation Sites

Decorators can be placed directly before an action invocation, sub-tree call, or inline lambda:

root main sequence {
    // Decorator on an action invocation
    retry(3) perform_task()
    
    // Decorator on a sequence block
    timeout(1000) sequence {
        fetch_data()
    }
    
    // Decorator on an inline lambda
    retry(5) lambda sequence {
        fetch_sensor_data()
        validate_reading()
    }
}

Detailed Examples

1. Inverter (inverter)

Inverts the result of condition checks or actions:

import "std::actions"

root main sequence {
    // Succeeds if obstacle_detected() fails
    inverter obstacle_detected()
    move_forward()
}

impl obstacle_detected();
impl move_forward();

2. Result Overrides (force_success & force_fail)

Enforce specific outcome states regardless of child execution:

root main sequence {
    // Attempt cleanup; continue even if cleanup returns Failure
    force_success cleanup_temp_files()
    proceed_main_task()
}

impl cleanup_temp_files();
impl proceed_main_task();

3. Repeat (repeat)

Executes a child multiple times (or infinitely for continuous control loops):

// Infinite execution loop
root main_idle repeat {
    background_health_check()
}

// Fixed 5-cycle loop
root main_fixed repeat(5) {
    execute_cycle()
}

impl background_health_check();
impl execute_cycle();

4. Retry (retry)

Automatically retries failing actions (ideal for network calls or LLM tool invocations):

root main sequence {
    // Retry up to 5 times if call_llm returns Failure
    retry(5) call_llm({"prompt": "analyze_image"})
}

impl call_llm(params: object);

5. Timeout (timeout) & Delay (delay)

Controls timing for async actions:

root main sequence {
    // Wait 500ms before starting initial tick
    delay(500) initialize_sensors()
    
    // Shut down async action if it runs longer than 3000ms
    timeout(3000) fetch_remote_data()
}

impl initialize_sensors();
impl fetch_remote_data();