bevy_sorting 0.4.0

Sorting bevy systems based on reads and writes
Documentation
use std::{any, collections::HashMap, vec};

use assert_unordered::assert_eq_unordered_sort;
use bevy::{
    ecs::schedule::{NodeId, ScheduleGraph},
    prelude::*,
};

use super::{InferFlow, InferFlowEach};

#[test]
fn simple_event_sorting() {
    let mut app = App::new();
    app.add_systems(
        Update,
        (
            some_reader_only.in_auto_sets(),
            some_writer_only.in_auto_sets(),
        ),
    );

    let graph = app.get_schedule(Update).unwrap().graph();
    let (read_event_set, write_event_set) = find_set_pair(graph, "SomeEvent");
    let reader_only_system = find_system(graph, &some_reader_only);
    let writer_only_system = find_system(graph, &some_writer_only);

    assert_eq_unordered_sort!(
        vec![reader_only_system],
        systems_for_set(graph, read_event_set)
    );
    assert_eq_unordered_sort!(
        vec![writer_only_system],
        systems_for_set(graph, write_event_set)
    );
}

#[test]
fn double_event_sorting() {
    let mut app = App::new();
    app.add_systems(
        Update,
        (
            some_reader_only.in_auto_sets(),
            two_writers.in_auto_sets(),
            two_readers.in_auto_sets(),
            some_writer_only.in_auto_sets(),
        ),
    );

    let graph = app.get_schedule(Update).unwrap().graph();
    let (read_some_set, write_some_set) = find_set_pair(graph, "SomeEvent");
    let (read_other_set, write_other_set) = find_set_pair(graph, "OtherEvent");
    let reader_only_system = find_system(graph, &some_reader_only);
    let writer_only_system = find_system(graph, &some_writer_only);
    let two_readers_system = find_system(graph, &two_readers);
    let two_writers_system = find_system(graph, &two_writers);

    assert_eq_unordered_sort!(
        vec![reader_only_system, two_readers_system],
        systems_for_set(graph, read_some_set)
    );
    assert_eq_unordered_sort!(
        vec![writer_only_system, two_writers_system],
        systems_for_set(graph, write_some_set)
    );
    assert_eq_unordered_sort!(
        vec![two_readers_system],
        systems_for_set(graph, read_other_set)
    );
    assert_eq_unordered_sort!(
        vec![two_writers_system],
        systems_for_set(graph, write_other_set)
    );
}

#[test]
fn miexed_event_sorting() {
    let mut app = App::new();
    app.add_systems(
        Update,
        (
            some_reader_only.in_auto_sets(),
            other_writer_only.in_auto_sets(),
            mixed_events.in_auto_sets(),
        ),
    );

    let graph = app.get_schedule(Update).unwrap().graph();
    let (read_some_set, write_some_set) = find_set_pair(graph, "SomeEvent");
    let (read_other_set, write_other_set) = find_set_pair(graph, "OtherEvent");
    let reader_only_system = find_system(graph, &some_reader_only);
    let other_writer_system = find_system(graph, &other_writer_only);
    let mixed_system = find_system(graph, &mixed_events);

    assert_eq_unordered_sort!(
        vec![reader_only_system],
        systems_for_set(graph, read_some_set)
    );
    assert_eq_unordered_sort!(vec![mixed_system], systems_for_set(graph, write_some_set));
    assert_eq_unordered_sort!(vec![mixed_system], systems_for_set(graph, read_other_set));
    assert_eq_unordered_sort!(
        vec![other_writer_system],
        systems_for_set(graph, write_other_set)
    );
}

#[test]
fn commands_do_not_create_autoset() {
    let mut app = App::new();
    app.add_systems(
        Update,
        (
            commands_only.in_auto_sets(),
            commands_and_reader.in_auto_sets(),
        ),
    );

    let graph = app.get_schedule(Update).unwrap().graph();
    assert_eq!(graph.system_sets().count(), 3);
}

#[test]
fn simple_resources_sorting() {
    let mut app = App::new();
    app.add_systems(
        Update,
        (
            resource_only.in_auto_sets(),
            resource_mut_only.in_auto_sets(),
        ),
    );

    let graph = app.get_schedule(Update).unwrap().graph();
    let (read_something_set, write_something_set) = find_set_pair(graph, "Something");
    let res_system = find_system(graph, &resource_only);
    let res_mut_system = find_system(graph, &resource_mut_only);

    assert_eq_unordered_sort!(vec![res_system], systems_for_set(graph, read_something_set));

    assert_eq_unordered_sort!(
        vec![res_mut_system],
        systems_for_set(graph, write_something_set)
    )
}

#[test]
fn mixed_resource_sorting() {
    let mut app = App::new();
    app.add_systems(
        Update,
        (
            resource_mixed.in_auto_sets(),
            resource_mut_only.in_auto_sets(),
            other_res_only.in_auto_sets(),
        ),
    );

    let graph = app.get_schedule(Update).unwrap().graph();
    let (read_something_set, write_something_set) = find_set_pair(graph, "Something");
    let (read_other_set, write_other_set) = find_set_pair(graph, "SomethingElse");
    let something_mut_system = find_system(graph, &resource_mut_only);
    let mixed_system = find_system(graph, &resource_mixed);
    let other_system = find_system(graph, &other_res_only);

    assert_eq_unordered_sort!(
        vec![mixed_system],
        systems_for_set(graph, read_something_set)
    );

    assert_eq_unordered_sort!(
        vec![something_mut_system],
        systems_for_set(graph, write_something_set)
    );

    assert_eq_unordered_sort!(vec![other_system], systems_for_set(graph, read_other_set));

    assert_eq_unordered_sort!(vec![mixed_system], systems_for_set(graph, write_other_set));
}

#[test]
fn queries_create_autosets() {
    let mut app = App::new();
    app.add_systems(Update, with_query.in_auto_sets());

    let graph = app.get_schedule(Update).unwrap().graph();
    let read_some_data_set = find_set(graph, "Reads(\"SomeData\")");
    let write_other_data_set = find_set(graph, "Writes(\"OtherData\")");
    let system = find_system(graph, &with_query);

    assert_eq!(3, graph.system_sets().count());

    assert_eq_unordered_sort!(vec![system], systems_for_set(graph, read_some_data_set));

    assert_eq_unordered_sort!(vec![system], systems_for_set(graph, write_other_data_set))
}

#[test]
fn query_filters_influence_autosets() {
    let mut app = App::new();
    app.add_systems(Update, with_query_filter.in_auto_sets());

    let graph = app.get_schedule(Update).unwrap().graph();
    let read_some_data_set = find_set(graph, "Reads(\"SomeData\")");
    let read_other_data_set = find_set(graph, "Reads(\"OtherData\")");
    let read_third_data_set = find_set(graph, "Reads(\"DataNumberThree\")");
    let read_fourth_data_set = find_set(graph, "Reads(\"DataNumberFour\")");
    let system = find_system(graph, &with_query_filter);

    for set in [
        read_some_data_set,
        read_other_data_set,
        read_third_data_set,
        read_fourth_data_set,
    ] {
        assert_eq_unordered_sort!(vec![system], systems_for_set(graph, set));
    }
}

#[test]
fn big_system_test() {
    let mut app = App::new();
    app.add_systems(Update, big_system.in_auto_sets());

    let graph = app.get_schedule(Update).unwrap().graph();
    let read_some_data_set = find_set(graph, "Reads(\"SomeData\")");
    let write_other_data_set = find_set(graph, "Writes(\"OtherData\")");
    let write_third_data_set = find_set(graph, "Writes(\"DataNumberThree\")");
    let read_fourth_data_set = find_set(graph, "Reads(\"DataNumberFour\")");
    let write_res_set = find_set(graph, "Writes(\"Something\")");
    let system = find_system(graph, &big_system);

    for set in [
        read_some_data_set,
        write_other_data_set,
        write_third_data_set,
        read_fourth_data_set,
        write_res_set,
    ] {
        assert_eq_unordered_sort!(vec![system], systems_for_set(graph, set));
    }
}

