├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── examples ├── egui_console.rs └── example.rs └── src ├── app.rs ├── ecs.rs ├── lib.rs ├── reflect.rs └── std_io_plugin.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Version 0.0.3 - 2022/08/16 4 | * Updated for bevy 0.8 5 | 6 | ## Version 0.0.2 - 2021/08/24 7 | ### Added 8 | * Added `examples\egui_console.rs` for integration with egui console 9 | 10 | ### Changed 11 | * `ConsoleDebugPlugin` no longer pauses the main loop to work. Commands can be entered while game is running. The `pause` command now is used to pause the main loop. 12 | 13 | ## Version 0.0.1 14 | * First release -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_mod_debug_console" 3 | categories = ["game-engines"] 4 | description = "Bevy plugin to use console to get information from ECS" 5 | keywords = ["game", "tool", "bevy", "debug"] 6 | license = "MIT OR MIT-0 OR Apache-2.0" 7 | authors = ["Mike Hsu "] 8 | edition = "2021" 9 | readme = "README.md" 10 | version = "0.1.0" 11 | homepage = "https://github.com/hymm/bevy_mod_debug_console" 12 | repository = "https://github.com/hymm/bevy_mod_debug_console" 13 | documentation = "https://docs.rs/bevy_mod_debug_console" 14 | 15 | [dependencies] 16 | bevy = {version = "0.8", default-features = false} 17 | clap = "3.2" 18 | crossbeam = "0.8" 19 | 20 | [dev-dependencies] 21 | bevy = "0.8" 22 | bevy_console = "0.4" 23 | 24 | [[example]] 25 | name="example" 26 | path="examples/example.rs" 27 | 28 | [[example]] 29 | name="egui_console" 30 | path="examples/egui_console.rs" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bevy_mod_debug_console 2 | 3 | The standard plugin takes over the stdin/stdout from bevy to get runtime information 4 | from bevy. In `examples/egui_console.rs` you can also see how to integrate with bevy_console. 5 | 6 | ``` 7 | Running `target\release\bevy_test_game.exe` 8 | Bevy Console Debugger. Type 'help' for list of commands. 9 | >>> archetypes find --componentname Player 10 | 11 | archetype ids: 12 | 8, 9, 10, 13 | 14 | >>> archetype info --id 10 15 | 16 | id: ArchetypeId(8) 17 | table_id: TableId(7) 18 | entities (1): 262, 19 | table_components (17): 114 Transform, 115 GlobalTransform, 116 Draw, 120 Animations, 121 Animator, 122 Handle, 123 TextureAtlasSprite, 126 PixelPosition, 128 Layer, 129 SpriteSize, 130 Hurtbox, 131 Player, 136 Curr 21 | entPosition, 145 Visible, 147 RenderPipelines, 153 MainPass, 155 Handle, 22 | sparse set components (0): 23 | ``` 24 | 25 | ## Usage 26 | 27 | Add to you `Cargo.toml` file: 28 | 29 | ```toml 30 | [dependencies] 31 | bevy = "0.5" 32 | bevy_mod_debug_console = "0.1.0" 33 | ``` 34 | 35 | Add Plugin: 36 | 37 | ```rs 38 | use bevy::prelude::*; 39 | use bevy_mod_debug_console::ConsoleDebugPlugin; 40 | 41 | fn main() { 42 | App::build() 43 | .add_plugins(DefaultPlugins) 44 | .add_plugin(ConsoleDebugPlugin) 45 | .run(); 46 | } 47 | ``` 48 | 49 | Once your bevy application is running type commands into the the console. Type `help` to get a list of commands. 50 | 51 | ## Selection of Available Commands 52 | 53 | * `archetype info --id 10` lists id, table_id, entities, table_components, and sparse set components belonging to archetype id `10` 54 | * `components list --long --filter bevy_test_game` lists components from the `bevy_test_game` namespace. 55 | * `counts` print counts of archetypes, components, and entities. 56 | * `pause` pause the game loop to freeze changes in the ecs for inspection. **Warning** This can have adverse affects with physics as the tick is paused and the time delta on resume can then be very large. 57 | 58 | ## Future Work 59 | 60 | * Add RenderGraph information 61 | * Add System and Schedule information 62 | * Add Reflection information 63 | 64 | -------------------------------------------------------------------------------- /examples/egui_console.rs: -------------------------------------------------------------------------------- 1 | // pausing game loop does not work with egui_console 2 | // press the GRAVE key to open the console 3 | 4 | use bevy::prelude::*; 5 | use bevy::{ 6 | ecs::{archetype::Archetypes, component::Components, entity::Entities}, 7 | reflect::TypeRegistry, 8 | }; 9 | use bevy_console::{ 10 | ConsoleCommandEntered, ConsoleConfiguration, ConsolePlugin, FromValue, PrintConsoleLine, 11 | }; 12 | use bevy_mod_debug_console::{build_commands, match_commands, Pause}; 13 | 14 | #[derive(Component)] 15 | struct Thing(String); 16 | 17 | fn debug_console( 18 | mut console_events: EventReader, 19 | mut console_line: EventWriter, 20 | a: &Archetypes, 21 | c: &Components, 22 | e: &Entities, 23 | mut pause: ResMut, 24 | reflect: Res, 25 | ) { 26 | let app_name = ""; 27 | for event in console_events.iter() { 28 | let console_app = build_commands(app_name); 29 | let mut args = vec![app_name]; 30 | args.push(&event.command); 31 | 32 | let split: Vec = event 33 | .args 34 | .iter() 35 | .filter_map(|x| String::from_value(x, 0).ok()) 36 | .collect(); 37 | args.append(&mut split.iter().map(|s| s.as_ref()).collect()); 38 | 39 | let matches_result = console_app.try_get_matches_from(args); 40 | 41 | if let Err(e) = matches_result { 42 | console_line.send(PrintConsoleLine::new(e.to_string())); 43 | return; 44 | } 45 | 46 | let output = match_commands(&matches_result.unwrap(), a, c, e, &mut pause, &*reflect); 47 | 48 | console_line.send(PrintConsoleLine::new(output)); 49 | } 50 | } 51 | 52 | fn setup(mut commands: Commands) { 53 | // Adds some Entities to test out `entities list` command 54 | commands.spawn().insert(Thing("Entity 1".to_string())); 55 | commands.spawn().insert(Thing("Entity 2".to_string())); 56 | } 57 | 58 | fn main() { 59 | App::new() 60 | .add_plugins(DefaultPlugins) 61 | .insert_resource(ConsoleConfiguration { 62 | // override config here 63 | ..Default::default() 64 | }) 65 | .add_plugin(ConsolePlugin) 66 | .insert_resource(Pause(false)) 67 | .add_startup_system(setup) 68 | .add_system(debug_console) 69 | .run(); 70 | } 71 | -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_mod_debug_console::ConsoleDebugPlugin; 3 | 4 | fn main() { 5 | App::new() 6 | .add_plugins(DefaultPlugins) 7 | .add_plugin(ConsoleDebugPlugin) 8 | .run(); 9 | } -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use crate::ecs; 2 | use crate::reflect; 3 | use bevy::{ 4 | ecs::{archetype::Archetypes, component::Components, entity::Entities, schedule::ShouldRun}, 5 | prelude::{Input, KeyCode, Local, Res, ResMut}, 6 | reflect::TypeRegistry, 7 | }; 8 | use clap::{App, ArgMatches}; 9 | use std::process::exit; 10 | 11 | pub fn build_commands<'a>(app_name: &'a str) -> App { 12 | let app = App::new(app_name); 13 | 14 | let app = build_app_commands(app); 15 | let app = ecs::build_commands(app); 16 | let app = reflect::build_commands(app); 17 | 18 | app 19 | } 20 | 21 | pub fn match_commands( 22 | matches: &ArgMatches, 23 | a: &Archetypes, 24 | c: &Components, 25 | e: &Entities, 26 | pause: &mut Pause, 27 | reflect: &TypeRegistry, 28 | ) -> String { 29 | let mut output = String::new(); 30 | 31 | output.push_str(&match_app_commands(matches, pause)); 32 | output.push_str(&ecs::match_commands(matches, a, c, e)); 33 | output.push_str(&reflect::match_commands(matches, reflect)); 34 | 35 | output 36 | } 37 | 38 | fn build_app_commands(app: App) -> App { 39 | let app = app 40 | .subcommand(App::new("resume").about("resume running game")) 41 | .subcommand(App::new("pause").about("pause game tick")) 42 | .subcommand(App::new("quit").about("quit game")); 43 | 44 | app 45 | } 46 | 47 | fn match_app_commands(matches: &ArgMatches, mut pause: &mut Pause) -> String { 48 | let mut output = String::new(); 49 | match matches.subcommand() { 50 | Some(("resume", _)) => { 51 | pause.0 = false; 52 | output.push_str("...resuming game."); 53 | } 54 | Some(("pause", _)) => { 55 | pause.0 = true; 56 | output.push_str("pausing game..."); 57 | } 58 | Some(("quit", _)) => exit(0), 59 | _ => {} 60 | } 61 | 62 | output 63 | } 64 | 65 | #[derive(Default)] 66 | pub struct Pause(pub bool); 67 | pub struct EnteringConsole(pub bool); 68 | pub fn pause( 69 | pause: Res, 70 | mut last_pause: Local, 71 | mut entering_console: ResMut, 72 | ) -> ShouldRun { 73 | entering_console.0 = (pause.0 != last_pause.0) && pause.0; 74 | last_pause.0 = pause.0; 75 | if pause.0 { 76 | ShouldRun::YesAndCheckAgain 77 | } else { 78 | ShouldRun::Yes 79 | } 80 | } 81 | 82 | pub fn input_pause(keyboard_input: Res>, mut pause: ResMut) { 83 | if keyboard_input.pressed(KeyCode::F10) { 84 | pause.0 = true; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/ecs.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | ecs::{ 3 | archetype::{ArchetypeId, Archetypes}, 4 | component::{ComponentId, Components, StorageType}, 5 | entity::{Entities, Entity}, 6 | }, 7 | utils::get_short_name, 8 | }; 9 | use clap::{App, AppSettings, ArgGroup, ArgMatches, arg}; 10 | 11 | pub fn list_resources(archetypes: &Archetypes, components: &Components) -> String { 12 | let mut output = String::new(); 13 | 14 | let mut r: Vec = 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 | 35 | fn 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 | 62 | fn 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 | 75 | fn 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 | 89 | fn 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 | 103 | fn 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 | 112 | fn 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 | 147 | fn 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 | 162 | pub fn get_archetype_id_by_entity_id(a: &Archetypes, entity_id: u32) -> Option { 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 | 171 | fn 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 | 184 | fn 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 | 208 | fn 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 | 221 | fn 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 | 278 | fn 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 | 296 | fn 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 | 307 | pub 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 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 "find types that have components with ComponentId"), 331 | arg!(--componentname "find types that have components with ComponentName"), 332 | arg!(--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 to get"), 356 | arg!(--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 "find types that have components with ComponentId"), 377 | arg!(--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 | 398 | pub 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 | } 464 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod ecs; 3 | mod reflect; 4 | mod std_io_plugin; 5 | 6 | pub use crate::app::{build_commands, match_commands, Pause}; 7 | pub use crate::std_io_plugin::ConsoleDebugPlugin; 8 | pub use crate::ecs::{get_archetype_id_by_entity_id}; 9 | -------------------------------------------------------------------------------- /src/reflect.rs: -------------------------------------------------------------------------------- 1 | use bevy::reflect::TypeRegistry; 2 | use clap::{App, AppSettings, ArgMatches}; 3 | 4 | pub fn build_commands<'a>(app: App<'a>) -> App<'a> { 5 | let app = app.subcommand( 6 | App::new("reflect") 7 | .about("get reflection info") 8 | .setting(AppSettings::SubcommandRequiredElseHelp) 9 | .subcommand(App::new("list").about("list all reflection types")), 10 | ); 11 | 12 | app 13 | } 14 | 15 | pub fn match_commands(matches: &ArgMatches, reflect: &TypeRegistry) -> String { 16 | match matches.subcommand() { 17 | Some(("reflect", matches)) => match matches.subcommand() { 18 | Some(("list", _)) => list_reflection(reflect), 19 | _ => String::from("this line should not be able to be run"), 20 | }, 21 | _ => String::from(""), 22 | } 23 | } 24 | 25 | fn list_reflection(reflect: &TypeRegistry) -> String { 26 | let mut output = String::new(); 27 | 28 | let type_registry = reflect.read(); 29 | 30 | type_registry.iter().for_each(|type_registration| { 31 | output.push_str(&format!("{}\n", type_registration.short_name())) 32 | }); 33 | 34 | output 35 | } 36 | -------------------------------------------------------------------------------- /src/std_io_plugin.rs: -------------------------------------------------------------------------------- 1 | use crate::app::{build_commands, input_pause, match_commands, pause, EnteringConsole, Pause}; 2 | use bevy::{ 3 | ecs::{archetype::Archetypes, component::Components, entity::Entities}, 4 | prelude::*, 5 | reflect::TypeRegistry, 6 | tasks::AsyncComputeTaskPool, 7 | }; 8 | use crossbeam::channel::{bounded, Receiver}; 9 | use std::io::{self, BufRead, Write}; 10 | 11 | fn parse_input( 12 | a: &Archetypes, 13 | c: &Components, 14 | e: &Entities, 15 | reflect: Res, 16 | mut pause: ResMut, 17 | line_channel: Res>, 18 | ) { 19 | if let Ok(line) = line_channel.try_recv() { 20 | let app_name = ""; 21 | println!(""); 22 | let split = line.split_whitespace(); 23 | let mut args = vec![app_name]; 24 | args.append(&mut split.collect()); 25 | 26 | let matches_result = build_commands(app_name).try_get_matches_from(args); 27 | 28 | if let Err(e) = matches_result { 29 | println!("{}", e.to_string()); 30 | print!(">>> "); 31 | io::stdout().flush().unwrap(); 32 | return; 33 | } 34 | 35 | let matches = matches_result.unwrap(); 36 | 37 | let output = match_commands(&matches, a, c, e, &mut pause, &*reflect); 38 | 39 | println!("{}", output); 40 | print!(">>> "); 41 | io::stdout().flush().unwrap(); 42 | } 43 | } 44 | 45 | fn spawn_io_thread(mut commands: Commands) { 46 | let thread_pool = AsyncComputeTaskPool::get(); 47 | println!("Bevy Console Debugger. Type 'help' for list of commands."); 48 | print!(">>> "); 49 | io::stdout().flush().unwrap(); 50 | 51 | let (tx, rx) = bounded(1); 52 | let task = thread_pool.spawn(async move { 53 | let stdin = io::stdin(); 54 | loop { 55 | let line = stdin.lock().lines().next().unwrap().unwrap(); 56 | tx.send(line) 57 | .expect("error sending user input to other thread"); 58 | } 59 | }); 60 | task.detach(); 61 | commands.insert_resource(rx); 62 | } 63 | 64 | pub struct ConsoleDebugPlugin; 65 | impl Plugin for ConsoleDebugPlugin { 66 | fn build(&self, app: &mut App) { 67 | app.insert_resource(Pause(false)) 68 | .insert_resource(EnteringConsole(false)) 69 | .add_startup_system(spawn_io_thread) 70 | .add_system(parse_input.with_run_criteria(pause)) 71 | .add_system(input_pause); 72 | } 73 | } 74 | --------------------------------------------------------------------------------