use std::mem::{ManuallyDrop, MaybeUninit};
use super::Command;
use crate::world::World;
struct CommandMeta {
offset: usize,
func: unsafe fn(value: *mut MaybeUninit<u8>, world: &mut World),
}
#[derive(Default)]
pub struct CommandQueue {
bytes: Vec<MaybeUninit<u8>>,
metas: Vec<CommandMeta>,
}
unsafe impl Send for CommandQueue {}
unsafe impl Sync for CommandQueue {}
impl CommandQueue {
#[inline]
pub fn push<C>(&mut self, command: C)
where
C: Command,
{
unsafe fn write_command<T: Command>(command: *mut MaybeUninit<u8>, world: &mut World) {
let command = command.cast::<T>().read_unaligned();
command.write(world);
}
let size = std::mem::size_of::<C>();
let old_len = self.bytes.len();
self.metas.push(CommandMeta {
offset: old_len,
func: write_command::<C>,
});
let command = ManuallyDrop::new(command);
if size > 0 {
self.bytes.reserve(size);
unsafe {
std::ptr::copy_nonoverlapping(
&*command as *const C as *const MaybeUninit<u8>,
self.bytes.as_mut_ptr().add(old_len),
size,
);
self.bytes.set_len(old_len + size);
}
}
}
#[inline]
pub fn apply(&mut self, world: &mut World) {
world.flush();
unsafe { self.bytes.set_len(0) };
for meta in self.metas.drain(..) {
unsafe {
(meta.func)(self.bytes.as_mut_ptr().add(meta.offset), world);
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::{
panic::AssertUnwindSafe,
sync::{
atomic::{AtomicU32, Ordering},
Arc,
},
};
struct DropCheck(Arc<AtomicU32>);
impl DropCheck {
fn new() -> (Self, Arc<AtomicU32>) {
let drops = Arc::new(AtomicU32::new(0));
(Self(drops.clone()), drops)
}
}
impl Drop for DropCheck {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
impl Command for DropCheck {
fn write(self, _: &mut World) {}
}
#[test]
fn test_command_queue_inner_drop() {
let mut queue = CommandQueue::default();
let (dropcheck_a, drops_a) = DropCheck::new();
let (dropcheck_b, drops_b) = DropCheck::new();
queue.push(dropcheck_a);
queue.push(dropcheck_b);
assert_eq!(drops_a.load(Ordering::Relaxed), 0);
assert_eq!(drops_b.load(Ordering::Relaxed), 0);
let mut world = World::new();
queue.apply(&mut world);
assert_eq!(drops_a.load(Ordering::Relaxed), 1);
assert_eq!(drops_b.load(Ordering::Relaxed), 1);
}
struct SpawnCommand;
impl Command for SpawnCommand {
fn write(self, world: &mut World) {
world.spawn_empty();
}
}
#[test]
fn test_command_queue_inner() {
let mut queue = CommandQueue::default();
queue.push(SpawnCommand);
queue.push(SpawnCommand);
let mut world = World::new();
queue.apply(&mut world);
assert_eq!(world.entities().len(), 2);
queue.apply(&mut world);
assert_eq!(world.entities().len(), 2);
}
struct PanicCommand(String);
impl Command for PanicCommand {
fn write(self, _: &mut World) {
panic!("command is panicking");
}
}
#[test]
fn test_command_queue_inner_panic_safe() {
std::panic::set_hook(Box::new(|_| {}));
let mut queue = CommandQueue::default();
queue.push(PanicCommand("I panic!".to_owned()));
queue.push(SpawnCommand);
let mut world = World::new();
let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
queue.apply(&mut world);
}));
assert_eq!(queue.bytes.len(), 0);
assert_eq!(queue.metas.len(), 0);
queue.push(SpawnCommand);
queue.push(SpawnCommand);
queue.apply(&mut world);
assert_eq!(world.entities().len(), 2);
}
fn assert_is_send_impl(_: impl Send) {}
fn assert_is_send(command: impl Command) {
assert_is_send_impl(command);
}
#[test]
fn test_command_is_send() {
assert_is_send(SpawnCommand);
}
struct CommandWithPadding(u8, u16);
impl Command for CommandWithPadding {
fn write(self, _: &mut World) {}
}
#[cfg(miri)]
#[test]
fn test_uninit_bytes() {
let mut queue = CommandQueue::default();
queue.push(CommandWithPadding(0, 0));
let _ = format!("{:?}", queue.bytes);
}
}