use crate::Handler;
use crate::handler::Param;
use crate::world::{Registry, World};
pub struct Callback<C, F, Params: Param> {
pub ctx: C,
pub(crate) f: F,
pub(crate) state: Params::State,
pub(crate) name: &'static str,
}
#[diagnostic::on_unimplemented(
message = "this function cannot be converted into a callback",
note = "callback signature: `fn(&mut Context, Res<A>, ..., Event)` — context first, then resources, event last",
note = "closures with resource parameters are not supported — use a named `fn` when using Param resources"
)]
pub trait IntoCallback<C, E, Params> {
type Callback: Handler<E>;
#[must_use = "the callback must be stored or dispatched — discarding it does nothing"]
fn into_callback(self, ctx: C, registry: &Registry) -> Self::Callback;
}
impl<C: Send + 'static, E, F: FnMut(&mut C, E) + Send + 'static> IntoCallback<C, E, ()> for F {
type Callback = Callback<C, F, ()>;
fn into_callback(self, ctx: C, registry: &Registry) -> Self::Callback {
Callback {
ctx,
f: self,
state: <() as Param>::init(registry),
name: std::any::type_name::<F>(),
}
}
}
impl<C: Send + 'static, E, F: FnMut(&mut C, E) + Send + 'static> Handler<E> for Callback<C, F, ()> {
fn run(&mut self, _world: &mut World, event: E) {
(self.f)(&mut self.ctx, event);
}
fn name(&self) -> &'static str {
self.name
}
}
macro_rules! impl_into_callback {
($($P:ident),+) => {
impl<C: Send + 'static, E, F: Send + 'static, $($P: Param + 'static),+>
IntoCallback<C, E, ($($P,)+)> for F
where
for<'a> &'a mut F:
FnMut(&mut C, $($P,)+ E) +
FnMut(&mut C, $($P::Item<'a>,)+ E),
{
type Callback = Callback<C, F, ($($P,)+)>;
fn into_callback(self, ctx: C, registry: &Registry) -> Self::Callback {
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>()),
)+
]);
}
Callback { ctx, f: self, state, name: std::any::type_name::<F>() }
}
}
impl<C: Send + 'static, E, F: Send + 'static, $($P: Param + 'static),+>
Handler<E> for Callback<C, F, ($($P,)+)>
where
for<'a> &'a mut F:
FnMut(&mut C, $($P,)+ E) +
FnMut(&mut C, $($P::Item<'a>,)+ E),
{
#[allow(non_snake_case)]
fn run(&mut self, world: &mut World, event: E) {
#[allow(clippy::too_many_arguments)]
fn call_inner<Ctx, $($P,)+ Ev>(
mut f: impl FnMut(&mut Ctx, $($P,)+ Ev),
ctx: &mut Ctx,
$($P: $P,)+
event: Ev,
) {
f(ctx, $($P,)+ event);
}
#[cfg(debug_assertions)]
world.clear_borrows();
let ($($P,)+) = unsafe {
<($($P,)+) as Param>::fetch(world, &mut self.state)
};
call_inner(&mut self.f, &mut self.ctx, $($P,)+ event);
}
fn name(&self) -> &'static str {
self.name
}
}
};
}
all_tuples!(impl_into_callback);
#[cfg(test)]
mod tests {
use super::*;
use crate::{Local, Res, ResMut, WorldBuilder};
struct TimerCtx {
order_id: u64,
call_count: u64,
}
struct OrderCache {
expired: Vec<u64>,
}
impl crate::world::Resource for OrderCache {}
fn ctx_only_handler(ctx: &mut TimerCtx, _event: u32) {
ctx.call_count += 1;
}
#[test]
fn ctx_only_no_params() {
let mut world = WorldBuilder::new().build();
let mut cb = ctx_only_handler.into_callback(
TimerCtx {
order_id: 1,
call_count: 0,
},
world.registry_mut(),
);
cb.run(&mut world, 42u32);
assert_eq!(cb.ctx.call_count, 1);
}
fn ctx_one_res_handler(ctx: &mut TimerCtx, cache: Res<OrderCache>, _event: u32) {
ctx.call_count += cache.expired.len() as u64;
}
#[test]
fn ctx_one_res() {
let mut builder = WorldBuilder::new();
builder.register::<OrderCache>(OrderCache {
expired: vec![1, 2, 3],
});
let mut world = builder.build();
let mut cb = ctx_one_res_handler.into_callback(
TimerCtx {
order_id: 1,
call_count: 0,
},
world.registry_mut(),
);
cb.run(&mut world, 0u32);
assert_eq!(cb.ctx.call_count, 3);
}
fn ctx_one_res_mut_handler(ctx: &mut TimerCtx, mut cache: ResMut<OrderCache>, _event: u32) {
cache.expired.push(ctx.order_id);
ctx.call_count += 1;
}
#[test]
fn ctx_one_res_mut() {
let mut builder = WorldBuilder::new();
builder.register::<OrderCache>(OrderCache { expired: vec![] });
let mut world = builder.build();
let mut cb = ctx_one_res_mut_handler.into_callback(
TimerCtx {
order_id: 42,
call_count: 0,
},
world.registry_mut(),
);
cb.run(&mut world, 0u32);
assert_eq!(cb.ctx.call_count, 1);
assert_eq!(world.resource::<OrderCache>().expired, vec![42]);
}
fn ctx_two_params_handler(
ctx: &mut TimerCtx,
counter: Res<u64>,
mut cache: ResMut<OrderCache>,
_event: u32,
) {
cache.expired.push(*counter);
ctx.call_count += 1;
}
#[test]
fn ctx_two_params() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(99);
builder.register::<OrderCache>(OrderCache { expired: vec![] });
let mut world = builder.build();
let mut cb = ctx_two_params_handler.into_callback(
TimerCtx {
order_id: 0,
call_count: 0,
},
world.registry_mut(),
);
cb.run(&mut world, 0u32);
assert_eq!(cb.ctx.call_count, 1);
assert_eq!(world.resource::<OrderCache>().expired, vec![99]);
}
fn ctx_three_params_handler(
ctx: &mut TimerCtx,
a: Res<u64>,
b: Res<bool>,
mut c: ResMut<OrderCache>,
_event: u32,
) {
if *b {
c.expired.push(*a);
}
ctx.call_count += 1;
}
#[test]
fn ctx_three_params() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(7);
builder.register::<bool>(true);
builder.register::<OrderCache>(OrderCache { expired: vec![] });
let mut world = builder.build();
let mut cb = ctx_three_params_handler.into_callback(
TimerCtx {
order_id: 0,
call_count: 0,
},
world.registry_mut(),
);
cb.run(&mut world, 0u32);
assert_eq!(cb.ctx.call_count, 1);
assert_eq!(world.resource::<OrderCache>().expired, vec![7]);
}
#[test]
fn ctx_mutated_persists() {
let mut world = WorldBuilder::new().build();
let mut cb = ctx_only_handler.into_callback(
TimerCtx {
order_id: 1,
call_count: 0,
},
world.registry_mut(),
);
cb.run(&mut world, 0u32);
cb.run(&mut world, 0u32);
cb.run(&mut world, 0u32);
assert_eq!(cb.ctx.call_count, 3);
}
#[test]
fn ctx_accessible_outside_dispatch() {
let mut world = WorldBuilder::new().build();
let mut cb = ctx_only_handler.into_callback(
TimerCtx {
order_id: 42,
call_count: 0,
},
world.registry_mut(),
);
assert_eq!(cb.ctx.order_id, 42);
assert_eq!(cb.ctx.call_count, 0);
cb.run(&mut world, 0u32);
assert_eq!(cb.ctx.call_count, 1);
}
#[test]
fn ctx_mutated_outside_dispatch() {
let mut world = WorldBuilder::new().build();
let mut cb = ctx_only_handler.into_callback(
TimerCtx {
order_id: 1,
call_count: 0,
},
world.registry_mut(),
);
cb.ctx.order_id = 99;
cb.run(&mut world, 0u32);
assert_eq!(cb.ctx.order_id, 99);
assert_eq!(cb.ctx.call_count, 1);
}
#[test]
#[should_panic(expected = "not registered")]
fn panics_on_missing_resource() {
let mut world = WorldBuilder::new().build();
fn needs_cache(_ctx: &mut TimerCtx, _cache: Res<OrderCache>, _e: u32) {}
let _cb = needs_cache.into_callback(
TimerCtx {
order_id: 0,
call_count: 0,
},
world.registry_mut(),
);
}
#[test]
#[should_panic(expected = "not registered")]
fn panics_on_second_missing() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
fn needs_two(_ctx: &mut TimerCtx, _a: Res<u64>, _b: Res<OrderCache>, _e: u32) {}
let _cb = needs_two.into_callback(
TimerCtx {
order_id: 0,
call_count: 0,
},
world.registry_mut(),
);
}
#[test]
#[should_panic(expected = "conflicting access")]
fn callback_duplicate_access_panics() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
fn bad(_ctx: &mut u64, a: Res<u64>, b: ResMut<u64>, _e: ()) {
let _ = (*a, &*b);
}
let _cb = bad.into_callback(0u64, world.registry_mut());
}
#[test]
fn box_dyn_handler() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
fn add_ctx(ctx: &mut u64, mut val: ResMut<u64>, event: u64) {
*val += event + *ctx;
}
let cb = add_ctx.into_callback(10u64, world.registry_mut());
let mut boxed: Box<dyn Handler<u64>> = Box::new(cb);
boxed.run(&mut world, 5u64);
assert_eq!(*world.resource::<u64>(), 15);
}
#[test]
fn callback_in_vec_dyn() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
fn add(ctx: &mut u64, mut val: ResMut<u64>, _e: ()) {
*val += *ctx;
}
fn mul(ctx: &mut u64, mut val: ResMut<u64>, _e: ()) {
*val *= *ctx;
}
let cb_add = add.into_callback(3u64, world.registry_mut());
let cb_mul = mul.into_callback(2u64, world.registry_mut());
let mut handlers: Vec<Box<dyn Handler<()>>> = vec![Box::new(cb_add), Box::new(cb_mul)];
for h in &mut handlers {
h.run(&mut world, ());
}
assert_eq!(*world.resource::<u64>(), 6);
}
fn with_local(_ctx: &mut u64, mut local: Local<u64>, mut val: ResMut<u64>, _e: ()) {
*local += 1;
*val = *local;
}
#[test]
fn callback_with_local() {
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
let mut cb = with_local.into_callback(0u64, world.registry_mut());
cb.run(&mut world, ());
cb.run(&mut world, ());
cb.run(&mut world, ());
assert_eq!(*world.resource::<u64>(), 3);
}
#[test]
fn callback_interop_with_handler() {
use crate::IntoHandler;
let mut builder = WorldBuilder::new();
builder.register::<u64>(0);
let mut world = builder.build();
fn sys_add(mut val: ResMut<u64>, event: u64) {
*val += event;
}
fn cb_mul(ctx: &mut u64, mut val: ResMut<u64>, _e: u64) {
*val *= *ctx;
}
let sys = sys_add.into_handler(world.registry_mut());
let cb = cb_mul.into_callback(3u64, world.registry_mut());
let mut handlers: Vec<Box<dyn Handler<u64>>> = vec![Box::new(sys), Box::new(cb)];
for h in &mut handlers {
h.run(&mut world, 5u64);
}
assert_eq!(*world.resource::<u64>(), 15);
}
}