Skip to content

Instantly share code, notes, and snippets.

@matthewjberger
Last active July 12, 2026 13:40
Show Gist options
  • Select an option

  • Save matthewjberger/4578b6d03514523f5f345951c7395b40 to your computer and use it in GitHub Desktop.

Select an option

Save matthewjberger/4578b6d03514523f5f345951c7395b40 to your computer and use it in GitHub Desktop.
Build your own ECS in Rust, part 3: change detection, events, tags, commands
use std::collections::HashMap;
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Entity {
pub id: u32,
pub generation: u32,
}
#[derive(Default)]
pub struct EntityAllocator {
pub next_id: u32,
pub free: Vec<(u32, u32)>,
}
impl EntityAllocator {
pub fn allocate(&mut self) -> Entity {
if let Some((id, generation)) = self.free.pop() {
Entity { id, generation }
} else {
let id = self.next_id;
self.next_id += 1;
Entity { id, generation: 0 }
}
}
pub fn deallocate(&mut self, entity: Entity) {
self.free.push((entity.id, entity.generation.wrapping_add(1)));
}
}
#[derive(Default, Copy, Clone)]
pub struct EntityLocation {
pub generation: u32,
pub table_index: u32,
pub array_index: u32,
pub allocated: bool,
}
#[derive(Default)]
pub struct EntityLocations {
pub locations: Vec<EntityLocation>,
}
impl EntityLocations {
pub fn ensure_slot(&mut self, id: u32) {
let needed = id as usize + 1;
if needed > self.locations.len() {
self.locations.resize(needed, EntityLocation::default());
}
}
pub fn get(&self, entity: Entity) -> Option<(usize, usize)> {
let location = self.locations.get(entity.id as usize)?;
if !location.allocated || location.generation != entity.generation {
return None;
}
Some((location.table_index as usize, location.array_index as usize))
}
pub fn set(&mut self, entity: Entity, table_index: usize, array_index: usize) {
self.ensure_slot(entity.id);
self.locations[entity.id as usize] = EntityLocation {
generation: entity.generation,
table_index: table_index as u32,
array_index: array_index as u32,
allocated: true,
};
}
pub fn mark_deallocated(&mut self, id: u32) {
if let Some(location) = self.locations.get_mut(id as usize) {
location.allocated = false;
}
}
}
#[derive(Default, Clone, Debug)]
pub struct Position {
pub x: f32,
pub y: f32,
}
#[derive(Default, Clone, Debug)]
pub struct Velocity {
pub x: f32,
pub y: f32,
}
pub const POSITION: u64 = 1 << 0;
pub const VELOCITY: u64 = 1 << 1;
pub const COMPONENT_COUNT: usize = 2;
pub fn component_index(mask: u64) -> Option<usize> {
match mask {
POSITION => Some(0),
VELOCITY => Some(1),
_ => None,
}
}
#[derive(Default)]
pub struct ComponentArrays {
pub mask: u64,
pub entities: Vec<Entity>,
pub positions: Vec<Position>,
pub positions_changed: Vec<u32>,
pub velocities: Vec<Velocity>,
pub velocities_changed: Vec<u32>,
}
#[derive(Default, Clone)]
pub struct TableEdges {
pub add_edges: [Option<usize>; COMPONENT_COUNT],
pub remove_edges: [Option<usize>; COMPONENT_COUNT],
}
#[derive(Clone)]
pub struct EventChannel<T> {
pub events: Vec<T>,
pub base_sequence: u64,
pub previous_update_sequence: u64,
}
impl<T> Default for EventChannel<T> {
fn default() -> Self {
Self {
events: Vec::new(),
base_sequence: 0,
previous_update_sequence: 0,
}
}
}
impl<T> EventChannel<T> {
pub fn send(&mut self, event: T) {
self.events.push(event);
}
pub fn sequence(&self) -> u64 {
self.base_sequence + self.events.len() as u64
}
pub fn events_since(&self, cursor: u64) -> &[T] {
let start = cursor
.saturating_sub(self.base_sequence)
.min(self.events.len() as u64) as usize;
&self.events[start..]
}
pub fn read(&self) -> impl Iterator<Item = &T> {
self.events.iter()
}
pub fn trim(&mut self, up_to_sequence: u64) {
let drop_count = up_to_sequence
.saturating_sub(self.base_sequence)
.min(self.events.len() as u64) as usize;
self.events.drain(..drop_count);
self.base_sequence += drop_count as u64;
}
pub fn update(&mut self) {
let expire = self.previous_update_sequence;
self.trim(expire);
self.previous_update_sequence = self.sequence();
}
pub fn len(&self) -> usize {
self.events.len()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
const TAG_ABSENT: u32 = u32::MAX;
#[derive(Default, Clone)]
pub struct SparseTagSet {
pub dense: Vec<Entity>,
pub sparse: Vec<u32>,
}
impl SparseTagSet {
pub fn insert(&mut self, entity: Entity) -> bool {
let index = entity.id as usize;
if index >= self.sparse.len() {
self.sparse.resize(index + 1, TAG_ABSENT);
}
let slot = self.sparse[index];
if slot != TAG_ABSENT {
let existing = &mut self.dense[slot as usize];
if *existing == entity {
return false;
}
*existing = entity;
return true;
}
self.sparse[index] = self.dense.len() as u32;
self.dense.push(entity);
true
}
pub fn remove(&mut self, entity: Entity) -> bool {
let index = entity.id as usize;
let Some(&slot) = self.sparse.get(index) else {
return false;
};
if slot == TAG_ABSENT || self.dense[slot as usize] != entity {
return false;
}
self.dense.swap_remove(slot as usize);
self.sparse[index] = TAG_ABSENT;
if (slot as usize) < self.dense.len() {
let moved = self.dense[slot as usize];
self.sparse[moved.id as usize] = slot;
}
true
}
pub fn contains(&self, entity: Entity) -> bool {
self.sparse
.get(entity.id as usize)
.is_some_and(|&slot| slot != TAG_ABSENT && self.dense[slot as usize] == entity)
}
pub fn iter(&self) -> impl Iterator<Item = Entity> + '_ {
self.dense.iter().copied()
}
pub fn len(&self) -> usize {
self.dense.len()
}
pub fn is_empty(&self) -> bool {
self.dense.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct CollisionEvent {
pub entity_a: Entity,
pub entity_b: Entity,
}
pub enum Command {
Spawn { mask: u64 },
Despawn { entity: Entity },
AddComponents { entity: Entity, mask: u64 },
RemoveComponents { entity: Entity, mask: u64 },
SetPosition { entity: Entity, value: Position },
SetVelocity { entity: Entity, value: Velocity },
AddPlayer { entity: Entity },
RemovePlayer { entity: Entity },
AddEnemy { entity: Entity },
RemoveEnemy { entity: Entity },
}
#[derive(Default)]
pub struct Resources {
pub delta_time: f32,
pub game_time: f32,
pub collision_cursor: u64,
}
#[derive(Default)]
pub struct World {
pub allocator: EntityAllocator,
pub entity_locations: EntityLocations,
pub tables: Vec<ComponentArrays>,
pub table_lookup: HashMap<u64, usize>,
pub table_edges: Vec<TableEdges>,
pub query_cache: HashMap<u64, Vec<usize>>,
pub current_tick: u32,
pub last_tick: u32,
pub collisions: EventChannel<CollisionEvent>,
pub players: SparseTagSet,
pub enemies: SparseTagSet,
pub command_buffer: Vec<Command>,
pub resources: Resources,
}
impl World {
pub fn invalidate_query_cache_for_new_table(
&mut self,
new_mask: u64,
new_table_index: usize,
) {
for (query_mask, cached_tables) in self.query_cache.iter_mut() {
if new_mask & query_mask == *query_mask {
cached_tables.push(new_table_index);
}
}
}
pub fn get_or_create_table(&mut self, mask: u64) -> usize {
if let Some(&index) = self.table_lookup.get(&mask) {
return index;
}
let new_table_index = self.tables.len();
self.tables.push(ComponentArrays {
mask,
..Default::default()
});
self.table_edges.push(TableEdges::default());
self.table_lookup.insert(mask, new_table_index);
self.invalidate_query_cache_for_new_table(mask, new_table_index);
for component_bit in [POSITION, VELOCITY] {
let Some(component_index_value) = component_index(component_bit) else {
continue;
};
for (existing_index, existing) in self.tables.iter().enumerate() {
if existing.mask | component_bit == mask {
self.table_edges[existing_index].add_edges[component_index_value] =
Some(new_table_index);
}
if existing.mask & !component_bit == mask {
self.table_edges[existing_index].remove_edges[component_index_value] =
Some(new_table_index);
}
}
}
for component_bit in [POSITION, VELOCITY] {
let Some(component_index_value) = component_index(component_bit) else {
continue;
};
if let Some(&dest) = self.table_lookup.get(&(mask | component_bit)) {
self.table_edges[new_table_index].add_edges[component_index_value] = Some(dest);
}
if mask & component_bit != 0
&& let Some(&dest) = self.table_lookup.get(&(mask & !component_bit))
{
self.table_edges[new_table_index].remove_edges[component_index_value] = Some(dest);
}
}
new_table_index
}
pub fn cached_tables(&mut self, mask: u64) -> &[usize] {
if !self.query_cache.contains_key(&mask) {
let matching: Vec<usize> = self
.tables
.iter()
.enumerate()
.filter(|(_, table)| table.mask & mask == mask)
.map(|(index, _)| index)
.collect();
self.query_cache.insert(mask, matching);
}
&self.query_cache[&mask]
}
pub fn current_tick(&self) -> u32 {
self.current_tick
}
pub fn last_tick(&self) -> u32 {
self.last_tick
}
pub fn step(&mut self) {
self.collisions.update();
self.last_tick = self.current_tick;
self.current_tick = self.current_tick.wrapping_add(1);
}
pub fn spawn(&mut self, mask: u64) -> Entity {
let entity = self.allocator.allocate();
let table_index = self.get_or_create_table(mask);
let current_tick = self.current_tick;
let table = &mut self.tables[table_index];
let array_index = table.entities.len();
table.entities.push(entity);
if mask & POSITION != 0 {
table.positions.push(Position::default());
table.positions_changed.push(current_tick);
}
if mask & VELOCITY != 0 {
table.velocities.push(Velocity::default());
table.velocities_changed.push(current_tick);
}
self.entity_locations.set(entity, table_index, array_index);
entity
}
pub fn get_position(&self, entity: Entity) -> Option<&Position> {
let (table_index, array_index) = self.entity_locations.get(entity)?;
let table = &self.tables[table_index];
if table.mask & POSITION == 0 {
return None;
}
Some(&table.positions[array_index])
}
pub fn get_position_mut(&mut self, entity: Entity) -> Option<&mut Position> {
let (table_index, array_index) = self.entity_locations.get(entity)?;
let current_tick = self.current_tick;
let table = &mut self.tables[table_index];
if table.mask & POSITION == 0 {
return None;
}
table.positions_changed[array_index] = current_tick;
Some(&mut table.positions[array_index])
}
pub fn get_velocity(&self, entity: Entity) -> Option<&Velocity> {
let (table_index, array_index) = self.entity_locations.get(entity)?;
let table = &self.tables[table_index];
if table.mask & VELOCITY == 0 {
return None;
}
Some(&table.velocities[array_index])
}
pub fn get_velocity_mut(&mut self, entity: Entity) -> Option<&mut Velocity> {
let (table_index, array_index) = self.entity_locations.get(entity)?;
let current_tick = self.current_tick;
let table = &mut self.tables[table_index];
if table.mask & VELOCITY == 0 {
return None;
}
table.velocities_changed[array_index] = current_tick;
Some(&mut table.velocities[array_index])
}
pub fn set_position(&mut self, entity: Entity, value: Position) {
let Some((table_index, array_index)) = self.entity_locations.get(entity) else {
return;
};
let current_tick = self.current_tick;
if self.tables[table_index].mask & POSITION != 0 {
self.tables[table_index].positions[array_index] = value;
self.tables[table_index].positions_changed[array_index] = current_tick;
return;
}
self.add_components(entity, POSITION);
if let Some((table_index, array_index)) = self.entity_locations.get(entity) {
self.tables[table_index].positions[array_index] = value;
self.tables[table_index].positions_changed[array_index] = current_tick;
}
}
pub fn set_velocity(&mut self, entity: Entity, value: Velocity) {
let Some((table_index, array_index)) = self.entity_locations.get(entity) else {
return;
};
let current_tick = self.current_tick;
if self.tables[table_index].mask & VELOCITY != 0 {
self.tables[table_index].velocities[array_index] = value;
self.tables[table_index].velocities_changed[array_index] = current_tick;
return;
}
self.add_components(entity, VELOCITY);
if let Some((table_index, array_index)) = self.entity_locations.get(entity) {
self.tables[table_index].velocities[array_index] = value;
self.tables[table_index].velocities_changed[array_index] = current_tick;
}
}
pub fn move_entity(
&mut self,
entity: Entity,
from_table: usize,
from_index: usize,
to_table: usize,
) {
let from_mask = self.tables[from_table].mask;
let current_tick = self.current_tick;
let position = if from_mask & POSITION != 0 {
Some(std::mem::take(
&mut self.tables[from_table].positions[from_index],
))
} else {
None
};
let velocity = if from_mask & VELOCITY != 0 {
Some(std::mem::take(
&mut self.tables[from_table].velocities[from_index],
))
} else {
None
};
let to_array_index = {
let to = &mut self.tables[to_table];
let array_index = to.entities.len();
to.entities.push(entity);
if to.mask & POSITION != 0 {
to.positions.push(position.unwrap_or_default());
to.positions_changed.push(current_tick);
}
if to.mask & VELOCITY != 0 {
to.velocities.push(velocity.unwrap_or_default());
to.velocities_changed.push(current_tick);
}
array_index
};
self.entity_locations.set(entity, to_table, to_array_index);
let from = &mut self.tables[from_table];
let last_index = from.entities.len() - 1;
let swapped = if from_index < last_index {
Some(from.entities[last_index])
} else {
None
};
from.entities.swap_remove(from_index);
if from.mask & POSITION != 0 {
from.positions.swap_remove(from_index);
from.positions_changed.swap_remove(from_index);
}
if from.mask & VELOCITY != 0 {
from.velocities.swap_remove(from_index);
from.velocities_changed.swap_remove(from_index);
}
if let Some(moved) = swapped {
self.entity_locations.set(moved, from_table, from_index);
}
}
pub fn add_components(&mut self, entity: Entity, mask: u64) -> bool {
let Some((table_index, array_index)) = self.entity_locations.get(entity) else {
return false;
};
let current_mask = self.tables[table_index].mask;
if current_mask & mask == mask {
return true;
}
let cached = if mask.count_ones() == 1 {
component_index(mask)
.and_then(|index| self.table_edges[table_index].add_edges[index])
} else {
None
};
let new_table_index = match cached {
Some(index) => index,
None => self.get_or_create_table(current_mask | mask),
};
self.move_entity(entity, table_index, array_index, new_table_index);
true
}
pub fn remove_components(&mut self, entity: Entity, mask: u64) -> bool {
let Some((table_index, array_index)) = self.entity_locations.get(entity) else {
return false;
};
let current_mask = self.tables[table_index].mask;
if current_mask & mask == 0 {
return true;
}
let cached = if mask.count_ones() == 1 {
component_index(mask)
.and_then(|index| self.table_edges[table_index].remove_edges[index])
} else {
None
};
let new_table_index = match cached {
Some(index) => index,
None => self.get_or_create_table(current_mask & !mask),
};
self.move_entity(entity, table_index, array_index, new_table_index);
true
}
pub fn despawn(&mut self, entity: Entity) -> bool {
let Some((table_index, array_index)) = self.entity_locations.get(entity) else {
return false;
};
self.entity_locations.mark_deallocated(entity.id);
self.allocator.deallocate(entity);
self.players.remove(entity);
self.enemies.remove(entity);
let table = &mut self.tables[table_index];
let last_index = table.entities.len() - 1;
let swapped = if array_index < last_index {
Some(table.entities[last_index])
} else {
None
};
table.entities.swap_remove(array_index);
if table.mask & POSITION != 0 {
table.positions.swap_remove(array_index);
table.positions_changed.swap_remove(array_index);
}
if table.mask & VELOCITY != 0 {
table.velocities.swap_remove(array_index);
table.velocities_changed.swap_remove(array_index);
}
if let Some(moved) = swapped {
self.entity_locations.set(moved, table_index, array_index);
}
true
}
pub fn query_entities(&self, mask: u64) -> impl Iterator<Item = Entity> + '_ {
self.tables
.iter()
.filter(move |table| table.mask & mask == mask)
.flat_map(|table| table.entities.iter().copied())
}
pub fn for_each<F>(&self, include: u64, exclude: u64, mut f: F)
where
F: FnMut(Entity, &ComponentArrays, usize),
{
for table in &self.tables {
if table.mask & include != include || table.mask & exclude != 0 {
continue;
}
for array_index in 0..table.entities.len() {
let entity = table.entities[array_index];
f(entity, table, array_index);
}
}
}
pub fn for_each_mut<F>(&mut self, include: u64, exclude: u64, mut f: F)
where
F: FnMut(Entity, &mut ComponentArrays, usize),
{
let table_indices: Vec<usize> = self.cached_tables(include).to_vec();
for table_index in table_indices {
let table = &mut self.tables[table_index];
if table.mask & exclude != 0 {
continue;
}
for array_index in 0..table.entities.len() {
let entity = table.entities[array_index];
f(entity, table, array_index);
}
}
}
pub fn for_each_mut_changed<F>(&mut self, include: u64, exclude: u64, mut f: F)
where
F: FnMut(Entity, &mut ComponentArrays, usize),
{
let since_tick = self.last_tick;
let table_indices: Vec<usize> = self.cached_tables(include).to_vec();
for table_index in table_indices {
let table = &mut self.tables[table_index];
if table.mask & exclude != 0 {
continue;
}
for array_index in 0..table.entities.len() {
let mut changed = false;
if include & POSITION != 0
&& table.mask & POSITION != 0
&& table.positions_changed[array_index] > since_tick
{
changed = true;
}
if include & VELOCITY != 0
&& table.mask & VELOCITY != 0
&& table.velocities_changed[array_index] > since_tick
{
changed = true;
}
if changed {
let entity = table.entities[array_index];
f(entity, table, array_index);
}
}
}
}
pub fn send_collision(&mut self, event: CollisionEvent) {
self.collisions.send(event);
}
pub fn read_collisions(&self) -> impl Iterator<Item = &CollisionEvent> {
self.collisions.read()
}
pub fn read_collisions_since(&self, cursor: u64) -> &[CollisionEvent] {
self.collisions.events_since(cursor)
}
pub fn collision_sequence(&self) -> u64 {
self.collisions.sequence()
}
pub fn add_player(&mut self, entity: Entity) {
if self.entity_locations.get(entity).is_some() {
self.players.insert(entity);
}
}
pub fn remove_player(&mut self, entity: Entity) -> bool {
self.players.remove(entity)
}
pub fn has_player(&self, entity: Entity) -> bool {
self.players.contains(entity)
}
pub fn query_players(&self) -> impl Iterator<Item = Entity> + '_ {
self.players.iter()
}
pub fn add_enemy(&mut self, entity: Entity) {
if self.entity_locations.get(entity).is_some() {
self.enemies.insert(entity);
}
}
pub fn remove_enemy(&mut self, entity: Entity) -> bool {
self.enemies.remove(entity)
}
pub fn has_enemy(&self, entity: Entity) -> bool {
self.enemies.contains(entity)
}
pub fn query_enemies(&self) -> impl Iterator<Item = Entity> + '_ {
self.enemies.iter()
}
pub fn queue_spawn(&mut self, mask: u64) {
self.command_buffer.push(Command::Spawn { mask });
}
pub fn queue_despawn(&mut self, entity: Entity) {
self.command_buffer.push(Command::Despawn { entity });
}
pub fn queue_add_components(&mut self, entity: Entity, mask: u64) {
self.command_buffer
.push(Command::AddComponents { entity, mask });
}
pub fn queue_remove_components(&mut self, entity: Entity, mask: u64) {
self.command_buffer
.push(Command::RemoveComponents { entity, mask });
}
pub fn queue_set_position(&mut self, entity: Entity, value: Position) {
self.command_buffer
.push(Command::SetPosition { entity, value });
}
pub fn queue_set_velocity(&mut self, entity: Entity, value: Velocity) {
self.command_buffer
.push(Command::SetVelocity { entity, value });
}
pub fn queue_add_player(&mut self, entity: Entity) {
self.command_buffer.push(Command::AddPlayer { entity });
}
pub fn queue_remove_player(&mut self, entity: Entity) {
self.command_buffer.push(Command::RemovePlayer { entity });
}
pub fn queue_add_enemy(&mut self, entity: Entity) {
self.command_buffer.push(Command::AddEnemy { entity });
}
pub fn queue_remove_enemy(&mut self, entity: Entity) {
self.command_buffer.push(Command::RemoveEnemy { entity });
}
pub fn apply_commands(&mut self) {
let commands = std::mem::take(&mut self.command_buffer);
for command in commands {
match command {
Command::Spawn { mask } => {
self.spawn(mask);
}
Command::Despawn { entity } => {
self.despawn(entity);
}
Command::AddComponents { entity, mask } => {
self.add_components(entity, mask);
}
Command::RemoveComponents { entity, mask } => {
self.remove_components(entity, mask);
}
Command::SetPosition { entity, value } => {
self.set_position(entity, value);
}
Command::SetVelocity { entity, value } => {
self.set_velocity(entity, value);
}
Command::AddPlayer { entity } => {
self.add_player(entity);
}
Command::RemovePlayer { entity } => {
self.remove_player(entity);
}
Command::AddEnemy { entity } => {
self.add_enemy(entity);
}
Command::RemoveEnemy { entity } => {
self.remove_enemy(entity);
}
}
}
}
}
pub type SystemFn = Box<dyn FnMut(&mut World)>;
#[derive(Default)]
pub struct Schedule {
pub systems: Vec<(&'static str, SystemFn)>,
}
impl Schedule {
pub fn add<F>(&mut self, name: &'static str, system: F) -> &mut Self
where
F: FnMut(&mut World) + 'static,
{
self.systems.push((name, Box::new(system)));
self
}
pub fn run(&mut self, world: &mut World) {
for (_, system) in &mut self.systems {
system(world);
}
}
}
#[derive(Default)]
pub struct EntityBuilder {
position: Option<Position>,
velocity: Option<Velocity>,
}
impl EntityBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_position(mut self, value: Position) -> Self {
self.position = Some(value);
self
}
pub fn with_velocity(mut self, value: Velocity) -> Self {
self.velocity = Some(value);
self
}
pub fn spawn(self, world: &mut World) -> Entity {
let mut mask = 0u64;
if self.position.is_some() {
mask |= POSITION;
}
if self.velocity.is_some() {
mask |= VELOCITY;
}
let entity = world.spawn(mask);
if let Some(value) = self.position {
world.set_position(entity, value);
}
if let Some(value) = self.velocity {
world.set_velocity(entity, value);
}
entity
}
}
fn physics_system(world: &mut World) {
let dt = world.resources.delta_time;
let current_tick = world.current_tick();
world.for_each_mut(POSITION | VELOCITY, 0, |_entity, table, index| {
table.positions[index].x += table.velocities[index].x * dt;
table.positions[index].y += table.velocities[index].y * dt;
table.positions_changed[index] = current_tick;
});
}
fn collision_system(world: &mut World) {
let positions: Vec<(Entity, Position)> = world
.query_entities(POSITION)
.filter_map(|entity| world.get_position(entity).map(|position| (entity, position.clone())))
.collect();
for index_a in 0..positions.len() {
for index_b in (index_a + 1)..positions.len() {
let (entity_a, position_a) = &positions[index_a];
let (entity_b, position_b) = &positions[index_b];
let delta_x = position_a.x - position_b.x;
let delta_y = position_a.y - position_b.y;
if delta_x * delta_x + delta_y * delta_y < 1.0 {
world.send_collision(CollisionEvent {
entity_a: *entity_a,
entity_b: *entity_b,
});
}
}
}
}
fn collision_reporter(world: &mut World) {
let cursor = world.resources.collision_cursor;
for event in world.read_collisions_since(cursor) {
println!("collision: {:?} and {:?}", event.entity_a, event.entity_b);
}
world.resources.collision_cursor = world.collision_sequence();
}
fn render_changed_system(world: &mut World) {
world.for_each_mut_changed(POSITION, 0, |entity, table, index| {
println!(
"redraw {:?}: ({}, {})",
entity, table.positions[index].x, table.positions[index].y
);
});
}
fn main() {
let mut world = World::default();
world.resources.delta_time = 1.0 / 60.0;
let player = EntityBuilder::new()
.with_position(Position { x: 0.0, y: 0.0 })
.with_velocity(Velocity { x: 30.0, y: 0.0 })
.spawn(&mut world);
world.add_player(player);
let enemy = EntityBuilder::new()
.with_position(Position { x: 3.0, y: 0.0 })
.with_velocity(Velocity { x: -30.0, y: 0.0 })
.spawn(&mut world);
world.add_enemy(enemy);
let _landmark = EntityBuilder::new()
.with_position(Position { x: 10.0, y: 10.0 })
.spawn(&mut world);
let mut schedule = Schedule::default();
schedule
.add("physics", physics_system)
.add("collision", collision_system)
.add("collision_reporter", collision_reporter)
.add("render_changed", render_changed_system);
world.step();
for frame in 0..4 {
println!("--- frame {frame} ---");
schedule.run(&mut world);
if frame == 2 {
world.queue_despawn(enemy);
}
world.apply_commands();
world.resources.game_time += world.resources.delta_time;
world.step();
}
println!("--- final ---");
println!("players: {}", world.query_players().count());
println!("enemies: {}", world.query_enemies().count());
println!("entities with position: {}", world.query_entities(POSITION).count());
println!("game_time: {:.4}", world.resources.game_time);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment