use core::any::TypeId;
use core::mem;
use core::ops::Range;
use core::ptr::{self, NonNull};
use crate::alloc::alloc::{alloc, dealloc, Layout};
use crate::alloc::vec::Vec;
use crate::archetype::TypeInfo;
use crate::{align, DynamicBundle};
use crate::{Bundle, Entity};
use crate::{Component, World};
pub struct CommandBuffer {
cmds: Vec<Cmd>,
storage: NonNull<u8>,
layout: Layout,
cursor: usize,
components: Vec<ComponentInfo>,
ids: Vec<TypeId>,
}
impl CommandBuffer {
pub fn new() -> Self {
Self::default()
}
unsafe fn grow(
min_size: usize,
cursor: usize,
align: usize,
storage: NonNull<u8>,
) -> (NonNull<u8>, Layout) {
let layout = Layout::from_size_align(min_size.next_power_of_two().max(64), align).unwrap();
let new_storage = NonNull::new_unchecked(alloc(layout));
ptr::copy_nonoverlapping(storage.as_ptr(), new_storage.as_ptr(), cursor);
(new_storage, layout)
}
unsafe fn add_inner(&mut self, ptr: *mut u8, ty: TypeInfo) {
let offset = align(self.cursor, ty.layout().align());
let end = offset + ty.layout().size();
if end > self.layout.size() || ty.layout().align() > self.layout.align() {
let new_align = self.layout.align().max(ty.layout().align());
let (new_storage, new_layout) = Self::grow(end, self.cursor, new_align, self.storage);
if self.layout.size() != 0 {
dealloc(self.storage.as_ptr(), self.layout);
}
self.storage = new_storage;
self.layout = new_layout;
}
let addr = self.storage.as_ptr().add(offset);
ptr::copy_nonoverlapping(ptr, addr, ty.layout().size());
self.components.push(ComponentInfo { ty, offset });
self.cursor = end;
}
pub fn insert(&mut self, entity: Entity, components: impl DynamicBundle) {
let first_component = self.components.len();
unsafe {
components.put(|ptr, ty| self.add_inner(ptr, ty));
}
self.components[first_component..].sort_unstable_by_key(|c| c.ty);
self.cmds.push(Cmd::SpawnOrInsert(EntityIndex {
entity: Some(entity),
components: first_component..self.components.len(),
}));
}
pub fn insert_one(&mut self, entity: Entity, component: impl Component) {
self.insert(entity, (component,));
}
pub fn remove<T: Bundle + 'static>(&mut self, ent: Entity) {
fn remove_bundle_and_ignore_result<T: Bundle + 'static>(world: &mut World, ents: Entity) {
let _ = world.remove::<T>(ents);
}
self.cmds.push(Cmd::Remove(RemovedComps {
remove: remove_bundle_and_ignore_result::<T>,
entity: ent,
}));
}
pub fn remove_one<T: Component>(&mut self, ent: Entity) {
self.remove::<(T,)>(ent);
}
pub fn despawn(&mut self, entity: Entity) {
self.cmds.push(Cmd::Despawn(entity));
}
pub fn spawn(&mut self, components: impl DynamicBundle) {
let first_component = self.components.len();
unsafe {
components.put(|ptr, ty| self.add_inner(ptr, ty));
}
self.components[first_component..].sort_unstable_by_key(|c| c.ty);
self.cmds.push(Cmd::SpawnOrInsert(EntityIndex {
entity: None,
components: first_component..self.components.len(),
}));
}
pub fn run_on(&mut self, world: &mut World) {
for i in 0..self.cmds.len() {
match mem::replace(&mut self.cmds[i], Cmd::Despawn(Entity::DANGLING)) {
Cmd::SpawnOrInsert(entity) => {
let components = self.build(entity.components);
match entity.entity {
Some(entity) => {
let _ = world.insert(entity, components);
}
None => {
world.spawn(components);
}
}
}
Cmd::Remove(remove) => {
(remove.remove)(world, remove.entity);
}
Cmd::Despawn(entity) => {
let _ = world.despawn(entity);
}
}
}
self.components.clear();
self.clear();
}
fn build(&mut self, components: Range<usize>) -> RecordedEntity<'_> {
self.ids.clear();
self.ids.extend(
self.components[components.clone()]
.iter()
.map(|x| x.ty.id()),
);
RecordedEntity {
cmd: self,
components,
}
}
pub fn clear(&mut self) {
self.ids.clear();
self.cursor = 0;
for info in self.components.drain(..) {
unsafe {
info.ty.drop(self.storage.as_ptr().add(info.offset));
}
}
self.cmds.clear();
}
}
unsafe impl Send for CommandBuffer {}
unsafe impl Sync for CommandBuffer {}
impl Drop for CommandBuffer {
fn drop(&mut self) {
self.clear();
if self.layout.size() != 0 {
unsafe {
dealloc(self.storage.as_ptr(), self.layout);
}
}
}
}
impl Default for CommandBuffer {
fn default() -> Self {
Self {
cmds: Vec::new(),
storage: NonNull::dangling(),
layout: Layout::from_size_align(0, 8).unwrap(),
cursor: 0,
components: Vec::new(),
ids: Vec::new(),
}
}
}
struct RecordedEntity<'a> {
cmd: &'a mut CommandBuffer,
components: Range<usize>,
}
unsafe impl DynamicBundle for RecordedEntity<'_> {
fn with_ids<T>(&self, f: impl FnOnce(&[TypeId]) -> T) -> T {
f(&self.cmd.ids)
}
fn type_info(&self) -> Vec<TypeInfo> {
self.cmd.components[self.components.clone()]
.iter()
.map(|x| x.ty)
.collect()
}
unsafe fn put(mut self, mut f: impl FnMut(*mut u8, TypeInfo)) {
let components = mem::replace(&mut self.components, 0..0);
for info in &self.cmd.components[components] {
let ptr = self.cmd.storage.as_ptr().add(info.offset);
f(ptr, info.ty);
}
}
}
impl Drop for RecordedEntity<'_> {
fn drop(&mut self) {
unsafe {
for info in &self.cmd.components[self.components.clone()] {
info.ty.drop(self.cmd.storage.as_ptr().add(info.offset));
}
}
}
}
struct ComponentInfo {
ty: TypeInfo,
offset: usize,
}
struct EntityIndex {
entity: Option<Entity>,
components: Range<usize>,
}
struct RemovedComps {
remove: fn(&mut World, Entity),
entity: Entity,
}
enum Cmd {
SpawnOrInsert(EntityIndex),
Remove(RemovedComps),
Despawn(Entity),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn populate_archetypes() {
let mut world = World::new();
let mut buffer = CommandBuffer::new();
let ent = world.reserve_entity();
let enta = world.reserve_entity();
let entb = world.reserve_entity();
let entc = world.reserve_entity();
buffer.insert(ent, (true, "a"));
buffer.insert(entc, (true, "a"));
buffer.insert(enta, (1, 1.0));
buffer.insert(entb, (1.0, "a"));
buffer.run_on(&mut world);
assert_eq!(world.archetypes().len(), 4);
}
#[test]
fn failed_insert_regression() {
#[derive(Clone)]
struct A;
let mut world = World::new();
let a = world.spawn((A,));
let b = world.spawn((A,));
world.clear();
let mut cmd = CommandBuffer::new();
cmd.insert_one(a, A);
cmd.insert_one(b, A);
world.spawn_at(a, ());
cmd.run_on(&mut world);
assert!(world.satisfies::<&A>(a));
}
#[test]
fn insert_then_remove() {
let mut world = World::new();
let a = world.spawn(());
let mut cmd = CommandBuffer::new();
cmd.insert_one(a, 42i32);
cmd.remove_one::<i32>(a);
cmd.run_on(&mut world);
assert!(!world.satisfies::<&i32>(a));
}
#[test]
fn remove_then_insert() {
let mut world = World::new();
let a = world.spawn((17i32,));
let mut cmd = CommandBuffer::new();
cmd.remove_one::<i32>(a);
cmd.insert_one(a, 42i32);
cmd.run_on(&mut world);
assert_eq!(*world.get::<&i32>(a).unwrap(), 42);
}
}