"functions as systems"
This commit is contained in:
parent
0c3a77ac9f
commit
d5dcc96c39
@ -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}
|
||||
};
|
||||
}
|
||||
|
104
crates/bevy_legion/legion_core/src/system_fn_types.rs
Normal file
104
crates/bevy_legion/legion_core/src/system_fn_types.rs
Normal 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;
|
||||
}
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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>
|
||||
|
121
crates/bevy_legion/legion_systems/src/system_fn.rs
Normal file
121
crates/bevy_legion/legion_systems/src/system_fn.rs
Normal 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);
|
||||
}
|
||||
}
|
24
crates/bevy_legion/legion_systems/src/system_fn_types.rs
Normal file
24
crates/bevy_legion/legion_systems/src/system_fn_types.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user