Runtime Execution

Master the BTreeExecutor for production-grade tree execution with lifecycle management, bounds enforcement, and cancellation support.


BTreeExecutor Overview

The BTreeExecutor is a high-level wrapper that automates the tick loop and provides production-ready execution features:

  • Automatic tick loop - No manual ticking required
  • Max ticks enforcement - Prevent infinite loops
  • Deadline enforcement - Time-based limits
  • Cancellation support - Graceful shutdown
  • Execution metadata - Detailed results and statistics
  • Tracing integration - Debug logging
  • Visualizer support - Real-time monitoring

Quick Start

Basic Execution

use igris_btree::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Create executor
    let executor = BTreeExecutor::new();

    // Build tree
    let mut tree = Sequence::new("mission");
    let mut context = BTreeContext::new();

    // Execute (automatic tick loop)
    let result = executor.execute(&mut tree, &mut context).await?;

    println!("Status: {:?}", result.status);
    println!("Ticks: {}", result.tick_count);
    println!("Duration: {:?}", result.duration);

    Ok(())
}

ExecutorConfig

Configure execution bounds and behavior.

Structure

pub struct ExecutorConfig {
    /// Maximum ticks before stopping (None = unlimited)
    pub max_ticks: Option<u64>,

    /// Maximum duration before stopping (None = unlimited)
    pub deadline: Option<Duration>,

    /// Enable execution tracing (logs)
    pub enable_tracing: bool,

    /// Delay between ticks (rate limiting)
    pub tick_delay: Option<Duration>,
}

Builder Methods

let executor = BTreeExecutor::new()
    .with_max_ticks(100)
    .with_deadline(Duration::from_secs(30))
    .with_tracing(true)
    .with_tick_delay(Duration::from_millis(10));

Execution Bounds

Max Ticks

Limit total number of ticks:

let executor = BTreeExecutor::new()
    .with_max_ticks(1000);

let result = executor.execute(&mut tree, &mut context).await?;

if result.max_ticks_reached {
    println!("Stopped due to max ticks limit");
}

Use cases:

  • Prevent infinite loops in development
  • Bound computational cost
  • Test with known iteration limits
  • Safety constraint for real-time systems

Recommended values:

  • Development: 100-1000 ticks
  • Testing: 10-100 ticks
  • Production: 10000+ or None (use deadline instead)

Deadline

Time-based execution limit:

use std::time::Duration;

let executor = BTreeExecutor::new()
    .with_deadline(Duration::from_secs(30));

let result = executor.execute(&mut tree, &mut context).await?;

if result.deadline_exceeded {
    println!("Stopped due to deadline");
}

Use cases:

  • Real-time constraints (robot must respond in 100ms)
  • Request timeouts (API must respond in 5s)
  • Resource management (limit CPU time)
  • Safety guarantees (watchdog backup)

Recommended values:

  • Fast reactions: 100ms - 1s
  • Normal operations: 5s - 30s
  • Long missions: 1min - 1hour
  • Batch processing: Hours or None

Combining Bounds

Use both for robust control:

let executor = BTreeExecutor::new()
    .with_max_ticks(10000)     // Computational bound
    .with_deadline(Duration::from_secs(60));  // Time bound

// Stops on whichever limit is reached first

Execution Flow

Standard Execution

pub async fn execute(
    &self,
    tree: &mut dyn BTreeNode,
    context: &mut BTreeContext,
) -> Result<ExecutionResult>

Flow:

  1. Start timer
  2. Loop:
    • Check max ticks
    • Check deadline
    • Tick tree
    • Apply tick delay (if configured)
    • If status is terminal, break
  3. Return ExecutionResult

With Cancellation

pub async fn execute_with_cancel(
    &self,
    tree: &mut dyn BTreeNode,
    context: &mut BTreeContext,
    cancel_rx: watch::Receiver<bool>,
) -> Result<ExecutionResult>

Flow:

  1. Start timer
  2. Loop:
    • Check cancellation channel
    • Check max ticks
    • Check deadline
    • Tick tree
    • Apply tick delay
    • If status is terminal, break
  3. Halt tree if cancelled
  4. Return ExecutionResult

Cancellation Support

Basic Cancellation

use tokio::sync::watch;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let executor = BTreeExecutor::new();
    let mut tree = Sequence::new("mission");
    let mut context = BTreeContext::new();

    // Create cancellation channel
    let (cancel_tx, cancel_rx) = watch::channel(false);

    // Spawn execution task
    let handle = tokio::spawn(async move {
        executor.execute_with_cancel(&mut tree, &mut context, cancel_rx).await
    });

    // Simulate work
    tokio::time::sleep(Duration::from_secs(1)).await;

    // Cancel execution
    cancel_tx.send(true)?;

    // Wait for completion
    let result = handle.await??;

    println!("Cancelled: {}", result.cancelled);

    Ok(())
}

Graceful Shutdown

Cancellation halts the tree gracefully:

// When cancel signal received:
// 1. Current tick completes
// 2. tree.halt() is called
// 3. All nodes clean up (async operations cancelled)
// 4. ExecutionResult returned with cancelled=true

Timeout Pattern

