Skip to content

Commit eba64a2

Browse files
committed
callback as asset
1 parent b4c33da commit eba64a2

File tree

3 files changed

+419
-22
lines changed

3 files changed

+419
-22
lines changed

crates/bevy_asset/src/callback.rs

Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
use bevy_ecs::{
2+
prelude::*,
3+
system::{Command, DriveSystem},
4+
};
5+
use bevy_reflect::prelude::*;
6+
use bevy_utils::tracing;
7+
use thiserror::Error;
8+
9+
use crate::{Asset, Assets, Handle, VisitAssetDependencies};
10+
11+
// TODO: Add docs
12+
#[derive(TypePath)]
13+
pub struct Callback<I = (), O = ()> {
14+
inner: Option<DriveSystem<I, O>>,
15+
}
16+
17+
impl<I: 'static, O: 'static> Callback<I, O> {
18+
// TODO: Add docs
19+
pub fn from_system<M, S: IntoSystem<I, O, M>>(system: S) -> Self {
20+
Self {
21+
inner: Some(DriveSystem::new(Box::new(IntoSystem::into_system(system)))),
22+
}
23+
}
24+
}
25+
26+
impl<I, O> VisitAssetDependencies for Callback<I, O> {
27+
fn visit_dependencies(&self, _visit: &mut impl FnMut(crate::UntypedAssetId)) {
28+
// TODO: Would there be a way to get this info from the IntoSystem used to contruct the callback?
29+
// TODO: Should there be a way to pass this info through a different construct function?
30+
}
31+
}
32+
33+
impl<I: TypePath, O: TypePath> Asset for Callback<I, O> {}
34+
35+
// TODO: Add docs
36+
#[derive(Error)]
37+
pub enum CallbackError<I: TypePath, O: TypePath> {
38+
// TODO: Add docs
39+
#[error("Callback {0:?} was not found")]
40+
HandleNotFound(Handle<Callback<I, O>>),
41+
// TODO: Add docs
42+
#[error("Callback {0:?} tried to run itself recursively")]
43+
Recursive(Handle<Callback<I, O>>),
44+
// TODO: Add docs
45+
#[error("Callback {0:?} removed itself")]
46+
RemovedSelf(Handle<Callback<I, O>>),
47+
}
48+
49+
impl<I: TypePath, O: TypePath> std::fmt::Debug for CallbackError<I, O> {
50+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51+
match self {
52+
Self::HandleNotFound(arg0) => f.debug_tuple("HandleNotFound").field(arg0).finish(),
53+
Self::Recursive(arg0) => f.debug_tuple("Recursive").field(arg0).finish(),
54+
Self::RemovedSelf(arg0) => f.debug_tuple("RemovedSelf").field(arg0).finish(),
55+
}
56+
}
57+
}
58+
59+
// TODO: Add docs
60+
pub trait RunCallbackWorld {
61+
// TODO: Add docs
62+
fn run_callback_with_input<In: TypePath + Send + 'static, Out: TypePath + 'static>(
63+
&mut self,
64+
handle: Handle<Callback<In, Out>>,
65+
input: In,
66+
) -> Result<Out, CallbackError<In, Out>>;
67+
68+
// TODO: Add docs
69+
fn run_callback<Out: TypePath + 'static>(
70+
&mut self,
71+
handle: Handle<Callback<(), Out>>,
72+
) -> Result<Out, CallbackError<(), Out>> {
73+
self.run_callback_with_input(handle, ())
74+
}
75+
}
76+
77+
impl RunCallbackWorld for World {
78+
fn run_callback_with_input<In: TypePath + Send + 'static, Out: TypePath + 'static>(
79+
&mut self,
80+
handle: Handle<Callback<In, Out>>,
81+
input: In,
82+
) -> Result<Out, CallbackError<In, Out>> {
83+
let mut assets = self.resource_mut::<Assets<Callback<In, Out>>>();
84+
let mut callback = assets
85+
.get_mut(&handle)
86+
.ok_or_else(|| CallbackError::HandleNotFound(handle.clone()))?
87+
.inner
88+
.take()
89+
.ok_or_else(|| CallbackError::Recursive(handle.clone()))?;
90+
91+
let result = callback.run_with_input(self, input);
92+
let mut assets = self.resource_mut::<Assets<Callback<In, Out>>>();
93+
assets
94+
.get_mut(&handle)
95+
.ok_or_else(|| CallbackError::RemovedSelf(handle))?
96+
.inner = Some(callback);
97+
98+
Ok(result)
99+
}
100+
}
101+
102+
// TODO: add docs
103+
#[derive(Debug, Clone)]
104+
pub struct RunCallbackWithInput<I: TypePath + 'static> {
105+
handle: Handle<Callback<I>>,
106+
input: I,
107+
}
108+
109+
// TODO: add docs
110+
pub type RunCallback = RunCallbackWithInput<()>;
111+
112+
impl RunCallback {
113+
// TODO: add docs
114+
pub fn new(handle: Handle<Callback>) -> Self {
115+
Self::new_with_input(handle, ())
116+
}
117+
}
118+
119+
impl<I: TypePath + 'static> RunCallbackWithInput<I> {
120+
// TODO: add docs
121+
pub fn new_with_input(handle: Handle<Callback<I>>, input: I) -> Self {
122+
Self { handle, input }
123+
}
124+
}
125+
126+
impl<I: TypePath + Send + 'static> Command for RunCallbackWithInput<I> {
127+
#[inline]
128+
fn apply(self, world: &mut World) {
129+
if let Err(error) = world.run_callback_with_input(self.handle, self.input) {
130+
tracing::error!("{error}");
131+
}
132+
}
133+
}
134+
135+
// TODO: Add docs
136+
pub trait RunCallbackCommands {
137+
// TODO: Add docs
138+
fn run_callback_with_input<I: TypePath + Send + 'static>(
139+
&mut self,
140+
handle: Handle<Callback<I>>,
141+
input: I,
142+
);
143+
144+
// TODO: Add docs
145+
fn run_callback(&mut self, handle: Handle<Callback>) {
146+
self.run_callback_with_input(handle, ());
147+
}
148+
}
149+
150+
impl<'w, 's> RunCallbackCommands for Commands<'w, 's> {
151+
fn run_callback_with_input<I: TypePath + Send + 'static>(
152+
&mut self,
153+
handle: Handle<Callback<I>>,
154+
input: I,
155+
) {
156+
self.add(RunCallbackWithInput::new_with_input(handle, input));
157+
}
158+
}
159+
160+
#[cfg(test)]
161+
mod tests {
162+
use crate::*;
163+
use bevy_ecs::prelude::*;
164+
165+
#[derive(Resource, Default, PartialEq, Debug)]
166+
struct Counter(u8);
167+
168+
#[test]
169+
fn change_detection() {
170+
#[derive(Resource, Default)]
171+
struct ChangeDetector;
172+
173+
fn count_up_iff_changed(
174+
mut counter: ResMut<Counter>,
175+
change_detector: ResMut<ChangeDetector>,
176+
) {
177+
if change_detector.is_changed() {
178+
counter.0 += 1;
179+
}
180+
}
181+
182+
let mut app = App::new();
183+
app.add_plugins(AssetPlugin::default());
184+
app.init_resource::<ChangeDetector>();
185+
app.init_resource::<Counter>();
186+
assert_eq!(*app.world.resource::<Counter>(), Counter(0));
187+
188+
// Resources are changed when they are first added.
189+
let mut callbacks = app.world.resource_mut::<Assets<Callback>>();
190+
let handle = callbacks.add(Callback::from_system(count_up_iff_changed));
191+
app.world
192+
.run_callback(handle.clone())
193+
.expect("system runs successfully");
194+
assert_eq!(*app.world.resource::<Counter>(), Counter(1));
195+
// Nothing changed
196+
app.world
197+
.run_callback(handle.clone())
198+
.expect("system runs successfully");
199+
assert_eq!(*app.world.resource::<Counter>(), Counter(1));
200+
// Making a change
201+
app.world.resource_mut::<ChangeDetector>().set_changed();
202+
app.world
203+
.run_callback(handle)
204+
.expect("system runs successfully");
205+
assert_eq!(*app.world.resource::<Counter>(), Counter(2));
206+
}
207+
208+
#[test]
209+
fn local_variables() {
210+
// The `Local` begins at the default value of 0
211+
fn doubling(mut last_counter: Local<Counter>, mut counter: ResMut<Counter>) {
212+
counter.0 += last_counter.0;
213+
last_counter.0 = counter.0;
214+
}
215+
216+
let mut app = App::new();
217+
app.add_plugins(AssetPlugin::default());
218+
app.insert_resource(Counter(1));
219+
assert_eq!(*app.world.resource::<Counter>(), Counter(1));
220+
221+
let mut callbacks = app.world.resource_mut::<Assets<Callback>>();
222+
let handle = callbacks.add(Callback::from_system(doubling));
223+
app.world
224+
.run_callback(handle.clone())
225+
.expect("system runs successfully");
226+
assert_eq!(*app.world.resource::<Counter>(), Counter(1));
227+
app.world
228+
.run_callback(handle.clone())
229+
.expect("system runs successfully");
230+
assert_eq!(*app.world.resource::<Counter>(), Counter(2));
231+
app.world
232+
.run_callback(handle.clone())
233+
.expect("system runs successfully");
234+
assert_eq!(*app.world.resource::<Counter>(), Counter(4));
235+
app.world
236+
.run_callback(handle)
237+
.expect("system runs successfully");
238+
assert_eq!(*app.world.resource::<Counter>(), Counter(8));
239+
}
240+
241+
#[test]
242+
fn input_values() {
243+
// Verify that a non-Copy, non-Clone type can be passed in.
244+
#[derive(TypePath)]
245+
struct NonCopy(u8);
246+
247+
fn increment_sys(In(NonCopy(increment_by)): In<NonCopy>, mut counter: ResMut<Counter>) {
248+
counter.0 += increment_by;
249+
}
250+
251+
let mut app = App::new();
252+
app.add_plugins(AssetPlugin::default());
253+
app.init_asset::<Callback<NonCopy>>();
254+
255+
let mut callbacks = app.world.resource_mut::<Assets<Callback<NonCopy>>>();
256+
let handle = callbacks.add(Callback::from_system(increment_sys));
257+
258+
// Insert the resource after registering the system.
259+
app.insert_resource(Counter(1));
260+
assert_eq!(*app.world.resource::<Counter>(), Counter(1));
261+
262+
app.world
263+
.run_callback_with_input(handle.clone(), NonCopy(1))
264+
.expect("system runs successfully");
265+
assert_eq!(*app.world.resource::<Counter>(), Counter(2));
266+
267+
app.world
268+
.run_callback_with_input(handle.clone(), NonCopy(1))
269+
.expect("system runs successfully");
270+
assert_eq!(*app.world.resource::<Counter>(), Counter(3));
271+
272+
app.world
273+
.run_callback_with_input(handle.clone(), NonCopy(20))
274+
.expect("system runs successfully");
275+
assert_eq!(*app.world.resource::<Counter>(), Counter(23));
276+
277+
app.world
278+
.run_callback_with_input(handle, NonCopy(1))
279+
.expect("system runs successfully");
280+
assert_eq!(*app.world.resource::<Counter>(), Counter(24));
281+
}
282+
283+
#[test]
284+
fn output_values() {
285+
// Verify that a non-Copy, non-Clone type can be returned.
286+
#[derive(TypePath, Eq, PartialEq, Debug)]
287+
struct NonCopy(u8);
288+
289+
fn increment_sys(mut counter: ResMut<Counter>) -> NonCopy {
290+
counter.0 += 1;
291+
NonCopy(counter.0)
292+
}
293+
294+
let mut app = App::new();
295+
app.add_plugins(AssetPlugin::default());
296+
app.init_asset::<Callback<(), NonCopy>>();
297+
298+
let mut callbacks = app.world.resource_mut::<Assets<Callback<(), NonCopy>>>();
299+
let handle = callbacks.add(Callback::from_system(increment_sys));
300+
301+
// Insert the resource after registering the system.
302+
app.insert_resource(Counter(1));
303+
assert_eq!(*app.world.resource::<Counter>(), Counter(1));
304+
305+
let output = app
306+
.world
307+
.run_callback(handle.clone())
308+
.expect("system runs successfully");
309+
assert_eq!(*app.world.resource::<Counter>(), Counter(2));
310+
assert_eq!(output, NonCopy(2));
311+
312+
let output = app
313+
.world
314+
.run_callback(handle)
315+
.expect("system runs successfully");
316+
assert_eq!(*app.world.resource::<Counter>(), Counter(3));
317+
assert_eq!(output, NonCopy(3));
318+
}
319+
320+
#[test]
321+
fn nested_systems() {
322+
#[derive(Component)]
323+
struct Call(Handle<Callback>);
324+
325+
fn nested(query: Query<&Call>, mut commands: Commands) {
326+
for call in query.iter() {
327+
commands.run_callback(call.0.clone());
328+
}
329+
}
330+
331+
let mut app = App::new();
332+
app.add_plugins(AssetPlugin::default());
333+
app.insert_resource(Counter(0));
334+
335+
let mut callbacks = app.world.resource_mut::<Assets<Callback>>();
336+
337+
let increment_two = callbacks.add(Callback::from_system(|mut counter: ResMut<Counter>| {
338+
counter.0 += 2;
339+
}));
340+
let increment_three =
341+
callbacks.add(Callback::from_system(|mut counter: ResMut<Counter>| {
342+
counter.0 += 3;
343+
}));
344+
let nested_handle = callbacks.add(Callback::from_system(nested));
345+
346+
app.world.spawn(Call(increment_two));
347+
app.world.spawn(Call(increment_three));
348+
let _ = app.world.run_callback(nested_handle);
349+
assert_eq!(*app.world.resource::<Counter>(), Counter(5));
350+
}
351+
352+
#[test]
353+
fn nested_systems_with_inputs() {
354+
#[derive(Component)]
355+
struct Call(Handle<Callback<u8>>, u8);
356+
357+
fn nested(query: Query<&Call>, mut commands: Commands) {
358+
for callback in query.iter() {
359+
commands.run_callback_with_input(callback.0.clone(), callback.1);
360+
}
361+
}
362+
363+
let mut app = App::new();
364+
app.add_plugins(AssetPlugin::default());
365+
app.init_asset::<Callback<u8>>();
366+
app.insert_resource(Counter(0));
367+
368+
let mut callbacks = app.world.resource_mut::<Assets<Callback<u8>>>();
369+
370+
let increment_by = callbacks.add(Callback::from_system(
371+
|In(amt): In<u8>, mut counter: ResMut<Counter>| {
372+
counter.0 += amt;
373+
},
374+
));
375+
let mut callbacks = app.world.resource_mut::<Assets<Callback>>();
376+
let nested_id = callbacks.add(Callback::from_system(nested));
377+
378+
app.world.spawn(Call(increment_by.clone(), 2));
379+
app.world.spawn(Call(increment_by, 3));
380+
let _ = app.world.run_callback(nested_id);
381+
assert_eq!(*app.world.resource::<Counter>(), Counter(5));
382+
}
383+
}

0 commit comments

Comments
 (0)