"functions as systems"

This commit is contained in:
Carter Anderson 2020-04-28 01:00:30 -07:00
parent 0c3a77ac9f
commit d5dcc96c39
9 changed files with 283 additions and 27 deletions

View File

@ -17,6 +17,7 @@ pub mod serialize;
mod tuple;
mod zip;
mod system_fn_types;
pub mod prelude {
pub use crate::{
@ -26,5 +27,7 @@ pub mod prelude {
filter::filter_fns::*,
query::{IntoQuery, Query, Read, Tagged, TryRead, TryWrite, Write},
world::{Universe, World},
// used by system_fn
borrow::{Ref, RefMut}
};
}

View File

@ -0,0 +1,104 @@
use crate::{borrow::{RefIterMut, RefMut, RefIter, Ref}, storage::{ArchetypeData, Component, ComponentStorage, ComponentTypeId}, query::{View, DefaultFilter, ViewElement}, filter::{ComponentFilter, EntityFilterTuple, Passthrough}, index::{SetIndex, ChunkIndex}};
use std::{any::TypeId, slice::{IterMut, Iter}};
impl<'a, T: Component> DefaultFilter for RefMut<'static, T> {
type Filter = EntityFilterTuple<ComponentFilter<T>, Passthrough, Passthrough>;
fn filter() -> Self::Filter { super::filter::filter_fns::component() }
}
impl<'a, T: Component> View<'a> for RefMut<'static, T>{
type Iter = RefIterMut<'a, T, IterMut<'a, T>>;
#[inline]
fn fetch(
_: &'a ArchetypeData,
chunk: &'a ComponentStorage,
_: ChunkIndex,
_: SetIndex,
) -> Self::Iter {
let (slice_borrow, slice) = unsafe {
chunk
.components(ComponentTypeId::of::<T>())
.unwrap_or_else(|| {
panic!(
"Component of type {:?} not found in chunk when fetching Write view",
std::any::type_name::<T>()
)
})
.data_slice_mut::<T>()
.deconstruct()
};
RefIterMut::new(slice_borrow, slice.iter_mut())
}
#[inline]
fn validate() -> bool { true }
#[inline]
fn reads<D: Component>() -> bool { TypeId::of::<T>() == TypeId::of::<D>() }
#[inline]
fn writes<D: Component>() -> bool { TypeId::of::<T>() == TypeId::of::<D>() }
#[inline]
fn read_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
#[inline]
fn write_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
}
impl<'a, T: Component> ViewElement for RefMut<'static, T> {
type Component = T;
}
impl<'a, T: Component> DefaultFilter for Ref<'static, T> {
type Filter = EntityFilterTuple<ComponentFilter<T>, Passthrough, Passthrough>;
fn filter() -> Self::Filter { super::filter::filter_fns::component() }
}
impl<'a, T: Component> View<'a> for Ref<'static, T>{
type Iter = RefIter<'a, T, Iter<'a, T>>;
#[inline]
fn fetch(
_: &'a ArchetypeData,
chunk: &'a ComponentStorage,
_: ChunkIndex,
_: SetIndex,
) -> Self::Iter {
let (slice_borrow, slice) = unsafe {
chunk
.components(ComponentTypeId::of::<T>())
.unwrap_or_else(|| {
panic!(
"Component of type {:?} not found in chunk when fetching Write view",
std::any::type_name::<T>()
)
})
.data_slice::<T>()
.deconstruct()
};
RefIter::new(slice_borrow, slice.iter())
}
#[inline]
fn validate() -> bool { true }
#[inline]
fn reads<D: Component>() -> bool { TypeId::of::<T>() == TypeId::of::<D>() }
#[inline]
fn writes<D: Component>() -> bool { false }
#[inline]
fn read_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
#[inline]
fn write_types() -> Vec<ComponentTypeId> { Vec::new() }
}
impl<'a, T: Component> ViewElement for Ref<'static, T> {
type Component = T;
}

View File

@ -1,16 +1,21 @@
pub mod resource;
pub mod schedule;
mod system_fn;
mod system_fn_types;
mod system;
pub use bit_set;
pub use system::*;
pub use system_fn::*;
pub mod prelude {
pub use crate::{
bit_set::BitSet,
resource::{ResourceSet, Resources},
// aliased preparedread and preparedwrite used by system_fn
resource::{ResourceSet, Resources, PreparedRead as Resource, PreparedWrite as ResourceMut},
schedule::{Executor, Runnable, Schedulable, Schedule},
System, SystemBuilder,
into_system,
};
}

