Skip to content

Instantly share code, notes, and snippets.

@dmlary
Created July 21, 2023 00:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmlary/943af6900ce3abe152ae295afc746bd8 to your computer and use it in GitHub Desktop.
Save dmlary/943af6900ce3abe152ae295afc746bd8 to your computer and use it in GitHub Desktop.
bevy 0.10.1 stun race-condition resolution
//! Example workaround for ecs-based race-conditions when used in muds.
//! The key problem is two characters stunning each other in the same
//! frame, only one of them should end up stunned.
//!
//! This solution in bevy uses an exclusive system that runs without
//! world deferment.
#[allow(unused)]
use anyhow::{bail, Result};
use bevy::{log::LogPlugin, prelude::*};
fn main() {
App::new()
.add_plugin(LogPlugin::default())
.add_startup_system(setup)
.add_systems((perform_actions, check_state).chain())
.add_event::<ActionEvent>()
.run();
}
trait Action: Sync + Send + std::fmt::Debug {
fn run(&mut self, world: &mut World) -> Result<()>;
}
struct ActionEvent(Box<dyn Action>);
#[derive(Component)]
struct Stunned;
#[derive(Debug)]
struct StunAction {
actor: Entity,
target: Entity,
}
impl Action for StunAction {
fn run(&mut self, world: &mut World) -> Result<()> {
if world.entity(self.actor).contains::<Stunned>() {
bail!("actor is stunned");
}
world.entity_mut(self.target).insert(Stunned);
Ok(())
}
}
impl<T: Action + 'static> From<T> for ActionEvent {
fn from(value: T) -> Self {
let action: Box<dyn Action> = Box::new(value);
ActionEvent(action)
}
}
fn setup(mut commands: Commands, mut events: EventWriter<ActionEvent>) {
let a = commands.spawn((Name::new("player_a"),)).id();
let b = commands.spawn((Name::new("player_b"),)).id();
events.send(
StunAction {
actor: a,
target: b,
}
.into(),
);
events.send(
StunAction {
actor: b,
target: a,
}
.into(),
);
}
fn perform_actions(world: &mut World) {
let mut events = world.remove_resource::<Events<ActionEvent>>().unwrap();
world.init_resource::<Events<ActionEvent>>();
for mut action in events.drain() {
if let Err(e) = action.0.run(world) {
warn!("Action failed {:#?} -> {:?}", action.0, e);
}
}
}
fn check_state(stunned: Query<Entity, With<Stunned>>) {
assert_eq!(stunned.iter().len(), 1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment