1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#![doc = include_str!("../README.md")]

use std::io;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};

use clap::Parser;
use valence::prelude::*;

#[derive(Parser)]
#[command(author, version, about)]
struct Cli {
    /// Name of the schedule to dump. If absent, the list of available
    /// schedules is printed to stdout.
    schedule: Option<String>,
    /// Output SVG file path.
    #[clap(short, long, default_value = "graph.svg")]
    output: PathBuf,
    /// Disables transitive reduction of the output schedule graph.
    #[clap(short = 't', long)]
    no_tred: bool,
}

fn main() -> io::Result<()> {
    let cli = Cli::parse();

    let mut app = App::new();

    app.add_plugins(DefaultPlugins);

    let schedules = app.world().resource::<Schedules>();

    let Some(sched_name) = cli.schedule else {
        print_available_schedules(schedules);
        return Ok(());
    };

    let Some((_, schedule)) = schedules
        .iter()
        .find(|(label, _)| format!("{label:?}") == sched_name)
    else {
        eprintln!("Unknown schedule \"{sched_name}\"");
        print_available_schedules(schedules);
        std::process::exit(1)
    };

    let dot_graph = bevy_mod_debugdump::schedule_graph::schedule_graph_dot(
        schedule,
        app.world(),
        &bevy_mod_debugdump::schedule_graph::Settings {
            ambiguity_enable: false,
            ..Default::default()
        },
    );

    let mut dot_command = Command::new("dot");
    dot_command.arg("-Tsvg").arg("-o").arg(cli.output);

    if cli.no_tred {
        let mut dot_child = dot_command.stdin(Stdio::piped()).spawn()?;

        dot_child
            .stdin
            .as_mut()
            .unwrap()
            .write_all(dot_graph.as_bytes())?;

        dot_child.wait_with_output()?;
    } else {
        let tred_child = Command::new("tred")
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .spawn()?;

        let dot_child = dot_command.stdin(tred_child.stdout.unwrap()).spawn()?;

        tred_child.stdin.unwrap().write_all(dot_graph.as_bytes())?;

        dot_child.wait_with_output()?;
    };

    Ok(())
}

fn print_available_schedules(schedules: &Schedules) {
    eprintln!("==== Available Schedules ====");

    for (label, _) in schedules.iter() {
        println!("{label:?}");
    }

    eprintln!("\nSee `--help` for more information.");
}