making hierarchial progress timer to replace flamegraphs. using in many,

but not all, places
This commit is contained in:
Dustin Carlino 2018-10-28 15:39:24 -07:00
parent 51fa5a0aba
commit f011f8f56d
18 changed files with 221 additions and 100 deletions

View File

@ -21,6 +21,6 @@ pub use io::{
write_json,
};
pub use logs::{format_log_record, LogAdapter};
pub use time::{elapsed_seconds, Progress};
pub use time::{elapsed_seconds, Timer};
const PROGRESS_FREQUENCY_SECONDS: f64 = 0.2;

View File

@ -7,7 +7,7 @@ pub fn elapsed_seconds(since: Instant) -> f64 {
(dt.as_secs() as f64) + (f64::from(dt.subsec_nanos()) * 1e-9)
}
pub struct Progress {
struct Progress {
label: String,
processed_items: usize,
total_items: usize,
@ -16,7 +16,7 @@ pub struct Progress {
}
impl Progress {
pub fn new(label: &str, total_items: usize) -> Progress {
fn new(label: &str, total_items: usize) -> Progress {
Progress {
label: label.to_string(),
processed_items: 0,
@ -26,7 +26,8 @@ impl Progress {
}
}
pub fn next(&mut self) {
// Returns when done
fn next(&mut self, padding: String) -> Option<String> {
self.processed_items += 1;
if self.processed_items > self.total_items {
panic!(
@ -38,20 +39,116 @@ impl Progress {
let done = self.processed_items == self.total_items;
if elapsed_seconds(self.last_printed_at) >= PROGRESS_FREQUENCY_SECONDS || done {
self.last_printed_at = Instant::now();
// TODO blank till end of current line
print!(
let line = format!(
"{}{}: {}/{}... {}s",
"\r",
padding,
self.label,
self.processed_items,
self.total_items,
elapsed_seconds(self.started_at),
elapsed_seconds(self.started_at)
);
// TODO blank till end of current line
print!("\r{}", line);
if done {
println!("");
return Some(line);
} else {
stdout().flush().unwrap();
}
}
None
}
}
enum StackEntry {
TimerSpan(TimerSpan),
Progress(Progress),
}
// Hierarchial magic
pub struct Timer {
results: Vec<String>,
stack: Vec<StackEntry>,
}
struct TimerSpan {
name: String,
started_at: Instant,
}
impl Timer {
pub fn new() -> Timer {
Timer {
results: Vec::new(),
stack: Vec::new(),
}
}
pub fn done(self) {
assert!(self.stack.is_empty());
println!("");
for line in self.results {
println!("{}", line);
}
println!("");
}
pub fn start(&mut self, name: &str) {
println!("{}- {}...", " ".repeat(self.stack.len()), name);
self.stack.push(StackEntry::TimerSpan(TimerSpan {
name: name.to_string(),
started_at: Instant::now(),
}));
}
pub fn stop(&mut self, name: &str) {
let span = match self.stack.pop().unwrap() {
StackEntry::TimerSpan(s) => s,
StackEntry::Progress(p) => panic!(
"stop({}) while a Progress({}, {}/{}) is top of the stack",
name, p.label, p.processed_items, p.total_items
),
};
assert_eq!(span.name, name);
let line = format!(
"{}- {} took {}s",
" ".repeat(self.stack.len()),
name,
elapsed_seconds(span.started_at)
);
println!("{}", line);
self.results.push(line);
}
pub fn start_iter(&mut self, name: &str, total_items: usize) {
if total_items == 0 {
panic!("Can't start_iter({}, 0)", name);
}
if let Some(StackEntry::Progress(p)) = self.stack.last() {
panic!(
"Can't start_iter({}) while Progress({}) is top of the stack",
name, p.label
);
}
self.stack
.push(StackEntry::Progress(Progress::new(name, total_items)));
}
pub fn next(&mut self) {
let padding = format!("{} - ", " ".repeat(self.stack.len() - 1));
let done = if let Some(StackEntry::Progress(ref mut progress)) = self.stack.last_mut() {
if let Some(result) = progress.next(padding) {
self.results.push(result);
true
} else {
false
}
} else {
panic!("Can't next() while a TimerSpan is top of the stack");
};
if done {
self.stack.pop();
}
}
}

View File

@ -9,8 +9,6 @@
- run clippy everywhere
- presubmit script
- also enforce consistent style (import order, extern crate only in mod.rs or lib.rs, derive order, struct initialization order)
- profiling
- https://github.com/TyOverby/flame
## Conga line idea

View File

@ -12,7 +12,6 @@ cpuprofiler = "0.0.3"
dimensioned = { git = "https://github.com/paholg/dimensioned", rev = "0e1076ebfa5128d1ee544bdc9754c948987b6fe3", features = ["serde"] }
downcast = "0.9.2"
ezgui = { path = "../ezgui" }
flame = "0.2.2"
generator = "0.6"
geo = "0.9.1"
geom = { path = "../geom" }

View File

@ -9,7 +9,6 @@ extern crate dimensioned;
#[macro_use]
extern crate downcast;
extern crate ezgui;
extern crate flame;
#[macro_use]
extern crate generator;
extern crate geo;

View File

@ -15,13 +15,17 @@ pub struct NeighborhoodSummary {
}
impl NeighborhoodSummary {
pub fn new(map: &Map) -> NeighborhoodSummary {
pub fn new(map: &Map, timer: &mut abstutil::Timer) -> NeighborhoodSummary {
let neighborhoods = abstutil::load_all_objects("neighborhoods", map.get_name());
timer.start_iter("precompute neighborhood members", neighborhoods.len());
NeighborhoodSummary {
regions: abstutil::load_all_objects("neighborhoods", map.get_name())
regions: neighborhoods
.into_iter()
.enumerate()
.map(|(idx, (_, n))| Region::new(idx, n, map))
.collect(),
.map(|(idx, (_, n))| {
timer.next();
Region::new(idx, n, map)
}).collect(),
active: false,
last_summary: None,
}

View File

@ -1,8 +1,8 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use abstutil::Progress;
use aabb_quadtree::geom::{Point, Rect};
use aabb_quadtree::QuadTree;
use abstutil::Timer;
use control::ControlMap;
use geom::{Bounds, Pt2D};
use kml::{ExtraShape, ExtraShapeID};
@ -43,11 +43,16 @@ pub struct DrawMap {
}
impl DrawMap {
pub fn new(map: &Map, control_map: &ControlMap, raw_extra_shapes: Vec<ExtraShape>) -> DrawMap {
pub fn new(
map: &Map,
control_map: &ControlMap,
raw_extra_shapes: Vec<ExtraShape>,
timer: &mut Timer,
) -> DrawMap {
let mut lanes: Vec<DrawLane> = Vec::new();
let mut progress = Progress::new("make DrawLanes", map.all_lanes().len());
timer.start_iter("make DrawLanes", map.all_lanes().len());
for l in map.all_lanes() {
progress.next();
timer.next();
lanes.push(DrawLane::new(l, map, control_map));
}
@ -89,6 +94,7 @@ impl DrawMap {
let bounds = map.get_bounds();
let map_bbox = get_bbox(bounds);
timer.start("create quadtree");
let mut quadtree = QuadTree::default(map_bbox);
// TODO use iter chain if everything was boxed as a renderable...
for obj in &lanes {
@ -112,6 +118,7 @@ impl DrawMap {
for obj in &areas {
quadtree.insert_with_box(obj.get_id(), get_bbox(obj.get_bounds()));
}
timer.stop("create quadtree");
DrawMap {
lanes,

View File

@ -7,7 +7,6 @@ use colors::ColorScheme;
use control::ControlMap;
//use cpuprofiler;
use ezgui::{Canvas, Color, EventLoopMode, GfxCtx, Text, UserInput, BOTTOM_LEFT, GUI};
use flame;
use kml;
use map_model::{IntersectionID, Map};
use objects::{Ctx, ID, ROOT_MENU};
@ -232,27 +231,27 @@ impl PluginsPerMap {
impl PerMapUI {
pub fn new(flags: SimFlags, kml: &Option<String>) -> (PerMapUI, PluginsPerMap) {
flame::start("setup");
let (map, control_map, sim) = sim::load(flags.clone(), Some(sim::Tick::from_seconds(30)));
let mut timer = abstutil::Timer::new();
timer.start("PerMapUI setup");
let (map, control_map, sim) =
sim::load(flags.clone(), Some(sim::Tick::from_seconds(30)), &mut timer);
let extra_shapes = if let Some(path) = kml {
kml::load(&path, &map.get_gps_bounds()).expect("Couldn't load extra KML shapes")
} else {
Vec::new()
};
flame::start("draw_map");
let draw_map = DrawMap::new(&map, &control_map, extra_shapes);
flame::end("draw_map");
timer.start("draw_map");
let draw_map = DrawMap::new(&map, &control_map, extra_shapes, &mut timer);
timer.stop("draw_map");
flame::start("steepness_viz");
let steepness_viz = plugins::steep::SteepnessVisualizer::new(&map);
flame::end("steepness_viz");
flame::start("neighborhood_summary");
let neighborhood_summary = plugins::neighborhood_summary::NeighborhoodSummary::new(&map);
flame::end("neighborhood_summary");
let neighborhood_summary =
plugins::neighborhood_summary::NeighborhoodSummary::new(&map, &mut timer);
flame::end("setup");
flame::dump_stdout();
timer.stop("PerMapUI setup");
timer.done();
let state = PerMapUI {
map,

View File

@ -7,7 +7,6 @@ authors = ["Dustin Carlino <dabreegster@gmail.com>"]
aabb-quadtree = "0.1.0"
abstutil = { path = "../abstutil" }
dimensioned = { git = "https://github.com/paholg/dimensioned", rev = "0e1076ebfa5128d1ee544bdc9754c948987b6fe3", features = ["serde"] }
flame = "0.2.2"
geo = "0.9.1"
geom = { path = "../geom" }
gtfs = { path = "../gtfs" }

View File

@ -1,7 +1,6 @@
extern crate aabb_quadtree;
extern crate abstutil;
extern crate dimensioned;
extern crate flame;
extern crate geo;
extern crate geom;
extern crate gtfs;

View File

@ -1,4 +1,4 @@
use abstutil::Progress;
use abstutil::Timer;
use dimensioned::si;
use geom::{Bounds, HashablePt2D, Line, PolyLine, Pt2D};
use make::sidewalk_finder::find_sidewalk_points;
@ -12,13 +12,15 @@ pub fn make_all_buildings(
gps_bounds: &Bounds,
bounds: &Bounds,
lanes: &Vec<Lane>,
timer: &mut Timer,
) {
timer.start("convert buildings");
let mut pts_per_bldg: Vec<Vec<Pt2D>> = Vec::new();
let mut center_per_bldg: Vec<HashablePt2D> = Vec::new();
let mut query: HashSet<HashablePt2D> = HashSet::new();
let mut progress = Progress::new("get building center points", input.len());
timer.start_iter("get building center points", input.len());
for b in input {
progress.next();
timer.next();
let pts = b
.points
.iter()
@ -31,11 +33,11 @@ pub fn make_all_buildings(
}
// Skip buildings that're too far away from their sidewalk
let sidewalk_pts = find_sidewalk_points(bounds, query, lanes, 100.0 * si::M);
let sidewalk_pts = find_sidewalk_points(bounds, query, lanes, 100.0 * si::M, timer);
let mut progress = Progress::new("create building front paths", pts_per_bldg.len());
timer.start_iter("create building front paths", pts_per_bldg.len());
for (idx, points) in pts_per_bldg.into_iter().enumerate() {
progress.next();
timer.next();
let bldg_center = center_per_bldg[idx];
if let Some((sidewalk, dist_along)) = sidewalk_pts.get(&bldg_center) {
let (sidewalk_pt, _) = lanes[sidewalk.0].dist_along(*dist_along);
@ -64,6 +66,7 @@ pub fn make_all_buildings(
discarded
);
}
timer.stop("convert buildings");
}
// Adjust the path to start on the building's border, not center

View File

@ -1,3 +1,4 @@
use abstutil::Timer;
use dimensioned::si;
use geom::{Bounds, HashablePt2D, Pt2D};
use gtfs;
@ -14,7 +15,9 @@ pub fn make_bus_stops(
bus_routes: &Vec<gtfs::Route>,
gps_bounds: &Bounds,
bounds: &Bounds,
timer: &mut Timer,
) -> (BTreeMap<BusStopID, BusStop>, Vec<BusRoute>) {
timer.start("make bus stops");
let mut bus_stop_pts: HashSet<HashablePt2D> = HashSet::new();
let mut route_lookups: MultiMap<String, HashablePt2D> = MultiMap::new();
for route in bus_routes {
@ -29,7 +32,7 @@ pub fn make_bus_stops(
let mut stops_per_sidewalk: MultiMap<LaneID, (si::Meter<f64>, HashablePt2D)> = MultiMap::new();
for (pt, (lane, dist_along)) in
find_sidewalk_points(bounds, bus_stop_pts, lanes, 10.0 * si::M).iter()
find_sidewalk_points(bounds, bus_stop_pts, lanes, 10.0 * si::M, timer).iter()
{
stops_per_sidewalk.insert(*lane, (*dist_along, *pt));
}
@ -95,6 +98,7 @@ pub fn make_bus_stops(
});
}
}
timer.stop("make bus stops");
(bus_stops, routes)
}

View File

@ -1,3 +1,4 @@
use abstutil::Timer;
use dimensioned::si;
use geom::{Bounds, HashablePt2D, Pt2D};
use make::sidewalk_finder::find_sidewalk_points;
@ -11,7 +12,9 @@ pub fn make_all_parcels(
gps_bounds: &Bounds,
bounds: &Bounds,
lanes: &Vec<Lane>,
timer: &mut Timer,
) {
timer.start("convert parcels");
let mut pts_per_parcel: Vec<Vec<Pt2D>> = Vec::new();
let mut center_per_parcel: Vec<HashablePt2D> = Vec::new();
let mut query: HashSet<HashablePt2D> = HashSet::new();
@ -28,7 +31,7 @@ pub fn make_all_parcels(
}
// Trim parcels that are too far away from the nearest sidewalk
let sidewalk_pts = find_sidewalk_points(bounds, query, lanes, 100.0 * si::M);
let sidewalk_pts = find_sidewalk_points(bounds, query, lanes, 100.0 * si::M, timer);
for (idx, center) in center_per_parcel.into_iter().enumerate() {
if sidewalk_pts.contains_key(&center) {
@ -47,4 +50,5 @@ pub fn make_all_parcels(
discarded
);
}
timer.stop("convert parcels");
}

View File

@ -1,6 +1,6 @@
use aabb_quadtree::geom::{Point, Rect};
use aabb_quadtree::QuadTree;
use abstutil::Progress;
use abstutil::Timer;
use dimensioned::si;
use geo;
use geo::prelude::{ClosestPoint, EuclideanDistance};
@ -16,13 +16,18 @@ pub fn find_sidewalk_points(
pts: HashSet<HashablePt2D>,
lanes: &Vec<Lane>,
max_dist_away: si::Meter<f64>,
timer: &mut Timer,
) -> HashMap<HashablePt2D, (LaneID, si::Meter<f64>)> {
if pts.is_empty() {
return HashMap::new();
}
// Convert all sidewalks to LineStrings and index them with a quadtree.
let mut lane_lines_quadtree: QuadTree<usize> = QuadTree::default(get_bbox(bounds));
let mut lane_lines: Vec<(LaneID, geo::LineString<f64>)> = Vec::new();
let mut progress = Progress::new("lanes to LineStrings", lanes.len());
timer.start_iter("lanes to LineStrings", lanes.len());
for l in lanes {
progress.next();
timer.next();
if l.is_sidewalk() {
lane_lines.push((l.id, lane_to_line_string(l)));
lane_lines_quadtree.insert_with_box(lane_lines.len() - 1, lane_to_rect(l));
@ -32,9 +37,9 @@ pub fn find_sidewalk_points(
// For each point, find the closest point to any sidewalk, using the quadtree to prune the
// search.
let mut results: HashMap<HashablePt2D, (LaneID, si::Meter<f64>)> = HashMap::new();
let mut progress = Progress::new("find closest sidewalk point", pts.len());
timer.start_iter("find closest sidewalk point", pts.len());
for query_pt in pts {
progress.next();
timer.next();
let query_geo_pt = geo::Point::new(query_pt.x(), query_pt.y());
let query_bbox = Rect {
top_left: Point {

View File

@ -1,9 +1,8 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use abstutil;
use abstutil::{Error, Progress};
use abstutil::{Error, Timer};
use edits::RoadEdits;
use flame;
use geom::{Bounds, HashablePt2D, LonLat, PolyLine, Pt2D};
use make;
use raw_data;
@ -35,13 +34,11 @@ pub struct Map {
}
impl Map {
pub fn new(path: &str, road_edits: RoadEdits) -> Result<Map, io::Error> {
// TODO I think I want something a bit different than flame:
// - Print as each phase occurs
// - Print with nicely formatted durations
flame::start("read raw_data");
pub fn new(path: &str, road_edits: RoadEdits, timer: &mut Timer) -> Result<Map, io::Error> {
// TODO use read_binary's timer magic, not this
timer.start("read raw_data");
let data: raw_data::Map = abstutil::read_binary(path)?;
flame::end("read raw_data");
timer.stop("read raw_data");
Ok(Map::create_from_raw(
path::Path::new(path)
.file_stem()
@ -51,10 +48,17 @@ impl Map {
.unwrap(),
data,
road_edits,
timer,
))
}
pub fn create_from_raw(name: String, data: raw_data::Map, road_edits: RoadEdits) -> Map {
pub fn create_from_raw(
name: String,
data: raw_data::Map,
road_edits: RoadEdits,
timer: &mut Timer,
) -> Map {
timer.start("raw_map to Map");
let gps_bounds = data.get_gps_bounds();
let bounds = gps_to_map_bounds(&gps_bounds);
let mut m = Map {
@ -90,7 +94,9 @@ impl Map {
}
let mut counter = 0;
timer.start_iter("expand roads to lanes", data.roads.len());
for (idx, r) in data.roads.iter().enumerate() {
timer.next();
let road_id = RoadID(idx);
let road_center_pts = PolyLine::new(
r.points
@ -158,9 +164,9 @@ impl Map {
}
}
let mut progress = Progress::new("trim lanes at each intersection", m.intersections.len());
timer.start_iter("trim lanes at each intersection", m.intersections.len());
for i in &m.intersections {
progress.next();
timer.next();
make::trim_lines(&mut m.lanes, i);
if i.incoming_lanes.is_empty() && i.outgoing_lanes.is_empty() {
panic!("{:?} is orphaned!", i);
@ -173,6 +179,7 @@ impl Map {
&data.bus_routes,
&gps_bounds,
&bounds,
timer,
);
m.bus_stops = stops;
@ -186,30 +193,26 @@ impl Map {
m.intersections[t.parent.0].turns.push(t.id);
}
{
let _guard = flame::start_guard(format!("make {} buildings", data.buildings.len()));
make::make_all_buildings(
&mut m.buildings,
&data.buildings,
&gps_bounds,
&bounds,
&m.lanes,
);
for b in &m.buildings {
m.lanes[b.front_path.sidewalk.0].building_paths.push(b.id);
}
make::make_all_buildings(
&mut m.buildings,
&data.buildings,
&gps_bounds,
&bounds,
&m.lanes,
timer,
);
for b in &m.buildings {
m.lanes[b.front_path.sidewalk.0].building_paths.push(b.id);
}
{
let _guard = flame::start_guard(format!("make {} parcels", data.parcels.len()));
make::make_all_parcels(
&mut m.parcels,
&data.parcels,
&gps_bounds,
&bounds,
&m.lanes,
);
}
make::make_all_parcels(
&mut m.parcels,
&data.parcels,
&gps_bounds,
&bounds,
&m.lanes,
timer,
);
for (idx, a) in data.areas.iter().enumerate() {
m.areas.push(Area {
@ -225,11 +228,9 @@ impl Map {
});
}
{
let _guard = flame::start_guard(format!("verify {} bus routes", routes.len()));
m.bus_routes = make::verify_bus_routes(&m, routes);
}
m.bus_routes = make::verify_bus_routes(&m, routes);
timer.stop("raw_map to Map");
m
}

View File

@ -9,7 +9,6 @@ backtrace = "0.3.9"
control = { path = "../control" }
derivative = "1.0.0"
dimensioned = { git = "https://github.com/paholg/dimensioned", rev = "0e1076ebfa5128d1ee544bdc9754c948987b6fe3", features = ["serde"] }
flame = "0.2.2"
geom = { path = "../geom" }
lazy_static = "1.1.0"
log = "0.4.5"

View File

@ -1,6 +1,5 @@
use abstutil;
use control::ControlMap;
use flame;
use map_model::{BuildingID, BusRoute, BusStopID, LaneID, Map, RoadID};
use std::collections::{BTreeSet, VecDeque};
use {
@ -40,14 +39,18 @@ impl SimFlags {
}
// Convenience method to setup everything.
pub fn load(flags: SimFlags, savestate_every: Option<Tick>) -> (Map, ControlMap, Sim) {
pub fn load(
flags: SimFlags,
savestate_every: Option<Tick>,
timer: &mut abstutil::Timer,
) -> (Map, ControlMap, Sim) {
if flags.load.contains("data/save/") {
assert_eq!(flags.edits_name, "no_edits");
info!("Resuming from {}", flags.load);
flame::start("read sim savestate");
timer.start("read sim savestate");
let sim: Sim = abstutil::read_json(&flags.load).expect("loading sim state failed");
flame::end("read sim savestate");
timer.stop("read sim savestate");
let edits: MapEdits = abstutil::read_json(&format!(
"../data/edits/{}/{}.json",
@ -60,7 +63,7 @@ pub fn load(flags: SimFlags, savestate_every: Option<Tick>) -> (Map, ControlMap,
sim.map_name, sim.edits_name
)).unwrap_or_else(|_| {
let map_path = format!("../data/raw_maps/{}.abst", sim.map_name);
Map::new(&map_path, edits.road_edits.clone())
Map::new(&map_path, edits.road_edits.clone(), timer)
.expect(&format!("Couldn't load map from {}", map_path))
});
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
@ -77,7 +80,7 @@ pub fn load(flags: SimFlags, savestate_every: Option<Tick>) -> (Map, ControlMap,
scenario.map_name, edits.edits_name
)).unwrap_or_else(|_| {
let map_path = format!("../data/raw_maps/{}.abst", scenario.map_name);
Map::new(&map_path, edits.road_edits.clone())
Map::new(&map_path, edits.road_edits.clone(), timer)
.expect(&format!("Couldn't load map from {}", map_path))
});
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
@ -99,19 +102,21 @@ pub fn load(flags: SimFlags, savestate_every: Option<Tick>) -> (Map, ControlMap,
.to_string();
info!("Loading map {}", flags.load);
let edits = load_edits(&map_name, &flags);
let map = Map::new(&flags.load, edits.road_edits.clone()).expect("Couldn't load map");
let map =
Map::new(&flags.load, edits.road_edits.clone(), timer).expect("Couldn't load map");
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
flame::start("create sim");
timer.start("create sim");
let sim = Sim::new(&map, flags.run_name, flags.rng_seed, savestate_every);
flame::end("create sim");
timer.stop("create sim");
(map, control_map, sim)
} else if flags.load.contains("data/maps/") {
assert_eq!(flags.edits_name, "no_edits");
info!("Loading map {}", flags.load);
flame::start("load binary map");
// TODO dont do this
timer.start("load binary map");
let map: Map = abstutil::read_binary(&flags.load).expect("Couldn't load map");
flame::end("load binary map");
timer.stop("load binary map");
// TODO Bit sad to load edits to reconstitute ControlMap, but this is necessary right now
let edits: MapEdits = abstutil::read_json(&format!(
"../data/edits/{}/{}.json",
@ -119,9 +124,9 @@ pub fn load(flags: SimFlags, savestate_every: Option<Tick>) -> (Map, ControlMap,
map.get_road_edits().edits_name
)).unwrap();
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
flame::start("create sim");
timer.start("create sim");
let sim = Sim::new(&map, flags.run_name, flags.rng_seed, savestate_every);
flame::end("create sim");
timer.stop("create sim");
(map, control_map, sim)
} else {
panic!("Don't know how to load {}", flags.load);

View File

@ -6,7 +6,6 @@ extern crate control;
#[macro_use]
extern crate derivative;
extern crate dimensioned;
extern crate flame;
extern crate geom;
#[macro_use]
extern crate lazy_static;