simple_bt 1.0.1

minimal(-ish) behavior tree implementation
Documentation
//! an example of using this behavior tree implementation

// Riveting gameplay of an agent starting at a random position, and attempting to move within 1 unit of (100, 100),
// where the speed the actor can move at is equal to 1.0 + dist where dist is the distance from the current point to
// the origin (0, 0).

// example name is a misnomer as this example has nothing to do with Beehave, but the structure simple_bt
// is based on was almost directly inspired by Beehave, so we'll keep the name.

use core::fmt;

use simple_bt::{
    composite::ParallelSelector, BehaviorNode, BehaviorRunner, NodeResult, ReportStatus,
};

struct Game {
    pos: glam::Vec2,

    // info
    dt: std::time::Duration,
}

impl fmt::Debug for Game {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Game")
            .field("pos", &self.pos)
            .finish_non_exhaustive()
    }
}

/// Is the game player at a specific point
#[derive(Debug)]
struct AtPoint {
    tolerance: f32,
    point: glam::Vec2,
}

impl BehaviorNode<Game> for AtPoint {
    fn tick(self: std::sync::Arc<Self>, context: &mut Game) -> simple_bt::NodeResult<Game> {
        if context.pos.distance_squared(self.point) <= self.tolerance.powi(2) {
            NodeResult::Success
        } else {
            NodeResult::Failure
        }
    }

    fn status(&self, parent: ReportStatus, context: &Game) -> simple_bt::BehaviorStatus {
        simple_bt::BehaviorStatus {
            name: "AtPoint".into(),
            status: parent.unexecuted_or_else(|| {
                if context.pos.distance_squared(self.point) <= self.tolerance.powi(2) {
                    ReportStatus::Success
                } else {
                    ReportStatus::Failure
                }
            }),
            children: vec![],
            current: None,
        }
    }
}

/// Move position at a speed sensitive to the game context
struct Move {
    speed_func: Box<dyn Fn(&Game) -> f32 + Send + Sync>,
    toward: glam::Vec2,
}

impl fmt::Debug for Move {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Move").finish_non_exhaustive()
    }
}

impl BehaviorNode<Game> for Move {
    fn tick(self: std::sync::Arc<Self>, context: &mut Game) -> NodeResult<Game> {
        let speed = (self.speed_func)(context);
        context.pos +=
            (self.toward - context.pos).normalize_or_zero() * speed * context.dt.as_secs_f32();
        NodeResult::Running(self)
    }

    fn status(&self, parent: ReportStatus, _context: &Game) -> simple_bt::BehaviorStatus {
        simple_bt::BehaviorStatus {
            name: "Move".into(),
            status: parent.unexecuted_or(ReportStatus::Running),
            children: vec![],
            current: None,
        }
    }
}

fn main() {
    const FRAME_TIME: std::time::Duration = std::time::Duration::from_millis(16);

    // generate a random new game, and "play" it.
    let new_game = Game {
        pos: glam::Vec2::from_array([
            rand::random_range(-100.0..=100.0),
            rand::random_range(-100.0..=100.0),
        ]),
        dt: FRAME_TIME,
    };
    game(new_game, [100.0, 100.0].into())
}

fn game(mut game: Game, goal: glam::Vec2) {
    let goal_tree = ParallelSelector::<Game>::from_iter([
        AtPoint {
            tolerance: 1.0,
            point: goal,
        }
        .arc(),
        Move {
            speed_func: Box::new(|game| game.pos.length() + 1.0),
            toward: goal,
        }
        .arc(),
    ])
    .arc();

    // To reflect actual usage, clone the goal-tree as an Arc
    //
    // This design is generally for fixed logic with disposable runners (and many runners being able to share the
    // same tree structure)
    let mut runner = BehaviorRunner::new(goal_tree.clone());
    eprintln!(
        "Initial state: {game:?} (status: {:?})",
        runner.status(&game)
    );
    while runner.proceed(&mut game).is_none() {
        eprintln!("Runner status: {:#?}", runner.status(&game));
        eprintln!("Intermediate state (after {:?}): {game:?}", game.dt,);
    }
    eprintln!("Final state: {game:?}");
}