1
21
Fork
You've already forked big-brain
5

Alt design pattern with In Out Systems #90

Open
opened 2023年12月31日 09:15:30 +01:00 by stargazing-dino · 1 comment
stargazing-dino commented 2023年12月31日 09:15:30 +01:00 (Migrated from github.com)
Copy link

Hiyo ! I was playing around with other possible patterns - mostly out of curiosity and as a learning experience and was recently reading a one shot systems related PR and thought that'd be cool. Anyways, here's how I'd think it'd make sense as a utility ai architecture

pubstruct BrainPlugin;implPluginforBrainPlugin{fn build(&self,app: &mutApp){app.add_systems(Startup,setup).add_systems(Update,(decide_best_action,sleepiness_tick));}}#[derive(Component)]pubstruct Thinker;#[derive(Component)]pubstruct Sleepiness(f32);#[derive(Bundle)]pubstruct MyActorBundle{pubthinker: Thinker,pubconsiderations: Considerations,pubaction_state: ActionState,pubsleepiness: Sleepiness,}#[derive(Component)]pubstruct Considerations{pubconsiderations: Vec<Consideration>,}#[derive(Component)]pubenum ActionState{Idle,Executing,Done,}pubstruct Consideration{pubname: String,pubscorer: SystemId<Entity,f32>,pubaction: SystemId<ActionState,ActionState>,}fn my_sleep_scorer(In(entity): In<Entity>,sleepiness: Query<&Sleepiness>)-> f32 {letsleepiness=sleepiness.get(entity).unwrap();sleepiness.0/100.0}fn sleep(In(action_state): In<ActionState>)-> ActionState{todo!();}fn decide_best_action(world: &mutWorld){letmuthighest_consideration: Option<&Consideration>=None;letmuthighest_score=0.0;letmutquery=world.query::<(Entity,&Considerations)>();for(actor_entity,considerations)inquery.iter(world){forconsiderationin&considerations.considerations{// This doesn't compile because of multiple borrows :(
letOk(score)=world.run_system_with_input(consideration.scorer,actor_entity)else{continue;};ifscore>highest_score{highest_consideration=Some(consideration);highest_score=score;}}}ifletSome(consideration)=highest_consideration{letOk(next)=world.run_system_with_input(consideration.action,ActionState::Idle)else{return;};todo!("set next action state");}}fn setup(world: &mutWorld){letscorer=world.register_system(my_sleep_scorer);letaction=world.register_system(sleep);world.spawn(MyActorBundle{thinker: Thinker,action_state: ActionState::Idle,considerations: Considerations{considerations: vec![Consideration{name: "Sleep".into(),scorer: scorer,action: action,}],},sleepiness: Sleepiness(0.0),});}fn sleepiness_tick(mutsleepiness: Query<&mutSleepiness>){formutsleepinessinsleepiness.iter_mut(){sleepiness.0+=1.0;}}

I hope you find this interesting ! I thought so and just wanted to share so I could hear your opinion on it :D

I'm a big rust noob so this might be totally wrong/absurd

(also sorry if this was better suited as a discussion ! lol)