View File

@ -807,18 +807,18 @@ where
Queries = <Q as QuerySet>::Queries,
>,
{
name: SystemId,
_resources: PhantomData<R>,
queries: AtomicRefCell<Q>,
run_fn: AtomicRefCell<F>,
archetypes: ArchetypeAccess,
pub name: SystemId,
pub _resources: PhantomData<R>,
pub queries: AtomicRefCell<Q>,
pub run_fn: AtomicRefCell<F>,
pub archetypes: ArchetypeAccess,
// These are stored statically instead of always iterated and created from the
// query types, which would make allocations every single request
access: SystemAccess,
pub access: SystemAccess,
// We pre-allocate a command buffer for ourself. Writes are self-draining so we never have to rellocate.
command_buffer: FxHashMap<WorldId, AtomicRefCell<CommandBuffer>>,
pub command_buffer: FxHashMap<WorldId, AtomicRefCell<CommandBuffer>>,
}
impl<R, Q, F> Runnable for System<R, Q, F>
@ -896,9 +896,9 @@ pub trait SystemFn {
);
}
struct SystemFnWrapper<R, Q, F: FnMut(&mut CommandBuffer, &mut SubWorld, &mut R, &mut Q) + 'static>(
F,
PhantomData<(R, Q)>,
pub struct SystemFnWrapper<R, Q, F: FnMut(&mut CommandBuffer, &mut SubWorld, &mut R, &mut Q) + 'static>(
pub F,
pub PhantomData<(R, Q)>,
);
impl<F, R, Q> SystemFn for SystemFnWrapper<R, Q, F>

View File

@ -0,0 +1,121 @@
use crate::{
resource::{ResourceSet, ResourceTypeId},
schedule::{ArchetypeAccess, Schedulable},
Access, System, SystemAccess, SystemFnWrapper, SystemQuery,
};
use fxhash::FxHashMap;
use legion_core::{
borrow::{AtomicRefCell},
filter::EntityFilter,
query::{DefaultFilter, IntoQuery, View},
storage::{ComponentTypeId},
};
use std::marker::PhantomData;
use bit_set::BitSet;
pub fn into_system<'a, Q, F, R, X>(name: &'static str, system: F) -> Box<dyn Schedulable>
where
Q: IntoQuery + DefaultFilter<Filter = R>,
<Q as View<'a>>::Iter: Iterator<Item = Q> + 'a,
F: Fn(&mut X, Q) + Send + Sync + 'static,
R: EntityFilter + Sync + 'static,
X: ResourceSet<PreparedResources = X> + 'static,
{
let resource_access: Access<ResourceTypeId> = Access::default();
let component_access: Access<ComponentTypeId> = Access::default();
let run_fn = SystemFnWrapper(
move |_,
world,
resources: &mut X,
query: &mut SystemQuery<Q, <Q as DefaultFilter>::Filter>| {
for components in query.iter_mut(world) {
system(resources, components);
}
},
PhantomData,
);
Box::new(System {
name: name.into(),
queries: AtomicRefCell::new(Q::query()),
access: SystemAccess {
resources: resource_access,
components: component_access,
tags: Access::default(),
},
archetypes: ArchetypeAccess::Some(BitSet::default()),
_resources: PhantomData::<X>,
command_buffer: FxHashMap::default(),
run_fn: AtomicRefCell::new(run_fn),
})
}
#[cfg(test)]
mod tests {
use super::into_system;
use crate::{
resource::{PreparedRead, PreparedWrite, Resources},
};
use legion_core::{
borrow::{Ref, RefMut},
world::World,
};
#[derive(Debug, Eq, PartialEq)]
struct A(usize);
#[derive(Debug, Eq, PartialEq)]
struct B(usize);
#[derive(Debug, Eq, PartialEq)]
struct Y(usize);
#[derive(Debug, Eq, PartialEq)]
struct X(usize);
#[test]
fn test_system_fn() {
fn read_write_system(_: &mut (), (_x, mut y): (Ref<X>, RefMut<Y>)) {
y.0 += 1;
}
let mut world = World::new();
let mut resources = Resources::default();
world.insert((), vec![(X(1), Y(1)), (X(2), Y(2))]);
let mut system = into_system("read_write", read_write_system);
system.run(&mut world, &mut resources);
}
#[test]
fn test_resource_system_fn() {
fn my_system(
(a, b): &mut (PreparedWrite<A>, PreparedRead<B>),
(x, mut y): (Ref<X>, RefMut<Y>),
) {
assert_eq!(**b, B(1));
// assert_eq!(**b, B(0));
if a.0 == 0 {
assert_eq!(*x, X(2));
assert_eq!(*y, Y(3));
} else if a.0 == 1 {
assert_eq!(*x, X(4));
assert_eq!(*y, Y(5));
y.0 += 1;
assert_eq!(*y, Y(6));
} else {
panic!("unexpected value");
}
a.0 += 1;
}
let mut world = World::new();
let mut resources = Resources::default();
resources.insert(A(0));
resources.insert(B(1));
world.insert((), vec![(X(2), Y(3)), (X(4), Y(5))]);
let mut my_system = into_system("read_resources", my_system);
my_system.run(&mut world, &mut resources);
}
}

