use crate::entity::Entity;
use crate::world::World;
use std::collections::{HashMap, VecDeque};
#[derive(Clone, Copy)]
pub struct Pooled;
crate::impl_component!(Pooled);
pub struct ObjectPool {
pub prefab_id: u32,
pub inactive: VecDeque<Entity>,
}
impl ObjectPool {
pub fn new(prefab_id: u32) -> Self {
Self {
prefab_id,
inactive: VecDeque::new(),
}
}
}
pub struct PoolManager {
pools: HashMap<String, ObjectPool>,
}
impl Default for PoolManager {
fn default() -> Self {
Self::new()
}
}
impl PoolManager {
pub fn new() -> Self {
Self {
pools: HashMap::new(),
}
}
pub fn register_pool(&mut self, name: &str, prefab_entity: Entity) {
self.pools
.insert(name.to_string(), ObjectPool::new(prefab_entity.id()));
}
pub fn register_pool_hidden(&mut self, world: &mut World, name: &str, prefab_entity: Entity) {
world.add_component(prefab_entity, Pooled);
self.pools
.insert(name.to_string(), ObjectPool::new(prefab_entity.id()));
}
pub fn register<B: crate::component::Bundle>(
&mut self,
world: &mut World,
name: &str,
bundle: B,
) {
let prefab = world.spawn_bundle(bundle);
self.register_pool(name, prefab);
}
pub fn instantiate(&mut self, world: &mut World, name: &str) -> Option<Entity> {
let pool = self.pools.get_mut(name)?;
if let Some(entity) = pool.inactive.pop_front() {
world.remove_component::<Pooled>(entity);
Some(entity)
} else {
let new_entities = world.clone_entity(pool.prefab_id, 1)?;
let new_ent = new_entities[0];
world.remove_component::<Pooled>(new_ent);
Some(new_ent)
}
}
pub fn destroy(&mut self, world: &mut World, name: &str, entity: Entity) {
if let Some(pool) = self.pools.get_mut(name) {
if !world.is_alive(entity) {
tracing::debug!(
entity = entity.id(),
pool = name,
"PoolManager::destroy: entity is already dead; not parked"
);
return;
}
if pool.inactive.contains(&entity) {
tracing::debug!(
entity = entity.id(),
pool = name,
"PoolManager::destroy: entity is already parked; ignoring double retire"
);
return;
}
world.add_component(entity, Pooled);
pool.inactive.push_back(entity);
} else {
world.despawn(entity);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Copy)]
struct Bullet;
crate::impl_component!(Bullet);
fn setup() -> (World, PoolManager) {
let mut world = World::new();
let prefab = world.spawn();
world.add_component(prefab, Bullet);
let mut pools = PoolManager::new();
pools.register_pool("bullets", prefab);
(world, pools)
}
#[test]
fn destroy_parks_an_entity_at_most_once() {
let (mut world, mut pools) = setup();
let e = pools.instantiate(&mut world, "bullets").expect("pool exists");
pools.destroy(&mut world, "bullets", e);
pools.destroy(&mut world, "bullets", e);
let a = pools.instantiate(&mut world, "bullets").expect("pool exists");
let b = pools.instantiate(&mut world, "bullets").expect("pool exists");
assert_eq!(a, e, "the parked entity is reused first");
assert_ne!(
a, b,
"the same entity was handed to two callers (double-parked)"
);
}
#[test]
fn destroy_does_not_park_a_dead_entity() {
let (mut world, mut pools) = setup();
let e = pools.instantiate(&mut world, "bullets").expect("pool exists");
world.despawn(e);
pools.destroy(&mut world, "bullets", e);
let next = pools.instantiate(&mut world, "bullets").expect("pool exists");
assert!(
world.is_alive(next),
"instantiate handed out a despawned entity"
);
assert_ne!(next, e, "the dead handle must not come back out of the pool");
}
#[test]
fn destroy_on_unknown_pool_despawns() {
let (mut world, mut pools) = setup();
let e = pools.instantiate(&mut world, "bullets").expect("pool exists");
pools.destroy(&mut world, "no_such_pool", e);
assert!(!world.is_alive(e), "unknown pool falls back to despawn");
}
}