#![expect(unexpected_cfgs)]
use std::borrow::Cow;
use bevy::{
ecs::{
archetype::ArchetypeComponentId,
component::{
ComponentId,
Tick,
},
query::Access,
schedule::InternedSystemSet,
system::SystemParamValidationError,
world::{
DeferredWorld,
unsafe_world_cell::UnsafeWorldCell,
},
},
prelude::*,
};
use variadics_please::all_tuples;
use crate::prelude::*;
pub struct Flow<F> {
systems: Vec<FlowSystem<F>>,
components: Access<ComponentId>,
archetypes: Access<ArchetypeComponentId>,
initialized: bool,
name: Cow<'static, str>,
}
impl<F> Default for Flow<F> {
fn default() -> Self {
Self {
systems: Vec::new(),
components: Access::new(),
archetypes: Access::new(),
initialized: false,
name: "Flow[]".into(),
}
}
}
impl<F> Flow<F>
where
F: 'static,
{
pub fn new() -> Self {
Self::default()
}
pub fn from_boxed_system(system: FlowSystem<F>) -> Self {
let mut flow = Self::new();
flow.push(system);
flow
}
fn update(&mut self) {
self.initialized = false;
self.name = format!(
"Flow[{}]",
self.systems
.iter()
.map(|s| s.name())
.collect::<Vec<_>>()
.join(",")
)
.into();
}
pub fn push(&mut self, system: FlowSystem<F>) {
self.systems.push(system);
self.update();
}
pub fn extend(&mut self, systems: impl IntoIterator<Item = FlowSystem<F>>) {
self.systems.extend(systems);
self.update();
}
pub fn merge(&mut self, flow: Self) {
self.extend(flow.systems);
}
pub fn is_readonly(&self) -> Option<bool> {
self.initialized
.then(|| !self.components.has_any_write() && !self.archetypes.has_any_write())
}
}
impl<F> System for Flow<F>
where
F: 'static,
{
type In = In<F>;
type Out = F;
fn name(&self) -> Cow<'static, str> {
self.name.clone()
}
fn component_access(&self) -> &Access<ComponentId> {
&self.components
}
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
&self.archetypes
}
fn is_send(&self) -> bool {
self.systems.iter().all(|s| s.is_send())
}
fn is_exclusive(&self) -> bool {
self.systems.iter().any(|s| s.is_exclusive())
}
fn has_deferred(&self) -> bool {
self.systems.iter().any(|s| s.has_deferred())
}
unsafe fn run_unsafe(
&mut self,
input: SystemIn<'_, Self>,
world: UnsafeWorldCell,
) -> Self::Out {
unsafe {
self.systems
.iter_mut()
.fold(input, |last, system| system.run_unsafe(last, world))
}
}
fn apply_deferred(&mut self, world: &mut World) {
self.systems
.iter_mut()
.for_each(|s| s.apply_deferred(world));
}
fn queue_deferred(&mut self, mut world: DeferredWorld) {
self.systems
.iter_mut()
.for_each(|s| s.queue_deferred(world.reborrow()));
}
unsafe fn validate_param_unsafe(
&mut self,
world: UnsafeWorldCell,
) -> Result<(), SystemParamValidationError> {
unsafe {
self.systems
.iter_mut()
.try_for_each(|s| s.validate_param_unsafe(world))
}
}
fn validate_param(&mut self, world: &World) -> Result<(), SystemParamValidationError> {
self.systems
.iter_mut()
.try_for_each(|s| s.validate_param(world))
}
fn initialize(&mut self, world: &mut World) {
self.systems.iter_mut().for_each(|s| {
s.initialize(world);
self.components.extend(s.component_access());
});
self.initialized = true;
}
fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
self.systems.iter_mut().for_each(|s| {
s.update_archetype_component_access(world);
self.archetypes.extend(s.archetype_component_access());
});
}
fn check_change_tick(&mut self, change_tick: Tick) {
self.systems
.iter_mut()
.for_each(|s| s.check_change_tick(change_tick));
}
fn default_system_sets(&self) -> Vec<InternedSystemSet> {
self.systems
.iter()
.flat_map(|s| s.default_system_sets())
.collect()
}
fn get_last_run(&self) -> Tick {
self.systems
.first()
.map(|s| s.get_last_run())
.unwrap_or_default()
}
fn set_last_run(&mut self, last_run: Tick) {
self.systems
.iter_mut()
.for_each(|s| s.set_last_run(last_run));
}
}
mod marker {
pub struct IsSystem;
pub struct IsTuple;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not describe a valid flow",
label = "invalid flow",
note = r#"
every system needs `In<{F}>` as the first parameter and `{F}` as the return type
read-only flows must have read-only systems (no Query<&mut T>, ResMut<T>)
"#
)]
pub trait IntoFlowSystems<F: 'static, Marker>: Sized {
fn into_flow_systems(self) -> Flow<F>;
}
impl<F, M, S> IntoFlowSystems<F, (marker::IsSystem, M)> for S
where
F: 'static,
S: IntoSystem<In<F>, F, M>,
{
fn into_flow_systems(self) -> Flow<F> {
Flow::<F>::from_boxed_system(Box::new(IntoSystem::into_system(self)))
}
}
macro_rules! impl_into_flow_systems {
($(#[$meta:meta])* $(($S:ident, $M:ident)),*) => {
$(#[$meta])*
#[allow(non_snake_case)]
#[allow(unused_parens)]
#[allow(unused_variables)]
#[allow(unused_mut)]
impl<F, $($M,)* $($S),*> IntoFlowSystems<F, (marker::IsTuple, $($M),*)> for ($($S,)*)
where
F: 'static,
$($S: IntoFlowSystems<F, $M>),*
{
fn into_flow_systems(self) -> Flow<F> {
let ($($S,)*) = self;
let mut flow = Flow::new();
$(flow.merge($S.into_flow_systems());)*
flow
}
}
};
}
all_tuples!(
#[doc(fake_variadic)]
impl_into_flow_systems,
0,
15,
S,
M
);
#[cfg(test)]
mod test {
use bevy::prelude::*;
use crate::flows::systems::IntoFlowSystems;
#[derive(Default)]
struct Builder {
entities: Vec<Entity>,
}
#[derive(Resource, Default)]
struct Extracted(bool);
fn extract_transforms(In(mut b): In<Builder>, q: Query<Entity, With<Transform>>) -> Builder {
b.entities.extend(q.iter());
b
}
fn extract_vis(In(mut b): In<Builder>, q: Query<Entity, With<Visibility>>) -> Builder {
b.entities.extend(q.iter());
b
}
fn do_commands(In(b): In<Builder>, mut c: Commands) -> Builder {
c.spawn_empty();
b
}
fn update_resource(In(b): In<Builder>, mut res: ResMut<Extracted>) -> Builder {
res.0 = true;
b
}
#[test]
fn test_flow_into_systems() {
let mut flow_read = (extract_transforms, extract_vis).into_flow_systems();
let mut flow_cmds = (extract_transforms, extract_vis, do_commands).into_flow_systems();
let mut flow_write = (extract_transforms, extract_vis, update_resource).into_flow_systems();
let mut app = App::new();
app.init_resource::<Extracted>();
let world = app.world_mut();
world.spawn(Transform::default());
world.spawn(Transform::default());
world.spawn(Transform::default());
world.spawn((Transform::default(), Visibility::default()));
flow_read.initialize(world);
flow_cmds.initialize(world);
flow_write.initialize(world);
assert_eq!(flow_read.is_readonly(), Some(true));
assert_eq!(flow_cmds.is_readonly(), Some(true));
assert_eq!(flow_write.is_readonly(), Some(false));
let out = flow_read.run(Builder::default(), world);
assert_eq!(out.entities.len(), 5);
}
}