bevy_mod_debug_console/
ecs.rs

1use bevy::{
2    ecs::{
3        archetype::{ArchetypeId, Archetypes},
4        component::{ComponentId, Components, StorageType},
5        entity::{Entities, Entity},
6    },
7    utils::get_short_name,
8};
9use clap::{App, AppSettings, ArgGroup, ArgMatches, arg};
10
11pub fn list_resources(archetypes: &Archetypes, components: &Components) -> String {
12    let mut output = String::new();
13
14    let mut r: Vec<String> = archetypes
15        .resource()
16        .components()
17        .map(|id| components.get_info(id).unwrap())
18        // get_short_name removes the path information
19        // i.e. `bevy_audio::audio::Audio` -> `Audio`
20        // if you want to see the path info replace
21        // `get_short_name` with `String::from`
22        .map(|info| get_short_name(info.name()))
23        .collect();
24
25    // sort list alphebetically
26    r.sort();
27
28    output.push_str("[resource name]\n");
29    r.iter()
30        .for_each(|name| output.push_str(&format!("{}\n", name)));
31
32    output
33}
34
35fn get_components_by_name(
36    components: &Components,
37    short: bool,
38    filter: Option<&str>,
39) -> Vec<(usize, String)> {
40    let mut names = Vec::new();
41    for id in 1..components.len() {
42        if let Some(info) = components.get_info(ComponentId::new(id)) {
43            if short {
44                names.push((id, get_short_name(info.name())));
45            } else {
46                names.push((id, String::from(info.name())));
47            }
48        }
49    }
50
51    if let Some(filter) = filter {
52        names
53            .iter()
54            .cloned()
55            .filter(|(_, name)| name.contains(filter))
56            .collect()
57    } else {
58        names
59    }
60}
61
62fn list_components(c: &Components, short: bool, filter: Option<&str>) -> String {
63    let mut names = get_components_by_name(c, short, filter);
64    names.sort();
65
66    let mut output = String::new();
67    output.push_str("[component id] [component name]\n");
68    names
69        .iter()
70        .for_each(|(id, name)| output.push_str(&format!("{} {}\n", id, name)));
71
72    output
73}
74
75fn list_entities(e: &Entities) -> String {
76    let mut output = String::new();
77    output.push_str(&format!("[entity index] [archetype id]\n"));
78    for id in 0..e.len() {
79        if let Some(entity) = e.resolve_from_id(id) {
80            if let Some(location) = e.get(entity) {
81                output.push_str(&format!("{} {}\n", id, location.archetype_id.index()));
82            }
83        }
84    }
85
86    output
87}
88
89fn list_archetypes(a: &Archetypes) -> String {
90    let mut output = String::new();
91    output.push_str(&format!("[id] [entity count]\n"));
92    a.iter().for_each(|archetype| {
93        output.push_str(&format!(
94            "{} {}\n",
95            archetype.id().index(),
96            archetype.entities().iter().count()
97        ))
98    });
99
100    output
101}
102
103fn print_ecs_counts(a: &Archetypes, c: &Components, e: &Entities) -> String {
104    String::from(format!(
105        "entities: {}, components: {}, archetypes: {}\n",
106        e.len(),
107        c.len(),
108        a.len()
109    ))
110}
111
112fn find_archetypes_by_component_name(
113    a: &Archetypes,
114    c: &Components,
115    component_name: &str,
116) -> String {
117    let components = get_components_by_name(c, false, Some(component_name));
118
119    if components.len() == 0 {
120        return String::from(format!("No component found with name {}\n", component_name));
121    }
122
123    if components.len() > 1 {
124        let mut output = String::new();
125        output.push_str(&format!(
126            "More than one component found with name {}\n",
127            component_name
128        ));
129        output.push_str(&format!(
130            "Consider searching with '--componentid' instead\n\n"
131        ));
132        output.push_str(&format!("[component id] [component name]\n"));
133        components
134            .iter()
135            .for_each(|(id, name)| output.push_str(&format!("{} {}\n", id, name)));
136        return output;
137    }
138
139    if let Some(id_name) = components.iter().next() {
140        return find_archetypes_by_component_id(a, id_name.0);
141    };
142
143    // should never be hit as clap
144    String::from("unsupported command")
145}
146
147fn find_archetypes_by_component_id(a: &Archetypes, component_id: usize) -> String {
148    let mut output = String::new();
149
150    let archetypes = a
151        .iter()
152        .filter(|archetype| archetype.components().any(|c| c.index() == component_id))
153        .map(|archetype| archetype.id().index());
154
155    output.push_str(&format!("archetype ids:\n"));
156    archetypes.for_each(|id| output.push_str(&format!("{}, ", id)));
157    output.push_str("\n");
158
159    output
160}
161
162pub fn get_archetype_id_by_entity_id(a: &Archetypes, entity_id: u32) -> Option<usize> {
163    let mut archetypes = a
164        .iter()
165        .filter(|archetype| archetype.entities().iter().any(|e| e.id() == entity_id))
166        .map(|archetype| archetype.id().index());
167
168    archetypes.next()
169}
170
171fn find_archetype_by_entity_id(a: &Archetypes, entity_id: u32) -> String {
172    let mut output = String::new();
173
174    let archetype_id = get_archetype_id_by_entity_id(a, entity_id);
175
176    output.push_str(&format!("archetype id:\n"));
177    if let Some(id) = archetype_id {
178        output.push_str(&format!("{}", id))
179    }
180
181    output
182}
183
184fn find_entities_by_component_id(a: &Archetypes, component_id: usize) -> String {
185    let entities: Vec<&Entity> = a
186        .iter()
187        .filter(|archetype| archetype.components().any(|c| c.index() == component_id))
188        .map(|archetype| archetype.entities())
189        .flatten()
190        .collect();
191
192    if entities.iter().len() == 0 {
193        let mut output = String::new();
194        output.push_str("no entites found\n");
195        return output;
196    }
197
198    let mut output = String::new();
199    output.push_str(&format!("entity ids:\n"));
200    entities
201        .iter()
202        .for_each(|id| output.push_str(&format!("{}, ", id.id())));
203    output.push_str("\n");
204
205    output
206}
207
208fn find_entities_by_component_name(a: &Archetypes, c: &Components, component_name: &str) -> String {
209    let components = get_components_by_name(c, false, Some(component_name));
210
211    let mut output = String::new();
212    components.iter().for_each(|(id, name)| {
213        output.push_str(&format!("{}\n", name));
214        output.push_str(&find_entities_by_component_id(a, *id));
215        output.push_str("\n");
216    });
217
218    output
219}
220
221fn print_archetype(a: &Archetypes, c: &Components, archetype_id: ArchetypeId) -> String {
222    let mut output = String::new();
223    if let Some(archetype) = a.get(archetype_id) {
224        output.push_str(&format!("id: {:?}\n", archetype.id()));
225        output.push_str(&format!("table_id: {:?}\n", archetype.table_id()));
226        output.push_str(&format!(
227            "entities ({}): ",
228            archetype.entities().iter().count()
229        ));
230        archetype
231            .entities()
232            .iter()
233            .for_each(|entity| output.push_str(&format!("{}, ", entity.id())));
234        output.push_str(&format!("\n"));
235        // not sure what entity table rows is, so commenting out for now
236        // print!(
237        //     "entity table rows ({}): ",
238        //     archetype.entity_table_rows().iter().count()
239        // );
240        // archetype
241        //     .entity_table_rows()
242        //     .iter()
243        //     .for_each(|row| print!("{}, ", row));
244        // println!("");
245        output.push_str(&format!(
246            "table_components ({}): ",
247            archetype.table_components().iter().count()
248        ));
249        archetype
250            .table_components()
251            .iter()
252            .map(|id| (id.index(), c.get_info(*id).unwrap()))
253            .map(|(id, info)| (id, get_short_name(info.name())))
254            .for_each(|(id, name)| output.push_str(&format!("{} {}, ", id, name)));
255        output.push_str("\n");
256
257        output.push_str(&format!(
258            "sparse set components ({}): ",
259            archetype.sparse_set_components().iter().count()
260        ));
261        archetype
262            .sparse_set_components()
263            .iter()
264            .map(|id| (id.index(), c.get_info(*id).unwrap()))
265            .map(|(id, info)| (id, get_short_name(info.name())))
266            .for_each(|(id, name)| output.push_str(&format!("{} {}, ", id, name)));
267        output.push_str(&format!("\n"));
268    } else {
269        output.push_str(&format!(
270            "No archetype found with id: {}\n",
271            archetype_id.index()
272        ));
273    }
274
275    output
276}
277
278fn print_component(c: &Components, component_id: usize) -> String {
279    let mut output = String::new();
280    if let Some(info) = c.get_info(ComponentId::new(component_id)) {
281        output.push_str(&format!("Name: {}\n", info.name()));
282        output.push_str(&format!("Id: {}\n", info.id().index()));
283        output.push_str("StorageType: ");
284        match info.storage_type() {
285            StorageType::Table => output.push_str("Table\n"),
286            StorageType::SparseSet => output.push_str("SparseSet\n"),
287        }
288        output.push_str(&format!("SendAndSync: {}\n", info.is_send_and_sync()));
289    } else {
290        output.push_str(&format!("No component found with id: {}", component_id));
291    }
292
293    output
294}
295
296fn print_component_by_name(c: &Components, component_name: &str) -> String {
297    let components = get_components_by_name(c, false, Some(component_name));
298
299    let mut output = String::new();
300    components
301        .iter()
302        .for_each(|(id, _)| output.push_str(&format!("{}\n", &print_component(c, *id))));
303
304    output
305}
306
307pub fn build_commands<'a>(app: App<'a>) -> App<'a> {
308    let app = app.subcommand(
309            App::new("counts").about("print counts of archetypes, components, and entities"),
310        )
311        .subcommand(
312            App::new("archetypes")
313                .about("get archetypes info")
314                .alias("archetype")
315                .setting(AppSettings::SubcommandRequiredElseHelp)
316                .subcommand(App::new("list")
317                    .about("list all archetypes")
318                )
319                .subcommand(App::new("info")
320                    .about("get info of one archetype")
321                    .arg(arg!(--id <Id> "id to get"))
322                    .group(ArgGroup::new("search params")
323                        .args(&["id"])
324                        .required(true)
325                    )
326                )
327                .subcommand(App::new("find")
328                    .about("find a archetype")
329                    .args([
330                        arg!(--componentid <ComponentId> "find types that have components with ComponentId"),
331                        arg!(--componentname <ComponentName> "find types that have components with ComponentName"),
332                        arg!(--entityid <EntityId> "find types that have entities with EntityId")
333                    ])
334                    .group(ArgGroup::new("search params")
335                        .args(&["componentid", "componentname", "entityid"])
336                        .required(true)
337                    )
338                )
339        )
340        .subcommand(
341            App::new("components")
342                .about("get components info")
343                .alias("component")
344                .setting(AppSettings::SubcommandRequiredElseHelp)
345                .subcommand(App::new("list")
346                    .about("list all components")
347                    .args([
348                        arg!(-f --filter [Filter] "filter list"),
349                        arg!(-l --long "display long name")
350                    ])
351                )
352                .subcommand(App::new("info")
353                    .about("get info of one component")
354                    .args([
355                        arg!(--id <Id> "id to get"),
356                        arg!(--name <Name> "name to get")
357                    ])
358                    .group(ArgGroup::new("search params")
359                        .args(&["id", "name"])
360                        .required(true)
361                    )
362                )
363        )
364        .subcommand(
365            App::new("entities")
366                .about("get entity info")
367                .setting(AppSettings::SubcommandRequiredElseHelp)
368                .subcommand(
369                    App::new("list")
370                        .about("list all entities")
371                )
372                .subcommand(
373                    App::new("find")
374                        .about("find entity matching search params")
375                        .args([
376                            arg!(--componentid <ComponentId> "find types that have components with ComponentId"),
377                            arg!(--componentname <ComponentName> "find types that have components with ComponentName")
378                        ])
379                        .group(ArgGroup::new("search params")
380                            .args(&["componentid", "componentname"])
381                            .required(true)
382                        )
383                )
384        )
385        .subcommand(
386            App::new("resources")
387                .about("get resource info")
388                .setting(AppSettings::SubcommandRequiredElseHelp)
389                .subcommand(
390                    App::new("list")
391                        .about("list all resources")
392                )
393        );
394
395    app
396}
397
398pub fn match_commands(
399    matches: &ArgMatches,
400    a: &Archetypes,
401    c: &Components,
402    e: &Entities,
403) -> String {
404    match matches.subcommand() {
405        Some(("archetypes", matches)) => match matches.subcommand() {
406            Some(("list", _)) => list_archetypes(a),
407            Some(("find", matches)) => {
408                if let Ok(component_id) = matches.value_of_t("componentid") {
409                    find_archetypes_by_component_id(a, component_id)
410                } else if let Some(component_name) = matches.value_of("componentname") {
411                    find_archetypes_by_component_name(a, c, component_name)
412                } else if let Ok(entity_id) = matches.value_of_t("entityid") {
413                    find_archetype_by_entity_id(a, entity_id)
414                } else {
415                    // should never be hit as clap checks this
416                    String::from("this line should not be hittable")
417                }
418            }
419            Some(("info", matches)) => {
420                if let Ok(id) = matches.value_of_t("id") {
421                    print_archetype(a, c, ArchetypeId::new(id))
422                } else {
423                    String::from("this line should not be hittable")
424                }
425            }
426            _ => String::from("this line should not be hittable"),
427        },
428        Some(("components", matches)) => match matches.subcommand() {
429            Some(("list", matches)) => {
430                list_components(c, !matches.is_present("long"), matches.value_of("filter"))
431            }
432            Some(("info", matches)) => {
433                if let Ok(id) = matches.value_of_t("id") {
434                    print_component(c, id)
435                } else if let Some(name) = matches.value_of("name") {
436                    print_component_by_name(c, name)
437                } else {
438                    String::from("this line should not be hittable")
439                }
440            }
441            _ => String::from("this line should not be hittable"),
442        },
443        Some(("entities", matches)) => match matches.subcommand() {
444            Some(("list", _)) => list_entities(e),
445            Some(("find", matches)) => {
446                if let Ok(component_id) = matches.value_of_t("componentid") {
447                    find_entities_by_component_id(a, component_id)
448                } else if let Some(component_name) = matches.value_of("componentname") {
449                    find_entities_by_component_name(a, c, component_name)
450                } else {
451                    String::from("this line should not be hittable")
452                }
453            }
454            _ => String::from("this line should not be hittable"),
455        },
456        Some(("resources", matches)) => match matches.subcommand() {
457            Some(("list", _)) => list_resources(a, c),
458            _ => String::from("this line should not be hittable"),
459        },
460        Some(("counts", _)) => print_ecs_counts(a, c, e),
461        _ => String::from(""),
462    }
463}