<commit_before>extern crate chrono;
extern crate clap;
extern crate fern;
#[macro_use]
extern crate log;
extern crate mcts;
extern crate rand;
extern crate thud;
extern crate thud_game;

use ::clap::App;
use ::rand::isaac::IsaacRng;
use ::rand::SeedableRng;

use thud_game::board;

fn main() {
    // Set up arg handling.
    let matches = {
        let app = thud::set_common_args(
            App::new("console_mcts")
                .version("0.1.0")
                .author("Stu Black <trurl@freeshell.org>")
                .about("Plays out Thud MCTS iterations"),
            &[thud::ITERATION_COUNT_FLAG,
              thud::SIMULATION_COUNT_FLAG,
              thud::EXPLORATION_BIAS_FLAG,
              thud::INITIAL_BOARD_FLAG,
              thud::INITIAL_PLAYER_FLAG,
              thud::LOG_LEVEL_FLAG,
              thud::MOVE_SELECTION_CRITERION_FLAG]);
        app.get_matches()
    };
    let iteration_count =
        match matches.value_of(thud::ITERATION_COUNT_FLAG).unwrap().parse::<usize>() {
            Ok(x) => x,
            Err(e) => panic!("Bad iteration count: {}", e),
        };
    let simulation_count =
        match matches.value_of(thud::SIMULATION_COUNT_FLAG).unwrap().parse::<usize>() {
            Ok(x) => x,
            Err(e) => panic!("Bad simulation count: {}", e),
        };
    let exploration_bias =
        match matches.value_of(thud::EXPLORATION_BIAS_FLAG).unwrap().parse::<f64>() {
            Ok(x) => x,
            Err(e) => panic!("Bad exploration bias: {}", e),
        };
    let initial_cells =
        match matches.value_of(thud::INITIAL_BOARD_FLAG).map(|x| x.parse::<thud::InitialBoard>()) {
            Some(Ok(x)) => x.cells(),
            Some(Err(e)) => panic!("Bad initial board configuration: {}", e),
            None => board::Cells::default(),
        };
    let toggle_initial_player =
        match matches.value_of(thud::INITIAL_PLAYER_FLAG).map(|x| x.parse::<thud_game::Role>()) {
            None | Some(Ok(thud_game::Role::Dwarf)) => false,
            Some(Ok(thud_game::Role::Troll)) => true,
            Some(Err(x)) => panic!("{}", x),
        };
    let logging_level =
        match matches.value_of(thud::LOG_LEVEL_FLAG).map(|x| x.parse::<log::LogLevelFilter>()) {
            Some(Ok(x)) => x,
            Some(Err(_)) => panic!("Bad logging level '{}'", matches.value_of(thud::LOG_LEVEL_FLAG).unwrap()),
            None => log::LogLevelFilter::Info,
        };
    let move_selection_criterion =
        match matches.value_of(thud::MOVE_SELECTION_CRITERION_FLAG).map(|x| x.parse::<thud::MoveSelectionCriterion>()) {
            Some(Ok(x)) => x,
            Some(Err(e)) => panic!("Bad move selection criterion: {}", e),
            None => thud::MoveSelectionCriterion::VisitCount,
        };
    let rng =
        match matches.value_of(thud::RNG_SEED_FLAG).map(|x| x.parse::<u32>()) {
            Some(Ok(x)) => IsaacRng::from_seed(&[x]),
            Some(Err(e)) => panic!("Bad RNG seed: {}", e),
            None => IsaacRng::new_unseeded(),
        };
    let compact_graph =
        match matches.value_of(thud::COMPACT_SEARCH_GRAPH_FLAG) {
            Some(_) => true,
            None => false,
        };

    // Set up logging.
    let logger_config = fern::DispatchConfig {
        format: Box::new(|msg: &str, level: &log::LogLevel, _location: &log::LogLocation| {
            format!("[{}][{}] {}",
                    chrono::Local::now().format("%Y-%m-%d %T%.3f%z").to_string(), level, msg)
        }),
        output: vec![fern::OutputConfig::stdout()],
        level: logging_level,
    };
    if let Err(e) = fern::init_global_logger(logger_config, log::LogLevelFilter::Trace) {
        panic!("Failed to initialize global logger: {}", e);
    }

    // Play game.
    let mut state = mcts::State::new(initial_cells);
    if toggle_initial_player {
        state.toggle_active_role();
    }
    let mut graph = mcts::Graph::new();
    thud::initialize_search(state.clone(), &mut graph);

    let mut search_state = mcts::SearchState::new(rng, exploration_bias);
    let mut turn_number = 0;
    loop {
        info!("begin turn {}; board: {}", turn_number, board::format_board(state.board()));
        if graph.get_node(&state).is_none() {
            error!("board not found in playout graph; reinitializing");
            thud::initialize_search(state.clone(), &mut graph);
        }

        // {
        //     let mut available_actions: Vec<game::Action> =
        //         graph.get_node(&state).unwrap().get_child_list().iter()
        //         .map(|c| c.get_data().action).collect();
        //     available_actions.sort_by(thud_game::util::cmp_actions);
        //     let mut state_actions: Vec<game::Action> = state.actions().collect();
        //     state_actions.sort_by(thud_game::util::cmp_actions);
        //     debug!("Checking comparison of state_actions: {:?} vs. available actions: {:?}",
        //            state_actions, available_actions);
        //     assert_eq!(state_actions, available_actions);
        // }

        let mut best_action = None;
        for iteration in 0..iteration_count {
            if iteration % 100 == 0 {
                info!("iteration: {} / {} = {}%", iteration, iteration_count,
                      ((10000.0 * (iteration as f64) / (iteration_count as f64)) as usize as f64) / 100.0);
            }
            match search_state.search(&mut graph, &state,
                                      |_: usize| mcts::SearchSettings {
                                          simulation_count: simulation_count,
                                      }) {
                Ok(stats) => {
                    // TODO: this commented-out block is only valid when we haven't
                    // propagated a multi-visit payoff upwards from conneting to an
                    // extant vertex.
                    // if simulation_count == 1 && toplevel_visits != iteration + 1 {
                    //     console_ui::write_board(state.board());
                    //     for (action, stats) in stats.into_iter() {
                    //         println!("{:?}: [{}, {}] = {:?} / {}", action,
                    //                  (stats.payoff.values[0] as f64) / (stats.visits as f64),
                    //                  (stats.payoff.values[1] as f64) / (stats.visits as f64),
                    //                  stats.payoff, stats.visits);
                    //     }
                    //     panic!("total visits at top level is {}, but iteration count is {}",
                    //            toplevel_visits, iteration)
                    // }
                    if iteration % 1000 == 0 || iteration + 1 == iteration_count {
                        info!("root stats:");
                        let mut best_visits = ::std::u32::MIN;
                        let mut best_ucb = ::std::f64::MIN;
                        for (action, payoff, ucb) in stats.into_iter() {
                            info!("{:?}: {:?}; UCB = {:?}", action, payoff, ucb);
                            best_action = match (best_action, move_selection_criterion) {
                                (None, _) => Some(action),
                                (Some(_), thud::MoveSelectionCriterion::VisitCount) if best_visits < payoff.weight => {
                                    best_visits = payoff.weight;
                                    Some(action)
                                },
                                (Some(_), thud::MoveSelectionCriterion::Ucb) =>
                                    match ucb {
                                        Ok(mcts::UcbProxy::Value(x)) if best_ucb < x => {
                                            best_ucb = x;
                                            Some(action)
                                        },
                                        _ => best_action,
                                    },
                                _ => best_action,
                            }
                        }
                    }
                },
                Err(e) => {
                    error!("Error in seach iteration {}: {}", iteration, e);
                    break
                },
            }
        }

        // Finish turn.
        match best_action {
            Some(action) => {
                info!("turn {}: performing best action {:?}", turn_number, action);
                let mut canonical_state = graph.get_node(&state).unwrap().get_label().clone();
                canonical_state.do_action(&action);
                state.set_from_convolved(&canonical_state);
                if compact_graph {
                    debug!("collecting garbage and compacting search graph");
                    graph.retain_reachable_from(&[&canonical_state]);
                    debug!("done compacting search graph");
                }
                turn_number += 1;
            },
            None => {
                info!("turn {}: no best action. exiting.", turn_number);
                break
            }
        }
    }
    // console_ui::write_search_graph(&graph, &state);
}
<commit_msg>Make COMPACT_SEARCH_GRAPH_FLAG work in console_mcts.<commit_after>extern crate chrono;
extern crate clap;
extern crate fern;
#[macro_use]
extern crate log;
extern crate mcts;
extern crate rand;
extern crate thud;
extern crate thud_game;

use ::clap::App;
use ::rand::isaac::IsaacRng;
use ::rand::SeedableRng;

use thud_game::board;

