2024-03-31 02:12:00 +00:00
|
|
|
use std::{any::Any, cell::RefMut, mem::{self, MaybeUninit}, ptr::{self, NonNull}};
|
2024-02-23 21:34:21 +00:00
|
|
|
|
|
|
|
use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World};
|
|
|
|
|
2024-03-03 02:20:19 +00:00
|
|
|
/// A Command be used to delay mutation of the world until after this system is ran.
|
2024-02-24 16:09:26 +00:00
|
|
|
pub trait Command: Any {
|
|
|
|
fn as_any_boxed(self: Box<Self>) -> Box<dyn Any>;
|
|
|
|
|
2024-03-03 02:20:19 +00:00
|
|
|
/// Executes the command
|
|
|
|
fn run(self, world: &mut World);
|
2024-02-23 21:34:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<F> Command for F
|
|
|
|
where
|
2024-03-03 02:20:19 +00:00
|
|
|
F: FnOnce(&mut World) + 'static
|
2024-02-23 21:34:21 +00:00
|
|
|
{
|
2024-02-24 16:09:26 +00:00
|
|
|
fn as_any_boxed(self: Box<Self>) -> Box<dyn Any> {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-03-03 02:20:19 +00:00
|
|
|
fn run(self, world: &mut World) {
|
2024-02-23 21:34:21 +00:00
|
|
|
self(world)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-31 02:12:00 +00:00
|
|
|
type RunCommand = unsafe fn(cmd: *mut (), world: Option<&mut World>) -> usize;
|
|
|
|
|
|
|
|
#[repr(C, packed)]
|
|
|
|
struct PackedCommand<T: Command> {
|
|
|
|
run: RunCommand,
|
|
|
|
cmd: T,
|
|
|
|
}
|
2024-02-23 21:34:21 +00:00
|
|
|
|
2024-03-03 02:20:19 +00:00
|
|
|
/// Stores a queue of commands that will get executed after the system is ran.
|
|
|
|
///
|
|
|
|
/// This struct can be inserted as a resource into the world, and the commands will be
|
|
|
|
/// executed by the [`GraphExecutor`](crate::system::GraphExecutor) after the system is executed.
|
2024-02-23 21:34:21 +00:00
|
|
|
#[derive(Default)]
|
2024-03-31 02:12:00 +00:00
|
|
|
pub struct CommandQueue {
|
|
|
|
data: Vec<MaybeUninit<u8>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CommandQueue {
|
|
|
|
/// Execute the commands in the queue.
|
|
|
|
///
|
|
|
|
/// If `world` is `None`, the commands will just be dropped and the memory freed.
|
|
|
|
fn execute(&mut self, mut world: Option<&mut World>) {
|
|
|
|
let range = self.data.as_mut_ptr_range();
|
|
|
|
let mut current = range.start;
|
|
|
|
let end = range.end;
|
|
|
|
|
|
|
|
while current < end {
|
|
|
|
// Retrieve the runner for the command.
|
|
|
|
// Safety: current pointer will either be the start of the buffer, or at the start of a new PackedCommand
|
|
|
|
let run_fn = unsafe { current.cast::<RunCommand>().read_unaligned() };
|
|
|
|
|
|
|
|
// Retrieves the pointer to the command which is just after RunCommand due to PackedCommand.
|
|
|
|
// Safety: PackedCommand is repr C and packed, so it will be right after the RunCommand.
|
|
|
|
current = unsafe { current.add(mem::size_of::<RunCommand>()) };
|
|
|
|
|
|
|
|
// Now run the command, providing the type erased pointer to the command.
|
|
|
|
let read_size = unsafe { run_fn(current.cast(), world.as_deref_mut()) };
|
|
|
|
|
|
|
|
// The pointer is added to so that it is just after the command that was ran.
|
|
|
|
// Safety: the RunCommand returns the size of the command
|
|
|
|
current = unsafe { current.add(read_size) };
|
|
|
|
}
|
|
|
|
|
|
|
|
// Safety: all of the commands were just read from the pointers.
|
|
|
|
unsafe { self.data.set_len(0) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for CommandQueue {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if !self.data.is_empty() {
|
|
|
|
println!("CommandQueue has commands but is being dropped");
|
|
|
|
}
|
|
|
|
|
|
|
|
self.execute(None);
|
|
|
|
}
|
|
|
|
}
|
2024-02-23 21:34:21 +00:00
|
|
|
|
2024-03-03 02:20:19 +00:00
|
|
|
/// Used in a system to queue up commands that will run right after this system.
|
|
|
|
///
|
|
|
|
/// This can be used to delay the mutation of the world until after the system is ran. These
|
2024-03-31 02:12:00 +00:00
|
|
|
/// must be used if you're mutating the world inside a [`View`](crate::query::View).
|
2024-03-03 02:20:19 +00:00
|
|
|
///
|
|
|
|
/// ```nobuild
|
|
|
|
/// fn particle_spawner_system(
|
|
|
|
/// commands: Commands,
|
2024-03-31 02:12:00 +00:00
|
|
|
/// view: View<(&Campfire, &Transform)>
|
2024-03-03 02:20:19 +00:00
|
|
|
/// ) -> anyhow::Result<()> {
|
|
|
|
/// for (campfire, pos) in view.iter() {
|
|
|
|
/// // If you do not use commands to spawn this, the next iteration
|
|
|
|
/// // of the view will cause a segfault.
|
|
|
|
/// commands.spawn((pos, Particle::new(/* ... */)));
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// Ok(())
|
|
|
|
/// }
|
|
|
|
/// ```
|
2024-02-23 21:34:21 +00:00
|
|
|
pub struct Commands<'a, 'b> {
|
|
|
|
queue: &'b mut CommandQueue,
|
|
|
|
entities: &'a mut Entities,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'b> Commands<'a, 'b> {
|
|
|
|
pub fn new(queue: &'b mut CommandQueue, world: &'a mut World) -> Self {
|
|
|
|
Self {
|
|
|
|
queue,
|
|
|
|
entities: &mut world.entities,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add a command to the end of the command queue
|
2024-02-24 16:09:26 +00:00
|
|
|
pub fn add<C: Command>(&mut self, cmd: C) {
|
2024-03-31 02:12:00 +00:00
|
|
|
let run_fn = |cmd_ptr: *mut (), world: Option<&mut World>| {
|
|
|
|
// Safety: the pointer is a type-erased pointer to the command. The pointer is read
|
|
|
|
// then dropped out of scope, this closure will not be ran again so no use-after-free
|
|
|
|
// will occur.
|
|
|
|
let cmd: C = unsafe { ptr::read_unaligned(cmd_ptr.cast::<C>()) };
|
|
|
|
match world {
|
|
|
|
Some(world) => cmd.run(world),
|
|
|
|
None => {} // cmd just gets dropped
|
|
|
|
}
|
2024-02-24 16:09:26 +00:00
|
|
|
|
2024-03-31 02:12:00 +00:00
|
|
|
// the size of the command must be returned to increment the pointer when applying
|
|
|
|
// the command queue.
|
|
|
|
mem::size_of::<C>()
|
2024-02-23 21:34:21 +00:00
|
|
|
};
|
|
|
|
|
2024-03-31 02:12:00 +00:00
|
|
|
let data = &mut self.queue.data;
|
|
|
|
|
|
|
|
// Reserve enough bytes from the vec to store the packed command and its run fn.
|
|
|
|
let old_len = data.len();
|
|
|
|
data.reserve(mem::size_of::<PackedCommand<C>>());
|
|
|
|
|
|
|
|
// Get a pointer to the end of the packed data. Safe since we just reserved enough memory
|
|
|
|
// to store this command.
|
|
|
|
let end_ptr = unsafe { data.as_mut_ptr().add(old_len) };
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
// write the command and its runner into the buffer
|
|
|
|
end_ptr.cast::<PackedCommand<C>>()
|
|
|
|
// written unaligned to keep everything packed
|
|
|
|
.write_unaligned(PackedCommand {
|
|
|
|
run: run_fn,
|
|
|
|
cmd,
|
|
|
|
});
|
|
|
|
|
|
|
|
// we wrote to the vec's buffer without using its api, so we need manually
|
|
|
|
// set the length of the vec.
|
|
|
|
data.set_len(old_len + mem::size_of::<PackedCommand<C>>());
|
|
|
|
}
|
2024-02-23 21:34:21 +00:00
|
|
|
}
|
|
|
|
|
2024-03-03 02:20:19 +00:00
|
|
|
/// Spawn an entity into the World. See [`World::spawn`]
|
2024-02-24 16:09:26 +00:00
|
|
|
pub fn spawn<B: Bundle + 'static>(&mut self, bundle: B) -> Entity {
|
2024-02-23 21:34:21 +00:00
|
|
|
let e = self.entities.reserve();
|
|
|
|
|
|
|
|
self.add(move |world: &mut World| {
|
|
|
|
world.spawn_into(e, bundle);
|
|
|
|
});
|
|
|
|
|
|
|
|
e
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Execute all commands in the queue, in order of insertion
|
2024-03-31 02:12:00 +00:00
|
|
|
pub fn execute(&mut self, world: &mut World) {
|
|
|
|
self.queue.execute(Some(world));
|
2024-02-23 21:34:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FnArgFetcher for Commands<'_, '_> {
|
|
|
|
type State = CommandQueue;
|
|
|
|
type Arg<'a, 'state> = Commands<'a, 'state>;
|
|
|
|
|
|
|
|
fn world_access(&self) -> Access {
|
|
|
|
Access::Write
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn get<'a, 'state>(state: &'state mut Self::State, mut world_ptr: ptr::NonNull<World>) -> Self::Arg<'a, 'state> {
|
|
|
|
let world = world_ptr.as_mut();
|
|
|
|
Commands::new(state, world)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_state(_: NonNull<World>) -> Self::State {
|
|
|
|
CommandQueue::default()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn apply_deferred<'a>(mut state: Self::State, mut world_ptr: NonNull<World>) {
|
|
|
|
let world = unsafe { world_ptr.as_mut() };
|
|
|
|
|
|
|
|
let mut cmds = Commands::new(&mut state, world);
|
2024-02-24 16:09:26 +00:00
|
|
|
// safety: Commands has a mut borrow only to entities in the world
|
2024-02-23 21:34:21 +00:00
|
|
|
let world = unsafe { world_ptr.as_mut() };
|
2024-03-31 02:12:00 +00:00
|
|
|
cmds.execute(world);
|
2024-02-23 21:34:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-31 02:12:00 +00:00
|
|
|
/// A system for executing deferred commands that are stored in a [`World`] as a Resource.
|
|
|
|
///
|
|
|
|
/// Commands are usually added inside a system from a [`Commands`] object created just for it
|
|
|
|
/// as an fn argument. However, there may be cases that commands cannot be added that way, so
|
|
|
|
/// they can also be added as a resource and executed later in this system.
|
2024-02-23 21:34:21 +00:00
|
|
|
pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut<Commands>) -> anyhow::Result<()> {
|
2024-03-31 02:12:00 +00:00
|
|
|
commands.execute(world);
|
2024-02-23 21:34:21 +00:00
|
|
|
|
|
|
|
Ok(())
|
2024-02-24 16:09:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2024-03-31 02:12:00 +00:00
|
|
|
use std::{cell::Ref, ptr::NonNull, sync::{atomic::{AtomicU32, Ordering}, Arc}};
|
2024-02-24 16:09:26 +00:00
|
|
|
|
2024-03-31 02:12:00 +00:00
|
|
|
use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, CommandQueue, Commands, DynTypeId, World};
|
2024-02-24 16:09:26 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn deferred_commands() {
|
|
|
|
let mut world = World::new();
|
|
|
|
let vecs = vec![Vec2::rand(), Vec2::rand(), Vec2::rand()];
|
|
|
|
world.spawn((vecs[0],));
|
|
|
|
world.spawn((vecs[1],));
|
|
|
|
world.spawn((vecs[2],));
|
|
|
|
|
|
|
|
let spawned_vec = Vec2::rand();
|
|
|
|
|
|
|
|
let spawned_vec_cl = spawned_vec.clone();
|
|
|
|
let test_sys = move |mut commands: Commands| -> anyhow::Result<()> {
|
|
|
|
commands.spawn((spawned_vec_cl.clone(),));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut graph_exec = GraphExecutor::new();
|
|
|
|
graph_exec.insert_system("test", test_sys.into_system(), &[]);
|
|
|
|
graph_exec.execute(NonNull::from(&world), true).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(world.entities.len(), 4);
|
|
|
|
|
|
|
|
// there's only one archetype
|
|
|
|
let arch = world.archetypes.values().next().unwrap();
|
|
|
|
let col = arch.get_column(DynTypeId::of::<Vec2>()).unwrap();
|
2024-03-03 01:20:38 +00:00
|
|
|
let vec2: Ref<Vec2> = unsafe { col.get(3) };
|
2024-02-24 16:09:26 +00:00
|
|
|
assert_eq!(vec2.clone(), spawned_vec);
|
|
|
|
}
|
2024-03-31 02:12:00 +00:00
|
|
|
|
|
|
|
/// A test that ensures a command in a command queue will only ever run once.
|
|
|
|
#[test]
|
|
|
|
fn commands_only_one_exec() {
|
|
|
|
let mut world = World::new();
|
|
|
|
|
|
|
|
let counter = Arc::new(AtomicU32::new(0));
|
|
|
|
|
|
|
|
let mut queue = CommandQueue::default();
|
|
|
|
let mut commands = Commands::new(&mut queue, &mut world);
|
|
|
|
|
|
|
|
let counter_cl = counter.clone();
|
|
|
|
commands.add(move |_world: &mut World| {
|
|
|
|
counter_cl.fetch_add(1, Ordering::AcqRel);
|
|
|
|
});
|
|
|
|
|
|
|
|
queue.execute(Some(&mut world));
|
|
|
|
assert_eq!(1, counter.load(Ordering::Acquire));
|
|
|
|
|
|
|
|
queue.execute(Some(&mut world));
|
|
|
|
// If its not one, the command somehow was executed.
|
|
|
|
// I would be surprised it wouldn't cause some segfault but still increment the counter
|
|
|
|
assert_eq!(1, counter.load(Ordering::Acquire));
|
|
|
|
}
|
2024-02-23 21:34:21 +00:00
|
|
|
}
|