Combine cancellation with timeout:

use tokio::time::timeout;

let (cancel_tx, cancel_rx) = watch::channel(false);

let handle = tokio::spawn(async move {
    executor.execute_with_cancel(&mut tree, &mut context, cancel_rx).await
});

// Wait with timeout
match timeout(Duration::from_secs(10), handle).await {
    Ok(result) => println!("Completed: {:?}", result),
    Err(_) => {
        cancel_tx.send(true)?;
        println!("Timed out, cancelled");
    }
}

ExecutionResult

Detailed results from execution.

Structure

pub struct ExecutionResult {
    /// Final status of the tree
    pub status: NodeStatus,

    /// Total ticks executed
    pub tick_count: u64,

    /// Total execution duration
    pub duration: Duration,

    /// Whether execution was cancelled
    pub cancelled: bool,

    /// Whether max ticks was reached
    pub max_ticks_reached: bool,

    /// Whether deadline was exceeded
    pub deadline_exceeded: bool,

    /// Any errors during execution
    pub errors: Vec<String>,
}

Checking Results

let result = executor.execute(&mut tree, &mut context).await?;

// Check completion reason
if result.cancelled {
    println!("Execution was cancelled");
} else if result.max_ticks_reached {
    println!("Stopped at max ticks: {}", result.tick_count);
} else if result.deadline_exceeded {
    println!("Exceeded deadline: {:?}", result.duration);
} else {
    println!("Completed normally: {:?}", result.status);
}

// Check for errors
if !result.errors.is_empty() {
    println!("Errors: {:?}", result.errors);
}

Display Implementation

println!("Result: {}", result);
// Output: "ExecutionResult { status: Success, ticks: 42, duration: 1.5s }"

Tick Rate Control

Tick Delay

Add delay between ticks for rate limiting:

let executor = BTreeExecutor::new()
    .with_tick_delay(Duration::from_millis(10));

// Ticks at most 100 times per second