Hiyo ! I was playing around with other possible patterns - mostly out of curiosity and as a learning experience and was recently reading a [one shot systems related PR](https://github.com/bevyengine/bevy/pull/10380) and thought that'd be cool. Anyways, here's how I'd think it'd make sense as a utility ai architecture ```rs pub struct BrainPlugin; impl Plugin for BrainPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup) .add_systems(Update, (decide_best_action, sleepiness_tick)); } } #[derive(Component)] pub struct Thinker; #[derive(Component)] pub struct Sleepiness(f32); #[derive(Bundle)] pub struct MyActorBundle { pub thinker: Thinker, pub considerations: Considerations, pub action_state: ActionState, pub sleepiness: Sleepiness, } #[derive(Component)] pub struct Considerations { pub considerations: Vec<Consideration>, } #[derive(Component)] pub enum ActionState { Idle, Executing, Done, } pub struct Consideration { pub name: String, pub scorer: SystemId<Entity, f32>, pub action: SystemId<ActionState, ActionState>, } fn my_sleep_scorer(In(entity): In<Entity>, sleepiness: Query<&Sleepiness>) -> f32 { let sleepiness = sleepiness.get(entity).unwrap(); sleepiness.0 / 100.0 } fn sleep(In(action_state): In<ActionState>) -> ActionState { todo!(); } fn decide_best_action(world: &mut World) { let mut highest_consideration: Option<&Consideration> = None; let mut highest_score = 0.0; let mut query = world.query::<(Entity, &Considerations)>(); for (actor_entity, considerations) in query.iter(world) { for consideration in &considerations.considerations { // This doesn't compile because of multiple borrows :( let Ok(score) = world.run_system_with_input(consideration.scorer, actor_entity) else { continue; }; if score > highest_score { highest_consideration = Some(consideration); highest_score = score; } } } if let Some(consideration) = highest_consideration { let Ok(next) = world.run_system_with_input(consideration.action, ActionState::Idle) else { return; }; todo!("set next action state"); } } fn setup(world: &mut World) { let scorer = world.register_system(my_sleep_scorer); let action = world.register_system(sleep); world.spawn(MyActorBundle { thinker: Thinker, action_state: ActionState::Idle, considerations: Considerations { considerations: vec![Consideration { name: "Sleep".into(), scorer: scorer, action: action, }], }, sleepiness: Sleepiness(0.0), }); } fn sleepiness_tick(mut sleepiness: Query<&mut Sleepiness>) { for mut sleepiness in sleepiness.iter_mut() { sleepiness.0 += 1.0; } } ``` I hope you find this interesting ! I thought so and just wanted to share so I could hear your opinion on it :D I'm a big rust noob so this might be totally wrong/absurd (also sorry if this was better suited as a discussion ! lol)
stargazing-dino commented 2024年01月09日 17:49:13 +01:00 (Migrated from github.com)
Copy link
Here's a version 2 that's a little cleaner and compiles on bevy main
usebevy_app::{App,Plugin,Startup,Update};usebevy_ecs::{prelude::*,system::SystemId};usebevy_time::Time;usebevy_utils::hashbrown::HashMap;pubstruct BrainBoxPlugin;implPluginforBrainBoxPlugin{fn build(&self,app: &mutApp){app.add_systems(Startup,setup).add_systems(Update,(decide_best_action,sleepiness_tick));}}#[derive(Component)]pubstruct Thinker;#[derive(Bundle)]pubstruct MyActorBundle{pubthinker: Thinker,pubconsiderations: Considerations,pubaction_state: ActionState,pubsleepiness: Sleepiness,}#[derive(Component)]pubstruct Considerations{pubconsiderations: Vec<Consideration>,pubdefault_consideration: Option<Consideration>,}#[derive(Component, Clone, Copy)]pubenum ActionState{Idle,Executing,Done,}pubstruct Consideration{pubdebug_name: String,pubscorer: SystemId<Entity,f32>,pubaction: SystemId<Entity,ActionState>,}/// We need this in order to move out of the query
struct ConsiderationSystems{scorer: SystemId<Entity,f32>,pubaction: SystemId<Entity,ActionState>,}fn decide_best_action(world: &mutWorld,mutprevious_scores: Local<HashMap<Entity,f32>>,mutprevious_action_states: Local<HashMap<Entity,ActionState>>,){letmutquery=world.query::<(Entity,&Considerations)>();letmutentity_considerations=HashMap::<Entity,Vec<ConsiderationSystems>>::new();for(actor_entity,considerations)inquery.iter(world){forconsiderationin&considerations.considerations{entity_considerations.entry(actor_entity.clone()).or_insert_with(Vec::new).push(ConsiderationSystems{scorer: consideration.scorer.clone(),action: consideration.action.clone(),});}}for(actor_entity,considerations)inentity_considerations{forsystemsinconsiderations{letOk(score)=world.run_system_with_input(systems.scorer,actor_entity)else{continue;};letprevious_score=previous_scores.get(&actor_entity).copied().unwrap_or(0.0);letprevious_action_state=previous_action_states.get(&actor_entity).copied().unwrap_or(ActionState::Idle);letmutnext_action_state=previous_action_state;ifscore>previous_score{next_action_state=world.run_system_with_input(systems.action,actor_entity).unwrap();}else{// Run our default consideration
}previous_scores.insert(actor_entity,score);previous_action_states.insert(actor_entity,next_action_state);}}}// ------------------------------
// User code
#[derive(Component)]pubstruct Sleepiness{publevel: f32,/// While resting, how much sleepiness is reduced per second
pubper_second: f32,/// Until what level of sleepiness should we rest?
pubuntil: f32,}fn sleepiness_tick(mutsleepiness: Query<&mutSleepiness>){formutsleepinessinsleepiness.iter_mut(){sleepiness.level+=1.0;}}fn my_sleep_scorer(In(entity): In<Entity>,sleepiness: Query<&Sleepiness>)-> f32 {letsleepiness=sleepiness.get(entity).unwrap();sleepiness.level}fn sleep(In(entity): In<Entity>,time: Res<Time>,mutsleepiness: Query<&mutSleepiness>,)-> ActionState{letmutsleepiness=sleepiness.get_mut(entity).unwrap();ifsleepiness.level>=sleepiness.until{ActionState::Done}else{sleepiness.level-=time.delta_seconds()*sleepiness.per_second;ActionState::Executing}}fn setup(world: &mutWorld){// I'm sure we can register the systems ourselves rather than force that on the user.
// They'd just provide us function pointers in that case
letscorer=world.register_system(my_sleep_scorer);letaction=world.register_system(sleep);world.spawn(MyActorBundle{thinker: Thinker,action_state: ActionState::Idle,considerations: Considerations{considerations: vec![Consideration{debug_name: "Sleep".into(),scorer: scorer,action: action,}],default_consideration: None,},sleepiness: Sleepiness{level: 0.0,per_second: 1.0,until: 80.0,},});}