fn main() {
    // Set up arg handling.
    let matches = {
        let app = thud::set_common_args(
            App::new("console_mcts")
                .version("0.1.0")
                .author("Stu Black <trurl@freeshell.org>")
                .about("Plays out Thud MCTS iterations"),
            &[thud::ITERATION_COUNT_FLAG,
              thud::SIMULATION_COUNT_FLAG,
              thud::EXPLORATION_BIAS_FLAG,
              thud::INITIAL_BOARD_FLAG,
              thud::INITIAL_PLAYER_FLAG,
              thud::LOG_LEVEL_FLAG,
              thud::MOVE_SELECTION_CRITERION_FLAG,
              thud::COMPACT_SEARCH_GRAPH_FLAG,]);
        app.get_matches()
    };
    let iteration_count =
        match matches.value_of(thud::ITERATION_COUNT_FLAG).unwrap().parse::<usize>() {
            Ok(x) => x,
            Err(e) => panic!("Bad iteration count: {}", e),
        };
    let simulation_count =
        match matches.value_of(thud::SIMULATION_COUNT_FLAG).unwrap().parse::<usize>() {
            Ok(x) => x,
            Err(e) => panic!("Bad simulation count: {}", e),
        };
    let exploration_bias =
        match matches.value_of(thud::EXPLORATION_BIAS_FLAG).unwrap().parse::<f64>() {
            Ok(x) => x,
            Err(e) => panic!("Bad exploration bias: {}", e),
        };
    let initial_cells =
        match matches.value_of(thud::INITIAL_BOARD_FLAG).map(|x| x.parse::<thud::InitialBoard>()) {
            Some(Ok(x)) => x.cells(),
            Some(Err(e)) => panic!("Bad initial board configuration: {}", e),
            None => board::Cells::default(),
        };
    let toggle_initial_player =
        match matches.value_of(thud::INITIAL_PLAYER_FLAG).map(|x| x.parse::<thud_game::Role>()) {
            None | Some(Ok(thud_game::Role::Dwarf)) => false,
            Some(Ok(thud_game::Role::Troll)) => true,
            Some(Err(x)) => panic!("{}", x),
        };
    let logging_level =
        match matches.value_of(thud::LOG_LEVEL_FLAG).map(|x| x.parse::<log::LogLevelFilter>()) {
            Some(Ok(x)) => x,
            Some(Err(_)) => panic!("Bad logging level '{}'", matches.value_of(thud::LOG_LEVEL_FLAG).unwrap()),
            None => log::LogLevelFilter::Info,
        };
    let move_selection_criterion =
        match matches.value_of(thud::MOVE_SELECTION_CRITERION_FLAG).map(|x| x.parse::<thud::MoveSelectionCriterion>()) {
            Some(Ok(x)) => x,
            Some(Err(e)) => panic!("Bad move selection criterion: {}", e),
            None => thud::MoveSelectionCriterion::VisitCount,
        };
    let rng =
        match matches.value_of(thud::RNG_SEED_FLAG).map(|x| x.parse::<u32>()) {
            Some(Ok(x)) => IsaacRng::from_seed(&[x]),
            Some(Err(e)) => panic!("Bad RNG seed: {}", e),
            None => IsaacRng::new_unseeded(),
        };
    let compact_graph = matches.is_present(thud::COMPACT_SEARCH_GRAPH_FLAG);

    // Set up logging.
    let logger_config = fern::DispatchConfig {
        format: Box::new(|msg: &str, level: &log::LogLevel, _location: &log::LogLocation| {
            format!("[{}][{}] {}",
                    chrono::Local::now().format("%Y-%m-%d %T%.3f%z").to_string(), level, msg)
        }),
        output: vec![fern::OutputConfig::stdout()],
        level: logging_level,
    };
    if let Err(e) = fern::init_global_logger(logger_config, log::LogLevelFilter::Trace) {
        panic!("Failed to initialize global logger: {}", e);
    }

    // Play game.
    let mut state = mcts::State::new(initial_cells);
    if toggle_initial_player {
        state.toggle_active_role();
    }
    let mut graph = mcts::Graph::new();
    thud::initialize_search(state.clone(), &mut graph);

    let mut search_state = mcts::SearchState::new(rng, exploration_bias);
    let mut turn_number = 0;
    loop {
        info!("begin turn {}; board: {}", turn_number, board::format_board(state.board()));
        if graph.get_node(&state).is_none() {
            error!("board not found in playout graph; reinitializing");
            thud::initialize_search(state.clone(), &mut graph);
        }

        // {
        //     let mut available_actions: Vec<game::Action> =
        //         graph.get_node(&state).unwrap().get_child_list().iter()
        //         .map(|c| c.get_data().action).collect();
        //     available_actions.sort_by(thud_game::util::cmp_actions);
        //     let mut state_actions: Vec<game::Action> = state.actions().collect();
        //     state_actions.sort_by(thud_game::util::cmp_actions);
        //     debug!("Checking comparison of state_actions: {:?} vs. available actions: {:?}",
        //            state_actions, available_actions);
        //     assert_eq!(state_actions, available_actions);
        // }

        let mut best_action = None;
        for iteration in 0..iteration_count {
            if iteration % 100 == 0 {
                info!("iteration: {} / {} = {}%", iteration, iteration_count,
                      ((10000.0 * (iteration as f64) / (iteration_count as f64)) as usize as f64) / 100.0);
            }
            match search_state.search(&mut graph, &state,
                                      |_: usize| mcts::SearchSettings {
                                          simulation_count: simulation_count,
                                      }) {
                Ok(stats) => {
                    // TODO: this commented-out block is only valid when we haven't
                    // propagated a multi-visit payoff upwards from conneting to an
                    // extant vertex.
                    // if simulation_count == 1 && toplevel_visits != iteration + 1 {
                    //     console_ui::write_board(state.board());
                    //     for (action, stats) in stats.into_iter() {
                    //         println!("{:?}: [{}, {}] = {:?} / {}", action,
                    //                  (stats.payoff.values[0] as f64) / (stats.visits as f64),
                    //                  (stats.payoff.values[1] as f64) / (stats.visits as f64),
                    //                  stats.payoff, stats.visits);
                    //     }
                    //     panic!("total visits at top level is {}, but iteration count is {}",
                    //            toplevel_visits, iteration)
                    // }
                    if iteration % 1000 == 0 || iteration + 1 == iteration_count {
                        info!("root stats:");
                        let mut best_visits = ::std::u32::MIN;
                        let mut best_ucb = ::std::f64::MIN;
                        for (action, payoff, ucb) in stats.into_iter() {
                            info!("{:?}: {:?}; UCB = {:?}", action, payoff, ucb);
                            best_action = match (best_action, move_selection_criterion) {
                                (None, _) => Some(action),
                                (Some(_), thud::MoveSelectionCriterion::VisitCount) if best_visits < payoff.weight => {
                                    best_visits = payoff.weight;
                                    Some(action)
                                },
                                (Some(_), thud::MoveSelectionCriterion::Ucb) =>
                                    match ucb {
                                        Ok(mcts::UcbProxy::Value(x)) if best_ucb < x => {
                                            best_ucb = x;
                                            Some(action)
                                        },
                                        _ => best_action,
                                    },
                                _ => best_action,
                            }
                        }
                    }
                },
                Err(e) => {
                    error!("Error in seach iteration {}: {}", iteration, e);
                    break
                },
            }
        }

        // Finish turn.
        match best_action {
            Some(action) => {
                info!("turn {}: performing best action {:?}", turn_number, action);
                let mut canonical_state = graph.get_node(&state).unwrap().get_label().clone();
                canonical_state.do_action(&action);
                state.set_from_convolved(&canonical_state);
                if compact_graph {
                    debug!("collecting garbage and compacting search graph");
                    graph.retain_reachable_from(&[&canonical_state]);
                    debug!("done compacting search graph");
                } else {
                    debug!("potential memory leak: not compatcing search graph");
                }
                turn_number += 1;
            },
            None => {
                info!("turn {}: no best action. exiting.", turn_number);
                break
            }
        }
    }
    // console_ui::write_search_graph(&graph, &state);
}
<|endoftext|>


<commit_before>//
// Copyright (c) Pirmin Kalberer. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//

use crate::core::config::ApplicationCfg;
use crate::mvt_service::MvtService;
use crate::runtime_config::{config_from_args, service_from_args};
use crate::static_files::StaticFiles;
use actix_cors::Cors;
use actix_files as fs;
use actix_web::dev::BodyEncoding;
use actix_web::http::{header, ContentEncoding};
use actix_web::middleware::Compress;
use actix_web::{guard, middleware, web, App, HttpRequest, HttpResponse, HttpServer, Result};
use clap::ArgMatches;
use log::Level;
use num_cpus;
use open;
use std::collections::HashMap;
use std::str;
use std::str::FromStr;

static DINO: &'static str = "             xxxxxxxxx
        xxxxxxxxxxxxxxxxxxxxxxxx
      xxxxxxxxxxxxxxxxxxxxxxxxxxxx
     xxxxxxxxxxxxxxxxxxxxxxxxx xxxx
     xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
   xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   xxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   xxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx     xxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx      x
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxx    xxxxxxxxxxx
xxxxxxxxxxxxxx                   xxxxxx
xxxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxxx
xxxxxxxxx
xxxxxxx
xxxxxx
xxxxxxx";

async fn mvt_metadata(service: web::Data<MvtService>) -> Result<HttpResponse> {
    let json = service.get_mvt_metadata().unwrap();
    Ok(HttpResponse::Ok().json(json))
}

/// Font list for Maputnik
async fn fontstacks() -> Result<HttpResponse> {
    Ok(HttpResponse::Ok().json(["Roboto Medium", "Roboto Regular"]))
}

// Include method fonts() which returns HashMap with embedded font files
include!(concat!(env!("OUT_DIR"), "/fonts.rs"));

/// Fonts for Maputnik
/// Example: /fonts/Open%20Sans%20Regular,Arial%20Unicode%20MS%20Regular/0-255.pbf
async fn fonts_pbf(params: web::Path<(String, String)>) -> Result<HttpResponse> {
    let fontpbfs = fonts();
    let fontlist = &params.as_ref().0;
    let range = &params.as_ref().1;
    let mut fonts = fontlist.split(",").collect::<Vec<_>>();
    fonts.push("Roboto Regular"); // Fallback
    let mut resp = HttpResponse::NotFound().finish();
    for font in fonts {
        let key = format!("fonts/{}/{}.pbf", font.replace("%20", " "), range);
        debug!("Font lookup: {}", key);
        if let Some(pbf) = fontpbfs.get(&key as &str) {
            resp = HttpResponse::Ok()
                .content_type("application/x-protobuf")
                // data is already gzip compressed
                .encoding(ContentEncoding::Identity)
                .header(header::CONTENT_ENCODING, "gzip")
                .body(*pbf); // TODO: chunked response
            break;
        }
    }
    Ok(resp)
}

fn req_baseurl(req: &HttpRequest) -> String {
    let conninfo = req.connection_info();
    format!("{}://{}", conninfo.scheme(), conninfo.host())
}