Use cases:

  • Rate limiting (don't overwhelm systems)
  • Sensor polling intervals
  • Network request throttling
  • Energy efficiency (mobile robots)

Variable Tick Rates

Different rates for different phases:

// Fast ticks for reactive behaviors
let fast_executor = BTreeExecutor::new()
    .with_tick_delay(Duration::from_millis(1));  // 1000 Hz

// Slow ticks for planning
let slow_executor = BTreeExecutor::new()
    .with_tick_delay(Duration::from_millis(100));  // 10 Hz

Tracing & Logging

Enable Tracing

let executor = BTreeExecutor::new()
    .with_tracing(true);

// Logs to tracing subscriber
// Initialize with: tracing_subscriber::fmt::init();

Log Levels

  • INFO: Start/completion messages
  • DEBUG: Tick counts, status updates
  • WARN: Limit violations, cancellations

Example Output

INFO  igris_btree::runtime: Starting execution of tree 'mission' (type: Sequence)
DEBUG igris_btree::runtime: Config: ExecutorConfig { max_ticks: Some(100), deadline: Some(30s), enable_tracing: true, tick_delay: None }
DEBUG igris_btree::runtime: Tick 1: Running
DEBUG igris_btree::runtime: Tick 2: Running
DEBUG igris_btree::runtime: Tick 3: Success
INFO  igris_btree::runtime: Execution completed: Success (3 ticks, 15.2ms)

Manual Tick Loop

For custom control, use manual ticking:

Manual Ticking

let mut tree = Sequence::new("mission");
let mut context = BTreeContext::new();

let mut tick_count = 0;
let start = Instant::now();

loop {
    // Check limits
    if tick_count >= 100 {
        println!("Max ticks reached");
        break;
    }

    if start.elapsed() > Duration::from_secs(30) {
        println!("Deadline exceeded");
        break;
    }

    // Tick tree
    let status = tree.tick(&mut context).await?;

    tick_count += 1;
    println!("Tick {}: {:?}", tick_count, status);

    // Check terminal
    if status.is_terminal() {
        println!("Completed: {:?}", status);
        break;
    }

    // Optional: Add delay
    tokio::time::sleep(Duration::from_millis(10)).await;
}

When to Use Manual

  • Custom tick scheduling logic
  • Integration with external event loops
  • Fine-grained control between ticks
  • Custom monitoring/profiling
  • Debugging specific tick sequences

Visualizer Integration

Attach Visualizer

use igris_btree::visualizer::TreeVisualizer;
use std::sync::Arc;

let visualizer = Arc::new(TreeVisualizer::new());

let executor = BTreeExecutor::new()
    .with_visualizer(visualizer.clone());

let result = executor.execute(&mut tree, &mut context).await?;

// Export snapshot
let snapshot = visualizer
    .export_snapshot(&tree, &context, result.tick_count)
    .await?;

Monitoring During Execution

Visualizer automatically:

  • Records each tick
  • Tracks node statistics
  • Captures execution trace
  • Monitors metrics
  • Exports snapshots

Error Handling

Node Errors

Nodes can return errors:

async fn tick(&mut self, context: &mut BTreeContext) -> Result<NodeStatus>

Executor propagates errors immediately:

let result = executor.execute(&mut tree, &mut context).await;

match result {
    Ok(exec_result) => {
        println!("Completed: {:?}", exec_result.status);
    }
    Err(e) => {
        println!("Error during execution: {}", e);
    }
}

Error Recovery

Use Selector for automatic fallback:

Selector::new("robust_execution")
    .add_child(Box::new(PrimaryAction))
    .add_child(Box::new(FallbackAction))
    .add_child(Box::new(SafetyFallback))

Error Logging

Errors are added to ExecutionResult:

if !result.errors.is_empty() {
    for error in &result.errors {
        eprintln!("Error: {}", error);
    }
}

Performance Tuning

Optimize Tick Rate

Balance reactivity vs CPU usage:

// High reactivity (100-1000 Hz)
.with_tick_delay(Duration::from_millis(1))

// Moderate (10-100 Hz)
.with_tick_delay(Duration::from_millis(10))

// Low frequency (1-10 Hz)
.with_tick_delay(Duration::from_millis(100))

Reduce Overhead

Disable features in production:

let executor = BTreeExecutor::new()
    .with_tracing(false)  // Disable unless debugging
    .with_max_ticks(None)  // Remove if using deadline
    .with_deadline(Duration::from_secs(60));  // Keep deadline for safety

Profile Execution

Use visualizer for profiling:

let snapshot = visualizer.export_snapshot(&tree, &context, ticks).await?;

for node in &snapshot.root.children {
    println!(
        "Node {}: avg {}ms, {} ticks",
        node.name,
        node.stats.avg_execution_ms,
        node.stats.tick_count
    );
}

Production Patterns

Pattern 1: Web API Handler

async fn handle_request(request: Request) -> Result<Response> {
    let executor = BTreeExecutor::new()
        .with_max_ticks(100)
        .with_deadline(Duration::from_secs(5));  // API timeout

    let mut tree = build_tree_from_request(request)?;
    let mut context = BTreeContext::new();

    let result = executor.execute(&mut tree, &mut context).await?;

    Ok(Response::from_result(result))
}

Pattern 2: Background Task

async fn background_mission() -> Result<()> {
    let executor = BTreeExecutor::new()
        .with_max_ticks(100000)
        .with_deadline(Duration::from_hours(1))
        .with_tracing(true)
        .with_tick_delay(Duration::from_millis(100));

    let mut tree = build_mission_tree()?;
    let mut context = BTreeContext::new();

    let result = executor.execute(&mut tree, &mut context).await?;

    // Report results
    report_mission_status(result)?;

    Ok(())
}

Pattern 3: Cancellable Service

async fn robot_service(shutdown: watch::Receiver<bool>) -> Result<()> {
    let executor = BTreeExecutor::new()
        .with_tracing(true)
        .with_tick_delay(Duration::from_millis(10));

    let mut tree = Repeat::new("service_loop", mission_tree(), None);
    let mut context = BTreeContext::new();

    let result = executor
        .execute_with_cancel(&mut tree, &mut context, shutdown)
        .await?;

    println!("Service stopped: cancelled={}", result.cancelled);

    Ok(())
}

Pattern 4: Monitored Execution

async fn monitored_execution() -> Result<()> {
    let visualizer = Arc::new(TreeVisualizer::new());

    let executor = BTreeExecutor::new()
        .with_max_ticks(1000)
        .with_deadline(Duration::from_secs(30))
        .with_tracing(true)
        .with_visualizer(visualizer.clone());

    let mut tree = build_tree()?;
    let mut context = BTreeContext::new();

    let result = executor.execute(&mut tree, &mut context).await?;

    // Export metrics for monitoring
    let snapshot = visualizer
        .export_snapshot(&tree, &context, result.tick_count)
        .await?;

    export_to_monitoring_system(snapshot)?;

    Ok(())
}

Testing

Unit Tests

#[tokio::test]
async fn test_execution_with_max_ticks() {
    let executor = BTreeExecutor::new()
        .with_max_ticks(10);

    let mut tree = Repeat::new("infinite", simple_action(), None);
    let mut context = BTreeContext::new();

    let result = executor.execute(&mut tree, &mut context).await.unwrap();

    assert!(result.max_ticks_reached);
    assert_eq!(result.tick_count, 10);
}

#[tokio::test]
async fn test_execution_with_deadline() {
    let executor = BTreeExecutor::new()
        .with_deadline(Duration::from_millis(100));

    let mut tree = Repeat::new("slow", slow_action(), None);
    let mut context = BTreeContext::new();

    let result = executor.execute(&mut tree, &mut context).await.unwrap();

    assert!(result.deadline_exceeded);
    assert!(result.duration >= Duration::from_millis(100));
}

Next Steps


Summary

Key takeaways:

  • BTreeExecutor: High-level wrapper for automatic tick loops
  • Max ticks: Prevent infinite loops
  • Deadline: Time-based execution limits
  • Cancellation: Graceful shutdown support
  • ExecutionResult: Detailed execution metadata
  • Tracing: Debug logging integration
  • Visualizer: Real-time monitoring
  • Production ready: Robust error handling and bounds enforcement