Sollimann / bonsai

Rust implementation of AI behavior trees.
MIT License
327 stars 15 forks source link

Bevy code example #34

Closed feelingsonice closed 8 months ago

feelingsonice commented 8 months ago

Seeing that the repo is tagged with bevy I took a stab at using it along with Bevy, but I'm kind of stuck... The problem is essentially that I am expected to tick the BT and apply all action logic inside the closure passed to tick:

pub fn tick<E, F>(&mut self, e: &E, f: &mut F) -> (Status, f64)
    where
        E: UpdateEvent,
        F: FnMut(ActionArgs<E, A>, &mut BlackBoard<K>) -> (Status, f64),
        A: Debug,
{}

This means, like the example given under lib.rs, I am suppose to check the action enum I defined and do w/e I need to do there, for example:

let (_status, _dt) = bt.tick(&e, &mut |args, blackboard| match *args.action {
    Actions::Inc => {
        acc += 1;
        (Success, args.dt)
    }
    Actions::Dec => {
        acc -= 1;
        (Success, args.dt)
    }
});

In Bevy, this result in a monolithic system that tries to do everything... so afaik, a system with exclusive world access that looks something like:

fn tick_bt_tree(world: &mut World){
    let mut system_state: SystemState<(
        Res<Time>,
        Query<(Entity, BT)>,
    )> = SystemState::new(world);

    let (time, query) = system_state.get_mut(world);
    let e: Event = UpdateArgs { time.elapsed().to_f64() }.into();
    for (entity, bt) in &mut query {
        let (_status, _dt) = bt.tick(&e, &mut |args, blackboard| match *args.action {
            Actions::Inc => {
                // get data from `world`
                // but we can't do this because we're already borrowing `world` mutably
                (Success, args.dt)
            }
            // ...similarly for other actions
        }
    }
}

Like the comment added, I essentially can't access the same world that I retrieved the BT from since it's mutably borrowed (system_state is a piece of data mutably borrowed from world).

...This feels kind of like a dumb question but it's something that I nevertheless couldn't figure out. Not familiar with this repo but since it's tagged with bevy would it make sense to add something that could explain how someone would go about using it with bevy?

kaphula commented 8 months ago

It is a good point. I just clone my BT before ticking it and then assign the cloned BT to override the BT that is inside a bevy component. This is one way to avoid the borrow checker problem.

If you feel clone is wasteful, I think you may be able to avoid it if you wrap your BT inside an Option type in your bevy component and then use std::mem::take to transfer the BT value out of the bevy component to a temporary variable right before ticking the BT inside your bevy system. There might be more ergonomic ways to do the same thing but this should give you the general idea:


#[derive(Clone, Copy, Debug)]
enum AI {
    A, B
}

#[derive(Component)]
struct CompA {
    bt: Option<BT<AI, ()>>
}

#[derive(Resource)]
struct Resource1 {
    value: f32
}

fn tick_bt_tree(world: &mut World){
    let mut system_state: SystemState<(
        ResMut<Resource1>,
        Query<(Entity, &mut CompA)>
    )> = SystemState::new(world);

    let (
        mut resource,
        mut query
    ) = system_state.get_mut(world);

    let e: bonsai_bt::Event = UpdateArgs::zero_dt().into();
    for (entity, mut comp) in query.iter_mut() {

        // pull BT out from component (if it is there):
        if let Some(mut bt) = std::mem::take(&mut comp.bt) {
            let (_status, _dt) = bt.tick(&e, &mut |args, blackboard| {
                match *args.action {
                    AI::A => {
                        resource.value = 55.0; // mutate world data here
                        (bonsai_bt::Running, 0.0)
                    }
                    AI::B => {
                        (bonsai_bt::Running, 0.0)
                    }
                }
            });

            // give BT back to component:
            comp.bt = Some(bt);
        }

    }
}