async fn tileset_tilejson(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let json = service.get_tilejson(&req_baseurl(&req), &tileset).unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tileset_style_json(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let json = service.get_stylejson(&req_baseurl(&req), &tileset).unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tileset_metadata_json(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
) -> Result<HttpResponse> {
    let json = service.get_mbtiles_metadata(&tileset).unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tile_pbf(
    config: web::Data<ApplicationCfg>,
    service: web::Data<MvtService>,
    params: web::Path<(String, u8, u32, u32)>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let tileset = &params.as_ref().0;
    let z = params.1;
    let x = params.2;
    let y = params.3;
    let gzip = req
        .headers()
        .get(header::ACCEPT_ENCODING)
        .and_then(|headerval| {
            headerval
                .to_str()
                .ok()
                .and_then(|headerstr| Some(headerstr.contains("gzip")))
        })
        .unwrap_or(false);
    let tile = service.tile_cached(tileset, x, y, z, gzip, None);
    let cache_max_age = config.webserver.cache_control_max_age.unwrap_or(300);

    let resp = if let Some(tile) = tile {
        HttpResponse::Ok()
            .content_type("application/x-protobuf")
            .if_true(gzip, |r| {
                // data is already gzip compressed
                r.encoding(ContentEncoding::Identity)
                    .header(header::CONTENT_ENCODING, "gzip");
            })
            .header(header::CACHE_CONTROL, format!("max-age={}", cache_max_age))
            .body(tile) // TODO: chunked response
    } else {
        HttpResponse::NoContent().finish()
    };
    Ok(resp)
}

lazy_static! {
    static ref STATIC_FILES: StaticFiles = StaticFiles::init();
}

async fn static_file_handler(req: HttpRequest) -> Result<HttpResponse> {
    let key = req.path()[1..].to_string();
    let resp = if let Some(ref content) = STATIC_FILES.content(None, key) {
        HttpResponse::Ok()
            .header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*") // TOOD: use Actix middleware
            .content_type(content.1)
            .body(content.0) // TODO: chunked response
    } else {
        HttpResponse::NotFound().finish()
    };
    Ok(resp)
}

#[derive(Deserialize)]
struct DrilldownParams {
    minzoom: Option<u8>,
    maxzoom: Option<u8>,
    points: String, //x1,y1,x2,y2,..
}

async fn drilldown_handler(
    service: web::Data<MvtService>,
    params: web::Query<DrilldownParams>,
) -> Result<HttpResponse> {
    let tileset = None; // all tilesets
    let progress = false;
    let points: Vec<f64> = params
        .points
        .split(",")
        .map(|v| {
            v.parse()
                .expect("Error parsing 'point' as pair of float values")
            //FIXME: map_err(|_| error::ErrorInternalServerError("...")
        })
        .collect();
    let stats = service.drilldown(tileset, params.minzoom, params.maxzoom, points, progress);
    let json = stats.as_json().unwrap();
    Ok(HttpResponse::Ok().json(json))
}

#[actix_web::main]
pub async fn webserver(args: ArgMatches<'static>) -> std::io::Result<()> {
    let config = config_from_args(&args);
    let host = config
        .webserver
        .bind
        .clone()
        .unwrap_or("127.0.0.1".to_string());
    let port = config.webserver.port.unwrap_or(6767);
    let bind_addr = format!("{}:{}", host, port);
    let workers = config.webserver.threads.unwrap_or(num_cpus::get() as u8);
    let mvt_viewer = config.service.mvt.viewer;
    let openbrowser =
        bool::from_str(args.value_of("openbrowser").unwrap_or("true")).unwrap_or(false);
    let static_dirs = config.webserver.static_.clone();

    let mut service = service_from_args(&config, &args);
    service.prepare_feature_queries();
    service.init_cache();

    let server = HttpServer::new(move || {
        let mut app = App::new()
            .data(config.clone())
            .data(service.clone())
            .wrap(middleware::Logger::new("%r %s %b %Dms %a"))
            .wrap(Compress::default())
            .wrap(
                Cors::new()
                    .send_wildcard()
                    .allowed_methods(vec!["GET"])
                    .finish(),
            )
            .service(
                web::resource("/index.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(mvt_metadata),
                ),
            )
            .service(
                web::resource("/fontstacks.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fontstacks),
                ),
            )
            .service(
                web::resource("/fonts.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fontstacks),
                ),
            )
            .service(
                web::resource("/fonts/{fonts}/{range}.pbf").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fonts_pbf),
                ),
            );
        for static_dir in &static_dirs {
            let dir = &static_dir.dir;
            if std::path::Path::new(dir).is_dir() {
                info!("Serving static files from directory '{}'", dir);
                app = app.service(fs::Files::new(&static_dir.path, dir));
            } else {
                warn!("Static file directory '{}' not found", dir);
            }
        }
        app = app
            .service(
                web::resource("/{tileset}.style.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_style_json),
                ),
            )
            .service(
                web::resource("/{tileset}/metadata.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_metadata_json),
                ),
            )
            .service(
                web::resource("/{tileset}.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_tilejson),
                ),
            )
            .service(
                web::resource("/{tileset}/{z}/{x}/{y}.pbf").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tile_pbf),
                ),
            );
        if mvt_viewer {
            app = app.service(
                web::resource("/drilldown").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(drilldown_handler),
                ),
            );
            app = app.default_service(web::to(static_file_handler));
        }
        app
    })
    .workers(workers as usize)
    .bind(&bind_addr)
    .expect("Can not start server on given IP/Port")
    .shutdown_timeout(3) // default: 30s
    .run();

    if log_enabled!(Level::Info) {
        println!("{}", DINO);
    }

    if openbrowser && mvt_viewer {
        let _res = open::that(format!("http://{}:{}", &host, port));
    }

    server.await
}
<commit_msg>Use web::block in web server<commit_after>//
// Copyright (c) Pirmin Kalberer. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//

use crate::core::config::ApplicationCfg;
use crate::mvt_service::MvtService;
use crate::runtime_config::{config_from_args, service_from_args};
use crate::static_files::StaticFiles;
use actix_cors::Cors;
use actix_files as fs;
use actix_web::dev::BodyEncoding;
use actix_web::http::{header, ContentEncoding};
use actix_web::middleware::Compress;
use actix_web::{guard, middleware, web, App, HttpRequest, HttpResponse, HttpServer, Result};
use actix_web::web;
use clap::ArgMatches;
use log::Level;
use num_cpus;
use open;
use std::collections::HashMap;
use std::convert::Infallible;
use std::str;
use std::str::FromStr;

static DINO: &'static str = "             xxxxxxxxx
        xxxxxxxxxxxxxxxxxxxxxxxx
      xxxxxxxxxxxxxxxxxxxxxxxxxxxx
     xxxxxxxxxxxxxxxxxxxxxxxxx xxxx
     xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
   xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   xxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   xxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx     xxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx      x
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxx    xxxxxxxxxxx
xxxxxxxxxxxxxx                   xxxxxx
xxxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxxx
xxxxxxxxx
xxxxxxx
xxxxxx
xxxxxxx";

async fn mvt_metadata(service: web::Data<MvtService>) -> Result<HttpResponse> {
    let json = service.get_mvt_metadata().unwrap();
    Ok(HttpResponse::Ok().json(json))
}

/// Font list for Maputnik
async fn fontstacks() -> Result<HttpResponse> {
    Ok(HttpResponse::Ok().json(["Roboto Medium", "Roboto Regular"]))
}

// Include method fonts() which returns HashMap with embedded font files
include!(concat!(env!("OUT_DIR"), "/fonts.rs"));

/// Fonts for Maputnik
/// Example: /fonts/Open%20Sans%20Regular,Arial%20Unicode%20MS%20Regular/0-255.pbf
async fn fonts_pbf(params: web::Path<(String, String)>) -> Result<HttpResponse> {
    let fontpbfs = fonts();
    let fontlist = &params.as_ref().0;
    let range = &params.as_ref().1;
    let mut fonts = fontlist.split(",").collect::<Vec<_>>();
    fonts.push("Roboto Regular"); // Fallback
    let mut resp = HttpResponse::NotFound().finish();
    for font in fonts {
        let key = format!("fonts/{}/{}.pbf", font.replace("%20", " "), range);
        debug!("Font lookup: {}", key);
        if let Some(pbf) = fontpbfs.get(&key as &str) {
            resp = HttpResponse::Ok()
                .content_type("application/x-protobuf")
                // data is already gzip compressed
                .encoding(ContentEncoding::Identity)
                .header(header::CONTENT_ENCODING, "gzip")
                .body(*pbf); // TODO: chunked response
            break;
        }
    }
    Ok(resp)
}

fn req_baseurl(req: &HttpRequest) -> String {
    let conninfo = req.connection_info();
    format!("{}://{}", conninfo.scheme(), conninfo.host())
}

async fn tileset_tilejson(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let url = req_baseurl(&req);
    let json =
        web::block::<_, _, Infallible>(move || Ok(service.get_tilejson(&url, &tileset).unwrap()))
            .await
            .unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tileset_style_json(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let json = service.get_stylejson(&req_baseurl(&req), &tileset).unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tileset_metadata_json(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
) -> Result<HttpResponse> {
    let json =
        web::block::<_, _, Infallible>(move || Ok(service.get_mbtiles_metadata(&tileset).unwrap()))
            .await
            .unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tile_pbf(
    config: web::Data<ApplicationCfg>,
    service: web::Data<MvtService>,
    params: web::Path<(String, u8, u32, u32)>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let params = params.into_inner();
    let tileset = params.0;
    let z = params.1;
    let x = params.2;
    let y = params.3;
    let gzip = req
        .headers()
        .get(header::ACCEPT_ENCODING)
        .and_then(|headerval| {
            headerval
                .to_str()
                .ok()
                .and_then(|headerstr| Some(headerstr.contains("gzip")))
        })
        .unwrap_or(false);
    let tile = web::block::<_, _, Infallible>(move || {
        Ok(service.tile_cached(&tileset, x, y, z, gzip, None))
    })
    .await;

    let resp = match tile {
        Ok(Some(tile)) => {
            let cache_max_age = config.webserver.cache_control_max_age.unwrap_or(300);
            HttpResponse::Ok()
                .content_type("application/x-protobuf")
                .if_true(gzip, |r| {
                    // data is already gzip compressed
                    r.encoding(ContentEncoding::Identity)
                        .header(header::CONTENT_ENCODING, "gzip");
                })
                .header(header::CACHE_CONTROL, format!("max-age={}", cache_max_age))
                .body(tile) // TODO: chunked response
        }
        Ok(None) => HttpResponse::NoContent().finish(),
        Err(e) => {
            error!("{}", e);
            HttpResponse::InternalServerError().finish()
        }
    };
    Ok(resp)
}

lazy_static! {
    static ref STATIC_FILES: StaticFiles = StaticFiles::init();
}

async fn static_file_handler(req: HttpRequest) -> Result<HttpResponse> {
    let key = req.path()[1..].to_string();
    let resp = if let Some(ref content) = STATIC_FILES.content(None, key) {
        HttpResponse::Ok()
            .header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*") // TOOD: use Actix middleware
            .content_type(content.1)
            .body(content.0) // TODO: chunked response
    } else {
        HttpResponse::NotFound().finish()
    };
    Ok(resp)
}

#[derive(Deserialize)]
struct DrilldownParams {
    minzoom: Option<u8>,
    maxzoom: Option<u8>,
    points: String, //x1,y1,x2,y2,..
}

async fn drilldown_handler(
    service: web::Data<MvtService>,
    params: web::Query<DrilldownParams>,
) -> Result<HttpResponse> {
    let tileset = None; // all tilesets
    let progress = false;
    let points: Vec<f64> = params
        .points
        .split(",")
        .map(|v| {
            v.parse()
                .expect("Error parsing 'point' as pair of float values")
            //FIXME: map_err(|_| error::ErrorInternalServerError("...")
        })
        .collect();
    let stats = service.drilldown(tileset, params.minzoom, params.maxzoom, points, progress);
    let json = stats.as_json().unwrap();
    Ok(HttpResponse::Ok().json(json))
}

#[actix_web::main]
pub async fn webserver(args: ArgMatches<'static>) -> std::io::Result<()> {
    let config = config_from_args(&args);
    let host = config
        .webserver
        .bind
        .clone()
        .unwrap_or("127.0.0.1".to_string());
    let port = config.webserver.port.unwrap_or(6767);
    let bind_addr = format!("{}:{}", host, port);
    let workers = config.webserver.threads.unwrap_or(num_cpus::get() as u8);
    let mvt_viewer = config.service.mvt.viewer;
    let openbrowser =
        bool::from_str(args.value_of("openbrowser").unwrap_or("true")).unwrap_or(false);
    let static_dirs = config.webserver.static_.clone();

    let mut service = service_from_args(&config, &args);
    let service = web::block::<_, _, Infallible>(|| {
        service.prepare_feature_queries();
        service.init_cache();
        Ok(service)
    })
    .await
    .unwrap();

    let server = HttpServer::new(move || {
        let mut app = App::new()
            .data(config.clone())
            .data(service.clone())
            .wrap(middleware::Logger::new("%r %s %b %Dms %a"))
            .wrap(Compress::default())
            .wrap(
                Cors::new()
                    .send_wildcard()
                    .allowed_methods(vec!["GET"])
                    .finish(),
            )
            .service(
                web::resource("/index.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(mvt_metadata),
                ),
            )
            .service(
                web::resource("/fontstacks.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fontstacks),
                ),
            )
            .service(
                web::resource("/fonts.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fontstacks),
                ),
            )
            .service(
                web::resource("/fonts/{fonts}/{range}.pbf").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fonts_pbf),
                ),
            );
        for static_dir in &static_dirs {
            let dir = &static_dir.dir;
            if std::path::Path::new(dir).is_dir() {
                info!("Serving static files from directory '{}'", dir);
                app = app.service(fs::Files::new(&static_dir.path, dir));
            } else {
                warn!("Static file directory '{}' not found", dir);
            }
        }
        app = app
            .service(
                web::resource("/{tileset}.style.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_style_json),
                ),
            )
            .service(
                web::resource("/{tileset}/metadata.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_metadata_json),
                ),
            )
            .service(
                web::resource("/{tileset}.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_tilejson),
                ),
            )
            .service(
                web::resource("/{tileset}/{z}/{x}/{y}.pbf").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tile_pbf),
                ),
            );
        if mvt_viewer {
            app = app.service(
                web::resource("/drilldown").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(drilldown_handler),
                ),
            );
            app = app.default_service(web::to(static_file_handler));
        }
        app
    })
    .workers(workers as usize)
    .bind(&bind_addr)
    .expect("Can not start server on given IP/Port")
    .shutdown_timeout(3) // default: 30s
    .run();

    if log_enabled!(Level::Info) {
        println!("{}", DINO);
    }

    if openbrowser && mvt_viewer {
        let _res = open::that(format!("http://{}:{}", &host, port));
    }

    server.await
}
<|endoftext|>


