Skip to content

Commit

Permalink
Implement Custom Component Ids
Browse files Browse the repository at this point in the history
This replaces the use of TypeId for identifying components and instead
uses ComponentId which is an enum that can represent either a Rust
TypeId or an external id represented by a u64.
  • Loading branch information
zicklag committed Nov 3, 2020
1 parent 1aa832b commit 0798295
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 119 deletions.
22 changes: 11 additions & 11 deletions crates/bevy_ecs/hecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,17 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let n = tys.len();
let code = quote! {
impl #path::DynamicBundle for #ident {
fn with_ids<T>(&self, f: impl FnOnce(&[std::any::TypeId]) -> T) -> T {
fn with_ids<T>(&self, f: impl FnOnce(&[#path::ComponentId]) -> T) -> T {
Self::with_static_ids(f)
}

fn type_info(&self) -> Vec<#path::TypeInfo> {
Self::static_type_info()
}

unsafe fn put(mut self, mut f: impl FnMut(*mut u8, std::any::TypeId, usize) -> bool) {
unsafe fn put(mut self, mut f: impl FnMut(*mut u8, #path::ComponentId, usize) -> bool) {
#(
if f((&mut self.#fields as *mut #tys).cast::<u8>(), std::any::TypeId::of::<#tys>(), std::mem::size_of::<#tys>()) {
if f((&mut self.#fields as *mut #tys).cast::<u8>(), std::any::TypeId::of::<#tys>().into(), std::mem::size_of::<#tys>()) {
#[allow(clippy::forget_copy)]
std::mem::forget(self.#fields);
}
Expand All @@ -77,22 +77,22 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
}

impl #path::Bundle for #ident {
fn with_static_ids<T>(f: impl FnOnce(&[std::any::TypeId]) -> T) -> T {
fn with_static_ids<T>(f: impl FnOnce(&[#path::ComponentId]) -> T) -> T {
use std::any::TypeId;
use std::mem;

#path::lazy_static::lazy_static! {
static ref ELEMENTS: [TypeId; #n] = {
static ref ELEMENTS: [#path::ComponentId; #n] = {
let mut dedup = #path::bevy_utils::HashSet::default();
for &(ty, name) in [#((std::any::TypeId::of::<#tys>(), std::any::type_name::<#tys>())),*].iter() {
for &(ty, name) in [#((#path::ComponentId::from(std::any::TypeId::of::<#tys>()), std::any::type_name::<#tys>())),*].iter() {
if !dedup.insert(ty) {
panic!("{} has multiple {} fields; each type must occur at most once!", stringify!(#ident), name);
}
}

let mut tys = [#((mem::align_of::<#tys>(), TypeId::of::<#tys>())),*];
let mut tys = [#((mem::align_of::<#tys>(), #path::ComponentId::from(TypeId::of::<#tys>()))),*];
tys.sort_unstable_by(|x, y| x.0.cmp(&y.0).reverse().then(x.1.cmp(&y.1)));
let mut ids = [TypeId::of::<()>(); #n];
let mut ids: [#path::ComponentId; #n] = [TypeId::of::<()>().into(); #n];
for (id, info) in ids.iter_mut().zip(tys.iter()) {
*id = info.1;
}
Expand All @@ -104,16 +104,16 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
}

fn static_type_info() -> Vec<#path::TypeInfo> {
let mut info = vec![#(#path::TypeInfo::of::<#tys>()),*];
let mut info = vec![#(#path::TypeInfo::of::<#tys>().into()),*];
info.sort_unstable();
info
}

unsafe fn get(
mut f: impl FnMut(std::any::TypeId, usize) -> Option<std::ptr::NonNull<u8>>,
mut f: impl FnMut(#path::ComponentId, usize) -> Option<std::ptr::NonNull<u8>>,
) -> Result<Self, #path::MissingComponent> {
#(
let #fields = f(std::any::TypeId::of::<#tys>(), std::mem::size_of::<#tys>())
let #fields = f(std::any::TypeId::of::<#tys>().into(), std::mem::size_of::<#tys>())
.ok_or_else(#path::MissingComponent::new::<#tys>)?
.cast::<#tys>()
.as_ptr();
Expand Down
101 changes: 63 additions & 38 deletions crates/bevy_ecs/hecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ use crate::{
alloc::{alloc, dealloc, Layout},
vec::Vec,
},
world::ComponentId,
Entity,
};
use bevy_utils::AHasher;
use core::{
any::{type_name, TypeId},
any::TypeId,
cell::UnsafeCell,
hash::{BuildHasherDefault, Hasher},
mem,
Expand All @@ -40,7 +41,7 @@ use crate::{borrow::AtomicBorrow, Component};
#[derive(Debug)]
pub struct Archetype {
types: Vec<TypeInfo>,
state: TypeIdMap<TypeState>,
state: ComponentIdMap<TypeState>,
len: usize,
entities: Vec<Entity>,
// UnsafeCell allows unique references into `data` to be constructed while shared references
Expand Down Expand Up @@ -95,23 +96,23 @@ impl Archetype {
#[allow(missing_docs)]
#[inline]
pub fn has<T: Component>(&self) -> bool {
self.has_dynamic(TypeId::of::<T>())
self.has_dynamic(TypeId::of::<T>().into())
}

#[allow(missing_docs)]
#[inline]
pub fn has_type(&self, ty: TypeId) -> bool {
pub fn has_component(&self, ty: ComponentId) -> bool {
self.has_dynamic(ty)
}

pub(crate) fn has_dynamic(&self, id: TypeId) -> bool {
pub(crate) fn has_dynamic(&self, id: ComponentId) -> bool {
self.state.contains_key(&id)
}

#[allow(missing_docs)]
#[inline]
pub fn get<T: Component>(&self) -> Option<NonNull<T>> {
let state = self.state.get(&TypeId::of::<T>())?;
let state = self.state.get(&TypeId::of::<T>().into())?;
Some(unsafe {
NonNull::new_unchecked(
(*self.data.get()).as_ptr().add(state.offset).cast::<T>() as *mut T
Expand All @@ -122,7 +123,7 @@ impl Archetype {
#[allow(missing_docs)]
#[inline]
pub fn get_with_type_state<T: Component>(&self) -> Option<(NonNull<T>, &TypeState)> {
let state = self.state.get(&TypeId::of::<T>())?;
let state = self.state.get(&TypeId::of::<T>().into())?;
Some(unsafe {
(
NonNull::new_unchecked(
Expand All @@ -134,51 +135,71 @@ impl Archetype {
}

#[allow(missing_docs)]
pub fn get_type_state(&self, ty: TypeId) -> Option<&TypeState> {
pub fn get_type_state(&self, ty: ComponentId) -> Option<&TypeState> {
self.state.get(&ty)
}

#[allow(missing_docs)]
pub fn get_type_state_mut(&mut self, ty: TypeId) -> Option<&mut TypeState> {
pub fn get_type_state_mut(&mut self, ty: ComponentId) -> Option<&mut TypeState> {
self.state.get_mut(&ty)
}

#[allow(missing_docs)]
#[inline]
pub fn borrow<T: Component>(&self) {
if self
.state
.get(&TypeId::of::<T>())
.map_or(false, |x| !x.borrow.borrow())
{
panic!("{} already borrowed uniquely", type_name::<T>());
self.borrow_component(std::any::TypeId::of::<T>().into())
}

#[allow(missing_docs)]
#[inline]
pub fn borrow_component(&self, id: ComponentId) {
if self.state.get(&id).map_or(false, |x| !x.borrow.borrow()) {
panic!("{:?} already borrowed uniquely", id);
}
}

#[allow(missing_docs)]
#[inline]
pub fn borrow_mut<T: Component>(&self) {
self.borrow_component_mut(std::any::TypeId::of::<T>().into())
}

#[allow(missing_docs)]
#[inline]
pub fn borrow_component_mut(&self, id: ComponentId) {
if self
.state
.get(&TypeId::of::<T>())
.get(&id)
.map_or(false, |x| !x.borrow.borrow_mut())
{
panic!("{} already borrowed", type_name::<T>());
panic!("{:?} already borrowed", id);
}
}

#[allow(missing_docs)]
#[inline]
pub fn release<T: Component>(&self) {
if let Some(x) = self.state.get(&TypeId::of::<T>()) {
self.release_component(std::any::TypeId::of::<T>().into());
}

#[allow(missing_docs)]
#[inline]
pub fn release_component(&self, id: ComponentId) {
if let Some(x) = self.state.get(&id) {
x.borrow.release();
}
}

#[allow(missing_docs)]
#[inline]
pub fn release_mut<T: Component>(&self) {
if let Some(x) = self.state.get(&TypeId::of::<T>()) {
self.release_component_mut(std::any::TypeId::of::<T>().into())
}

#[allow(missing_docs)]
#[inline]
pub fn release_component_mut(&self, id: ComponentId) {
if let Some(x) = self.state.get(&id) {
x.borrow.release_mut();
}
}
Expand Down Expand Up @@ -218,7 +239,7 @@ impl Archetype {
/// `index` must be in-bounds
pub(crate) unsafe fn get_dynamic(
&self,
ty: TypeId,
ty: ComponentId,
size: usize,
index: usize,
) -> Option<NonNull<u8>> {
Expand Down Expand Up @@ -358,7 +379,7 @@ impl Archetype {
pub(crate) unsafe fn move_to(
&mut self,
index: usize,
mut f: impl FnMut(*mut u8, TypeId, usize, bool, bool),
mut f: impl FnMut(*mut u8, ComponentId, usize, bool, bool),
) -> Option<Entity> {
let last = self.len - 1;
for ty in &self.types {
Expand Down Expand Up @@ -402,7 +423,7 @@ impl Archetype {
pub unsafe fn put_dynamic(
&mut self,
component: *mut u8,
ty: TypeId,
ty: ComponentId,
size: usize,
index: usize,
added: bool,
Expand Down Expand Up @@ -481,7 +502,7 @@ impl TypeState {
/// Metadata required to store a component
#[derive(Debug, Copy, Clone)]
pub struct TypeInfo {
id: TypeId,
id: ComponentId,
layout: Layout,
drop: unsafe fn(*mut u8),
}
Expand All @@ -494,15 +515,15 @@ impl TypeInfo {
}

Self {
id: TypeId::of::<T>(),
id: TypeId::of::<T>().into(),
layout: Layout::new::<T>(),
drop: drop_ptr::<T>,
}
}

#[allow(missing_docs)]
#[inline]
pub fn id(&self) -> TypeId {
pub fn id(&self) -> ComponentId {
self.id
}

Expand All @@ -524,7 +545,7 @@ impl PartialOrd for TypeInfo {
}

impl Ord for TypeInfo {
/// Order by alignment, descending. Ties broken with TypeId.
/// Order by alignment, descending. Ties broken with ComponentId.
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.layout
.align()
Expand All @@ -547,16 +568,20 @@ fn align(x: usize, alignment: usize) -> usize {
(x + alignment - 1) & (!alignment + 1)
}

/// A hasher optimized for hashing a single TypeId.
/// A hasher optimized for hashing a single [`ComponentId`].
///
/// [`ComponentID`] is either a Rust [`TypeId`], which is already thoroughly hashed so there's no
/// reason to hash it again, or it is an external ID represented by a u64, that can also be taken
/// without changes for a hash.
///
/// TypeId is already thoroughly hashed, so there's no reason to hash it again.
/// Just leave the bits unchanged.
/// For [`u64`]'s and [`u128`]'s we don't do anything to the data, for the rest of the types we
/// fallback to [`AHasher`].
#[derive(Default)]
pub(crate) struct TypeIdHasher {
pub(crate) struct ComponentIdHasher {
hash: u64,
}

impl Hasher for TypeIdHasher {
impl Hasher for ComponentIdHasher {
fn write_u64(&mut self, n: u64) {
// Only a single value can be hashed, so the old hash should be zero.
debug_assert_eq!(self.hash, 0);
Expand All @@ -572,8 +597,8 @@ impl Hasher for TypeIdHasher {
fn write(&mut self, bytes: &[u8]) {
debug_assert_eq!(self.hash, 0);

// This will only be called if TypeId is neither u64 nor u128, which is not anticipated.
// In that case we'll just fall back to using a different hash implementation.
// This will only be called if TypeId is neither u64 nor u128, which is not anticipated. In
// that case we'll just fall back to using a different hash implementation.
let mut hasher = AHasher::default();
hasher.write(bytes);
self.hash = hasher.finish();
Expand All @@ -584,9 +609,9 @@ impl Hasher for TypeIdHasher {
}
}

/// A HashMap with TypeId keys
/// A HashMap with ComponentId keys
///
/// Because TypeId is already a fully-hashed u64 (including data in the high seven bits,
/// which hashbrown needs), there is no need to hash it again. Instead, this uses the much
/// faster no-op hash.
pub(crate) type TypeIdMap<V> = HashMap<TypeId, V, BuildHasherDefault<TypeIdHasher>>;
/// Because ComponentId is already a fully-hashed u64 (including data in the high seven bits, which
/// hashbrown needs), there is no need to hash it again. Instead, this uses the much faster no-op
/// hash.
pub(crate) type ComponentIdMap<V> = HashMap<ComponentId, V, BuildHasherDefault<ComponentIdHasher>>;
Loading

0 comments on commit 0798295

Please sign in to comment.