abstreet/game/src/debug/mod.rs

434 lines
15 KiB
Rust
Raw Normal View History

mod associated;
2019-04-26 20:46:41 +03:00
mod color_picker;
2019-04-26 02:35:09 +03:00
mod connected_roads;
mod floodfill;
2019-04-26 02:40:26 +03:00
mod objects;
2019-04-26 02:24:10 +03:00
mod polygons;
2019-06-25 00:48:47 +03:00
mod routes;
2019-04-26 02:02:40 +03:00
use crate::common::CommonState;
use crate::game::{msg, State, Transition, WizardState};
use crate::helpers::ID;
use crate::options;
use crate::render::MIN_ZOOM_FOR_DETAIL;
use crate::ui::{ShowLayers, ShowObject, UI};
2019-04-26 20:46:41 +03:00
use ezgui::{
hotkey, Color, Drawable, EventCtx, EventLoopMode, GeomBatch, GfxCtx, Key, Line,
MenuUnderButton, ModalMenu, Text, Wizard,
2019-04-26 20:46:41 +03:00
};
use geom::Duration;
use sim::Sim;
use std::collections::HashSet;
pub struct DebugMode {
2019-06-22 19:48:42 +03:00
menu: ModalMenu,
general_tools: MenuUnderButton,
save_tools: MenuUnderButton,
common: CommonState,
associated: associated::ShowAssociatedState,
2019-04-26 02:35:09 +03:00
connected_roads: connected_roads::ShowConnectedRoads,
2019-04-26 02:40:26 +03:00
objects: objects::ObjectDebugger,
2019-04-26 08:07:48 +03:00
hidden: HashSet<ID>,
layers: ShowLayers,
search_results: Option<SearchResults>,
2019-06-25 00:48:47 +03:00
all_routes: routes::AllRoutesViewer,
}
impl DebugMode {
2019-12-04 02:08:46 +03:00
pub fn new(ctx: &mut EventCtx) -> DebugMode {
DebugMode {
2019-06-22 19:48:42 +03:00
menu: ModalMenu::new(
"Debug Mode",
vec![
(hotkey(Key::Num1), "hide buildings"),
(hotkey(Key::Num2), "hide intersections"),
(hotkey(Key::Num3), "hide lanes"),
(hotkey(Key::Num4), "hide areas"),
(hotkey(Key::Num5), "hide extra shapes"),
(hotkey(Key::Num6), "show labels"),
(hotkey(Key::R), "show route for all agents"),
(None, "screenshot everything"),
(hotkey(Key::Slash), "search OSM metadata"),
(None, "configure colors"),
],
2019-06-22 19:48:42 +03:00
ctx,
),
general_tools: MenuUnderButton::new(
"assets/ui/hamburger.png",
"General",
vec![
(hotkey(Key::Escape), "return to previous mode"),
(None, "options"),
],
2019-10-30 01:45:26 +03:00
0.2,
ctx,
),
save_tools: MenuUnderButton::new(
"assets/ui/save.png",
"Savestates",
vec![
(hotkey(Key::O), "save sim state"),
(hotkey(Key::Y), "load previous sim state"),
(hotkey(Key::U), "load next sim state"),
(None, "pick a savestate to load"),
],
0.45,
ctx,
),
common: CommonState::new(ctx),
associated: associated::ShowAssociatedState::Inactive,
2019-04-26 02:35:09 +03:00
connected_roads: connected_roads::ShowConnectedRoads::new(),
2019-04-26 02:40:26 +03:00
objects: objects::ObjectDebugger::new(),
2019-04-26 08:07:48 +03:00
hidden: HashSet::new(),
layers: ShowLayers::new(),
search_results: None,
2019-06-25 00:48:47 +03:00
all_routes: routes::AllRoutesViewer::Inactive,
}
}
2019-06-22 19:48:42 +03:00
}
2019-06-22 19:48:42 +03:00
impl State for DebugMode {
2019-06-24 03:00:34 +03:00
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
2019-06-22 19:48:42 +03:00
if ctx.redo_mouseover() {
ui.primary.current_selection =
ui.calculate_current_selection(ctx, &ui.primary.sim, self, true);
2019-06-22 19:48:42 +03:00
}
2019-05-02 00:59:20 +03:00
{
let mut txt = Text::new();
if !self.hidden.is_empty() {
txt.add(Line(format!("Hiding {} things", self.hidden.len())));
}
if let Some(ref results) = self.search_results {
txt.add(Line(format!(
"Search for {} has {} results",
results.query,
results.ids.len()
)));
}
if let routes::AllRoutesViewer::Active(_, ref traces) = self.all_routes {
txt.add(Line(format!("Showing {} routes", traces.len())));
}
self.menu.set_info(ctx, txt);
2019-06-22 19:48:42 +03:00
}
self.menu.event(ctx);
self.general_tools.event(ctx);
self.save_tools.event(ctx);
2019-06-22 19:48:42 +03:00
ctx.canvas.handle_event(ctx.input);
if let Some(t) = self.common.event(ctx, ui) {
2019-06-24 03:00:34 +03:00
return t;
2019-06-22 19:48:42 +03:00
}
self.associated.event(ui);
2019-05-02 00:59:20 +03:00
if self.general_tools.action("return to previous mode") {
2019-06-24 03:00:34 +03:00
return Transition::Pop;
2019-06-22 19:48:42 +03:00
}
if self.general_tools.action("options") {
return Transition::Push(options::open_panel());
}
2019-05-02 00:59:20 +03:00
if self.save_tools.action("save sim state") {
ctx.loading_screen("savestate", |_, timer| {
timer.start("save sim state");
ui.primary.sim.save();
timer.stop("save sim state");
});
}
if self.save_tools.action("load previous sim state") {
if let Some(t) = ctx.loading_screen("load previous savestate", |ctx, mut timer| {
let prev_state = ui
.primary
.sim
.find_previous_savestate(ui.primary.sim.time());
match prev_state
.clone()
.and_then(|path| Sim::load_savestate(path, &mut timer).ok())
{
Some(new_sim) => {
ui.primary.sim = new_sim;
ui.recalculate_current_selection(ctx);
None
}
None => Some(Transition::Push(msg(
"Error",
vec![format!("Couldn't load previous savestate {:?}", prev_state)],
))),
}
}) {
return t;
}
}
if self.save_tools.action("load next sim state") {
if let Some(t) = ctx.loading_screen("load next savestate", |ctx, mut timer| {
let next_state = ui.primary.sim.find_next_savestate(ui.primary.sim.time());
match next_state
.clone()
.and_then(|path| Sim::load_savestate(path, &mut timer).ok())
{
Some(new_sim) => {
ui.primary.sim = new_sim;
ui.recalculate_current_selection(ctx);
None
}
None => Some(Transition::Push(msg(
"Error",
vec![format!("Couldn't load next savestate {:?}", next_state)],
))),
}
}) {
return t;
}
}
if self.save_tools.action("pick a savestate to load") {
return Transition::Push(WizardState::new(Box::new(load_savestate)));
}
self.all_routes.event(ui, &mut self.menu, ctx);
2019-06-22 19:48:42 +03:00
match ui.primary.current_selection {
Some(ID::Lane(_)) | Some(ID::Intersection(_)) | Some(ID::ExtraShape(_)) => {
let id = ui.primary.current_selection.clone().unwrap();
if ui.per_obj.action(ctx, Key::H, format!("hide {:?}", id)) {
2019-06-22 19:48:42 +03:00
println!("Hiding {:?}", id);
ui.primary.current_selection = None;
if self.hidden.is_empty() {
self.menu
.push_action(hotkey(Key::H), "unhide everything", ctx);
}
2019-06-22 19:48:42 +03:00
self.hidden.insert(id);
}
}
None => {
if !self.hidden.is_empty() && self.menu.consume_action("unhide everything", ctx) {
2019-06-22 19:48:42 +03:00
self.hidden.clear();
ui.primary.current_selection =
ui.calculate_current_selection(ctx, &ui.primary.sim, self, true);
2019-06-22 19:48:42 +03:00
}
}
_ => {}
}
2019-04-26 02:02:40 +03:00
2019-09-07 23:46:47 +03:00
if let Some(ID::Car(id)) = ui.primary.current_selection {
if ui
.per_obj
.action(ctx, Key::Backspace, "forcibly kill this car")
2019-09-07 23:46:47 +03:00
{
ui.primary.sim.kill_stuck_car(id, &ui.primary.map);
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
ui.primary.current_selection = None;
} else if ui.per_obj.action(ctx, Key::G, "find front of blockage") {
return Transition::Push(msg(
"Blockage results",
vec![format!(
"{} is ultimately blocked by {}",
id,
ui.primary.sim.find_blockage_front(id, &ui.primary.map)
)],
));
2019-09-07 23:46:47 +03:00
}
}
2019-06-22 19:48:42 +03:00
self.connected_roads.event(ctx, ui);
self.objects.event(ctx, ui);
if let Some(debugger) = polygons::PolygonDebugger::new(ctx, ui) {
2019-06-24 03:00:34 +03:00
return Transition::Push(Box::new(debugger));
2019-06-22 19:48:42 +03:00
}
2019-04-26 02:24:10 +03:00
2019-06-22 19:48:42 +03:00
{
let mut changed = false;
for (label, value) in vec![
("buildings", &mut self.layers.show_buildings),
("intersections", &mut self.layers.show_intersections),
("lanes", &mut self.layers.show_lanes),
("areas", &mut self.layers.show_areas),
("extra shapes", &mut self.layers.show_extra_shapes),
("labels", &mut self.layers.show_labels),
] {
let show = format!("show {}", label);
let hide = format!("hide {}", label);
2019-10-11 23:15:14 +03:00
if *value && self.menu.swap_action(&hide, &show, ctx) {
*value = false;
changed = true;
2019-10-11 23:15:14 +03:00
} else if !*value && self.menu.swap_action(&show, &hide, ctx) {
*value = true;
changed = true;
}
2019-06-22 19:48:42 +03:00
}
2019-04-26 02:24:10 +03:00
2019-06-22 19:48:42 +03:00
if changed {
ui.primary.current_selection =
ui.calculate_current_selection(ctx, &ui.primary.sim, self, true);
2019-06-22 19:48:42 +03:00
}
}
2019-04-26 02:16:21 +03:00
2019-06-22 19:48:42 +03:00
if self.menu.action("screenshot everything") {
let bounds = ui.primary.map.get_bounds();
assert!(bounds.min_x == 0.0 && bounds.min_y == 0.0);
2019-06-24 03:00:34 +03:00
return Transition::KeepWithMode(EventLoopMode::ScreenCaptureEverything {
2019-08-05 02:25:40 +03:00
dir: abstutil::path_pending_screenshots(ui.primary.map.get_name()),
2019-06-24 03:00:34 +03:00
zoom: 3.0,
max_x: bounds.max_x,
max_y: bounds.max_y,
});
2019-06-22 19:48:42 +03:00
}
2019-06-22 19:48:42 +03:00
if self.search_results.is_some() {
2019-10-11 23:15:14 +03:00
if self
.menu
.swap_action("clear OSM search results", "search OSM metadata", ctx)
{
2019-06-22 19:48:42 +03:00
self.search_results = None;
}
2019-10-11 23:15:14 +03:00
} else if self
.menu
.swap_action("search OSM metadata", "clear OSM search results", ctx)
{
// TODO If the wizard aborts (pressing escape), this crashes.
return Transition::Push(WizardState::new(Box::new(search_osm)));
2019-06-22 19:48:42 +03:00
} else if self.menu.action("configure colors") {
return Transition::Push(color_picker::ColorChooser::new());
2019-06-22 19:48:42 +03:00
}
if let Some(floodfiller) = floodfill::Floodfiller::new(ctx, ui) {
return Transition::Push(floodfiller);
}
2019-06-24 03:00:34 +03:00
Transition::Keep
2019-06-22 19:48:42 +03:00
}
2019-06-07 02:03:22 +03:00
2019-06-22 19:48:42 +03:00
fn draw_default_ui(&self) -> bool {
false
}
2019-06-22 19:48:42 +03:00
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
let mut opts = self.common.draw_options(ui);
2019-08-16 22:38:39 +03:00
opts.label_buildings = self.layers.show_labels;
opts.label_roads = self.layers.show_labels;
2019-06-22 19:48:42 +03:00
for l in &self.connected_roads.lanes {
opts.override_colors.insert(
ID::Lane(*l),
ui.cs.get("something associated with something else"),
);
}
if g.canvas.cam_zoom >= MIN_ZOOM_FOR_DETAIL {
if let Some(ref results) = self.search_results {
for id in &results.ids {
opts.override_colors
.insert(id.clone(), ui.cs.get("search result"));
}
2019-06-22 19:48:42 +03:00
}
}
self.associated
.override_colors(&mut opts.override_colors, ui);
2019-06-22 19:48:42 +03:00
ui.draw(g, opts, &ui.primary.sim, self);
if g.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL {
if let Some(ref results) = self.search_results {
g.redraw(&results.unzoomed);
}
}
2019-06-22 19:48:42 +03:00
self.objects.draw(g, ui);
2019-06-25 00:48:47 +03:00
self.all_routes.draw(g, ui);
2019-05-02 00:59:20 +03:00
2019-06-22 19:48:42 +03:00
if !g.is_screencap() {
self.menu.draw(g);
self.general_tools.draw(g);
self.save_tools.draw(g);
self.common.draw(g, ui);
}
}
}
impl ShowObject for DebugMode {
fn show(&self, obj: &ID) -> bool {
if self.hidden.contains(obj) {
return false;
}
match obj {
ID::Road(_) | ID::Lane(_) => self.layers.show_lanes,
ID::Building(_) => self.layers.show_buildings,
ID::Intersection(_) => self.layers.show_intersections,
ID::ExtraShape(_) => self.layers.show_extra_shapes,
ID::Area(_) => self.layers.show_areas,
_ => true,
}
}
fn layers(&self) -> &ShowLayers {
&self.layers
}
}
fn search_osm(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option<Transition> {
let filter = wiz.wrap(ctx).input_string("Search for what?")?;
let mut ids = HashSet::new();
let mut batch = GeomBatch::new();
2019-06-22 19:48:42 +03:00
let map = &ui.primary.map;
let color = ui.cs.get_def("search result", Color::RED);
for r in map.all_roads() {
if r.osm_tags
.iter()
.any(|(k, v)| format!("{} = {}", k, v).contains(&filter))
{
for l in r.all_lanes() {
ids.insert(ID::Lane(l));
2019-06-22 19:48:42 +03:00
}
batch.push(color, r.get_thick_polygon().unwrap());
2019-06-22 19:48:42 +03:00
}
}
for b in map.all_buildings() {
if b.osm_tags
.iter()
.any(|(k, v)| format!("{} = {}", k, v).contains(&filter))
{
ids.insert(ID::Building(b.id));
batch.push(color, b.polygon.clone());
}
2019-06-22 19:48:42 +03:00
}
for a in map.all_areas() {
if a.osm_tags
.iter()
.any(|(k, v)| format!("{} = {}", k, v).contains(&filter))
{
ids.insert(ID::Area(a.id));
batch.push(color, a.polygon.clone());
}
}
let results = SearchResults {
query: filter,
ids,
unzoomed: batch.upload(ctx),
};
Some(Transition::PopWithData(Box::new(|state, _, _| {
state.downcast_mut::<DebugMode>().unwrap().search_results = Some(results);
})))
2019-06-22 19:48:42 +03:00
}
struct SearchResults {
query: String,
ids: HashSet<ID>,
unzoomed: Drawable,
}
fn load_savestate(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option<Transition> {
let ss = wiz.wrap(ctx).choose_string("Load which savestate?", || {
abstutil::list_all_objects(ui.primary.sim.save_dir())
})?;
// TODO Oh no, we have to do path construction here :(
let ss_path = format!("{}/{}.bin", ui.primary.sim.save_dir(), ss);
ctx.loading_screen("load savestate", |ctx, mut timer| {
ui.primary.sim = Sim::load_savestate(ss_path, &mut timer).expect("Can't load savestate");
ui.recalculate_current_selection(ctx);
});
Some(Transition::Pop)
}