flax/system/
mod.rs

1mod context;
2mod input;
3mod traits;
4
5use crate::{
6    archetype::{ArchetypeId, ArchetypeInfo},
7    component::ComponentKey,
8    query::{QueryData, QueryStrategy},
9    util::TuplePush,
10    CommandBuffer, Fetch, FetchItem, Query, World,
11};
12use alloc::{
13    boxed::Box,
14    collections::BTreeMap,
15    format,
16    string::{String, ToString},
17    vec::Vec,
18};
19use core::{
20    any::{type_name, TypeId},
21    fmt::{self, Formatter},
22    marker::PhantomData,
23};
24
25pub use context::*;
26pub use input::IntoInput;
27pub use traits::{AsBorrowed, SystemAccess, SystemData, SystemFn};
28
29use self::traits::{WithCmd, WithCmdMut, WithInput, WithInputMut, WithWorld, WithWorldMut};
30
31#[cfg(feature = "rayon")]
32use rayon::prelude::{ParallelBridge, ParallelIterator};
33
34/// A system builder which allows incrementally adding data to a system
35/// function.
36pub struct SystemBuilder<Args> {
37    args: Args,
38    name: Option<String>,
39}
40
41impl SystemBuilder<()> {
42    /// Creates a new empty system builder.
43    pub fn new() -> Self {
44        Self {
45            args: (),
46            name: None,
47        }
48    }
49}
50
51impl Default for SystemBuilder<()> {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57#[doc(hidden)]
58pub struct ForEach<Func> {
59    func: Func,
60}
61
62impl<'a, Func, Q, F> SystemFn<'a, (QueryData<'a, Q, F>,), ()> for ForEach<Func>
63where
64    for<'x> Q: Fetch<'x>,
65    for<'x> F: Fetch<'x>,
66    for<'x> Func: FnMut(<Q as FetchItem<'x>>::Item),
67{
68    fn execute(&mut self, mut data: (QueryData<Q, F>,)) {
69        for item in &mut data.0.borrow() {
70            (self.func)(item)
71        }
72    }
73}
74
75#[doc(hidden)]
76pub struct TryForEach<Func, E> {
77    func: Func,
78    _marker: PhantomData<E>,
79}
80
81impl<'a, Func, Q, F, E> SystemFn<'a, (QueryData<'a, Q, F>,), Result<(), E>> for TryForEach<Func, E>
82where
83    for<'x> Q: Fetch<'x>,
84    for<'x> F: Fetch<'x>,
85    for<'x> Func: FnMut(<Q as FetchItem<'x>>::Item) -> Result<(), E>,
86    E: Send + Sync,
87{
88    fn execute(&mut self, mut data: (QueryData<Q, F>,)) -> Result<(), E> {
89        for item in &mut data.0.borrow() {
90            (self.func)(item)?;
91        }
92
93        Ok(())
94    }
95}
96/// Execute a function for each item in the query in parallel batches
97#[cfg(feature = "rayon")]
98pub struct ParForEach<F> {
99    func: F,
100}
101
102#[cfg(feature = "rayon")]
103impl<'a, Func, Q, F> SystemFn<'a, (QueryData<'a, Q, F>,), ()> for ParForEach<Func>
104where
105    for<'x> Q: Fetch<'x>,
106    for<'x> F: Fetch<'x>,
107    for<'x> <crate::filter::Filtered<Q, F> as Fetch<'x>>::Prepared: Send,
108    for<'x, 'y> <<Q as Fetch<'x>>::Prepared as crate::fetch::PreparedFetch<'y>>::Chunk: Send,
109    for<'x> Func: Fn(<Q as FetchItem<'x>>::Item) + Send + Sync,
110{
111    fn execute(&mut self, mut data: (QueryData<Q, F>,)) {
112        let mut borrow = data.0.borrow();
113        borrow
114            .iter_batched()
115            .par_bridge()
116            .for_each(|v| v.for_each(&self.func));
117    }
118}
119
120pub(crate) type TrySystem<F, Args, Err> = System<F, Args, Result<(), Err>>;
121
122impl<Q, F> SystemBuilder<(Query<Q, F>,)>
123where
124    for<'x> Q: Fetch<'x> + 'static,
125    for<'x> F: Fetch<'x> + 'static,
126{
127    /// Execute a function for each item in the query
128    pub fn for_each<Func>(self, func: Func) -> System<ForEach<Func>, (Query<Q, F>,), ()>
129    where
130        for<'x> Func: FnMut(<Q as FetchItem<'x>>::Item),
131    {
132        System::new(
133            self.name.unwrap_or_else(|| type_name::<Func>().to_string()),
134            ForEach { func },
135            self.args,
136        )
137    }
138
139    /// Execute a function for each item in the query
140    pub fn try_for_each<Func, E>(
141        self,
142        func: Func,
143    ) -> TrySystem<TryForEach<Func, E>, (Query<Q, F>,), E>
144    where
145        E: Into<anyhow::Error>,
146        for<'x> Func: FnMut(<Q as FetchItem<'x>>::Item) -> Result<(), E>,
147    {
148        System::new(
149            self.name.unwrap_or_else(|| type_name::<Func>().to_string()),
150            TryForEach {
151                func,
152                _marker: PhantomData,
153            },
154            self.args,
155        )
156    }
157}
158
159#[cfg(feature = "rayon")]
160impl<Q, F> SystemBuilder<(Query<Q, F>,)>
161where
162    for<'x> Q: 'static + Fetch<'x> + Send,
163    for<'x> F: 'static + Fetch<'x> + Send,
164    for<'x> <<Q as Fetch<'x>>::Prepared as crate::fetch::PreparedFetch<'x>>::Chunk: Send,
165    // for<'x, 'y> crate::query::Batch<'y, <Q as Fetch<'x>>::Prepared>: Send,
166{
167    /// Execute a function for each item in the query in parallel batches
168    pub fn par_for_each<Func>(self, func: Func) -> System<ParForEach<Func>, (Query<Q, F>,), ()>
169    where
170        for<'x> Func: Fn(<Q as FetchItem<'x>>::Item) + Send + Sync,
171    {
172        System::new(
173            self.name.unwrap_or_else(|| type_name::<Func>().to_string()),
174            ParForEach { func },
175            self.args,
176        )
177    }
178}
179
180impl<Args> SystemBuilder<Args> {
181    /// Use a query within the system
182    ///
183    /// Systems are automatically multithreaded on an archetype and component granularity level.
184    ///
185    /// This means that queries which access the same component mutably for *different* archetypes
186    /// will be scheduled in parallel, and queries which access *different* components mutably in
187    /// the same archetype will also be scheduled in parallel.
188    pub fn with_query<Q, F, S>(self, query: Query<Q, F, S>) -> SystemBuilder<Args::PushRight>
189    where
190        Q: 'static + for<'x> Fetch<'x>,
191        F: 'static + for<'x> Fetch<'x>,
192        S: 'static + for<'x> QueryStrategy<'x, Q, F>,
193        Args: TuplePush<Query<Q, F, S>>,
194    {
195        self.with(query)
196    }
197    /// Access the world
198    ///
199    /// **Note**: This still creates a barrier to queries in other systems as the archetypes can be
200    /// mutated by a shared reference
201    pub fn with_world(self) -> SystemBuilder<Args::PushRight>
202    where
203        Args: TuplePush<WithWorld>,
204    {
205        self.with(WithWorld)
206    }
207
208    /// Access the world mutably
209    pub fn with_world_mut(self) -> SystemBuilder<Args::PushRight>
210    where
211        Args: TuplePush<WithWorldMut>,
212    {
213        self.with(WithWorldMut)
214    }
215
216    /// Access the command buffer
217    pub fn with_cmd(self) -> SystemBuilder<Args::PushRight>
218    where
219        Args: TuplePush<WithCmd>,
220    {
221        self.with(WithCmd)
222    }
223
224    /// Access the command buffer mutably
225    /// **Note**: Add `.flush()` after the system in the schedule to have the changes visible in
226    /// the next system
227    pub fn with_cmd_mut(self) -> SystemBuilder<Args::PushRight>
228    where
229        Args: TuplePush<WithCmdMut>,
230    {
231        self.with(WithCmdMut)
232    }
233
234    /// Access schedule input
235    pub fn with_input<T>(self) -> SystemBuilder<Args::PushRight>
236    where
237        T: 'static,
238        Args: TuplePush<WithInput<T>>,
239    {
240        self.with(WithInput::<T>(PhantomData))
241    }
242
243    /// Access schedule input mutably
244    pub fn with_input_mut<T>(self) -> SystemBuilder<Args::PushRight>
245    where
246        T: 'static,
247        Args: TuplePush<WithInputMut<T>>,
248    {
249        self.with(WithInputMut::<T>(PhantomData))
250    }
251
252    /// Set the systems name
253    pub fn with_name(mut self, name: impl Into<String>) -> Self {
254        self.name = Some(name.into());
255        self
256    }
257
258    /// Access a shared resource mutable in the system.
259    ///
260    /// This is useful to avoid sharing `Arc<Mutex<_>>` and locking for each
261    /// system. In addition, the resource will be taken into account for the
262    /// schedule paralellization and will as such not block.
263    pub fn with_resource<R>(self, resource: SharedResource<R>) -> SystemBuilder<Args::PushRight>
264    where
265        Args: TuplePush<SharedResource<R>>,
266        R: Send + 'static,
267    {
268        self.with(resource)
269    }
270
271    /// Build the system by supplying a function to act upon the systems arguments,
272    pub fn build<Func, Ret>(self, func: Func) -> System<Func, Args, Ret>
273    where
274        Args: for<'a> SystemData<'a> + 'static,
275        Func: for<'this, 'a> SystemFn<'this, <Args as SystemData<'a>>::Value, Ret>,
276    {
277        System::new(
278            self.name.unwrap_or_else(|| type_name::<Func>().to_string()),
279            func,
280            self.args,
281        )
282    }
283
284    /// Add a new generic argument to the system
285    fn with<S>(self, other: S) -> SystemBuilder<Args::PushRight>
286    where
287        S: for<'x> SystemData<'x>,
288        Args: TuplePush<S>,
289    {
290        SystemBuilder {
291            name: self.name,
292            args: self.args.push_right(other),
293        }
294    }
295}
296
297/// Holds the data and an inner system satisfying all dependencies
298pub struct System<F, Args, Ret> {
299    name: String,
300    data: Args,
301    func: F,
302    _marker: PhantomData<Ret>,
303}
304
305struct FormatWith<F> {
306    func: F,
307}
308
309impl<F> fmt::Debug for FormatWith<F>
310where
311    F: Fn(&mut Formatter<'_>) -> fmt::Result,
312{
313    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
314        (self.func)(f)
315    }
316}
317
318/// Abstraction over a system with any kind of arguments and fallibility
319#[doc(hidden)]
320pub trait DynSystem {
321    fn name(&self) -> &str;
322    fn describe(&self, f: &mut Formatter<'_>) -> fmt::Result;
323    fn execute(&mut self, ctx: &SystemContext<'_, '_, '_>) -> anyhow::Result<()>;
324    fn access(&self, world: &World, dst: &mut Vec<Access>);
325}
326
327impl<F, Args, Err> DynSystem for System<F, Args, Result<(), Err>>
328where
329    Args: for<'x> SystemData<'x>,
330    F: for<'x> SystemFn<'x, <Args as SystemData<'x>>::Value, Result<(), Err>>,
331    Err: Into<anyhow::Error>,
332{
333    fn execute(&mut self, ctx: &SystemContext<'_, '_, '_>) -> anyhow::Result<()> {
334        profile_function!(self.name());
335
336        #[cfg(feature = "tracing")]
337        let _span = tracing::info_span!("system", name = self.name).entered();
338
339        let data = self.data.acquire(ctx);
340
341        let res: anyhow::Result<()> = self.func.execute(data).map_err(Into::into);
342        if let Err(err) = res {
343            return Err(err.context(format!("Failed to execute system: {:?}", self)));
344        }
345
346        Ok(())
347    }
348
349    fn describe(&self, f: &mut fmt::Formatter<'_>) -> core::fmt::Result {
350        f.write_str("fn ")?;
351        f.write_str(&self.name)?;
352        self.data.describe(f)?;
353        f.write_str(" -> ")?;
354        f.write_str(&tynm::type_name::<core::result::Result<(), Err>>())?;
355
356        Ok(())
357    }
358
359    fn access(&self, world: &World, dst: &mut Vec<Access>) {
360        self.data.access(world, dst)
361    }
362
363    fn name(&self) -> &str {
364        &self.name
365    }
366}
367
368impl<F, Args> DynSystem for System<F, Args, ()>
369where
370    Args: for<'x> SystemData<'x>,
371    F: for<'x> SystemFn<'x, <Args as SystemData<'x>>::Value, ()>,
372{
373    fn execute(&mut self, ctx: &SystemContext<'_, '_, '_>) -> anyhow::Result<()> {
374        profile_function!(self.name());
375
376        #[cfg(feature = "tracing")]
377        let _span = tracing::info_span!("system", name = self.name).entered();
378
379        let data = {
380            profile_scope!("acquire_data");
381            self.data.acquire(ctx)
382        };
383
384        {
385            profile_scope!("exec");
386            self.func.execute(data);
387        }
388
389        Ok(())
390    }
391
392    fn describe(&self, f: &mut fmt::Formatter<'_>) -> core::fmt::Result {
393        f.write_str("fn ")?;
394        f.write_str(&self.name)?;
395        self.data.describe(f)?;
396
397        Ok(())
398    }
399
400    fn access(&self, world: &World, dst: &mut Vec<Access>) {
401        self.data.access(world, dst)
402    }
403
404    fn name(&self) -> &str {
405        &self.name
406    }
407}
408
409impl<F, Args, Ret> fmt::Debug for System<F, Args, Ret>
410where
411    Self: DynSystem,
412{
413    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
414        self.describe(f)
415    }
416}
417
418impl<F, Args, Ret> System<F, Args, Ret> {
419    pub(crate) fn new(name: String, func: F, data: Args) -> Self {
420        Self {
421            name,
422            data,
423            func,
424            _marker: PhantomData,
425        }
426    }
427
428    /// Convert to a type erased Send + Sync system
429    pub fn boxed(self) -> BoxedSystem
430    where
431        Ret: Send + Sync + 'static,
432        Args: Send + Sync + 'static,
433        F: Send + Sync + 'static,
434        Self: DynSystem,
435    {
436        BoxedSystem::new(self)
437    }
438}
439
440impl System<(), (), ()> {
441    /// See [crate::SystemBuilder]
442    pub fn builder() -> SystemBuilder<()> {
443        SystemBuilder::new()
444    }
445}
446
447impl<F, Args, Ret> System<F, Args, Ret> {
448    /// Run the system on the world. Any commands will be applied
449    pub fn run<'a>(&'a mut self, world: &'a mut World) -> Ret
450    where
451        Ret: 'static,
452        for<'x> Args: SystemData<'x>,
453        for<'x> F: SystemFn<'x, <Args as SystemData<'x>>::Value, Ret>,
454    {
455        self.run_with(world, &mut ())
456    }
457}
458
459impl<F, Args, Ret> System<F, Args, Ret> {
460    /// Run the system on the world. Any commands will be applied
461    pub fn run_with<'a>(&mut self, world: &mut World, input: impl IntoInput<'a>) -> Ret
462    where
463        Ret: 'static,
464        for<'x> Args: SystemData<'x>,
465        for<'x> F: SystemFn<'x, <Args as SystemData<'x>>::Value, Ret>,
466    {
467        #[cfg(feature = "tracing")]
468        let _span = tracing::info_span!("run_on", name = self.name).entered();
469
470        let mut cmd = CommandBuffer::new();
471        let input = input.into_input();
472        let ctx = SystemContext::new(world, &mut cmd, &input);
473
474        let data = self.data.acquire(&ctx);
475
476        let ret = self.func.execute(data);
477        ctx.cmd_mut()
478            .apply(&mut ctx.world.borrow_mut())
479            .expect("Failed to apply commandbuffer");
480        ret
481    }
482}
483
484#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
485/// Describes a kind of access
486pub enum AccessKind {
487    /// Borrow a single component of an archetype
488    Archetype {
489        /// The archetype id
490        id: ArchetypeId,
491        /// The accessed component
492        component: ComponentKey,
493    },
494    /// A unit struct works as a synchronization barrier
495    External(TypeId),
496    /// Borrow the whole world
497    World,
498    /// Borrow the commandbuffer
499    CommandBuffer,
500    /// Data supplied by user in the execution context
501    Input(TypeId),
502}
503
504impl AccessKind {
505    /// Returns `true` if the access kind is [`Archetype`].
506    ///
507    /// [`Archetype`]: AccessKind::Archetype
508    #[must_use]
509    pub fn is_archetype(&self) -> bool {
510        matches!(self, Self::Archetype { .. })
511    }
512
513    /// Returns `true` if the access kind is [`World`].
514    ///
515    /// [`World`]: AccessKind::World
516    #[must_use]
517    pub fn is_world(&self) -> bool {
518        matches!(self, Self::World)
519    }
520
521    /// Returns `true` if the access kind is [`CommandBuffer`].
522    ///
523    /// [`CommandBuffer`]: AccessKind::CommandBuffer
524    #[must_use]
525    pub fn is_command_buffer(&self) -> bool {
526        matches!(self, Self::CommandBuffer)
527    }
528}
529
530/// An access for a component in an archetype
531#[derive(Default, Debug, Clone)]
532#[allow(dead_code)]
533struct ArchetypeAccess {
534    arch: ArchetypeInfo,
535    components: Vec<ComponentAccessInfo>,
536    change_events: Vec<ComponentAccessInfo>,
537}
538
539#[derive(Debug, Clone)]
540#[allow(dead_code)]
541struct ComponentAccessInfo {
542    mutable: bool,
543    name: &'static str,
544    id: ComponentKey,
545}
546
547/// Human friendly system access
548#[derive(Default, Debug, Clone)]
549pub struct AccessInfo {
550    archetypes: BTreeMap<ArchetypeId, ArchetypeAccess>,
551    world: Option<bool>,
552    cmd: Option<bool>,
553    external: Vec<TypeId>,
554    input: Vec<(TypeId, bool)>,
555}
556
557#[derive(Hash, Debug, Clone, PartialEq, Eq)]
558/// Describes an access for a system, allowing for dependency resolution and
559/// multithreading
560pub struct Access {
561    /// The kind of access
562    pub kind: AccessKind,
563    /// shared or unique/mutable access
564    pub mutable: bool,
565}
566
567/// Transform accesses into a human friendly format
568pub(crate) fn access_info(accesses: &[Access], world: &World) -> AccessInfo {
569    let mut result = AccessInfo::default();
570    for access in accesses {
571        match access.kind {
572            AccessKind::Archetype { id, component } => {
573                let arch = world.archetypes.get(id);
574                result
575                    .archetypes
576                    .entry(id)
577                    .or_insert_with(|| ArchetypeAccess {
578                        arch: arch.desc(),
579                        ..Default::default()
580                    })
581                    .components
582                    .push(ComponentAccessInfo {
583                        mutable: access.mutable,
584                        name: arch.component(component).unwrap().name(),
585                        id: component,
586                    })
587            }
588            AccessKind::External(ty) => result.external.push(ty),
589            AccessKind::Input(ty) => {
590                result.input.push((ty, access.mutable));
591            }
592            AccessKind::World => match result.world {
593                Some(true) => result.world = Some(true),
594                _ => result.world = Some(access.mutable),
595            },
596            AccessKind::CommandBuffer => match result.cmd {
597                Some(true) => result.cmd = Some(true),
598                _ => result.cmd = Some(access.mutable),
599            },
600        }
601    }
602
603    result
604}
605
606impl Access {
607    /// Returns true it both accesses can coexist
608    pub(crate) fn is_compatible_with(&self, other: &Self) -> bool {
609        !(self.kind == other.kind && (self.mutable || other.mutable))
610    }
611}
612
613/// A type erased system
614pub struct BoxedSystem {
615    inner: Box<dyn DynSystem + Send + Sync>,
616}
617
618impl core::fmt::Debug for BoxedSystem {
619    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
620        self.inner.describe(f)
621    }
622}
623
624impl BoxedSystem {
625    /// Same as execute but creates and applied a temporary commandbuffer
626    pub fn run<'a>(&'a mut self, world: &'a mut World) -> anyhow::Result<()> {
627        self.run_with(world, &mut ())
628    }
629}
630
631impl BoxedSystem {
632    /// Creates a new boxed system from any other kind of system
633    fn new<S>(system: S) -> Self
634    where
635        S: DynSystem + Send + Sync + 'static,
636    {
637        Self {
638            inner: Box::new(system),
639        }
640    }
641
642    /// Execute the system with the provided context
643    pub fn execute<'a>(&'a mut self, ctx: &'a SystemContext<'_, '_, '_>) -> anyhow::Result<()> {
644        self.inner.execute(ctx)
645    }
646
647    /// Same as execute but creates and applied a temporary command buffer
648    pub fn run_with<'a>(
649        &'a mut self,
650        world: &'a mut World,
651        input: impl IntoInput<'a>,
652    ) -> anyhow::Result<()> {
653        let mut cmd = CommandBuffer::new();
654        let input = input.into_input();
655        let ctx = SystemContext::new(world, &mut cmd, &input);
656        self.inner.execute(&ctx)?;
657
658        ctx.cmd_mut()
659            .apply(&mut ctx.world.borrow_mut())
660            .expect("Failed to apply commandbuffer");
661
662        Ok(())
663    }
664
665    /// Describes the system held within
666    pub fn describe(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
667        self.inner.describe(f)
668    }
669
670    /// Returns the accesses of the system held within
671    pub fn access(&self, world: &World, dst: &mut Vec<Access>) {
672        self.inner.access(world, dst)
673    }
674
675    /// Returns the boxed system's name
676    pub fn name(&self) -> &str {
677        self.inner.name()
678    }
679}
680
681impl<T> From<T> for BoxedSystem
682where
683    T: 'static + Send + Sync + DynSystem,
684{
685    fn from(system: T) -> Self {
686        Self::new(system)
687    }
688}
689
690#[cfg(test)]
691#[cfg(feature = "std")]
692mod test {
693
694    use crate::{component, CommandBuffer, Component, EntityBuilder, Query, QueryBorrow, World};
695
696    use super::*;
697
698    #[test]
699    fn system_builder() {
700        component! {
701            a: String,
702            b: i32,
703        };
704
705        let mut world = World::new();
706
707        let id = EntityBuilder::new()
708            .set(a(), "Foo".to_string())
709            .set(b(), 5)
710            .spawn(&mut world);
711
712        let mut system = System::builder()
713            .with(Query::new(a()))
714            // .with(Query::new(b()))
715            .build(|mut a: QueryBorrow<Component<String>>| assert_eq!(a.iter().count(), 1));
716
717        let mut fallible = System::builder()
718            // .with_name("Fallible")
719            .with(Query::new(b()))
720            .build(move |mut query: QueryBorrow<_>| -> anyhow::Result<()> {
721                // Lock archetypes
722                let item: &i32 = query.get(id)?;
723                eprintln!("Item: {item}");
724
725                Ok(())
726            });
727
728        let mut cmd = CommandBuffer::new();
729
730        #[allow(clippy::let_unit_value)]
731        let data = &mut ();
732        let ctx = SystemContext::new(&mut world, &mut cmd, data);
733
734        system.execute(&ctx).unwrap();
735
736        fallible.execute(&ctx).unwrap();
737
738        world.remove(id, b()).unwrap();
739
740        let mut boxed = fallible.boxed();
741
742        let ctx = SystemContext::new(&mut world, &mut cmd, data);
743        let res = boxed.execute(&ctx);
744        let _ = res.unwrap_err();
745    }
746
747    #[test]
748    fn system_builder_empty() {
749        let mut a = 5;
750        let mut system = System::builder().build(|| {
751            a += 1;
752        });
753
754        let mut world = World::new();
755        system.run(&mut world);
756
757        assert_eq!(a, 6);
758    }
759}