<commit_before>#![cfg(feature = "glutin")]
/*!

Backend implementation for the glutin library

# Features

Only available if the 'glutin' feature is enabled.

*/
pub extern crate glutin;

pub mod headless;

use {Frame, IncompatibleOpenGl, SwapBuffersError};
use debug;
use context;
use backend;
use backend::Context;
use backend::Backend;
use glutin::GlContext;
use std;
use std::cell::{Cell, RefCell, Ref};
use std::error::Error;
use std::fmt;
use std::rc::Rc;
use std::ops::Deref;
use std::os::raw::c_void;

/// A GL context combined with a facade for drawing upon.
///
/// The `Display` uses **glutin** for the **Window** and its associated GL **Context**.
///
/// These are stored alongside a glium-specific context.
#[derive(Clone)]
pub struct Display {
    // contains everything related to the current context and its state
    context: Rc<context::Context>,
    // The glutin Window alongside its associated GL Context.
    gl_window: Rc<RefCell<glutin::GlWindow>>,
    // Used to check whether the framebuffer dimensions have changed between frames. If they have,
    // the glutin context must be resized accordingly.
    last_framebuffer_dimensions: Cell<(u32, u32)>,
}

/// An implementation of the `Backend` trait for glutin.
#[derive(Clone)]
pub struct GlutinBackend(Rc<RefCell<glutin::GlWindow>>);

/// Error that can happen while creating a glium display.
#[derive(Debug)]
pub enum DisplayCreationError {
    /// An error has happened while creating the backend.
    GlutinCreationError(glutin::CreationError),
    /// The OpenGL implementation is too old.
    IncompatibleOpenGl(IncompatibleOpenGl),
}

impl Display {
    /// Create a new glium `Display` from the given context and window builders.
    ///
    /// Performs a compatibility check to make sure that all core elements of glium are supported
    /// by the implementation.
    pub fn new(
        window_builder: glutin::WindowBuilder,
        context_builder: glutin::ContextBuilder,
        events_loop: &glutin::EventsLoop,
    ) -> Result<Self, DisplayCreationError>
    {
        let gl_window = try!(glutin::GlWindow::new(window_builder, context_builder, events_loop));
        Self::from_gl_window(gl_window).map_err(From::from)
    }

    /// Create a new glium `Display`.
    ///
    /// Performs a compatibility check to make sure that all core elements of glium are supported
    /// by the implementation.
    pub fn from_gl_window(gl_window: glutin::GlWindow) -> Result<Self, IncompatibleOpenGl> {
        Self::with_debug(gl_window, Default::default())
    }

    /// Create a new glium `Display`.
    ///
    /// This function does the same as `build_glium`, except that the resulting context
    /// will assume that the current OpenGL context will never change.
    pub unsafe fn unchecked(gl_window: glutin::GlWindow) -> Result<Self, IncompatibleOpenGl> {
        Self::unchecked_with_debug(gl_window, Default::default())
    }

    /// The same as the `new` constructor, but allows for specifying debug callback behaviour.
    pub fn with_debug(gl_window: glutin::GlWindow, debug: debug::DebugCallbackBehavior)
        -> Result<Self, IncompatibleOpenGl>
    {
        Self::new_inner(gl_window, debug, true)
    }

    /// The same as the `unchecked` constructor, but allows for specifying debug callback behaviour.
    pub unsafe fn unchecked_with_debug(
        gl_window: glutin::GlWindow,
        debug: debug::DebugCallbackBehavior,
    ) -> Result<Self, IncompatibleOpenGl>
    {
        Self::new_inner(gl_window, debug, false)
    }

    fn new_inner(
        gl_window: glutin::GlWindow,
        debug: debug::DebugCallbackBehavior,
        checked: bool,
    ) -> Result<Self, IncompatibleOpenGl>
    {
        let gl_window = Rc::new(RefCell::new(gl_window));
        let glutin_backend = GlutinBackend(gl_window.clone());
        let framebuffer_dimensions = glutin_backend.get_framebuffer_dimensions();
        let context = try!(unsafe { context::Context::new(glutin_backend, checked, debug) });
        Ok(Display {
            gl_window: gl_window,
            context: context,
            last_framebuffer_dimensions: Cell::new(framebuffer_dimensions),
        })
    }

    /// Rebuilds the Display's `GlWindow` with the given window and context builders.
    ///
    /// This method ensures that the new `GlWindow`'s `Context` will share the display lists of the
    /// original `GlWindow`'s `Context`.
    pub fn rebuild(
        &self,
        window_builder: glutin::WindowBuilder,
        context_builder: glutin::ContextBuilder,
        events_loop: &glutin::EventsLoop,
    ) -> Result<(), DisplayCreationError>
    {
        // Share the display lists of the existing context.
        let new_gl_window = {
            let gl_window = self.gl_window.borrow();
            let context_builder = context_builder.with_shared_lists(gl_window.context());
            try!(glutin::GlWindow::new(window_builder, context_builder, events_loop))
        };

        {
            // Replace the stored GlWindow with the new one.
            let mut gl_window = self.gl_window.borrow_mut();
            std::mem::replace(&mut (*gl_window), new_gl_window);
        }

        // Rebuild the Context.
        let backend = GlutinBackend(self.gl_window.clone());
        try!(unsafe { self.context.rebuild(backend) });

        Ok(())
    }

    /// Borrow the inner glutin GlWindow.
    #[inline]
    pub fn gl_window(&self) -> Ref<glutin::GlWindow> {
        self.gl_window.borrow()
    }

    /// Start drawing on the backbuffer.
    ///
    /// This function returns a `Frame`, which can be used to draw on it. When the `Frame` is
    /// destroyed, the buffers are swapped.
    ///
    /// Note that destroying a `Frame` is immediate, even if vsync is enabled.
    ///
    /// If the framebuffer dimensions have changed since the last call to `draw`, the inner glutin
    /// context will be resized accordingly before returning the `Frame`.
    #[inline]
    pub fn draw(&self) -> Frame {
        let (w, h) = self.get_framebuffer_dimensions();

        // If the size of the framebuffer has changed, resize the context.
        if self.last_framebuffer_dimensions.get() != (w, h) {
            self.last_framebuffer_dimensions.set((w, h));
            self.gl_window.borrow().resize((w, h).into());
        }

        Frame::new(self.context.clone(), (w, h))
    }
}

impl fmt::Display for DisplayCreationError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(fmt, "{}", self.description())
    }
}

impl Error for DisplayCreationError {
    #[inline]
    fn description(&self) -> &str {
        match *self {
            DisplayCreationError::GlutinCreationError(ref err) => err.description(),
            DisplayCreationError::IncompatibleOpenGl(ref err) => err.description(),
        }
    }

    #[inline]
    fn cause(&self) -> Option<&Error> {
        match *self {
            DisplayCreationError::GlutinCreationError(ref err) => Some(err),
            DisplayCreationError::IncompatibleOpenGl(ref err) => Some(err),
        }
    }
}

impl From<glutin::CreationError> for DisplayCreationError {
    #[inline]
    fn from(err: glutin::CreationError) -> DisplayCreationError {
        DisplayCreationError::GlutinCreationError(err)
    }
}

impl From<IncompatibleOpenGl> for DisplayCreationError {
    #[inline]
    fn from(err: IncompatibleOpenGl) -> DisplayCreationError {
        DisplayCreationError::IncompatibleOpenGl(err)
    }
}

impl Deref for Display {
    type Target = Context;
    #[inline]
    fn deref(&self) -> &Context {
        &self.context
    }
}

impl backend::Facade for Display {
    #[inline]
    fn get_context(&self) -> &Rc<Context> {
        &self.context
    }
}

impl Deref for GlutinBackend {
    type Target = Rc<RefCell<glutin::GlWindow>>;
    #[inline]
    fn deref(&self) -> &Rc<RefCell<glutin::GlWindow>> {
        &self.0
    }
}

unsafe impl Backend for GlutinBackend {
    #[inline]
    fn swap_buffers(&self) -> Result<(), SwapBuffersError> {
        match self.borrow().swap_buffers() {
            Ok(()) => Ok(()),
            Err(glutin::ContextError::IoError(e)) => panic!("Error while swapping buffers: {:?}", e),
            Err(glutin::ContextError::ContextLost) => Err(SwapBuffersError::ContextLost),
        }
    }

    #[inline]
    unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
        self.borrow().get_proc_address(symbol) as *const _
    }

    #[inline]
    fn get_framebuffer_dimensions(&self) -> (u32, u32) {
        // TODO: 800x600 ?
        let gl_window = self.borrow();
        // get_inner_size() returns size in pixels (changed in winit 0.9 - this
        // used to return a hidpi scaled size)
        let (width, height) = gl_window.get_inner_size().map(Into::into).unwrap_or((800, 600));

        (width, height)
    }

    #[inline]
    fn is_current(&self) -> bool {
        self.borrow().is_current()
    }

    #[inline]
    unsafe fn make_current(&self) {
        self.borrow().make_current().unwrap();
    }
}
<commit_msg>Ensure get_framebuffer_dimensions returns physical pixels, not logical<commit_after>#![cfg(feature = "glutin")]
/*!

Backend implementation for the glutin library

# Features

Only available if the 'glutin' feature is enabled.

*/
pub extern crate glutin;

