From 61d1d2fe83447c132c0561a6b0d9851a2e611b74 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Fri, 1 Feb 2019 22:41:32 -0800 Subject: [PATCH] revamp handling of onscreen stuff. no more callback plumbing, and can plumb prerender successfully into dynamic stuff. just can't do as much work directly in DrawMap. --- docs/TODO_ux.md | 6 +- editor/src/render/map.rs | 136 +++----------------- editor/src/render/mod.rs | 14 ++- editor/src/render/pedestrian.rs | 12 +- editor/src/state.rs | 1 + editor/src/ui.rs | 217 ++++++++++++++++++++++---------- ezgui/src/drawing.rs | 16 +-- 7 files changed, 202 insertions(+), 200 deletions(-) diff --git a/docs/TODO_ux.md b/docs/TODO_ux.md index f5c5d929f4..9702f236e5 100644 --- a/docs/TODO_ux.md +++ b/docs/TODO_ux.md @@ -2,9 +2,9 @@ ## Performance -- cache draw stuff - - use a Drawable in DrawPed/DrawCar/DrawBike. - - fix the process_objects callback nonsense +- cache draw agents + - dont use traits for things... or move code back to DrawMap after all. + - actually use Drawables in the places ## Quick n easy diff --git a/editor/src/render/map.rs b/editor/src/render/map.rs index b417bf3a4d..1f30425162 100644 --- a/editor/src/render/map.rs +++ b/editor/src/render/map.rs @@ -7,10 +7,9 @@ use crate::render::extra_shape::{DrawExtraShape, ExtraShapeID}; use crate::render::intersection::DrawIntersection; use crate::render::lane::DrawLane; use crate::render::parcel::DrawParcel; -use crate::render::pedestrian::DrawPedestrian; use crate::render::turn::DrawTurn; -use crate::render::{draw_vehicle, Renderable}; -use crate::state::{Flags, ShowObjects}; +use crate::render::Renderable; +use crate::state::Flags; use aabb_quadtree::QuadTree; use abstutil::Timer; use ezgui::Prerender; @@ -19,17 +18,11 @@ use map_model::{ AreaID, BuildingID, BusStopID, FindClosest, IntersectionID, Lane, LaneID, Map, ParcelID, RoadID, Traversable, Turn, TurnID, LANE_THICKNESS, }; -use sim::{GetDrawAgents, Tick}; +use sim::Tick; use std::borrow::Borrow; use std::cell::RefCell; use std::collections::HashMap; -#[derive(PartialEq)] -pub enum RenderOrder { - BackToFront, - FrontToBack, -} - pub struct DrawMap { pub lanes: Vec, pub intersections: Vec, @@ -40,7 +33,8 @@ pub struct DrawMap { pub bus_stops: HashMap, pub areas: Vec, - agents: RefCell, + // TODO Move? + pub agents: RefCell, quadtree: QuadTree, } @@ -271,123 +265,23 @@ impl DrawMap { results } - // False from the callback means abort - pub fn handle_objects) -> bool>( - &self, - screen_bounds: Bounds, - map: &Map, - sim: &GetDrawAgents, - show_objs: &T, - order: RenderOrder, - mut callback: F, - ) { - // From background to foreground Z-order - let mut areas: Vec> = Vec::new(); - let mut parcels: Vec> = Vec::new(); - let mut lanes: Vec> = Vec::new(); - let mut intersections: Vec> = Vec::new(); - let mut buildings: Vec> = Vec::new(); - let mut extra_shapes: Vec> = Vec::new(); - let mut bus_stops: Vec> = Vec::new(); - let mut turn_icons: Vec> = Vec::new(); - // We can't immediately grab the Renderables; we need to do it all at once at the end. - let mut agents_on: Vec = Vec::new(); - - for &(id, _, _) in &self.quadtree.query(screen_bounds.as_bbox()) { - if show_objs.show(*id) { - match id { - ID::Area(id) => areas.push(Box::new(self.get_a(*id))), - ID::Parcel(id) => parcels.push(Box::new(self.get_p(*id))), - ID::Lane(id) => { - lanes.push(Box::new(self.get_l(*id))); - if !show_objs.show_icons_for(map.get_l(*id).dst_i) { - let on = Traversable::Lane(*id); - if !self.agents.borrow().has(sim.tick(), on) { - let mut list: Vec> = Vec::new(); - for c in sim.get_draw_cars(on, map).into_iter() { - list.push(draw_vehicle(c, map)); - } - for p in sim.get_draw_peds(on, map).into_iter() { - list.push(Box::new(DrawPedestrian::new(p, map))); - } - self.agents.borrow_mut().put(sim.tick(), on, list); - } - agents_on.push(on); - } - } - ID::Intersection(id) => { - intersections.push(Box::new(self.get_i(*id))); - for t in &map.get_i(*id).turns { - if show_objs.show_icons_for(*id) { - turn_icons.push(Box::new(self.get_t(*t))); - } else { - let on = Traversable::Turn(*t); - if !self.agents.borrow().has(sim.tick(), on) { - let mut list: Vec> = Vec::new(); - for c in sim.get_draw_cars(on, map).into_iter() { - list.push(draw_vehicle(c, map)); - } - for p in sim.get_draw_peds(on, map).into_iter() { - list.push(Box::new(DrawPedestrian::new(p, map))); - } - self.agents.borrow_mut().put(sim.tick(), on, list); - } - agents_on.push(on); - } - } - } - // TODO front paths will get drawn over buildings, depending on quadtree order. - // probably just need to make them go around other buildings instead of having - // two passes through buildings. - ID::Building(id) => buildings.push(Box::new(self.get_b(*id))), - ID::ExtraShape(id) => extra_shapes.push(Box::new(self.get_es(*id))), - ID::BusStop(id) => bus_stops.push(Box::new(self.get_bs(*id))), - - ID::Turn(_) | ID::Car(_) | ID::Pedestrian(_) | ID::Trip(_) => { - panic!("{:?} shouldn't be in the quadtree", id) - } - } - } - } - - let mut borrows: Vec> = Vec::new(); - borrows.extend(areas); - borrows.extend(parcels); - borrows.extend(lanes); - borrows.extend(intersections); - borrows.extend(buildings); - borrows.extend(extra_shapes); - borrows.extend(bus_stops); - borrows.extend(turn_icons); - let cache = self.agents.borrow(); - for on in agents_on { - for obj in cache.get(on) { - borrows.push(obj); - } - } - - // This is a stable sort. - borrows.sort_by_key(|r| r.get_zorder()); - - if order == RenderOrder::FrontToBack { - borrows.reverse(); - } - - for r in borrows { - if !callback(r) { - break; - } + // Unsorted, unexpanded, raw result. + pub fn get_matching_objects(&self, bounds: Bounds) -> Vec { + let mut results: Vec = Vec::new(); + for &(id, _, _) in &self.quadtree.query(bounds.as_bbox()) { + results.push(*id); } + results } } -struct AgentCache { +pub struct AgentCache { tick: Option, agents_per_on: HashMap>>, } impl AgentCache { - fn has(&self, tick: Tick, on: Traversable) -> bool { + pub fn has(&self, tick: Tick, on: Traversable) -> bool { if Some(tick) != self.tick { return false; } @@ -395,14 +289,14 @@ impl AgentCache { } // Must call has() first. - fn get(&self, on: Traversable) -> Vec> { + pub fn get(&self, on: Traversable) -> Vec> { self.agents_per_on[&on] .iter() .map(|obj| Box::new(obj.borrow())) .collect() } - fn put(&mut self, tick: Tick, on: Traversable, agents: Vec>) { + pub fn put(&mut self, tick: Tick, on: Traversable, agents: Vec>) { if Some(tick) != self.tick { self.agents_per_on.clear(); self.tick = Some(tick); diff --git a/editor/src/render/mod.rs b/editor/src/render/mod.rs index 7ec1939a46..1d87615f5e 100644 --- a/editor/src/render/mod.rs +++ b/editor/src/render/mod.rs @@ -11,6 +11,7 @@ mod parcel; mod pedestrian; mod turn; +use crate::colors::ColorScheme; use crate::objects::{Ctx, ID}; pub use crate::render::area::DrawArea; use crate::render::bike::DrawBike; @@ -18,10 +19,10 @@ use crate::render::car::DrawCar; pub use crate::render::extra_shape::ExtraShapeID; pub use crate::render::intersection::{draw_signal_cycle, draw_signal_diagram}; pub use crate::render::lane::DrawLane; -pub use crate::render::map::{DrawMap, RenderOrder}; +pub use crate::render::map::DrawMap; pub use crate::render::pedestrian::DrawPedestrian; pub use crate::render::turn::{DrawCrosswalk, DrawTurn}; -use ezgui::{Color, GfxCtx}; +use ezgui::{Color, GfxCtx, Prerender}; use geom::{Bounds, Distance, Pt2D}; use map_model::Map; use sim::{DrawCarInput, VehicleType}; @@ -61,6 +62,15 @@ pub struct RenderOptions { pub show_all_detail: bool, } +pub fn new_draw_vehicle( + input: DrawCarInput, + map: &Map, + _prerender: &Prerender, + _cs: &ColorScheme, +) -> Box { + draw_vehicle(input, map) +} + pub fn draw_vehicle(input: DrawCarInput, map: &Map) -> Box { if input.vehicle_type == VehicleType::Bike { Box::new(DrawBike::new(input)) diff --git a/editor/src/render/pedestrian.rs b/editor/src/render/pedestrian.rs index 19b241477e..07a3008093 100644 --- a/editor/src/render/pedestrian.rs +++ b/editor/src/render/pedestrian.rs @@ -1,6 +1,7 @@ +use crate::colors::ColorScheme; use crate::objects::{Ctx, ID}; use crate::render::{RenderOptions, Renderable}; -use ezgui::{Color, GfxCtx}; +use ezgui::{Color, GfxCtx, Prerender}; use geom::{Bounds, Circle, Distance, Line, Pt2D}; use map_model::Map; use sim::{DrawPedestrianInput, PedestrianID}; @@ -16,6 +17,15 @@ pub struct DrawPedestrian { } impl DrawPedestrian { + pub fn new_new( + input: DrawPedestrianInput, + map: &Map, + _prerender: &Prerender, + _cs: &ColorScheme, + ) -> DrawPedestrian { + DrawPedestrian::new(input, map) + } + pub fn new(input: DrawPedestrianInput, map: &Map) -> DrawPedestrian { let turn_arrow = if let Some(t) = input.waiting_for_turn { // TODO this isn't quite right, but good enough for now diff --git a/editor/src/state.rs b/editor/src/state.rs index e0556523db..b324fe74f6 100644 --- a/editor/src/state.rs +++ b/editor/src/state.rs @@ -376,6 +376,7 @@ impl UIState for DefaultUIState { } } +// TODO Doesn't need to be a trait anymore pub trait ShowObjects { fn show_icons_for(&self, id: IntersectionID) -> bool; fn show(&self, obj: ID) -> bool; diff --git a/editor/src/ui.rs b/editor/src/ui.rs index 6ae215497f..de677e67ef 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -1,15 +1,15 @@ use abstutil; //use cpuprofiler; use crate::objects::{Ctx, RenderingHints, ID}; -use crate::render::{RenderOptions, RenderOrder, Renderable}; -use crate::state::UIState; +use crate::render::{new_draw_vehicle, DrawPedestrian, RenderOptions, Renderable}; +use crate::state::{ShowObjects, UIState}; use ezgui::{ - Canvas, Color, EventCtx, EventLoopMode, Folder, GfxCtx, Key, ModalMenu, Text, TopMenu, - BOTTOM_LEFT, GUI, + Canvas, Color, EventCtx, EventLoopMode, Folder, GfxCtx, Key, ModalMenu, Prerender, Text, + TopMenu, BOTTOM_LEFT, GUI, }; use geom::{Bounds, Circle, Distance}; use kml; -use map_model::{BuildingID, LaneID}; +use map_model::{BuildingID, LaneID, Traversable}; use serde_derive::{Deserialize, Serialize}; use sim::GetDrawAgents; use std::collections::HashSet; @@ -237,27 +237,38 @@ impl GUI for UI { fn draw(&self, _: &mut GfxCtx, _: &RenderingHints) {} fn new_draw(&self, g: &mut GfxCtx, hints: &RenderingHints, screencap: bool) -> Option { + let state = self.state.get_state(); + g.clear( - self.state - .get_state() + state .cs .get_def("map background", Color::rgb(242, 239, 233)), ); + let (mut borrows, agents_on) = + self.get_renderables_back_to_front(g.get_screen_bounds(), &g.prerender()); + let cache = state.primary.draw_map.agents.borrow(); + for on in agents_on { + for obj in cache.get(on) { + borrows.push(obj); + } + } + // This is a stable sort. + borrows.sort_by_key(|r| r.get_zorder()); + let ctx = Ctx { - cs: &self.state.get_state().cs, - map: &self.state.get_state().primary.map, - draw_map: &self.state.get_state().primary.draw_map, - sim: &self.state.get_state().primary.sim, + cs: &state.cs, + map: &state.primary.map, + draw_map: &state.primary.draw_map, + sim: &state.primary.sim, hints: &hints, }; - let mut sample_intersection: Option = None; - self.handle_objects(g.get_screen_bounds(), RenderOrder::BackToFront, |obj| { + for obj in borrows { let opts = RenderOptions { - color: self.state.get_state().color_obj(obj.get_id(), &ctx), - debug_mode: self.state.get_state().layers.debug_mode.is_enabled(), - is_selected: self.state.get_state().primary.current_selection == Some(obj.get_id()), + color: state.color_obj(obj.get_id(), &ctx), + debug_mode: state.layers.debug_mode.is_enabled(), + is_selected: state.primary.current_selection == Some(obj.get_id()), // TODO If a ToggleableLayer is currently off, this won't affect it! show_all_detail: screencap, }; @@ -268,9 +279,7 @@ impl GUI for UI { sample_intersection = Some(format!("_i{}", id.0)); } } - - true - }); + } if !screencap { self.state.draw(g, &ctx); @@ -344,56 +353,34 @@ impl UI { fn mouseover_something(&self, ctx: &EventCtx) -> Option { let pt = ctx.canvas.get_cursor_in_map_space()?; - let mut id: Option = None; - - self.handle_objects( + let (mut borrows, agents_on) = self.get_renderables_back_to_front( Circle::new(pt, Distance::meters(3.0)).get_bounds(), - RenderOrder::FrontToBack, - |obj| { - // Don't mouseover parcels. - // TODO Might get fancier rules in the future, so we can't mouseover irrelevant things - // in intersection editor mode, for example. - match obj.get_id() { - ID::Parcel(_) => {} - _ => { - if obj.contains_pt(pt) { - id = Some(obj.get_id()); - return false; - } - } - }; - true - }, + ctx.prerender, ); - - id - } - - fn handle_objects) -> bool>( - &self, - bounds: Bounds, - order: RenderOrder, - callback: F, - ) { - let state = self.state.get_state(); - - let draw_agent_source: &GetDrawAgents = { - let tt = &state.primary_plugins.time_travel; - if tt.is_active() { - tt - } else { - &state.primary.sim + let cache = self.state.get_state().primary.draw_map.agents.borrow(); + for on in agents_on { + for obj in cache.get(on) { + borrows.push(obj); } - }; + } + // This is a stable sort. + borrows.sort_by_key(|r| r.get_zorder()); + borrows.reverse(); - state.primary.draw_map.handle_objects( - bounds, - &state.primary.map, - draw_agent_source, - state, - order, - callback, - ) + for obj in borrows { + // Don't mouseover parcels. + // TODO Might get fancier rules in the future, so we can't mouseover irrelevant things + // in intersection editor mode, for example. + match obj.get_id() { + ID::Parcel(_) => {} + _ => { + if obj.contains_pt(pt) { + return Some(obj.get_id()); + } + } + }; + } + None } fn save_editor_state(&self, canvas: &Canvas) { @@ -407,6 +394,106 @@ impl UI { abstutil::write_json("../editor_state", &state).expect("Saving editor_state failed"); info!("Saved editor_state"); } + + // TODO I guess this technically could go in DrawMap, but we have to pass lots of stuff again. + fn get_renderables_back_to_front( + &self, + bounds: Bounds, + prerender: &Prerender, + ) -> (Vec>, Vec) { + let state = self.state.get_state(); + let map = &state.primary.map; + let draw_map = &state.primary.draw_map; + + let mut areas: Vec> = Vec::new(); + let mut parcels: Vec> = Vec::new(); + let mut lanes: Vec> = Vec::new(); + let mut intersections: Vec> = Vec::new(); + let mut buildings: Vec> = Vec::new(); + let mut extra_shapes: Vec> = Vec::new(); + let mut bus_stops: Vec> = Vec::new(); + let mut turn_icons: Vec> = Vec::new(); + let mut agents_on: Vec = Vec::new(); + + for id in draw_map.get_matching_objects(bounds) { + if !state.show(id) { + continue; + } + match id { + ID::Area(id) => areas.push(Box::new(draw_map.get_a(id))), + ID::Parcel(id) => parcels.push(Box::new(draw_map.get_p(id))), + ID::Lane(id) => { + lanes.push(Box::new(draw_map.get_l(id))); + if !state.show_icons_for(map.get_l(id).dst_i) { + agents_on.push(Traversable::Lane(id)); + } + } + ID::Intersection(id) => { + intersections.push(Box::new(draw_map.get_i(id))); + for t in &map.get_i(id).turns { + if state.show_icons_for(id) { + turn_icons.push(Box::new(draw_map.get_t(*t))); + } else { + agents_on.push(Traversable::Turn(*t)); + } + } + } + // TODO front paths will get drawn over buildings, depending on quadtree order. + // probably just need to make them go around other buildings instead of having + // two passes through buildings. + ID::Building(id) => buildings.push(Box::new(draw_map.get_b(id))), + ID::ExtraShape(id) => extra_shapes.push(Box::new(draw_map.get_es(id))), + ID::BusStop(id) => bus_stops.push(Box::new(draw_map.get_bs(id))), + + ID::Turn(_) | ID::Car(_) | ID::Pedestrian(_) | ID::Trip(_) => { + panic!("{:?} shouldn't be in the quadtree", id) + } + } + } + + // From background to foreground Z-order + let mut borrows: Vec> = Vec::new(); + borrows.extend(areas); + borrows.extend(parcels); + borrows.extend(lanes); + borrows.extend(intersections); + borrows.extend(buildings); + borrows.extend(extra_shapes); + borrows.extend(bus_stops); + borrows.extend(turn_icons); + + // Make sure agents are cached, but we can't actually return the references to them here, + // since the RefCell borrow can't outlive this function. + { + let sim: &GetDrawAgents = { + let tt = &state.primary_plugins.time_travel; + if tt.is_active() { + tt + } else { + &state.primary.sim + } + }; + let tick = sim.tick(); + let mut agents = draw_map.agents.borrow_mut(); + + for on in &agents_on { + if !agents.has(tick, *on) { + let mut list: Vec> = Vec::new(); + for c in sim.get_draw_cars(*on, map).into_iter() { + list.push(new_draw_vehicle(c, map, prerender, &state.cs)); + } + for p in sim.get_draw_peds(*on, map).into_iter() { + list.push(Box::new(DrawPedestrian::new_new( + p, map, prerender, &state.cs, + ))); + } + agents.put(tick, *on, list); + } + } + } + + (borrows, agents_on) + } } #[derive(Serialize, Deserialize, Debug)] diff --git a/ezgui/src/drawing.rs b/ezgui/src/drawing.rs index ae33d84df0..2217a02bb7 100644 --- a/ezgui/src/drawing.rs +++ b/ezgui/src/drawing.rs @@ -56,6 +56,12 @@ impl<'a> GfxCtx<'a> { } } + pub fn prerender(&self) -> Prerender { + Prerender { + display: self.display, + } + } + // Up to the caller to call unfork()! // TODO Canvas doesn't understand this change, so things like text drawing that use // map_to_screen will just be confusing. @@ -120,19 +126,13 @@ impl<'a> GfxCtx<'a> { } pub fn draw_polygon(&mut self, color: Color, poly: &Polygon) { - let obj = Prerender { - display: self.display, - } - .upload_borrowed(vec![(color, poly)]); + let obj = self.prerender().upload_borrowed(vec![(color, poly)]); self.num_new_uploads += 1; self.redraw(&obj); } pub fn draw_polygon_batch(&mut self, list: Vec<(Color, &Polygon)>) { - let obj = Prerender { - display: self.display, - } - .upload_borrowed(list); + let obj = self.prerender().upload_borrowed(list); self.num_new_uploads += 1; self.redraw(&obj); }