diff --git a/src/lib.rs b/src/lib.rs index 1fc4bfd..5c641b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod builder; pub mod inspector; pub mod lend; mod loader; +mod query; mod resource; mod runtime; pub mod runtimes; diff --git a/src/query/mod.rs b/src/query/mod.rs new file mode 100644 index 0000000..dcdd09e --- /dev/null +++ b/src/query/mod.rs @@ -0,0 +1,148 @@ +mod ptr; +mod vec; + +pub use ptr::{ComponentPtr, ComponentPtrDense}; +pub use vec::VecPtr; + +#[allow(unreachable_code)] +pub(crate) unsafe fn debug_checked_unreachable() -> ! { + #[cfg(debug_assertions)] + unreachable!(); + std::hint::unreachable_unchecked(); +} + +#[cfg(test)] +mod tests { + use super::*; + use bevy::{ + prelude::*, + reflect::{ReflectFromPtr, TypeRegistry}, + }; + + #[derive(Component)] + struct A; + + #[derive(Component)] + #[component(storage = "SparseSet")] + struct B; + + #[derive(Component, Reflect, PartialEq, Debug)] + struct Data(String); + + #[test] + fn query_empty() { + let mut world = World::new(); + let component_id = world.init_component::(); + + let mut query = unsafe { + QueryState::<&ComponentPtrDense, ()>::new_with_state(&mut world, component_id, ()) + }; + let count = query.iter(&mut world).count(); + assert_eq!(count, 0); + + let mut query = unsafe { + QueryState::, ()>::new_with_state( + &mut world, + vec![component_id], + (), + ) + }; + let count = query.iter(&mut world).count(); + assert_eq!(count, 0); + } + + #[test] + fn query() { + let mut world = World::new(); + + let a = world.init_component::(); + let b = world.init_component::(); + + world.spawn((A, B)); + world.spawn(A); + + let mut query = + unsafe { QueryState::<&ComponentPtrDense, ()>::new_with_state(&mut world, a, ()) }; + let count = query.iter(&mut world).count(); + assert_eq!(count, 2); + + let mut query = + unsafe { QueryState::<&ComponentPtr, ()>::new_with_state(&mut world, b, ()) }; + let count = query.iter(&mut world).count(); + assert_eq!(count, 1); + + let mut query = unsafe { + QueryState::<(&ComponentPtrDense, &ComponentPtr), ()>::new_with_state( + &mut world, + (a, b), + (), + ) + }; + let count = query.iter(&mut world).count(); + assert_eq!(count, 1); + + // Pick VecPtr<&ComponentPtr> due to presence of &ComponentPtr in query + let mut query = unsafe { + QueryState::, ()>::new_with_state(&mut world, vec![a, b], ()) + }; + let count = query.iter(&mut world).count(); + assert_eq!(count, 1); + } + + #[test] + fn query_data() { + let mut world = World::new(); + let type_registry = TypeRegistry::default(); + let mut type_registry = type_registry.write(); + + type_registry.register::(); + let component_id = world.init_component::(); + + world.spawn(Data("Hello, World!".to_string())); + + let mut query = unsafe { + QueryState::<&ComponentPtrDense, ()>::new_with_state(&mut world, component_id, ()) + }; + + for data in query.iter(&mut world) { + let reflect_data = type_registry.get(std::any::TypeId::of::()).unwrap(); + let reflect_from_ptr = reflect_data.data::().unwrap(); + + let data = unsafe { reflect_from_ptr.as_reflect_ptr(data) }; + let data = data.as_any().downcast_ref::().unwrap(); + + assert_eq!(data, &Data("Hello, World!".to_string())) + } + } + + #[test] + fn query_data_multiple() { + let mut world = World::new(); + let type_registry = TypeRegistry::default(); + let mut type_registry = type_registry.write(); + + type_registry.register::(); + let component_id = world.init_component::(); + + world.spawn(Data("Hello, World!".to_string())); + world.spawn(Data("Hello, World!".to_string())); + world.spawn(Data("Hello, World!".to_string())); + + let mut query = unsafe { + QueryState::<&ComponentPtrDense, ()>::new_with_state(&mut world, component_id, ()) + }; + + let res = query.iter_mut(&mut world).collect::>(); + assert_eq!(res.len(), 3); + + for data in res.into_iter() { + let reflect_data = type_registry.get(std::any::TypeId::of::()).unwrap(); + let reflect_from_ptr = reflect_data.data::().unwrap(); + + let data = unsafe { reflect_from_ptr.as_reflect_ptr(data) }; + let data = data.as_any().downcast_ref::().unwrap(); + + assert_eq!(data.0, "Hello, World!".to_string()); + } + } +} diff --git a/src/query/ptr.rs b/src/query/ptr.rs new file mode 100644 index 0000000..5692137 --- /dev/null +++ b/src/query/ptr.rs @@ -0,0 +1,265 @@ +use super::debug_checked_unreachable; +use bevy::{ + ecs::{ + archetype::{Archetype, ArchetypeComponentId}, + component::{ComponentId, StorageType}, + query::{Access, FilteredAccess, ReadOnlyWorldQuery, WorldQuery, WorldQueryGats}, + storage::{ComponentSparseSet, Table, Tables}, + }, + prelude::*, + ptr::{Ptr, ThinSlicePtr}, +}; + +/// Type used to query dense components +pub struct ComponentPtrDense; + +#[doc(hidden)] +pub struct ReadFetchDense<'w> { + component_size: usize, + // T::Storage = TableStorage + table_components: Option>, + entity_table_rows: Option>, +} + +impl<'w> WorldQueryGats<'w> for &ComponentPtrDense { + type Item = Ptr<'w>; + type Fetch = ReadFetchDense<'w>; +} + +unsafe impl ReadOnlyWorldQuery for &ComponentPtrDense {} + +unsafe impl WorldQuery for &ComponentPtrDense { + type ReadOnly = Self; + type State = ComponentId; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Ptr<'wlong>) -> Ptr<'wshort> { + item + } + + const IS_DENSE: bool = true; + const IS_ARCHETYPAL: bool = true; + + unsafe fn init_fetch<'w>( + world: &'w World, + &component_id: &ComponentId, + _last_change_tick: u32, + _change_tick: u32, + ) -> ReadFetchDense<'w> { + let component_info = world.components().get_info(component_id).unwrap(); + + debug_assert_eq!(component_info.storage_type(), StorageType::Table); + + ReadFetchDense { + component_size: component_info.layout().size(), + table_components: None, + entity_table_rows: None, + } + } + + #[inline] + unsafe fn set_archetype<'w>( + fetch: &mut ReadFetchDense<'w>, + &component_id: &ComponentId, + archetype: &'w Archetype, + tables: &'w Tables, + ) { + fetch.entity_table_rows = Some(archetype.entity_table_rows().into()); + let column = tables[archetype.table_id()] + .get_column(component_id) + .unwrap(); + fetch.table_components = Some(column.get_data_ptr()); + } + + #[inline] + unsafe fn set_table<'w>( + fetch: &mut ReadFetchDense<'w>, + &component_id: &ComponentId, + table: &'w Table, + ) { + fetch.table_components = Some(table.get_column(component_id).unwrap().get_data_ptr()); + } + + #[inline] + unsafe fn archetype_fetch<'w>( + fetch: &mut >::Fetch, + archetype_index: usize, + ) -> >::Item { + let (entity_table_rows, table_components) = fetch + .entity_table_rows + .zip(fetch.table_components) + .unwrap_or_else(|| debug_checked_unreachable()); + + let table_row = *entity_table_rows.get(archetype_index); + table_components.byte_add(table_row * fetch.component_size) + } + + #[inline] + unsafe fn table_fetch<'w>( + fetch: &mut >::Fetch, + table_row: usize, + ) -> >::Item { + let components = fetch + .table_components + .unwrap_or_else(|| debug_checked_unreachable()); + components.byte_add(table_row * fetch.component_size) + } + + fn update_component_access( + &component_id: &ComponentId, + access: &mut FilteredAccess, + ) { + assert!( + !access.access().has_write(component_id), + "Read access to component with id: {:?} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", + component_id + ); + access.add_read(component_id); + } + + fn update_archetype_component_access( + &component_id: &ComponentId, + archetype: &Archetype, + access: &mut Access, + ) { + if let Some(archetype_component_id) = archetype.get_archetype_component_id(component_id) { + access.add_read(archetype_component_id); + } + } + + fn init_state(_world: &mut World) -> Self::State { + panic!("Dynamic queries can only be initialized through QueryState::new_with_state"); + } + + fn matches_component_set( + &component_id: &ComponentId, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(component_id) + } +} + +/// Type used to query non-dense components +pub struct ComponentPtr; + +#[doc(hidden)] +pub struct ReadFetchSparse<'w> { + // T::Storage = SparseStorage + entities: Option>, + sparse_set: Option<&'w ComponentSparseSet>, +} + +impl<'w> WorldQueryGats<'w> for &ComponentPtr { + type Item = Ptr<'w>; + type Fetch = ReadFetchSparse<'w>; +} + +unsafe impl ReadOnlyWorldQuery for &ComponentPtr {} + +unsafe impl WorldQuery for &ComponentPtr { + type ReadOnly = Self; + type State = ComponentId; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Ptr<'wlong>) -> Ptr<'wshort> { + item + } + + unsafe fn init_fetch<'w>( + world: &'w World, + &component_id: &ComponentId, + _last_change_tick: u32, + _change_tick: u32, + ) -> >::Fetch { + let storage_type = world + .components() + .get_info(component_id) + .unwrap() + .storage_type(); + + ReadFetchSparse { + entities: None, + sparse_set: (storage_type == StorageType::SparseSet) + .then(|| world.storages().sparse_sets.get(component_id).unwrap()), + } + } + + const IS_DENSE: bool = false; + const IS_ARCHETYPAL: bool = true; + + #[inline] + unsafe fn set_archetype<'w>( + fetch: &mut >::Fetch, + _component_id: &ComponentId, + archetype: &'w Archetype, + _tables: &'w Tables, + ) { + fetch.entities = Some(archetype.entities().into()) + } + + #[inline] + unsafe fn set_table<'w>( + _fetch: &mut >::Fetch, + _component_id: &ComponentId, + _table: &'w Table, + ) { + panic!("hi"); + debug_checked_unreachable(); + } + + #[inline] + unsafe fn archetype_fetch<'w>( + fetch: &mut >::Fetch, + archetype_index: usize, + ) -> >::Item { + let (entities, sparse_set) = fetch + .entities + .zip(fetch.sparse_set) + .unwrap_or_else(|| debug_checked_unreachable()); + + let entity = *entities.get(archetype_index); + sparse_set + .get(entity) + .unwrap_or_else(|| debug_checked_unreachable()) + } + + #[inline] + unsafe fn table_fetch<'w>( + _fetch: &mut >::Fetch, + _table_row: usize, + ) -> >::Item { + panic!("hi"); + debug_checked_unreachable(); + } + + fn update_component_access( + &component_id: &ComponentId, + access: &mut FilteredAccess, + ) { + assert!( + !access.access().has_write(component_id), + "Read access to component with id: {:?} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", + component_id + ); + access.add_read(component_id); + } + + fn update_archetype_component_access( + &component_id: &ComponentId, + archetype: &Archetype, + access: &mut Access, + ) { + if let Some(archetype_component_id) = archetype.get_archetype_component_id(component_id) { + access.add_read(archetype_component_id); + } + } + + fn init_state(_world: &mut World) -> Self::State { + panic!("Dynamic queries can only be initialized through QueryState::new_with_state"); + } + + fn matches_component_set( + &component_id: &ComponentId, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(component_id) + } +} diff --git a/src/runtimes/bevy/ext/bevy_ecs/03_bevy_ecs.js b/src/runtimes/bevy/ext/bevy_ecs/03_bevy_ecs.js index f195e50..8e3845a 100644 --- a/src/runtimes/bevy/ext/bevy_ecs/03_bevy_ecs.js +++ b/src/runtimes/bevy/ext/bevy_ecs/03_bevy_ecs.js @@ -31,7 +31,6 @@ this.worldResourceId = worldResourceId; } - // TODO: Could eventually return TypeRegistration but there is no need thus far static getTypeIdWithName(worldResourceId, typeName) { try { const buffer = new Uint8Array(8); diff --git a/src/runtimes/bevy/ext/bevy_ecs/05_query.js b/src/runtimes/bevy/ext/bevy_ecs/05_query.js index 81d3744..4b9b955 100644 --- a/src/runtimes/bevy/ext/bevy_ecs/05_query.js +++ b/src/runtimes/bevy/ext/bevy_ecs/05_query.js @@ -5,7 +5,15 @@ const { unwrapReflect, worldResourceId } = window.Bevy.ecs; class Query { - constructor() {} + constructor(worldResourceId) { + this.worldResourceId = worldResourceId; + } + + static query(fetch) { + + } + + static queryFiltered(fetch, filter) {} } Object.assign(window.Bevy.ecs, { Query }); diff --git a/src/runtimes/bevy/ext/bevy_ecs/mod.rs b/src/runtimes/bevy/ext/bevy_ecs/mod.rs index d4e4bdf..1dcb7e1 100644 --- a/src/runtimes/bevy/ext/bevy_ecs/mod.rs +++ b/src/runtimes/bevy/ext/bevy_ecs/mod.rs @@ -2,7 +2,6 @@ use crate as bjs; use bjs::{include_js_files, op, OpState}; use std::rc::Rc; -// mod component; mod entity; mod type_registry; mod world;