use crate::handler::Param;
use crate::world::{Registry, World};
pub trait System: Send {
fn run(&mut self, world: &mut World) -> bool;
fn name(&self) -> &'static str {
"<unnamed>"
}
}
pub struct SystemFn<F, Params: Param, Marker = bool> {
f: F,
state: Params::State,
name: &'static str,
_marker: std::marker::PhantomData<Marker>,
}
pub trait IntoSystem<Params, Marker = bool> {
type System: System + 'static;
fn into_system(self, registry: &Registry) -> Self::System;
}
impl<F: FnMut() -> bool + Send + 'static> IntoSystem<()> for F {
type System = SystemFn<F, ()>;
fn into_system(self, registry: &Registry) -> Self::System {
SystemFn {
f: self,
state: <() as Param>::init(registry),
name: std::any::type_name::<F>(),
_marker: std::marker::PhantomData,
}
}
}
impl<F: FnMut() -> bool + Send + 'static> System for SystemFn<F, ()> {
fn run(&mut self, _world: &mut World) -> bool {
(self.f)()
}
fn name(&self) -> &'static str {
self.name
}
}
impl<F: FnMut() + Send + 'static> IntoSystem<(), ()> for F {
type System = SystemFn<F, (), ()>;
fn into_system(self, registry: &Registry) -> Self::System {
SystemFn {
f: self,
state: <() as Param>::init(registry),
name: std::any::type_name::<F>(),
_marker: std::marker::PhantomData,
}
}
}
impl<F: FnMut() + Send + 'static> System for SystemFn<F, (), ()> {
fn run(&mut self, _world: &mut World) -> bool {
(self.f)();
true
}
fn name(&self) -> &'static str {
self.name
}
}
macro_rules! impl_into_system {
($($P:ident),+) => {
impl<F: Send + 'static, $($P: Param + 'static),+> IntoSystem<($($P,)+)> for F
where
for<'a> &'a mut F: FnMut($($P,)+) -> bool
+ FnMut($($P::Item<'a>,)+) -> bool,
{
type System = SystemFn<F, ($($P,)+)>;
fn into_system(self, registry: &Registry) -> Self::System {
let state = <($($P,)+) as Param>::init(registry);
{
#[allow(non_snake_case)]
let ($($P,)+) = &state;
registry.check_access(&[
$(
(<$P as Param>::resource_id($P),
std::any::type_name::<$P>()),
)+
]);
}
SystemFn {
f: self,
state,
name: std::any::type_name::<F>(),
_marker: std::marker::PhantomData,
}
}
}
impl<F: Send + 'static, $($P: Param + 'static),+> System
for SystemFn<F, ($($P,)+)>
where
for<'a> &'a mut F: FnMut($($P,)+) -> bool
+ FnMut($($P::Item<'a>,)+) -> bool,
{
#[allow(non_snake_case)]
fn run(&mut self, world: &mut World) -> bool {
#[allow(clippy::too_many_arguments)]
fn call_inner<$($P),+>(
mut f: impl FnMut($($P),+) -> bool,
$($P: $P,)+
) -> bool {
f($($P),+)
}
#[cfg(debug_assertions)]
world.clear_borrows();
let ($($P,)+) = unsafe {
<($($P,)+) as Param>::fetch(world, &mut self.state)
};
call_inner(&mut self.f, $($P),+)
}
fn name(&self) -> &'static str {
self.name
}
}
};
}
all_tuples!(impl_into_system);
macro_rules! impl_into_system_void {
($($P:ident),+) => {
impl<F: Send + 'static, $($P: Param + 'static),+> IntoSystem<($($P,)+), ()> for F
where
for<'a> &'a mut F: FnMut($($P,)+)
+ FnMut($($P::Item<'a>,)+),
{
type System = SystemFn<F, ($($P,)+), ()>;
fn into_system(self, registry: &Registry) -> Self::System {
let state = <($($P,)+) as Param>::init(registry);
{
#[allow(non_snake_case)]
let ($($P,)+) = &state;
registry.check_access(&[
$(
(<$P as Param>::resource_id($P),
std::any::type_name::<$P>()),
)+
]);
}
SystemFn {
f: self,
state,
name: std::any::type_name::<F>(),
_marker: std::marker::PhantomData,
}
}
}
impl<F: Send + 'static, $($P: Param + 'static),+> System
for SystemFn<F, ($($P,)+), ()>
where
for<'a> &'a mut F: FnMut($($P,)+)
+ FnMut($($P::Item<'a>,)+),
{
#[allow(non_snake_case)]
fn run(&mut self, world: &mut World) -> bool {
#[allow(clippy::too_many_arguments)]
fn call_inner<$($P),+>(
mut f: impl FnMut($($P),+),
$($P: $P,)+
) {
f($($P),+)
}
#[cfg(debug_assertions)]
world.clear_borrows();
let ($($P,)+) = unsafe {
<($($P,)+) as Param>::fetch(world, &mut self.state)
};
call_inner(&mut self.f, $($P),+);
true
}
fn name(&self) -> &'static str {
self.name
}
}
};
}
all_tuples!(impl_into_system_void);
#[cfg(test)]
mod tests {
use super::*;
use crate::{Local, Res, ResMut, WorldBuilder};
fn always_true() -> bool {
true
}
#[test]
fn arity_0_system() {
let mut world = WorldBuilder::new().build();
let mut sys = always_true.into_system(world.registry());
assert!(sys.run(&mut world));
}
fn check_threshold(val: Res<u64>) -> bool {
*val > 10
}
#[test]
fn single_param_system() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(42);
let mut world = builder.build();
let mut sys = check_threshold.into_system(world.registry());
assert!(sys.run(&mut world));
}
#[test]
fn single_param_system_false() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(5);
let mut world = builder.build();
let mut sys = check_threshold.into_system(world.registry());
assert!(!sys.run(&mut world));
}
fn reconcile(val: Res<u64>, mut flag: ResMut<bool>) -> bool {
if *val > 10 {
*flag = true;
true
} else {
false
}
}
#[test]
fn two_param_system() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(42);
builder.register::<bool>(false);
let mut world = builder.build();
let mut sys = reconcile.into_system(world.registry());
assert!(sys.run(&mut world));
assert!(*world.resource::<bool>());
}
#[test]
fn box_dyn_system() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(42);
let mut world = builder.build();
let mut boxed: Box<dyn System> = Box::new(check_threshold.into_system(world.registry()));
assert!(boxed.run(&mut world));
}
#[test]
#[should_panic(expected = "conflicting access")]
fn system_access_conflict_panics() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let world = builder.build();
fn bad(a: Res<u64>, b: ResMut<u64>) -> bool {
let _ = (*a, &*b);
true
}
let _sys = bad.into_system(world.registry());
}
fn counting_system(mut count: Local<u64>, mut val: ResMut<u64>) -> bool {
*count += 1;
*val = *count;
*count < 3
}
#[test]
fn local_in_system() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
let mut sys = counting_system.into_system(world.registry());
assert!(sys.run(&mut world)); assert!(sys.run(&mut world)); assert!(!sys.run(&mut world)); assert_eq!(*world.resource::<u64>(), 3);
}
#[test]
fn system_has_name() {
let world = WorldBuilder::new().build();
let sys = always_true.into_system(world.registry());
assert!(sys.name().contains("always_true"));
}
fn noop() {}
#[test]
fn arity_0_void_system() {
let mut world = WorldBuilder::new().build();
let mut sys = noop.into_system(world.registry());
assert!(sys.run(&mut world));
}
fn write_val(mut v: ResMut<u64>) {
*v = 99;
}
#[test]
fn arity_n_void_system() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
let mut sys = write_val.into_system(world.registry());
assert!(sys.run(&mut world));
assert_eq!(*world.resource::<u64>(), 99);
}
#[test]
fn box_dyn_void_system() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
let mut boxed: Box<dyn System> = Box::new(write_val.into_system(world.registry()));
assert!(boxed.run(&mut world));
assert_eq!(*world.resource::<u64>(), 99);
}
fn void_read_only(val: Res<u64>, flag: Res<bool>) {
let _ = (*val, *flag);
}
#[test]
fn void_two_params_read_only() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(42);
builder.register::<bool>(true);
let mut world = builder.build();
let mut sys = void_read_only.into_system(world.registry());
assert!(sys.run(&mut world));
}
fn void_two_params_write(mut a: ResMut<u64>, mut b: ResMut<bool>) {
*a = 77;
*b = true;
}
#[test]
fn void_two_params_mixed() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
builder.register::<bool>(false);
let mut world = builder.build();
let mut sys = void_two_params_write.into_system(world.registry());
assert!(sys.run(&mut world));
assert_eq!(*world.resource::<u64>(), 77);
assert!(*world.resource::<bool>());
}
fn void_with_local(mut count: Local<u64>, mut out: ResMut<u64>) {
*count += 1;
*out = *count;
}
#[test]
fn void_local_persists() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
let mut sys = void_with_local.into_system(world.registry());
assert!(sys.run(&mut world));
assert_eq!(*world.resource::<u64>(), 1);
assert!(sys.run(&mut world));
assert_eq!(*world.resource::<u64>(), 2);
assert!(sys.run(&mut world));
assert_eq!(*world.resource::<u64>(), 3);
}
#[test]
fn void_system_has_name() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let world = builder.build();
let sys = write_val.into_system(world.registry());
assert!(sys.name().contains("write_val"));
}
#[test]
#[should_panic(expected = "conflicting access")]
fn void_system_access_conflict_panics() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let world = builder.build();
fn bad_void(a: Res<u64>, b: ResMut<u64>) {
let _ = (*a, &*b);
}
let _sys = bad_void.into_system(world.registry());
}
}