I also played around with a version that uses bevy-trait-query and I like the look of that one more than the vec of considerations but it's surprisingly difficult to get right. It also requires that the user registers each consideration type as a Consideration which is a bit odd.

Semantically it makes a lot of sense tho:

#[bevy_trait_query::queryable]pubtraitConsideration{fn score(&self,entity: Entity,world: &mutWorld)-> f32;fn action(&self,entity: Entity,world: &mutWorld)-> ActionState;}#[derive(Component)]pubstruct Sleep{pubsleepiness: f32,}implConsiderationforSleep{fn score(&self,entity: Entity,world: &mutWorld)-> f32 {self.sleepiness}fn action(&self,entity: Entity,world: &mutWorld)-> ActionState{todo!();}}

(look at that exclusive world access tho 😅 )

I'll post the full length version if I get it running.

<details> <summary>Here's a version 2 that's a little cleaner and compiles on bevy main</summary> ```rs use bevy_app::{App, Plugin, Startup, Update}; use bevy_ecs::{prelude::*, system::SystemId}; use bevy_time::Time; use bevy_utils::hashbrown::HashMap; pub struct BrainBoxPlugin; impl Plugin for BrainBoxPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup) .add_systems(Update, (decide_best_action, sleepiness_tick)); } } #[derive(Component)] pub struct Thinker; #[derive(Bundle)] pub struct MyActorBundle { pub thinker: Thinker, pub considerations: Considerations, pub action_state: ActionState, pub sleepiness: Sleepiness, } #[derive(Component)] pub struct Considerations { pub considerations: Vec<Consideration>, pub default_consideration: Option<Consideration>, } #[derive(Component, Clone, Copy)] pub enum ActionState { Idle, Executing, Done, } pub struct Consideration { pub debug_name: String, pub scorer: SystemId<Entity, f32>, pub action: SystemId<Entity, ActionState>, } /// We need this in order to move out of the query struct ConsiderationSystems { scorer: SystemId<Entity, f32>, pub action: SystemId<Entity, ActionState>, } fn decide_best_action( world: &mut World, mut previous_scores: Local<HashMap<Entity, f32>>, mut previous_action_states: Local<HashMap<Entity, ActionState>>, ) { let mut query = world.query::<(Entity, &Considerations)>(); let mut entity_considerations = HashMap::<Entity, Vec<ConsiderationSystems>>::new(); for (actor_entity, considerations) in query.iter(world) { for consideration in &considerations.considerations { entity_considerations .entry(actor_entity.clone()) .or_insert_with(Vec::new) .push(ConsiderationSystems { scorer: consideration.scorer.clone(), action: consideration.action.clone(), }); } } for (actor_entity, considerations) in entity_considerations { for systems in considerations { let Ok(score) = world.run_system_with_input(systems.scorer, actor_entity) else { continue; }; let previous_score = previous_scores.get(&actor_entity).copied().unwrap_or(0.0); let previous_action_state = previous_action_states .get(&actor_entity) .copied() .unwrap_or(ActionState::Idle); let mut next_action_state = previous_action_state; if score > previous_score { next_action_state = world .run_system_with_input(systems.action, actor_entity) .unwrap(); } else { // Run our default consideration } previous_scores.insert(actor_entity, score); previous_action_states.insert(actor_entity, next_action_state); } } } // ------------------------------ // User code #[derive(Component)] pub struct Sleepiness { pub level: f32, /// While resting, how much sleepiness is reduced per second pub per_second: f32, /// Until what level of sleepiness should we rest? pub until: f32, } fn sleepiness_tick(mut sleepiness: Query<&mut Sleepiness>) { for mut sleepiness in sleepiness.iter_mut() { sleepiness.level += 1.0; } } fn my_sleep_scorer(In(entity): In<Entity>, sleepiness: Query<&Sleepiness>) -> f32 { let sleepiness = sleepiness.get(entity).unwrap(); sleepiness.level } fn sleep( In(entity): In<Entity>, time: Res<Time>, mut sleepiness: Query<&mut Sleepiness>, ) -> ActionState { let mut sleepiness = sleepiness.get_mut(entity).unwrap(); if sleepiness.level >= sleepiness.until { ActionState::Done } else { sleepiness.level -= time.delta_seconds() * sleepiness.per_second; ActionState::Executing } } fn setup(world: &mut World) { // I'm sure we can register the systems ourselves rather than force that on the user. // They'd just provide us function pointers in that case let scorer = world.register_system(my_sleep_scorer); let action = world.register_system(sleep); world.spawn(MyActorBundle { thinker: Thinker, action_state: ActionState::Idle, considerations: Considerations { considerations: vec![Consideration { debug_name: "Sleep".into(), scorer: scorer, action: action, }], default_consideration: None, }, sleepiness: Sleepiness { level: 0.0, per_second: 1.0, until: 80.0, }, }); } ``` </details> I also played around with a version that uses bevy-trait-query and I like the look of that one more than the vec of considerations but it's surprisingly difficult to get right. It also requires that the user registers each consideration type as a `Consideration` which is a bit odd. Semantically it makes a lot of sense tho: ```rs #[bevy_trait_query::queryable] pub trait Consideration { fn score(&self, entity: Entity, world: &mut World) -> f32; fn action(&self, entity: Entity, world: &mut World) -> ActionState; } #[derive(Component)] pub struct Sleep { pub sleepiness: f32, } impl Consideration for Sleep { fn score(&self, entity: Entity, world: &mut World) -> f32 { self.sleepiness } fn action(&self, entity: Entity, world: &mut World) -> ActionState { todo!(); } } ``` (look at that exclusive world access tho 😅 ) I'll post the full length version if I get it running.
Sign in to join this conversation.
No Branch/Tag specified
main
zkat/tutorial
zkat/docs
zkat/dorfs-example
big-brain-derive-v0.22.0
v0.22.0
big-brain-derive-v0.21.1
v0.21.1
v0.21.0
v0.20.0
big-brain-derive-v0.19.0
v0.19.0
big-brain-derive-v0.18.0
v0.18.0
big-brain-derive-v0.17.0
v0.17.0
big-brain-derive-v0.16.0
v0.16.0
v0.15.0
v0.14.0
v0.13.0
v0.12.0
v0.11.0
v0.10.0
v0.9.0
v0.8.0
v0.7.0
0.6.0
0.5.0
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
v0.1.1
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
zkat/big-brain#90
Reference in a new issue
zkat/big-brain
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?