#[test]
fn sanity_each() {
    (resource_only,).each_in_auto_sets();
    (resource_only, resource_mut_only).each_in_auto_sets();
}

fn find_set(graph: &ScheduleGraph, name: &str) -> NodeId {
    graph
        .system_sets()
        .find(|s| format!("{:?}", s.1) == name)
        .map(|s| s.0)
        .unwrap()
}

fn find_set_pair(graph: &ScheduleGraph, type_name: &str) -> (NodeId, NodeId) {
    (
        find_set(graph, &format!("Reads(\"{type_name}\")")),
        find_set(graph, &format!("Writes(\"{type_name}\")")),
    )
}

fn find_system<I: SystemInput, O, M, T: IntoSystem<I, O, M>>(
    graph: &ScheduleGraph,
    _: &T,
) -> NodeId {
    let name = any::type_name::<T>();
    graph
        .systems()
        .find(|s| s.1.name() == name)
        .map(|s| s.0)
        .unwrap()
}

fn systems_for_set(graph: &ScheduleGraph, set: NodeId) -> Vec<NodeId> {
    graph.hierarchy().graph().neighbors(set).collect()
}

#[allow(dead_code)]
fn debug_graphs(schedule: &Schedule) {
    panic!(
        "\n\n{:?}\n\n{:?}\n\n{:?}\n\n{:?}\n\n",
        schedule
            .graph()
            .system_sets()
            .map(|s| (s.0, s.1))
            .filter(|s| s.1.system_type().is_none())
            .collect::<HashMap<_, _>>(),
        schedule
            .graph()
            .systems()
            .map(|s| (s.0, s.1))
            .collect::<HashMap<_, _>>(),
        schedule.graph().hierarchy().graph(),
        schedule.graph().dependency().graph()
    );
}

#[derive(Resource)]
struct Something;

#[derive(Resource)]
struct SomethingElse;

#[derive(Event)]
struct SomeEvent;

#[derive(Event)]
struct OtherEvent;

#[derive(Component)]
struct SomeData;

#[derive(Component)]
struct OtherData;

#[derive(Component)]
struct DataNumberThree;

#[derive(Component)]
struct DataNumberFour;

fn some_reader_only(_reader: EventReader<SomeEvent>) {}

fn some_writer_only(_writer: EventWriter<SomeEvent>) {}

fn two_readers(_reader: EventReader<SomeEvent>, _reader2: EventReader<OtherEvent>) {}

fn two_writers(_writer: EventWriter<OtherEvent>, _writer2: EventWriter<SomeEvent>) {}

fn other_writer_only(_writer: EventWriter<OtherEvent>) {}

fn mixed_events(_writer: EventWriter<SomeEvent>, _reader: EventReader<OtherEvent>) {}

fn commands_only(_commands: Commands) {}

fn commands_and_reader(_commands: Commands, _reader: EventReader<SomeEvent>) {}

fn resource_only(_resource: Res<Something>) {}

fn resource_mut_only(_resource: ResMut<Something>) {}

fn other_res_only(_resource: Res<SomethingElse>) {}

fn resource_mixed(_read: Res<Something>, _write: ResMut<SomethingElse>) {}

fn with_query(_q: Query<(&SomeData, &mut OtherData, Entity)>) {}

fn with_query_filter(
    _q: Query<
        &SomeData,
        (
            With<OtherData>,
            Or<(Added<DataNumberThree>, Changed<DataNumberFour>)>,
        ),
    >,
) {
}

fn big_system(
    _ps: ParamSet<(
        ParallelCommands,
        Query<(&mut OtherData, &mut DataNumberThree), With<SomeData>>,
        ResMut<Something>,
        Option<Single<(&mut DataNumberThree, &DataNumberFour), Changed<SomeData>>>,
    )>,
) {
}