use std::{any::TypeId, borrow::Cow, collections::HashMap, marker::PhantomData};
use bit_set::BitSet;
use super::{
command::CommandBuffer,
resources::{Resource, ResourceSet, ResourceTypeId, UnsafeResources},
schedule::Runnable,
};
use crate::internals::{
cons::{ConsAppend, ConsFlatten},
permissions::Permissions,
query::{
filter::EntityFilter,
view::{read::Read, write::Write, IntoView, View},
Query,
},
storage::{
archetype::ArchetypeIndex,
component::{Component, ComponentTypeId},
},
subworld::{ArchetypeAccess, ComponentAccess, SubWorld},
world::{World, WorldId},
};
pub trait QuerySet: Send + Sync {
fn filter_archetypes(&mut self, world: &World, archetypes: &mut BitSet);
}
macro_rules! queryset_tuple {
($head_ty:ident) => {
impl_queryset_tuple!($head_ty);
};
($head_ty:ident, $( $tail_ty:ident ),*) => (
impl_queryset_tuple!($head_ty, $( $tail_ty ),*);
queryset_tuple!($( $tail_ty ),*);
);
}
macro_rules! impl_queryset_tuple {
($($ty: ident),*) => {
#[allow(unused_parens, non_snake_case)]
impl<$( $ty, )*> QuerySet for ($( $ty, )*)
where
$( $ty: QuerySet, )*
{
fn filter_archetypes(&mut self, world: &World, bitset: &mut BitSet) {
let ($($ty,)*) = self;
$( $ty.filter_archetypes(world, bitset); )*
}
}
};
}
#[cfg(feature = "extended-tuple-impls")]
queryset_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
#[cfg(not(feature = "extended-tuple-impls"))]
queryset_tuple!(A, B, C, D, E, F, G, H);
impl QuerySet for () {
fn filter_archetypes(&mut self, _: &World, _: &mut BitSet) {}
}
impl<AV, AF> QuerySet for Query<AV, AF>
where
AV: IntoView + Send + Sync,
AF: EntityFilter,
{
fn filter_archetypes(&mut self, world: &World, bitset: &mut BitSet) {
for &ArchetypeIndex(arch) in self.find_archetypes(world) {
bitset.insert(arch as usize);
}
}
}
#[derive(Debug, Clone)]
pub struct SystemAccess {
resources: Permissions<ResourceTypeId>,
components: Permissions<ComponentTypeId>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SystemId {
name: Cow<'static, str>,
type_id: TypeId,
}
struct Unspecified;
impl std::fmt::Display for SystemId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
impl<T: Into<Cow<'static, str>>> From<T> for SystemId {
fn from(name: T) -> SystemId {
SystemId {
name: name.into(),
type_id: TypeId::of::<Unspecified>(),
}
}
}
struct ResourceMarker<T>(PhantomData<*const T>);
unsafe impl<T: Send> Send for ResourceMarker<T> {}
unsafe impl<T: Sync> Sync for ResourceMarker<T> {}
pub struct System<R, Q, F> {
name: Option<SystemId>,
_resources: ResourceMarker<R>,
queries: Q,
run_fn: F,
archetypes: ArchetypeAccess,
access: SystemAccess,
command_buffer: HashMap<WorldId, CommandBuffer>,
}
impl<R, Q, F> Runnable for System<R, Q, F>
where
R: for<'a> ResourceSet<'a>,
Q: QuerySet,
F: SystemFn<R, Q>,
{
fn name(&self) -> Option<&SystemId> {
self.name.as_ref()
}
fn reads(&self) -> (&[ResourceTypeId], &[ComponentTypeId]) {
(
&self.access.resources.reads_only(),
&self.access.components.reads_only(),
)
}
fn writes(&self) -> (&[ResourceTypeId], &[ComponentTypeId]) {
(
&self.access.resources.writes(),
&self.access.components.writes(),
)
}
fn prepare(&mut self, world: &World) {
if let ArchetypeAccess::Some(bitset) = &mut self.archetypes {
self.queries.filter_archetypes(world, bitset);
}
}
fn accesses_archetypes(&self) -> &ArchetypeAccess {
&self.archetypes
}
fn command_buffer_mut(&mut self, world: WorldId) -> Option<&mut CommandBuffer> {
self.command_buffer.get_mut(&world)
}
unsafe fn run_unsafe(&mut self, world: &World, resources: &UnsafeResources) {
let resources_static = &*(resources as *const UnsafeResources);
let mut resources = R::fetch_unchecked(resources_static);
let queries = &mut self.queries;
let component_access = ComponentAccess::Allow(Cow::Borrowed(&self.access.components));
let mut world_shim =
SubWorld::new_unchecked(world, component_access, self.archetypes.bitset());
let cmd = self
.command_buffer
.entry(world.id())
.or_insert_with(|| CommandBuffer::new(world));
let borrow = &mut self.run_fn;
borrow.run(cmd, &mut world_shim, &mut resources, queries);
}
}
pub trait SystemFn<R: ResourceSet<'static>, Q: QuerySet> {
fn run(
&mut self,
commands: &mut CommandBuffer,
world: &mut SubWorld,
resources: &mut R::Result,
queries: &mut Q,
);
}
impl<F, R, Q> SystemFn<R, Q> for F
where
R: ResourceSet<'static>,
Q: QuerySet,
F: FnMut(&mut CommandBuffer, &mut SubWorld, &mut R::Result, &mut Q) + 'static,
{
fn run(
&mut self,
commands: &mut CommandBuffer,
world: &mut SubWorld,
resources: &mut R::Result,
queries: &mut Q,
) {
(self)(commands, world, resources, queries)
}
}
pub struct SystemBuilder<Q = (), R = ()> {
name: Option<SystemId>,
queries: Q,
resources: R,
resource_access: Permissions<ResourceTypeId>,
component_access: Permissions<ComponentTypeId>,
access_all_archetypes: bool,
}
impl SystemBuilder<(), ()> {
pub fn new<T: Into<SystemId>>(name: T) -> Self {
Self {
name: Some(name.into()),
..Self::default()
}
}
}
impl Default for SystemBuilder<(), ()> {
fn default() -> Self {
Self {
name: None,
queries: (),
resources: (),
resource_access: Permissions::default(),
component_access: Permissions::default(),
access_all_archetypes: false,
}
}
}
impl<Q, R> SystemBuilder<Q, R>
where
Q: 'static + Send + ConsFlatten,
R: 'static + ConsFlatten,
{
pub fn with_name(self, name: SystemId) -> Self {
Self {
name: Some(name),
..self
}
}
pub fn with_query<V, F>(
mut self,
query: Query<V, F>,
) -> SystemBuilder<<Q as ConsAppend<Query<V, F>>>::Output, R>
where
V: IntoView,
F: 'static + EntityFilter,
Q: ConsAppend<Query<V, F>>,
{
self.component_access.add(V::View::requires_permissions());
SystemBuilder {
name: self.name,
queries: ConsAppend::append(self.queries, query),
resources: self.resources,
resource_access: self.resource_access,
component_access: self.component_access,
access_all_archetypes: self.access_all_archetypes,
}
}
pub fn read_resource<T>(mut self) -> SystemBuilder<Q, <R as ConsAppend<Read<T>>>::Output>
where
T: 'static + Resource,
R: ConsAppend<Read<T>>,
<R as ConsAppend<Read<T>>>::Output: ConsFlatten,
{
self.resource_access.push_read(ResourceTypeId::of::<T>());
SystemBuilder {
name: self.name,
queries: self.queries,
resources: ConsAppend::append(self.resources, Read::<T>::default()),
resource_access: self.resource_access,
component_access: self.component_access,
access_all_archetypes: self.access_all_archetypes,
}
}
pub fn write_resource<T>(mut self) -> SystemBuilder<Q, <R as ConsAppend<Write<T>>>::Output>
where
T: 'static + Resource,
R: ConsAppend<Write<T>>,
<R as ConsAppend<Write<T>>>::Output: ConsFlatten,
{
self.resource_access.push(ResourceTypeId::of::<T>());
SystemBuilder {
name: self.name,
queries: self.queries,
resources: ConsAppend::append(self.resources, Write::<T>::default()),
resource_access: self.resource_access,
component_access: self.component_access,
access_all_archetypes: self.access_all_archetypes,
}
}
pub fn read_component<T>(mut self) -> Self
where
T: Component,
{
self.component_access.push_read(ComponentTypeId::of::<T>());
self.access_all_archetypes = true;
self
}
pub fn write_component<T>(mut self) -> Self
where
T: Component,
{
self.component_access.push(ComponentTypeId::of::<T>());
self.access_all_archetypes = true;
self
}
pub fn build<F>(
self,
run_fn: F,
) -> System<<R as ConsFlatten>::Output, <Q as ConsFlatten>::Output, F>
where
<R as ConsFlatten>::Output: for<'a> ResourceSet<'a>,
<Q as ConsFlatten>::Output: QuerySet,
F: FnMut(
&mut CommandBuffer,
&mut SubWorld,
&mut <<R as ConsFlatten>::Output as ResourceSet<'static>>::Result,
&mut <Q as ConsFlatten>::Output,
),
{
System {
name: self.name,
run_fn,
_resources: ResourceMarker(PhantomData),
queries: self.queries.flatten(),
archetypes: if self.access_all_archetypes {
ArchetypeAccess::All
} else {
ArchetypeAccess::Some(BitSet::default())
},
access: SystemAccess {
resources: self.resource_access,
components: self.component_access,
},
command_buffer: HashMap::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
internals::{query::IntoQuery, systems::system::SystemBuilder},
Resources, Schedule,
};
#[test]
fn subworld_split_in_system_read() {
struct Ball;
struct Paddle;
struct Transform;
let mut world = World::default();
let mut resources = Resources::default();
world.push((Ball, Transform));
world.push((Paddle, Transform));
world.push((Paddle, Transform));
let system = SystemBuilder::new("PaddleSystem")
.with_query(<(&mut Ball, &Transform)>::query())
.with_query(<(&Paddle, &Transform)>::query())
.build(|_, world, _, (ball_query, paddle_query)| {
let (mut balls, paddles) = world.split_for_query(ball_query);
for _ in ball_query.iter_mut(&mut balls) {
for _ in paddle_query.iter(&paddles) {}
}
});
let mut schedule = Schedule::builder().add_thread_local(system).build();
schedule.execute(&mut world, &mut resources);
}
}