use crate::stabletypeid::StableTypeId;
use core::{
mem,
ops::Range,
ptr::{self, NonNull},
};
use crate::{
align,
alloc::{
alloc::{alloc, dealloc},
vec::Vec,
},
archetype::TypeInfo,
layout::Layout,
Bundle, Component, DynamicBundle, Entity, World,
};
use gloss_utils::abi_stable_aliases::std_types::{ROption, ROption::RNone, ROption::RSome, RVec};
#[cfg(not(target_arch = "wasm32"))]
use abi_stable::sabi_extern_fn;
#[cfg(not(target_arch = "wasm32"))]
use gloss_utils::abi_stable_aliases::StableAbi;
#[repr(C)]
#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
pub struct CommandBuffer {
cmds: RVec<Cmd>,
storage: NonNull<u8>,
layout: Layout,
cursor: usize,
components: RVec<ComponentInfo>,
ids: RVec<StableTypeId>,
}
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 core_layout = core::alloc::Layout::from_size_align(layout.size(), layout.align()).unwrap();
let new_storage = NonNull::new_unchecked(alloc(core_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();
let core_layout = core::alloc::Layout::from_size_align(self.layout.size(), self.layout.align()).unwrap();
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(), core_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: RSome(entity),
component_range_start: first_component,
component_range_end: 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) {
#[allow(clippy::semicolon_if_nothing_returned)]
#[cfg_attr(not(target_arch = "wasm32"), sabi_extern_fn)] 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: RNone,
component_range_start: first_component,
component_range_end: 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_range = std::ops::Range {
start: entity.component_range_start,
end: entity.component_range_end,
};
let components = self.build(components_range);
match entity.entity {
RSome(entity) => {
let _ = world.insert(entity, components);
}
RNone => {
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();
}
pub fn len(&mut self) -> usize {
self.cmds.len()
}
pub fn is_empty(&mut self) -> bool {
self.cmds.is_empty()
}
}
unsafe impl Send for CommandBuffer {}
unsafe impl Sync for CommandBuffer {}
impl Drop for CommandBuffer {
fn drop(&mut self) {
let core_layout = core::alloc::Layout::from_size_align(self.layout.size(), self.layout.align()).unwrap();
self.clear();
if self.layout.size() != 0 {
unsafe {
dealloc(self.storage.as_ptr(), core_layout);
}
}
}
}
impl Default for CommandBuffer {
fn default() -> Self {
Self {
cmds: RVec::new(),
storage: NonNull::dangling(),
layout: Layout::from_size_align(0, 8).unwrap(),
cursor: 0,
components: RVec::new(),
ids: RVec::new(),
}
}
}
struct RecordedEntity<'a> {
cmd: &'a mut CommandBuffer,
components: Range<usize>,
}
unsafe impl DynamicBundle for RecordedEntity<'_> {
fn with_ids<T>(&self, f: impl FnOnce(&[StableTypeId]) -> 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));
}
}
}
}
#[repr(C)]
#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
struct ComponentInfo {
ty: TypeInfo,
offset: usize,
}
#[repr(C)]
#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
struct EntityIndex {
entity: ROption<Entity>,
component_range_start: usize,
component_range_end: usize,
}
#[repr(C)]
#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
struct RemovedComps {
#[cfg(target_arch = "wasm32")] remove: fn(&mut World, Entity),
#[cfg(not(target_arch = "wasm32"))]
remove: extern "C" fn(&mut World, Entity),
entity: Entity,
}
#[repr(C)]
#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
enum Cmd {
SpawnOrInsert(EntityIndex),
Remove(RemovedComps),
Despawn(Entity),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::similar_names)]
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).unwrap());
}
#[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).unwrap());
}
#[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);
}
}