View File

@ -0,0 +1,24 @@
use crate::resource::{PreparedRead, ResourceSet, Resources, Resource, PreparedWrite};
use std::ops::{Deref, DerefMut};
impl<T: Resource> ResourceSet for PreparedRead<T> {
type PreparedResources = PreparedRead<T>;
unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources {
let resource = resources
.get::<T>()
.unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::<T>()));
PreparedRead::new(resource.deref() as *const T)
}
}
impl<T: Resource> ResourceSet for PreparedWrite<T> {
type PreparedResources = PreparedWrite<T>;
unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources {
let mut resource = resources
.get_mut::<T>()
.unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::<T>()));
PreparedWrite::new(resource.deref_mut() as *mut T)
}
}

View File

@ -13,9 +13,12 @@ Legion aims to be a feature rich high performance ECS library for Rust game proj
## Bevy Fork Info
This is a fork that enables dynamic plugin loading in bevy.
Bevy currently forks Legion to give it some additional functionality:
* Stable TypeIds across binaries to support dynamic plugin loading
* Simple "function only" system declarations
Here are the changes made:
* Add system_fn.rs containing "function only" system declarations
* ResourceTypeId, ComponentTypeId, TagTypeId use static str (std::any::type_name) instead of TypeId (std::any::TypeId is not constant across rust binaries)
* Implement "DowncastTypeName" to allow downcasting based on type name

View File

@ -9,23 +9,17 @@ fn main() {
..Default::default()
})
.add_startup_system(setup)
.add_system(build_move_system())
.add_system(into_system("move", move_system))
.run();
}
fn build_move_system() -> Box<dyn Schedulable> {
SystemBuilder::new("move")
.read_resource::<Time>()
.write_resource::<AssetStorage<StandardMaterial>>()
.with_query(<(Write<Translation>, Read<Handle<StandardMaterial>>)>::query())
.build(move |_, world, (time, material_storage), person_query| {
for (mut translation, material_handle) in person_query.iter_mut(world) {
let material = material_storage.get_mut(&material_handle).unwrap();
translation.0 += math::vec3(1.0, 0.0, 0.0) * time.delta_seconds;
material.albedo = material.albedo
+ Color::rgb(-time.delta_seconds, -time.delta_seconds, time.delta_seconds);
}
})
fn move_system(
(time, materials): &mut (Resource<Time>, ResourceMut<AssetStorage<StandardMaterial>>),
(mut translation, material_handle): (RefMut<Translation>, Ref<Handle<StandardMaterial>>),
) {
let material = materials.get_mut(&material_handle).unwrap();
translation.0 += math::vec3(1.0, 0.0, 0.0) * time.delta_seconds;
material.albedo += Color::rgb(-time.delta_seconds, -time.delta_seconds, time.delta_seconds);
}
fn setup(world: &mut World, resources: &mut Resources) {

View File

@ -45,15 +45,17 @@ pub use crate::{
};
pub use legion::{
command::CommandBuffer,
borrow::{Ref, RefMut},
entity::Entity,
event::Event as LegionEvent,
filter::filter_fns::*,
query::{IntoQuery, Query, Read, Tagged, TryRead, TryWrite, Write},
systems::{
bit_set::BitSet,
resource::{ResourceSet, Resources},
resource::{ResourceSet, Resources, PreparedRead as Resource, PreparedWrite as ResourceMut},
schedule::{Executor, Runnable, Schedulable, Schedule},
SubWorld, System, SystemBuilder,
into_system
},
world::{Universe, World},
};