pub mod headless;

use {Frame, IncompatibleOpenGl, SwapBuffersError};
use debug;
use context;
use backend;
use backend::Context;
use backend::Backend;
use glutin::GlContext;
use std;
use std::cell::{Cell, RefCell, Ref};
use std::error::Error;
use std::fmt;
use std::rc::Rc;
use std::ops::Deref;
use std::os::raw::c_void;

/// A GL context combined with a facade for drawing upon.
///
/// The `Display` uses **glutin** for the **Window** and its associated GL **Context**.
///
/// These are stored alongside a glium-specific context.
#[derive(Clone)]
pub struct Display {
    // contains everything related to the current context and its state
    context: Rc<context::Context>,
    // The glutin Window alongside its associated GL Context.
    gl_window: Rc<RefCell<glutin::GlWindow>>,
    // Used to check whether the framebuffer dimensions have changed between frames. If they have,
    // the glutin context must be resized accordingly.
    last_framebuffer_dimensions: Cell<(u32, u32)>,
}

/// An implementation of the `Backend` trait for glutin.
#[derive(Clone)]
pub struct GlutinBackend(Rc<RefCell<glutin::GlWindow>>);

/// Error that can happen while creating a glium display.
#[derive(Debug)]
pub enum DisplayCreationError {
    /// An error has happened while creating the backend.
    GlutinCreationError(glutin::CreationError),
    /// The OpenGL implementation is too old.
    IncompatibleOpenGl(IncompatibleOpenGl),
}

impl Display {
    /// Create a new glium `Display` from the given context and window builders.
    ///
    /// Performs a compatibility check to make sure that all core elements of glium are supported
    /// by the implementation.
    pub fn new(
        window_builder: glutin::WindowBuilder,
        context_builder: glutin::ContextBuilder,
        events_loop: &glutin::EventsLoop,
    ) -> Result<Self, DisplayCreationError>
    {
        let gl_window = try!(glutin::GlWindow::new(window_builder, context_builder, events_loop));
        Self::from_gl_window(gl_window).map_err(From::from)
    }

    /// Create a new glium `Display`.
    ///
    /// Performs a compatibility check to make sure that all core elements of glium are supported
    /// by the implementation.
    pub fn from_gl_window(gl_window: glutin::GlWindow) -> Result<Self, IncompatibleOpenGl> {
        Self::with_debug(gl_window, Default::default())
    }

    /// Create a new glium `Display`.
    ///
    /// This function does the same as `build_glium`, except that the resulting context
    /// will assume that the current OpenGL context will never change.
    pub unsafe fn unchecked(gl_window: glutin::GlWindow) -> Result<Self, IncompatibleOpenGl> {
        Self::unchecked_with_debug(gl_window, Default::default())
    }

    /// The same as the `new` constructor, but allows for specifying debug callback behaviour.
    pub fn with_debug(gl_window: glutin::GlWindow, debug: debug::DebugCallbackBehavior)
        -> Result<Self, IncompatibleOpenGl>
    {
        Self::new_inner(gl_window, debug, true)
    }

    /// The same as the `unchecked` constructor, but allows for specifying debug callback behaviour.
    pub unsafe fn unchecked_with_debug(
        gl_window: glutin::GlWindow,
        debug: debug::DebugCallbackBehavior,
    ) -> Result<Self, IncompatibleOpenGl>
    {
        Self::new_inner(gl_window, debug, false)
    }

    fn new_inner(
        gl_window: glutin::GlWindow,
        debug: debug::DebugCallbackBehavior,
        checked: bool,
    ) -> Result<Self, IncompatibleOpenGl>
    {
        let gl_window = Rc::new(RefCell::new(gl_window));
        let glutin_backend = GlutinBackend(gl_window.clone());
        let framebuffer_dimensions = glutin_backend.get_framebuffer_dimensions();
        let context = try!(unsafe { context::Context::new(glutin_backend, checked, debug) });
        Ok(Display {
            gl_window: gl_window,
            context: context,
            last_framebuffer_dimensions: Cell::new(framebuffer_dimensions),
        })
    }

    /// Rebuilds the Display's `GlWindow` with the given window and context builders.
    ///
    /// This method ensures that the new `GlWindow`'s `Context` will share the display lists of the
    /// original `GlWindow`'s `Context`.
    pub fn rebuild(
        &self,
        window_builder: glutin::WindowBuilder,
        context_builder: glutin::ContextBuilder,
        events_loop: &glutin::EventsLoop,
    ) -> Result<(), DisplayCreationError>
    {
        // Share the display lists of the existing context.
        let new_gl_window = {
            let gl_window = self.gl_window.borrow();
            let context_builder = context_builder.with_shared_lists(gl_window.context());
            try!(glutin::GlWindow::new(window_builder, context_builder, events_loop))
        };

        {
            // Replace the stored GlWindow with the new one.
            let mut gl_window = self.gl_window.borrow_mut();
            std::mem::replace(&mut (*gl_window), new_gl_window);
        }

        // Rebuild the Context.
        let backend = GlutinBackend(self.gl_window.clone());
        try!(unsafe { self.context.rebuild(backend) });

        Ok(())
    }

    /// Borrow the inner glutin GlWindow.
    #[inline]
    pub fn gl_window(&self) -> Ref<glutin::GlWindow> {
        self.gl_window.borrow()
    }

    /// Start drawing on the backbuffer.
    ///
    /// This function returns a `Frame`, which can be used to draw on it. When the `Frame` is
    /// destroyed, the buffers are swapped.
    ///
    /// Note that destroying a `Frame` is immediate, even if vsync is enabled.
    ///
    /// If the framebuffer dimensions have changed since the last call to `draw`, the inner glutin
    /// context will be resized accordingly before returning the `Frame`.
    #[inline]
    pub fn draw(&self) -> Frame {
        let (w, h) = self.get_framebuffer_dimensions();

        // If the size of the framebuffer has changed, resize the context.
        if self.last_framebuffer_dimensions.get() != (w, h) {
            self.last_framebuffer_dimensions.set((w, h));
            self.gl_window.borrow().resize((w, h).into());
        }

        Frame::new(self.context.clone(), (w, h))
    }
}

impl fmt::Display for DisplayCreationError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(fmt, "{}", self.description())
    }
}

impl Error for DisplayCreationError {
    #[inline]
    fn description(&self) -> &str {
        match *self {
            DisplayCreationError::GlutinCreationError(ref err) => err.description(),
            DisplayCreationError::IncompatibleOpenGl(ref err) => err.description(),
        }
    }

    #[inline]
    fn cause(&self) -> Option<&Error> {
        match *self {
            DisplayCreationError::GlutinCreationError(ref err) => Some(err),
            DisplayCreationError::IncompatibleOpenGl(ref err) => Some(err),
        }
    }
}

impl From<glutin::CreationError> for DisplayCreationError {
    #[inline]
    fn from(err: glutin::CreationError) -> DisplayCreationError {
        DisplayCreationError::GlutinCreationError(err)
    }
}

impl From<IncompatibleOpenGl> for DisplayCreationError {
    #[inline]
    fn from(err: IncompatibleOpenGl) -> DisplayCreationError {
        DisplayCreationError::IncompatibleOpenGl(err)
    }
}

impl Deref for Display {
    type Target = Context;
    #[inline]
    fn deref(&self) -> &Context {
        &self.context
    }
}

impl backend::Facade for Display {
    #[inline]
    fn get_context(&self) -> &Rc<Context> {
        &self.context
    }
}

impl Deref for GlutinBackend {
    type Target = Rc<RefCell<glutin::GlWindow>>;
    #[inline]
    fn deref(&self) -> &Rc<RefCell<glutin::GlWindow>> {
        &self.0
    }
}

unsafe impl Backend for GlutinBackend {
    #[inline]
    fn swap_buffers(&self) -> Result<(), SwapBuffersError> {
        match self.borrow().swap_buffers() {
            Ok(()) => Ok(()),
            Err(glutin::ContextError::IoError(e)) => panic!("Error while swapping buffers: {:?}", e),
            Err(glutin::ContextError::ContextLost) => Err(SwapBuffersError::ContextLost),
        }
    }

    #[inline]
    unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
        self.borrow().get_proc_address(symbol) as *const _
    }

    #[inline]
    fn get_framebuffer_dimensions(&self) -> (u32, u32) {
        let gl_window = self.borrow();
        let (width, height) = gl_window.get_inner_size()
            .map(|logical_size| logical_size.to_physical(gl_window.get_hidpi_factor()))
            .map(Into::into)
            // TODO: 800x600 ?
            .unwrap_or((800, 600));
        (width, height)
    }

    #[inline]
    fn is_current(&self) -> bool {
        self.borrow().is_current()
    }

    #[inline]
    unsafe fn make_current(&self) {
        self.borrow().make_current().unwrap();
    }
}
<|endoftext|>


<commit_before>pub use client::ErrorResponse as MwsErrorResponse;
use types::GenericXmlResponseParseError;

