use crate::{
bundle::{Bundle, BundleColumns},
component::Component,
entity::Entity,
world::World,
};
use std::{
marker::PhantomData,
sync::{Arc, Mutex},
};
pub trait Command: Send + Sync + 'static {
fn execute(self, world: &mut World);
}
#[derive(Default)]
pub struct CommandBuffer {
#[allow(clippy::type_complexity)]
commands: Vec<Box<dyn FnOnce(&mut World) + Send + Sync>>,
}
impl CommandBuffer {
pub fn schedule(&mut self, command: impl FnOnce(&mut World) + Send + Sync + 'static) {
self.commands.push(Box::new(command));
}
pub fn command(&mut self, command: impl Command) {
self.schedule(|world| command.execute(world));
}
pub fn commands(&mut self, mut buffer: CommandBuffer) {
self.schedule(move |world| {
buffer.execute(world);
});
}
pub fn execute(&mut self, world: &mut World) {
for command in std::mem::take(&mut self.commands) {
(command)(world);
}
}
pub fn clear(&mut self) {
self.commands.clear();
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
pub fn len(&self) -> usize {
self.commands.len()
}
}
#[derive(Default, Clone)]
pub struct SharedCommandBuffer {
inner: Arc<Mutex<CommandBuffer>>,
}
impl SharedCommandBuffer {
pub fn with<R>(&mut self, f: impl FnOnce(&mut CommandBuffer) -> R) -> Option<R> {
if let Ok(mut buffer) = self.inner.lock() {
Some(f(&mut buffer))
} else {
None
}
}
pub fn try_with<R>(&mut self, f: impl FnOnce(&mut CommandBuffer) -> R) -> Option<R> {
if let Ok(mut buffer) = self.inner.try_lock() {
Some(f(&mut buffer))
} else {
None
}
}
}
pub struct SpawnCommand<T: Bundle + Send + Sync + 'static> {
bundle: T,
}
impl<T: Bundle + Send + Sync + 'static> SpawnCommand<T> {
pub fn new(bundle: T) -> Self {
Self { bundle }
}
}
impl<T: Bundle + Send + Sync + 'static> Command for SpawnCommand<T> {
fn execute(self, world: &mut World) {
world.spawn(self.bundle).unwrap();
}
}
pub struct SpawnManyCommand<T: Bundle + Send + Sync + 'static> {
bundles: Vec<T>,
}
impl<T: Bundle + Send + Sync + 'static> SpawnManyCommand<T> {
pub fn new(bundles: impl IntoIterator<Item = T>) -> Self {
Self {
bundles: bundles.into_iter().collect(),
}
}
}
impl<T: Bundle + Send + Sync + 'static> Command for SpawnManyCommand<T> {
fn execute(self, world: &mut World) {
for bundle in self.bundles {
world.spawn(bundle).unwrap();
}
}
}
pub struct DespawnCommand {
entity: Entity,
}
impl DespawnCommand {
pub fn new(entity: Entity) -> Self {
Self { entity }
}
}
impl Command for DespawnCommand {
fn execute(self, world: &mut World) {
world.despawn(self.entity).unwrap();
}
}
pub struct DespawnManyCommand {
entities: Vec<Entity>,
}
impl DespawnManyCommand {
pub fn new(entities: impl IntoIterator<Item = Entity>) -> Self {
Self {
entities: entities.into_iter().collect(),
}
}
}
impl Command for DespawnManyCommand {
fn execute(self, world: &mut World) {
for entity in self.entities {
world.despawn(entity).unwrap();
}
}
}
pub struct InsertCommand<T: Bundle + Send + Sync + 'static> {
entity: Entity,
bundle: T,
}
impl<T: Bundle + Send + Sync + 'static> InsertCommand<T> {
pub fn new(entity: Entity, bundle: T) -> Self {
Self { entity, bundle }
}
}
impl<T: Bundle + Send + Sync + 'static> Command for InsertCommand<T> {
fn execute(self, world: &mut World) {
world.insert(self.entity, self.bundle).unwrap();
}
}
pub struct RemoveCommand<T: BundleColumns> {
entity: Entity,
_phantom: PhantomData<fn() -> T>,
}
impl<T: Bundle + Send + Sync + 'static> RemoveCommand<T> {
pub fn new(entity: Entity) -> Self {
Self {
entity,
_phantom: PhantomData,
}
}
}
impl<T: Bundle + Send + Sync + 'static> Command for RemoveCommand<T> {
fn execute(self, world: &mut World) {
world.remove::<T>(self.entity).unwrap();
}
}
pub struct RelateCommand<const LOCKING: bool, T: Component> {
payload: T,
from: Entity,
to: Entity,
}
impl<const LOCKING: bool, T: Component> RelateCommand<LOCKING, T> {
pub fn new(payload: T, from: Entity, to: Entity) -> Self {
Self { payload, from, to }
}
}
impl<const LOCKING: bool, T: Component> Command for RelateCommand<LOCKING, T> {
fn execute(self, world: &mut World) {
world
.relate::<LOCKING, T>(self.payload, self.from, self.to)
.unwrap();
}
}
pub struct RelateOneCommand<const LOCKING: bool, T: Component> {
payload: T,
from: Entity,
to: Entity,
}
impl<const LOCKING: bool, T: Component> RelateOneCommand<LOCKING, T> {
pub fn new(payload: T, from: Entity, to: Entity) -> Self {
Self { payload, from, to }
}
}
impl<const LOCKING: bool, T: Component> Command for RelateOneCommand<LOCKING, T> {
fn execute(self, world: &mut World) {
world
.relate_one::<LOCKING, T>(self.payload, self.from, self.to)
.unwrap();
}
}
pub struct RelatePairCommand<const LOCKING: bool, I: Component, O: Component> {
payload_incoming: I,
payload_outgoing: O,
from: Entity,
to: Entity,
}
impl<const LOCKING: bool, I: Component, O: Component> RelatePairCommand<LOCKING, I, O> {
pub fn new(payload_incoming: I, payload_outgoing: O, from: Entity, to: Entity) -> Self {
Self {
payload_incoming,
payload_outgoing,
from,
to,
}
}
}
impl<const LOCKING: bool, I: Component, O: Component> Command for RelatePairCommand<LOCKING, I, O> {
fn execute(self, world: &mut World) {
world
.relate_pair::<LOCKING, I, O>(
self.payload_incoming,
self.payload_outgoing,
self.from,
self.to,
)
.unwrap();
}
}
pub struct UnrelateCommand<const LOCKING: bool, T: Component> {
from: Entity,
to: Entity,
_phantom: PhantomData<fn() -> T>,
}
impl<const LOCKING: bool, T: Component> UnrelateCommand<LOCKING, T> {
pub fn new(from: Entity, to: Entity) -> Self {
Self {
from,
to,
_phantom: PhantomData,
}
}
}
impl<const LOCKING: bool, T: Component> Command for UnrelateCommand<LOCKING, T> {
fn execute(self, world: &mut World) {
world.unrelate::<LOCKING, T>(self.from, self.to).unwrap();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::world::World;
#[test]
fn test_async() {
fn is_async<T: Send + Sync>() {}
is_async::<CommandBuffer>();
is_async::<SharedCommandBuffer>();
}
#[test]
fn test_command_buffer() {
let mut world = World::default();
let mut buffer = CommandBuffer::default();
assert!(world.is_empty());
buffer.command(SpawnCommand::new((1u8, 2u16, 3u32)));
buffer.execute(&mut world);
assert_eq!(world.len(), 1);
let entity = world.entities().next().unwrap();
buffer.command(DespawnCommand::new(entity));
buffer.execute(&mut world);
assert!(world.is_empty());
}
}