#[derive(Fail, Debug)]
pub enum MwsError {
  #[fail(display = "io error: {}", _0)]
  Io(#[cause] ::std::io::Error),
  #[fail(display = "http error: {}", _0)]
  Http(#[cause] ::reqwest::Error),
  #[fail(display = "xml reader error: {}", _0)]
  XmlReader(#[cause] ::xml::reader::Error),
  #[fail(display = "xml writer error: {}", _0)]
  XmlWriter(#[cause] ::xml::writer::Error),
  #[fail(display = "xml element parse error: {}", _0)]
  XmlElementParse(#[cause] ::xmltree::ParseError),
  #[fail(display = "generic xml response parse error: {}", _0)]
  GenericXmlResponseParse(#[cause] GenericXmlResponseParseError),
  #[fail(display = "csv error: {}", _0)]
  Csv(#[cause] ::csv::Error),
  #[fail(display = "utf8 error: {}", _0)]
  Utf8(#[cause] ::std::str::Utf8Error),
  #[fail(display = "MWS request is unsuccessful: {:?}", _0)]
  ErrorResponse(MwsErrorResponse),
  #[fail(display = "unexpected end of xml: {}", _0)]
  UnexpectedEndOfXml(String),
  #[fail(
    display = "unexpected xml event: expected '{}', found '{}'",
    expected, found
  )]
  UnexpectedXmlEvent { expected: String, found: String },
  #[fail(display = "parse string error: {} : {}", what, message)]
  ParseString { what: String, message: String },
  #[fail(display = "invalid path name: '{}'", _0)]
  InvalidPath(String),
  #[fail(display = "Content-MD5 header missing")]
  ContentMD5HeaderMissing,
  #[fail(display = "{}", _0)]
  Msg(String),
}

impl MwsError {
  /// The common response to a 500 or 503 service error is
  /// to try the request again. Such service errors are
  /// usually only temporary and will resolve themselves.
  pub fn should_try_again(&self) -> bool {
    match *self {
      MwsError::ErrorResponse(ref res) => {
        let code = res.status.as_u16();
        code >= 500 && code < 600
      }
      MwsError::Io(_) => true,
      _ => false,
    }
  }
}

macro_rules! impl_from {
  ($v:ident($t:ty)) => {
    impl From<$t> for MwsError {
      fn from(e: $t) -> Self {
        MwsError::$v(e)
      }
    }
  };
}

impl_from!(Io(::std::io::Error));
impl_from!(Http(::reqwest::Error));
impl_from!(XmlReader(::xml::reader::Error));
impl_from!(XmlWriter(::xml::writer::Error));
impl_from!(Csv(::csv::Error));
impl_from!(Utf8(::std::str::Utf8Error));
impl_from!(Msg(String));
impl_from!(XmlElementParse(::xmltree::ParseError));
impl_from!(GenericXmlResponseParse(GenericXmlResponseParseError));

pub type MwsResult<T> = Result<T, MwsError>;
<commit_msg>retry on http non-client error<commit_after>pub use client::ErrorResponse as MwsErrorResponse;
use types::GenericXmlResponseParseError;

#[derive(Fail, Debug)]
pub enum MwsError {
  #[fail(display = "io error: {}", _0)]
  Io(#[cause] ::std::io::Error),
  #[fail(display = "http error: {}", _0)]
  Http(#[cause] ::reqwest::Error),
  #[fail(display = "xml reader error: {}", _0)]
  XmlReader(#[cause] ::xml::reader::Error),
  #[fail(display = "xml writer error: {}", _0)]
  XmlWriter(#[cause] ::xml::writer::Error),
  #[fail(display = "xml element parse error: {}", _0)]
  XmlElementParse(#[cause] ::xmltree::ParseError),
  #[fail(display = "generic xml response parse error: {}", _0)]
  GenericXmlResponseParse(#[cause] GenericXmlResponseParseError),
  #[fail(display = "csv error: {}", _0)]
  Csv(#[cause] ::csv::Error),
  #[fail(display = "utf8 error: {}", _0)]
  Utf8(#[cause] ::std::str::Utf8Error),
  #[fail(display = "MWS request is unsuccessful: {:?}", _0)]
  ErrorResponse(MwsErrorResponse),
  #[fail(display = "unexpected end of xml: {}", _0)]
  UnexpectedEndOfXml(String),
  #[fail(
    display = "unexpected xml event: expected '{}', found '{}'",
    expected, found
  )]
  UnexpectedXmlEvent { expected: String, found: String },
  #[fail(display = "parse string error: {} : {}", what, message)]
  ParseString { what: String, message: String },
  #[fail(display = "invalid path name: '{}'", _0)]
  InvalidPath(String),
  #[fail(display = "Content-MD5 header missing")]
  ContentMD5HeaderMissing,
  #[fail(display = "{}", _0)]
  Msg(String),
}

impl MwsError {
  /// The common response to a 500 or 503 service error is
  /// to try the request again. Such service errors are
  /// usually only temporary and will resolve themselves.
  pub fn should_try_again(&self) -> bool {
    match *self {
      MwsError::ErrorResponse(ref res) => {
        let code = res.status.as_u16();
        code >= 500 && code < 600
      }
      MwsError::Io(_) => true,
      MwsError::Http(err) => {
        if err.is_redirect() {
          return false
        }

        if let Some(status) = err.status() {
          if status.is_client_error() {
            return false
          }
        }

        return true
      }
      _ => false,
    }
  }
}

macro_rules! impl_from {
  ($v:ident($t:ty)) => {
    impl From<$t> for MwsError {
      fn from(e: $t) -> Self {
        MwsError::$v(e)
      }
    }
  };
}

impl_from!(Io(::std::io::Error));
impl_from!(Http(::reqwest::Error));
impl_from!(XmlReader(::xml::reader::Error));
impl_from!(XmlWriter(::xml::writer::Error));
impl_from!(Csv(::csv::Error));
impl_from!(Utf8(::std::str::Utf8Error));
impl_from!(Msg(String));
impl_from!(XmlElementParse(::xmltree::ParseError));
impl_from!(GenericXmlResponseParse(GenericXmlResponseParseError));

pub type MwsResult<T> = Result<T, MwsError>;
<|endoftext|>


<commit_before>use regex::Regex;

static IGNORE_WHITESPACE: &'static str = r"\s*";
static ANY_WHITESPACE: &'static str = r"\s+";
static COMMENT: &'static str = r"(#.*)?";
static FIELD_TYPE: &'static str = r"([a-zA-Z0-9_/]+)";
static FIELD_NAME: &'static str = r"([a-zA-Z][a-zA-Z0-9_]*)";
static EMPTY_BRACKETS: &'static str = r"\[\s*\]";
static NUMBER_BRACKETS: &'static str = r"\[\s*([0-9]+)\s*\]";

fn match_nothing(data: &str) -> Option<String> {
    lazy_static! {
        static ref MATCHER: String = format!("^{}{}$", IGNORE_WHITESPACE, COMMENT);
        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
    }
    RE.captures(data).map(|captures| captures.get(1).map_or("", |v| v.as_str()).into())
}

fn match_field(data: &str) -> Option<FieldLine> {
    lazy_static! {
        static ref MATCHER: String = format!(
            "^{}{}{}{}{}{}$", IGNORE_WHITESPACE, FIELD_TYPE, ANY_WHITESPACE,
            FIELD_NAME, IGNORE_WHITESPACE, COMMENT);
        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
    }
    let captures = match RE.captures(data) {
        Some(v) => v,
        None => return None,
    };
    Some(FieldLine {
        field_type: captures.get(1).unwrap().as_str().into(),
        field_name: captures.get(2).unwrap().as_str().into(),
        comment: captures.get(3).map_or("", |v| v.as_str()).into(),
    })
}

fn match_vector_field(data: &str) -> Option<FieldLine> {
    lazy_static! {
        static ref MATCHER: String = format!(
            "^{}{}{}{}{}{}{}{}$", IGNORE_WHITESPACE, FIELD_TYPE, IGNORE_WHITESPACE,
            EMPTY_BRACKETS, ANY_WHITESPACE, FIELD_NAME, IGNORE_WHITESPACE, COMMENT);
        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
    }
    let captures = match RE.captures(data) {
        Some(v) => v,
        None => return None,
    };
    Some(FieldLine {
        field_type: captures.get(1).unwrap().as_str().into(),
        field_name: captures.get(2).unwrap().as_str().into(),
        comment: captures.get(3).map_or("", |v| v.as_str()).into(),
    })
}

fn match_array_field(data: &str) -> Option<(FieldLine, usize)> {
    lazy_static! {
        static ref MATCHER: String = format!(
            "^{}{}{}{}{}{}{}{}$", IGNORE_WHITESPACE, FIELD_TYPE, IGNORE_WHITESPACE,
            NUMBER_BRACKETS, ANY_WHITESPACE, FIELD_NAME, IGNORE_WHITESPACE, COMMENT);
        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
    }
    let captures = match RE.captures(data) {
        Some(v) => v,
        None => return None,
    };
    Some((FieldLine {
              field_type: captures.get(1).unwrap().as_str().into(),
              field_name: captures.get(3).unwrap().as_str().into(),
              comment: captures.get(4).map_or("", |v| v.as_str()).into(),
          },
          captures.get(2).unwrap().as_str().parse().unwrap()))
}

fn match_line(data: &str) -> Result<Option<FieldInfo>, ()> {
    if let Some(_) = match_nothing(data) {
        return Ok(None);
    }
    if let Some(info) = match_field(data) {
        return Ok(Some(FieldInfo {
            datatype: info.field_type,
            name: info.field_name,
            case: FieldCase::Unit,
        }));
    }
    if let Some(info) = match_vector_field(data) {
        return Ok(Some(FieldInfo {
            datatype: info.field_type,
            name: info.field_name,
            case: FieldCase::Vector,
        }));
    }
    if let Some((info, count)) = match_array_field(data) {
        return Ok(Some(FieldInfo {
            datatype: info.field_type,
            name: info.field_name,
            case: FieldCase::Array(count),
        }));
    }
    Err(())
}

#[derive(Debug,PartialEq)]
struct FieldLine {
    field_type: String,
    field_name: String,
    comment: String,
}

#[derive(Debug,PartialEq)]
enum FieldCase {
    Unit,
    Vector,
    Array(usize),
}

#[derive(Debug,PartialEq)]
struct FieldInfo {
    datatype: String,
    name: String,
    case: FieldCase,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn match_nothing_matches_comment() {
        assert_eq!(String::from("#just a comment"),
                   match_nothing("#just a comment").unwrap());
        assert_eq!(String::from("#  YOLO !   "),
                   match_nothing("#  YOLO !   ").unwrap());
    }

    #[test]
    fn match_nothing_matches_whitespace() {
        assert_eq!(String::from(""), match_nothing("      ").unwrap());
    }

    #[test]
    fn match_nothing_fails_to_match_lines_with_logic() {
        assert!(match_nothing("geom_msgs/Twist  # this clearly should fail").is_none());
    }

    #[test]
    fn match_field_matches_legal_field() {
        assert_eq!(FieldLine {
                       field_type: "geom_msgs/Twist".into(),
                       field_name: "myname".into(),
                       comment: "# this clearly should succeed".into(),
                   },
                   match_field("  geom_msgs/Twist   myname    # this clearly should succeed")
                       .unwrap());
    }

    #[test]
    fn match_vector_field_matches_legal_field() {
        assert_eq!(FieldLine {
                       field_type: "geom_msgs/Twist".into(),
                       field_name: "myname".into(),
                       comment: "# ...".into(),
                   },
                   match_vector_field("  geom_msgs/Twist [  ]   myname  # ...").unwrap());
    }

    #[test]
    fn match_array_field_matches_legal_field() {
        assert_eq!((FieldLine {
                        field_type: "geom_msgs/Twist".into(),
                        field_name: "myname".into(),
                        comment: "# comment".into(),
                    },
                    127),
                   match_array_field("  geom_msgs/Twist   [   127 ]   myname# comment").unwrap());
    }

    #[test]
    fn match_line_works_on_legal_data() {
        assert!(match_line("#just a comment").unwrap().is_none());
        assert!(match_line("#  YOLO !   ").unwrap().is_none());
        assert!(match_line("      ").unwrap().is_none());

        assert_eq!(FieldInfo {
                       datatype: "geom_msgs/Twist".into(),
                       name: "myname".into(),
                       case: FieldCase::Unit,
                   },
                   match_line("  geom_msgs/Twist   myname    # this clearly should succeed")
                       .unwrap()
                       .unwrap());

        assert_eq!(FieldInfo {
                       datatype: "geom_msgs/Twist".into(),
                       name: "myname".into(),
                       case: FieldCase::Vector,
                   },
                   match_line("  geom_msgs/Twist [  ]   myname  # ...").unwrap().unwrap());

        assert_eq!(FieldInfo {
                       datatype: "geom_msgs/Twist".into(),
                       name: "myname".into(),
                       case: FieldCase::Array(127),
                   },
                   match_line("  geom_msgs/Twist   [   127 ]   myname# comment").unwrap().unwrap());
    }
}
<commit_msg>Add const matching<commit_after>use regex::Regex;

static IGNORE_WHITESPACE: &'static str = r"\s*";
static ANY_WHITESPACE: &'static str = r"\s+";
static COMMENT: &'static str = r"(#.*)?";
static FIELD_TYPE: &'static str = r"([a-zA-Z0-9_/]+)";
static FIELD_NAME: &'static str = r"([a-zA-Z][a-zA-Z0-9_]*)";
static EMPTY_BRACKETS: &'static str = r"\[\s*\]";
static NUMBER_BRACKETS: &'static str = r"\[\s*([0-9]+)\s*\]";

fn match_nothing(data: &str) -> Option<String> {
    lazy_static! {
        static ref MATCHER: String = format!("^{}{}$", IGNORE_WHITESPACE, COMMENT);
        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
    }
    RE.captures(data).map(|captures| captures.get(1).map_or("", |v| v.as_str()).into())
}

fn match_field(data: &str) -> Option<FieldLine> {
    lazy_static! {
        static ref MATCHER: String = format!(
            "^{}{}{}{}{}$", FIELD_TYPE, ANY_WHITESPACE, FIELD_NAME, IGNORE_WHITESPACE, COMMENT);
        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
    }
    let captures = match RE.captures(data) {
        Some(v) => v,
        None => return None,
    };
    Some(FieldLine {
        field_type: captures.get(1).unwrap().as_str().into(),
        field_name: captures.get(2).unwrap().as_str().into(),
        comment: captures.get(3).map_or("", |v| v.as_str()).into(),
    })
}

fn match_vector_field(data: &str) -> Option<FieldLine> {
    lazy_static! {
        static ref MATCHER: String = format!(
            "^{}{}{}{}{}{}{}$", FIELD_TYPE, IGNORE_WHITESPACE, EMPTY_BRACKETS, ANY_WHITESPACE,
            FIELD_NAME, IGNORE_WHITESPACE, COMMENT);
        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
    }
    let captures = match RE.captures(data) {
        Some(v) => v,
        None => return None,
    };
    Some(FieldLine {
        field_type: captures.get(1).unwrap().as_str().into(),
        field_name: captures.get(2).unwrap().as_str().into(),
        comment: captures.get(3).map_or("", |v| v.as_str()).into(),
    })
}

fn match_array_field(data: &str) -> Option<(FieldLine, usize)> {
    lazy_static! {
        static ref MATCHER: String = format!(
            "^{}{}{}{}{}{}{}$", FIELD_TYPE, IGNORE_WHITESPACE, NUMBER_BRACKETS, ANY_WHITESPACE,
            FIELD_NAME, IGNORE_WHITESPACE, COMMENT);
        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
    }
    let captures = match RE.captures(data) {
        Some(v) => v,
        None => return None,
    };
    Some((FieldLine {
              field_type: captures.get(1).unwrap().as_str().into(),
              field_name: captures.get(3).unwrap().as_str().into(),
              comment: captures.get(4).map_or("", |v| v.as_str()).into(),
          },
          captures.get(2).unwrap().as_str().parse().unwrap()))
}

fn match_const_string(data: &str) -> Option<(FieldLine, String)> {
    lazy_static! {
        static ref MATCHER: String = format!(
            r"^{}{}{}{}={}(.*)$", FIELD_TYPE, ANY_WHITESPACE, FIELD_NAME, IGNORE_WHITESPACE,
            IGNORE_WHITESPACE);
        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
    }
    let captures = match RE.captures(data) {
        Some(v) => v,
        None => return None,
    };
    Some((FieldLine {
              field_type: captures.get(1).unwrap().as_str().into(),
              field_name: captures.get(2).unwrap().as_str().into(),
              comment: "".into(),
          },
          captures.get(3).unwrap().as_str().into()))
}

fn match_const_numeric(data: &str) -> Option<(FieldLine, String)> {
    lazy_static! {
        static ref MATCHER: String = format!(
            r"^{}{}{}{}={}(-?[0-9]+){}{}$", FIELD_TYPE, ANY_WHITESPACE, FIELD_NAME,
            IGNORE_WHITESPACE, IGNORE_WHITESPACE, IGNORE_WHITESPACE, COMMENT);
        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
    }
    let captures = match RE.captures(data) {
        Some(v) => v,
        None => return None,
    };
    Some((FieldLine {
              field_type: captures.get(1).unwrap().as_str().into(),
              field_name: captures.get(2).unwrap().as_str().into(),
              comment: captures.get(4).map_or("", |v| v.as_str()).into(),
          },
          captures.get(3).unwrap().as_str().into()))
}

fn match_line(data: &str) -> Result<Option<FieldInfo>, ()> {
    let data = data.trim();
    if let Some(_) = match_nothing(data) {
        return Ok(None);
    }
    if let Some(info) = match_field(data) {
        return Ok(Some(FieldInfo {
            datatype: info.field_type,
            name: info.field_name,
            case: FieldCase::Unit,
        }));
    }
    if let Some(info) = match_vector_field(data) {
        return Ok(Some(FieldInfo {
            datatype: info.field_type,
            name: info.field_name,
            case: FieldCase::Vector,
        }));
    }
    if let Some((info, count)) = match_array_field(data) {
        return Ok(Some(FieldInfo {
            datatype: info.field_type,
            name: info.field_name,
            case: FieldCase::Array(count),
        }));
    }
    if let Some((info, data)) = match_const_numeric(data) {
        return Ok(Some(FieldInfo {
            datatype: info.field_type,
            name: info.field_name,
            case: FieldCase::Const(data),
        }));
    }
    if let Some((info, data)) = match_const_string(data) {
        return Ok(Some(FieldInfo {
            datatype: info.field_type,
            name: info.field_name,
            case: FieldCase::Const(data),
        }));
    }
    Err(())
}

#[derive(Debug,PartialEq)]
struct FieldLine {
    field_type: String,
    field_name: String,
    comment: String,
}

#[derive(Debug,PartialEq)]
enum FieldCase {
    Unit,
    Vector,
    Array(usize),
    Const(String),
}

#[derive(Debug,PartialEq)]
struct FieldInfo {
    datatype: String,
    name: String,
    case: FieldCase,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn match_nothing_matches_comment() {
        assert_eq!(String::from("#just a comment"),
                   match_nothing("#just a comment").unwrap());
        assert_eq!(String::from("#  YOLO !"),
                   match_nothing("#  YOLO !").unwrap());
    }

    #[test]
    fn match_nothing_matches_whitespace() {
        assert_eq!(String::from(""), match_nothing("").unwrap());
    }

    #[test]
    fn match_nothing_fails_to_match_lines_with_logic() {
        assert!(match_nothing("geom_msgs/Twist  # this clearly should fail").is_none());
    }

    #[test]
    fn match_field_matches_legal_field() {
        assert_eq!(FieldLine {
                       field_type: "geom_msgs/Twist".into(),
                       field_name: "myname".into(),
                       comment: "# this clearly should succeed".into(),
                   },
                   match_field("geom_msgs/Twist   myname    # this clearly should succeed")
                       .unwrap());
    }

    #[test]
    fn match_vector_field_matches_legal_field() {
        assert_eq!(FieldLine {
                       field_type: "geom_msgs/Twist".into(),
                       field_name: "myname".into(),
                       comment: "# ...".into(),
                   },
                   match_vector_field("geom_msgs/Twist [  ]   myname  # ...").unwrap());
    }

    #[test]
    fn match_array_field_matches_legal_field() {
        assert_eq!((FieldLine {
                        field_type: "geom_msgs/Twist".into(),
                        field_name: "myname".into(),
                        comment: "# comment".into(),
                    },
                    127),
                   match_array_field("geom_msgs/Twist   [   127 ]   myname# comment").unwrap());
    }

    #[test]
    fn match_const_string_matches_legal_field() {
        assert_eq!((FieldLine {
                        field_type: "mytype".into(),
                        field_name: "myname".into(),
                        comment: "".into(),
                    },
                    "this is # data".into()),
                   match_const_string("mytype   myname  =  this is # data").unwrap());
    }

    #[test]
    fn match_const_numeric_matches_legal_field() {
        assert_eq!((FieldLine {
                        field_type: "mytype".into(),
                        field_name: "myname".into(),
                        comment: "# data".into(),
                    },
                    "-444".into()),
                   match_const_numeric("mytype   myname  =  -444 # data").unwrap());
    }

    #[test]
    fn match_line_works_on_legal_data() {
        assert!(match_line("#just a comment").unwrap().is_none());
        assert!(match_line("#  YOLO !   ").unwrap().is_none());
        assert!(match_line("      ").unwrap().is_none());

        assert_eq!(FieldInfo {
                       datatype: "geom_msgs/Twist".into(),
                       name: "myname".into(),
                       case: FieldCase::Unit,
                   },
                   match_line("  geom_msgs/Twist   myname    # this clearly should succeed")
                       .unwrap()
                       .unwrap());

        assert_eq!(FieldInfo {
                       datatype: "geom_msgs/Twist".into(),
                       name: "myname".into(),
                       case: FieldCase::Vector,
                   },
                   match_line("  geom_msgs/Twist [  ]   myname  # ...").unwrap().unwrap());

        assert_eq!(FieldInfo {
                       datatype: "geom_msgs/Twist".into(),
                       name: "myname".into(),
                       case: FieldCase::Array(127),
                   },
                   match_line("  geom_msgs/Twist   [   127 ]   myname# comment").unwrap().unwrap());
        assert_eq!(FieldInfo {
                       datatype: "geom_msgs/Twist".into(),
                       name: "myname".into(),
                       case: FieldCase::Const("this is # data".into()),
                   },
                   match_line("  geom_msgs/Twist  myname =   this is # data  ").unwrap().unwrap());
        assert_eq!(FieldInfo {
                       datatype: "geom_msgs/Twist".into(),
                       name: "myname".into(),
                       case: FieldCase::Const("-444".into()),
                   },
                   match_line("  geom_msgs/Twist  myname =   -444 # data  ").unwrap().unwrap());
    }
}
<|endoftext|>


<commit_before>use crossbeam_channel as channel;
use specs;
use specs::prelude::{Read, WriteStorage};

use crate::character::CharacterDrawable;
use crate::game::constants::{CHARACTER_X_SPEED, CHARACTER_Y_SPEED};
use crate::graphics::{camera::CameraInputState, can_move_to_tile, DeltaTime, orientation::{Orientation, Stance}};
use crate::shaders::Position;

pub struct CharacterInputState {
  pub movement: Position,
  pub orientation: Orientation,
  pub is_colliding: bool,
  pub is_shooting: bool,
}

impl CharacterInputState {
  pub fn new() -> CharacterInputState {
    CharacterInputState {
      movement: Position::origin(),
      orientation: Orientation::Normal,
      is_colliding: false,
      is_shooting: false,
    }
  }

  pub fn update(&mut self, camera: &mut CameraInputState, css: &CharacterControlSystem) {
    if css.y_move.is_none() && css.x_move.is_none() {
      self.orientation = Orientation::Normal;
    } else if css.x_move.is_none() {
      if let Some(y) = css.y_move {
        let vertical_movement = self.movement + Position::new(0.0, y);
        if !self.is_colliding || can_move_to_tile(vertical_movement) {
          self.movement = vertical_movement;
          camera.movement = camera.movement - Position::new(0.0, y);
          self.orientation = match y {
            y if y < 0.0 => Orientation::Up,
            y if y > 0.0 => Orientation::Down,
            _ => Orientation::Normal,
          };
        }
      }
    } else if let Some(x) = css.x_move {
      let horizontal_move = self.movement + Position::new(x, 0.0);
      if let Some(y) = css.y_move {
        let horizontal_movement = Position::new(x / 1.5, 0.0);
        let vertical_movement = Position::new(0.0, y / 1.666);
        if !self.is_colliding || can_move_to_tile(self.movement + horizontal_movement + vertical_movement) {
          self.movement = self.movement + horizontal_movement + vertical_movement;
          camera.movement = camera.movement + horizontal_movement - vertical_movement;

          self.orientation = match (x, y) {
            (x, y) if x > 0.0 && y > 0.0 => Orientation::DownLeft,
            (x, y) if x > 0.0 && y < 0.0 => Orientation::UpLeft,
            (x, y) if x < 0.0 && y > 0.0 => Orientation::DownRight,
            (x, y) if x < 0.0 && y < 0.0 => Orientation::UpRight,
            _ => Orientation::Normal,
          };
        }
      } else if css.y_move.is_none() && !self.is_colliding || can_move_to_tile(horizontal_move) {
        let horizontal_movement = Position::new(x, 0.0);
        self.movement = self.movement + horizontal_movement;
        camera.movement = camera.movement + horizontal_movement;
        self.orientation = match x {
          x if x < 0.0 => Orientation::Right,
          x if x > 0.0 => Orientation::Left,
          _ => Orientation::Normal,
        };
      }
    }
    self.is_shooting = css.is_ctrl_pressed;
  }
}

impl Default for CharacterInputState {
  fn default() -> Self {
    CharacterInputState::new()
  }
}

impl specs::prelude::Component for CharacterInputState {
  type Storage = specs::storage::VecStorage<CharacterInputState>;
}

pub enum CharacterControl {
  Left,
  Right,
  Up,
  Down,
  XMoveStop,
  YMoveStop,
  CtrlPressed,
  CtrlReleased,
  ReloadPressed,
  ReloadReleased,
}

pub struct CharacterControlSystem {
  queue: channel::Receiver<CharacterControl>,
  x_move: Option<f32>,
  y_move: Option<f32>,
  cool_down: f64,
  is_ctrl_pressed: bool,
  is_reloading: bool,
}

impl CharacterControlSystem {
  pub fn new() -> (CharacterControlSystem, channel::Sender<CharacterControl>) {
    let (tx, rx) = channel::unbounded();
    (CharacterControlSystem {
      queue: rx,
      x_move: None,
      y_move: None,
      cool_down: 1.0,
      is_ctrl_pressed: false,
      is_reloading: false,
    }, tx)
  }
}

impl<'a> specs::prelude::System<'a> for CharacterControlSystem {
  type SystemData = (WriteStorage<'a, CharacterInputState>,
                     WriteStorage<'a, CharacterDrawable>,
                     WriteStorage<'a, CameraInputState>,
                     Read<'a, DeltaTime>);

  fn run(&mut self, (mut character_input, mut character, mut camera_input, d): Self::SystemData) {
    use specs::join::Join;

    let delta = d.0;

    if self.cool_down == 0.0 {
      self.cool_down += 0.1;
    } else {
      self.cool_down = (self.cool_down - delta).max(0.0);
      while let Ok(control) = self.queue.try_recv() {
        match control {
          CharacterControl::Up => self.y_move = Some(-CHARACTER_Y_SPEED),
          CharacterControl::Down => self.y_move = Some(CHARACTER_Y_SPEED),
          CharacterControl::YMoveStop => self.y_move = None,
          CharacterControl::Right => self.x_move = Some(-CHARACTER_X_SPEED),
          CharacterControl::Left => self.x_move = Some(CHARACTER_X_SPEED),
          CharacterControl::XMoveStop => self.x_move = None,
          CharacterControl::CtrlPressed => self.is_ctrl_pressed = true,
          CharacterControl::CtrlReleased => self.is_ctrl_pressed = false,
          CharacterControl::ReloadPressed => self.is_reloading = true,
          CharacterControl::ReloadReleased => self.is_reloading = false,
        }
      }

      for (ci, c, camera) in (&mut character_input, &mut character, &mut camera_input).join() {
        if c.stance != Stance::NormalDeath {
          ci.update(camera, self);
        }
        if self.is_reloading && c.stats.magazines > 0 && c.stats.ammunition < 10 {
          c.stats.ammunition = 10;
          c.stats.magazines -= 1;
        }
      }
    }
  }
}
<commit_msg>Add some clarifying code comments<commit_after>use crossbeam_channel as channel;
use specs;
use specs::prelude::{Read, WriteStorage};

use crate::character::CharacterDrawable;
use crate::game::constants::{CHARACTER_X_SPEED, CHARACTER_Y_SPEED};
use crate::graphics::{camera::CameraInputState, can_move_to_tile, DeltaTime, orientation::{Orientation, Stance}};
use crate::shaders::Position;

pub struct CharacterInputState {
  pub movement: Position,
  pub orientation: Orientation,
  pub is_colliding: bool,
  pub is_shooting: bool,
}

impl CharacterInputState {
  pub fn new() -> CharacterInputState {
    CharacterInputState {
      movement: Position::origin(),
      orientation: Orientation::Normal,
      is_colliding: false,
      is_shooting: false,
    }
  }

  pub fn update(&mut self, camera: &mut CameraInputState, css: &CharacterControlSystem) {
    if css.y_move.is_none() && css.x_move.is_none() {
      self.orientation = Orientation::Normal;
    } else if css.x_move.is_none() {                  // Horizontal/vertical movement
      if let Some(y) = css.y_move {
        let vertical_movement = self.movement + Position::new(0.0, y);
        if !self.is_colliding || can_move_to_tile(vertical_movement) {
          self.movement = vertical_movement;
          camera.movement = camera.movement - Position::new(0.0, y);
          self.orientation = match y {
            y if y < 0.0 => Orientation::Up,
            y if y > 0.0 => Orientation::Down,
            _ => Orientation::Normal,
          };
        }
      }
    } else if let Some(x) = css.x_move {        // Diagonal movement
      let horizontal_move = self.movement + Position::new(x, 0.0);
      if let Some(y) = css.y_move {
        let horizontal_movement = Position::new(x / 1.5, 0.0);
        let vertical_movement = Position::new(0.0, y / 1.666);
        if !self.is_colliding || can_move_to_tile(self.movement + horizontal_movement + vertical_movement) {
          self.movement = self.movement + horizontal_movement + vertical_movement;
          camera.movement = camera.movement + horizontal_movement - vertical_movement;

          self.orientation = match (x, y) {
            (x, y) if x > 0.0 && y > 0.0 => Orientation::DownLeft,
            (x, y) if x > 0.0 && y < 0.0 => Orientation::UpLeft,
            (x, y) if x < 0.0 && y > 0.0 => Orientation::DownRight,
            (x, y) if x < 0.0 && y < 0.0 => Orientation::UpRight,
            _ => Orientation::Normal,
          };
        }
      } else if css.y_move.is_none() && !self.is_colliding || can_move_to_tile(horizontal_move) {
        let horizontal_movement = Position::new(x, 0.0);
        self.movement = self.movement + horizontal_movement;
        camera.movement = camera.movement + horizontal_movement;
        self.orientation = match x {
          x if x < 0.0 => Orientation::Right,
          x if x > 0.0 => Orientation::Left,
          _ => Orientation::Normal,
        };
      }
    }
    self.is_shooting = css.is_ctrl_pressed;
  }
}

impl Default for CharacterInputState {
  fn default() -> Self {
    CharacterInputState::new()
  }
}

impl specs::prelude::Component for CharacterInputState {
  type Storage = specs::storage::VecStorage<CharacterInputState>;
}

pub enum CharacterControl {
  Left,
  Right,
  Up,
  Down,
  XMoveStop,
  YMoveStop,
  CtrlPressed,
  CtrlReleased,
  ReloadPressed,
  ReloadReleased,
}

pub struct CharacterControlSystem {
  queue: channel::Receiver<CharacterControl>,
  x_move: Option<f32>,
  y_move: Option<f32>,
  cool_down: f64,
  is_ctrl_pressed: bool,
  is_reloading: bool,
}

impl CharacterControlSystem {
  pub fn new() -> (CharacterControlSystem, channel::Sender<CharacterControl>) {
    let (tx, rx) = channel::unbounded();
    (CharacterControlSystem {
      queue: rx,
      x_move: None,
      y_move: None,
      cool_down: 1.0,
      is_ctrl_pressed: false,
      is_reloading: false,
    }, tx)
  }
}

impl<'a> specs::prelude::System<'a> for CharacterControlSystem {
  type SystemData = (WriteStorage<'a, CharacterInputState>,
                     WriteStorage<'a, CharacterDrawable>,
                     WriteStorage<'a, CameraInputState>,
                     Read<'a, DeltaTime>);

  fn run(&mut self, (mut character_input, mut character, mut camera_input, d): Self::SystemData) {
    use specs::join::Join;

    let delta = d.0;

    if self.cool_down == 0.0 {
      self.cool_down += 0.1;
    } else {
      self.cool_down = (self.cool_down - delta).max(0.0);
      while let Ok(control) = self.queue.try_recv() {
        match control {
          CharacterControl::Up => self.y_move = Some(-CHARACTER_Y_SPEED),
          CharacterControl::Down => self.y_move = Some(CHARACTER_Y_SPEED),
          CharacterControl::YMoveStop => self.y_move = None,
          CharacterControl::Right => self.x_move = Some(-CHARACTER_X_SPEED),
          CharacterControl::Left => self.x_move = Some(CHARACTER_X_SPEED),
          CharacterControl::XMoveStop => self.x_move = None,
          CharacterControl::CtrlPressed => self.is_ctrl_pressed = true,
          CharacterControl::CtrlReleased => self.is_ctrl_pressed = false,
          CharacterControl::ReloadPressed => self.is_reloading = true,
          CharacterControl::ReloadReleased => self.is_reloading = false,
        }
      }

      for (ci, c, camera) in (&mut character_input, &mut character, &mut camera_input).join() {
        if c.stance != Stance::NormalDeath {
          ci.update(camera, self);
        }
        if self.is_reloading && c.stats.magazines > 0 && c.stats.ammunition < 10 {
          c.stats.ammunition = 10;
          c.stats.magazines -= 1;
        }
      }
    }
  }
}
<|endoftext|>