<commit_before>use std::io::prelude::*;
use std::{process, env};
use std::path::Path;
use std::error::Error;
use std::fs::{self, OpenOptions};
/* Alias to avoid naming conflict for write_all!() */
use std::fmt::Write as std_write;

use url::Url;
use url_open::UrlOpen;

use serde_json::{from_str, to_string};

use util::*;
use config::Config;
use sheet::traits::HasHTML;

use sheet::session::Session;
use sheet::session::EventType;

#[derive(Serialize, Deserialize, Debug)]
pub struct Timesheet {
    start            : u64,
    end              : u64,
    config           : Config,
    sessions         : Vec<Session>,
}

impl Timesheet {
    /** Initializes the .trk/timesheet.json file which holds
     * the serialized timesheet
     * Returns Some(newTimesheet) if operation succeeded */
    pub fn init(author_name: Option<&str>) -> Option<Timesheet> {
        /* Check if file already exists (no init permitted) */
        if Timesheet::is_init() {
            println!("Timesheet is already initialized!");
            return None;
        }
        /* File does not exist, initialize */
        let git_author_name = git_author();
        let author_name = match author_name {
            Some(name) => name,
            None => {
                match git_author_name {
                    Some(ref git_name) => git_name,
                    None => {
                        println!("Empty name not permitted. \
                                  Please run with 'trk init <name>'");
                        process::exit(0);
                    }
                }
            }
        };
        let mut config = Config::new();
        config.user_name = Some(author_name.to_string());
        let now = get_seconds();
        let sheet = Timesheet {
            start        : now,
            end          : now + 1,
            config,
            sessions     : Vec::<Session>::new(),
        };
        if sheet.write_files() {
            git_init_trk();
            Some(sheet)
        } else {
            None
        }
    }

    fn is_init() -> bool {
        Path::new("./.trk/timesheet.json").exists() && Timesheet::load_from_file().is_some()
    }

    pub fn new_session(&mut self, timestamp: Option<u64>) -> bool {
        let possible = self.sessions
            .last_mut()
            .map_or(true, |session| {
                if session.is_running() {
                    println!("Last session is still running.");
                }
                !session.is_running()
        });
        if possible {
            match timestamp {
                Some(timestamp) => {
                    let is_valid_ts = match self.sessions.last() {
                        None => timestamp > self.start,
                        Some(last_session) => timestamp > last_session.end,
                    };
                    if is_valid_ts {
                        self.sessions.push(Session::new(Some(timestamp)));
                    } else {
                        println!("That timestamp is invalid.");
                        process::exit(0);
                    }
                }
                None => {
                    self.sessions.push(Session::new(None));
                }
            };
        }
        possible
    }

    pub fn end_session(&mut self, timestamp: Option<u64>) {
        match self.sessions.last_mut() {
            Some(session) => {
                session.update_end();
                session.finalize(timestamp);
                self.end = session.end + 1;
            }
            None => println!("No session to finalize."),
        }
    }

    pub fn pause(&mut self, timestamp: Option<u64>, note: Option<String>) {
        match self.sessions.last_mut() {
            Some(session) => {
                session.push_event(timestamp, note, EventType::Pause);
            }
            None => println!("No session to pause."),
        }
    }

    pub fn resume(&mut self, timestamp: Option<u64>) {
        match self.sessions.last_mut() {
            Some(session) => {
                session.push_event(timestamp, None, EventType::Resume);
            }
            None => println!("No session to pause."),
        }
    }

    pub fn note(&mut self, timestamp: Option<u64>, note_text: String) {
        match self.sessions.last_mut() {
            Some(session) => {
                session.push_event(timestamp, Some(note_text), EventType::Note);
            }
            None => println!("No session to add note to."),
        }
    }

    pub fn add_commit(&mut self, hash: String) {
        let new_needed = self.sessions
            .last()
            .map_or(true, |session| !session.is_running());
        if new_needed {
            self.new_session(None);
        }
        match self.sessions.last_mut() {
            Some(session) => {
                let message = git_commit_message(&hash).unwrap_or_default();
                session.push_event(None, Some(message), EventType::Commit { hash });
            }
            None => println!("No session to add commit to."),
        }
    }

    pub fn add_branch(&mut self, name: String) {
        self.sessions
            .last_mut()
            .map(|session| session.add_branch(name));
    }

    fn write_to_html(&self, ago: Option<u64>) -> bool {
        // TODO: avoid time-of-check-to-time-of-use race risk
        let path = Path::new("./timesheet.html");
        let file = OpenOptions::new()
            .write(true)
            .truncate(true)
            .create(true)
            .open(&path);

        match file {
            Ok(mut file) => {
                file.write_all(self.to_html(ago).as_bytes()).unwrap();
                format_file("timesheet.html");
                /* Save was successful */
                true
            }
            Err(why) => {
                println!("Could not report sheet! {}", why.description());
                false
            }
        }
    }

    fn write_last_session_html(&self) -> bool {
        let session = match self.sessions.last() {
            Some(session) => session,
            None => return true,
        };
        let path = Path::new("./session.html");
        let file = OpenOptions::new()
            .write(true)
            .truncate(true)
            .create(true)
            .open(&path);

        let mut file = match file {
            Ok(file) => file,
            Err(why) => {
                println!("Could not write report to session.html! {}",
                         why.description());
                return false;
            }
        };

        let stylesheets = if self.config.show_commits {
            r#"<link rel="stylesheet" type="text/css" href=".trk/style.css">
"#      } else {
            r#"<link rel="stylesheet" type="text/css" href=".trk/style.css">
<link rel="stylesheet" type="text/css" href=".trk/no_git_info.css">
"#
        };

        let html = format!(r#"<!DOCTYPE html>

  {}
  <title>{} for {}</title>

{}

"#,
               stylesheets,
               "Session",
               "Rafael Bachmann",
               session.to_html());
    file.write_all(html.as_bytes()).unwrap();
    format_file("session.html");
    /* Save was successful */
    true
    }

    fn write_to_json(&self) -> bool {
        if !Path::new("./.trk").exists() {
            match fs::create_dir("./.trk") {
                Ok(_) => {}
                _ => {
                    println!("Could not create .trk directory.");
                    process::exit(0);
                }
            }
        }

        let path = Path::new("./.trk/timesheet.json");
        let file = OpenOptions::new()
            .write(true)
            .truncate(true)
            .create(true)
            .open(&path);

        match file {
            Ok(mut file) => {
                /* Convert the sheet to a JSON string. */
                let serialized =
                    to_string(&self).expect("Could not write serialized time sheet.");
                file.write_all(serialized.as_bytes()).unwrap();
                /* Save was successful */
                true
            }
            Err(why) => {
                println!("Could not open timesheet.json file: {}",
                         why.description());
                false
            }
        }
    }

    fn write_stylesheets(filename: &str, content: &'static str) -> bool {
        // TODO: avoid time-of-check-to-time-of-use race risk
        let mut file_path = env::current_dir().unwrap();
        file_path.push(filename);
        if !file_path.exists() {
            let file = OpenOptions::new()
                .write(true)
                .truncate(true)
                .create(true)
                .open(&file_path);
            match file {
                Ok(mut file) => {
                    file.write_all(content.as_bytes()).unwrap();
                    /* Save was successful */
                    true
                }
                Err(why) => {
                    println!("Could not report sheet! {}", why.description());
                    false
                }
            }
        } else { true }
    }

    pub fn write_files(&self) -> bool {
        /* TODO: avoid time-of-check-to-time-of-use race risk */
        self.write_to_json() && self.write_to_html(None) && self.write_last_session_html()
    }

    /** Return a Some(Timesheet) struct if a timesheet.json file
     * is present and valid in the .trk directory, and None otherwise.
     * */
pub fn load_from_file() -> Option<Timesheet> {
    let mut path = env::current_dir().unwrap();
    loop {
        path.push(".trk");
        if path.exists() {
            env::set_current_dir(&path).is_ok();
            break;
        } else {
            path.pop();
            if !path.pop() {
                return None;
            }
        }
    }

    path.push("timesheet.json");
    let file = OpenOptions::new().read(true).open(&path);
    let result = match file {
        Ok(mut file) => {
            let mut serialized = String::new();
            match file.read_to_string(&mut serialized) {
                Ok(..) => {
                    let style             : &'static str = include_str!("../../style.css");
                    let no_git_info_style : &'static str = include_str!("../../no_git_info.css");
                    let trk_gitignore     : &'static str = include_str!("trk_gitignore");
                    Timesheet::write_stylesheets("style.css", style);
                    Timesheet::write_stylesheets("no_git_info.css", no_git_info_style);
                    Timesheet::write_stylesheets(".gitignore", trk_gitignore);
                    from_str(&serialized).unwrap_or(None)
                }
                Err(..) => {
                    println!("IO error while reading the timesheet file.");
                    process::exit(0);
                }
            }
        }
        Err(..) => None,
    };
    path.pop();
    env::set_current_dir(path).unwrap();
    result
}

    pub fn clear() {
        /* Try to get user name */
        let sheet = Timesheet::load_from_file();
        /* In case there is a sheet, there must also be a name */
        let name: Option<String> = sheet.map(|s| s.config.user_name.unwrap());

        let path = Path::new("./.trk/timesheet.json");
        if path.exists() {
            fs::remove_file(&path).unwrap_or_else(|why| {
                println!("Could not remove sessions file: {}", why.description());
            });
        }
        Timesheet::init(name.as_ref().map(|s| s.as_str()));
    }

    pub fn timesheet_status(&self) -> String {
        let mut status = format!("Sheet running for {}\n",
                sec_to_hms_string(get_seconds() - self.start));
        match self.sessions.len() {
            0 => write!(&mut status, "No sessions yet.\n").unwrap(),
            n => {
                write!(&mut status,
                       "{} session(s) so far.\nLast session:\n{}",
                       n,
                       self.sessions[n - 1].status())
                       .unwrap()
            }
        };
        status
    }

    pub fn last_session_status(&self) -> String {
        let status = self.sessions.last().map(|session| session.status());
        status.unwrap_or_else(|| String::from("No session yet."))
    }

    fn open_local_html(&self, filename: String) {
        let file_url = match env::current_dir() {
            Ok(dir) => {
                match dir.join(&filename).to_str() {
                    Some(path) => format!("file://{}", path),
                    None => {
                        println!("Invalid filename: {}.", filename);
                        process::exit(0)
                    }
                }
            }
            Err(why) => {
                println!("Couldn't obtain current directory: {}", why.description());
                process::exit(0)
            }
        };
        match Url::parse(&file_url) {
            Ok(url) => url.open(),
            Err(why) => println!("Couldn't open file: {}", why.description()),
        }
    }

    pub fn report_last_session(&self) {
        self.write_to_html(None);
        self.open_local_html("session.html".to_string());
    }

    pub fn report_sheet(&self, ago: Option<u64>) {
        self.write_to_html(ago);
        self.open_local_html("timesheet.html".to_string());
        /* Leave complete sheet html */
        self.write_to_html(None);
    }

    pub fn show_commits(&mut self, on_off: bool) {
        self.config.show_commits = on_off;
    }

    pub fn set_repo_url(&mut self, repo: String) {
        let repo =
            if repo == "" { None } else { Some(repo) };
        self.config.repository = repo;
    }

    pub fn pause_time(&self) -> u64 {
        self.sessions.iter().fold(
            0, |total, session| total + session.pause_time())
    }

    pub fn work_time(&self) -> u64 {
        self.sessions.iter().fold(
            0, |total, session| total + session.work_time())
    }

    fn to_html(&self, ago: Option<u64>) -> String {
        let timestamp = ago.unwrap_or(self.start);
        let mut sessions_html = String::new();
        for session in &self.sessions {
            if session.start > timestamp {
                sessions_html.push_str(&format!("{}
", session.to_html()));
            }
        }

        let stylesheets = if self.config.show_commits {
            "<link rel=\"stylesheet\" type=\"text/css\" href=\".trk/style.css\">\n".to_string()
        } else {
            r#"<link rel="stylesheet" type="text/css" href=".trk/style.css">
<link rel="stylesheet" type="text/css" href=".trk/no_git_info.css">
"#.to_string()
        };

        let mut html = format!(r#"<!DOCTYPE html>

        {}
        <title>{} for {}</title>

    {}"#,
              stylesheets,
              "Timesheet",
              "Rafael Bachmann",
              sessions_html);

        write!(&mut html,
               r#"

Worked for {}

Paused for {}

"#,
               sec_to_hms_string(self.work_time()),
               sec_to_hms_string(self.pause_time()))
               .unwrap();
        write!(&mut html, "\n").unwrap();
        html
    }
}
<commit_msg>clippy fixes<commit_after>use std::io::prelude::*;
use std::{process, env};
use std::path::Path;
use std::error::Error;
use std::fs::{self, OpenOptions};
/* Alias to avoid naming conflict for write_all!() */
use std::fmt::Write as std_write;

use url::Url;
use url_open::UrlOpen;

use serde_json::{from_str, to_string};

use util::*;
use config::Config;
use sheet::traits::HasHTML;

use sheet::session::Session;
use sheet::session::EventType;

#[derive(Serialize, Deserialize, Debug)]
pub struct Timesheet {
    start            : u64,
    end              : u64,
    config           : Config,
    sessions         : Vec<Session>,
}

impl Timesheet {
    /** Initializes the .trk/timesheet.json file which holds
     * the serialized timesheet
     * Returns Some(newTimesheet) if operation succeeded */
    pub fn init(author_name: Option<&str>) -> Option<Timesheet> {
        /* Check if file already exists (no init permitted) */
        if Timesheet::is_init() {
            println!("Timesheet is already initialized!");
            return None;
        }
        /* File does not exist, initialize */
        let git_author_name = git_author();
        let author_name = match author_name {
            Some(name) => name,
            None => {
                match git_author_name {
                    Some(ref git_name) => git_name,
                    None => {
                        println!("Empty name not permitted. \
                                  Please run with 'trk init <name>'");
                        process::exit(0);
                    }
                }
            }
        };
        let mut config = Config::new();
        config.user_name = Some(author_name.to_string());
        let now = get_seconds();
        let sheet = Timesheet {
            start        : now,
            end          : now + 1,
            config,
            sessions     : Vec::<Session>::new(),
        };
        if sheet.write_files() {
            git_init_trk();
            Some(sheet)
        } else {
            None
        }
    }

    fn is_init() -> bool {
        Path::new("./.trk/timesheet.json").exists() && Timesheet::load_from_file().is_some()
    }

    pub fn new_session(&mut self, timestamp: Option<u64>) -> bool {
        let possible = self.sessions
            .last_mut()
            .map_or(true, |session| {
                if session.is_running() {
                    println!("Last session is still running.");
                }
                !session.is_running()
        });
        if possible {
            match timestamp {
                Some(timestamp) => {
                    let is_valid_ts = match self.sessions.last() {
                        None => timestamp > self.start,
                        Some(last_session) => timestamp > last_session.end,
                    };
                    if is_valid_ts {
                        self.sessions.push(Session::new(Some(timestamp)));
                    } else {
                        println!("That timestamp is invalid.");
                        process::exit(0);
                    }
                }
                None => {
                    self.sessions.push(Session::new(None));
                }
            };
        }
        possible
    }

    pub fn end_session(&mut self, timestamp: Option<u64>) {
        match self.sessions.last_mut() {
            Some(session) => {
                session.update_end();
                session.finalize(timestamp);
                self.end = session.end + 1;
            }
            None => println!("No session to finalize."),
        }
    }

    pub fn pause(&mut self, timestamp: Option<u64>, note: Option<String>) {
        match self.sessions.last_mut() {
            Some(session) => {
                session.push_event(timestamp, note, EventType::Pause);
            }
            None => println!("No session to pause."),
        }
    }

    pub fn resume(&mut self, timestamp: Option<u64>) {
        match self.sessions.last_mut() {
            Some(session) => {
                session.push_event(timestamp, None, EventType::Resume);
            }
            None => println!("No session to pause."),
        }
    }

    pub fn note(&mut self, timestamp: Option<u64>, note_text: String) {
        match self.sessions.last_mut() {
            Some(session) => {
                session.push_event(timestamp, Some(note_text), EventType::Note);
            }
            None => println!("No session to add note to."),
        }
    }

    pub fn add_commit(&mut self, hash: String) {
        let new_needed = self.sessions
            .last()
            .map_or(true, |session| !session.is_running());
        if new_needed {
            self.new_session(None);
        }
        match self.sessions.last_mut() {
            Some(session) => {
                let message = git_commit_message(&hash).unwrap_or_default();
                session.push_event(None, Some(message), EventType::Commit { hash });
            }
            None => println!("No session to add commit to."),
        }
    }

    pub fn add_branch(&mut self, name: String) {
        if let Some(session) = self.sessions.last_mut() {
            session.add_branch(name)
        }
    }

    fn write_to_html(&self, ago: Option<u64>) -> bool {
        // TODO: avoid time-of-check-to-time-of-use race risk
        let path = Path::new("./timesheet.html");
        let file = OpenOptions::new()
            .write(true)
            .truncate(true)
            .create(true)
            .open(&path);

        match file {
            Ok(mut file) => {
                file.write_all(self.to_html(ago).as_bytes()).unwrap();
                format_file("timesheet.html");
                /* Save was successful */
                true
            }
            Err(why) => {
                println!("Could not report sheet! {}", why.description());
                false
            }
        }
    }

    fn write_last_session_html(&self) -> bool {
        let session = match self.sessions.last() {
            Some(session) => session,
            None => return true,
        };
        let path = Path::new("./session.html");
        let file = OpenOptions::new()
            .write(true)
            .truncate(true)
            .create(true)
            .open(&path);

        let mut file = match file {
            Ok(file) => file,
            Err(why) => {
                println!("Could not write report to session.html! {}",
                         why.description());
                return false;
            }
        };

        let stylesheets = if self.config.show_commits {
            r#"<link rel="stylesheet" type="text/css" href=".trk/style.css">
"#      } else {
            r#"<link rel="stylesheet" type="text/css" href=".trk/style.css">
<link rel="stylesheet" type="text/css" href=".trk/no_git_info.css">
"#
        };

        let html = format!(r#"<!DOCTYPE html>

  {}
  <title>{} for {}</title>

{}

"#,
               stylesheets,
               "Session",
               "Rafael Bachmann",
               session.to_html());
    file.write_all(html.as_bytes()).unwrap();
    format_file("session.html");
    /* Save was successful */
    true
    }

    fn write_to_json(&self) -> bool {
        if !Path::new("./.trk").exists() {
            match fs::create_dir("./.trk") {
                Ok(_) => {}
                _ => {
                    println!("Could not create .trk directory.");
                    process::exit(0);
                }
            }
        }

        let path = Path::new("./.trk/timesheet.json");
        let file = OpenOptions::new()
            .write(true)
            .truncate(true)
            .create(true)
            .open(&path);

        match file {
            Ok(mut file) => {
                /* Convert the sheet to a JSON string. */
                let serialized =
                    to_string(&self).expect("Could not write serialized time sheet.");
                file.write_all(serialized.as_bytes()).unwrap();
                /* Save was successful */
                true
            }
            Err(why) => {
                println!("Could not open timesheet.json file: {}",
                         why.description());
                false
            }
        }
    }

    fn write_stylesheets(filename: &str, content: &'static str) -> bool {
        // TODO: avoid time-of-check-to-time-of-use race risk
        let mut file_path = env::current_dir().unwrap();
        file_path.push(filename);
        if !file_path.exists() {
            let file = OpenOptions::new()
                .write(true)
                .truncate(true)
                .create(true)
                .open(&file_path);
            match file {
                Ok(mut file) => {
                    file.write_all(content.as_bytes()).unwrap();
                    /* Save was successful */
                    true
                }
                Err(why) => {
                    println!("Could not report sheet! {}", why.description());
                    false
                }
            }
        } else { true }
    }

    pub fn write_files(&self) -> bool {
        /* TODO: avoid time-of-check-to-time-of-use race risk */
        self.write_to_json() && self.write_to_html(None) && self.write_last_session_html()
    }

    /** Return a Some(Timesheet) struct if a timesheet.json file
     * is present and valid in the .trk directory, and None otherwise.
     * */
pub fn load_from_file() -> Option<Timesheet> {
    let mut path = env::current_dir().unwrap();
    loop {
        path.push(".trk");
        if path.exists() {
            env::set_current_dir(&path).is_ok();
            break;
        } else {
            path.pop();
            if !path.pop() {
                return None;
            }
        }
    }

    path.push("timesheet.json");
    let file = OpenOptions::new().read(true).open(&path);
    let result = match file {
        Ok(mut file) => {
            let mut serialized = String::new();
            match file.read_to_string(&mut serialized) {
                Ok(..) => {
                    let style             : &'static str = include_str!("../../style.css");
                    let no_git_info_style : &'static str = include_str!("../../no_git_info.css");
                    let trk_gitignore     : &'static str = include_str!("trk_gitignore");
                    Timesheet::write_stylesheets("style.css", style);
                    Timesheet::write_stylesheets("no_git_info.css", no_git_info_style);
                    Timesheet::write_stylesheets(".gitignore", trk_gitignore);
                    from_str(&serialized).unwrap_or(None)
                }
                Err(..) => {
                    println!("IO error while reading the timesheet file.");
                    process::exit(0);
                }
            }
        }
        Err(..) => None,
    };
    path.pop();
    env::set_current_dir(path).unwrap();
    result
}

    pub fn clear() {
        /* Try to get user name */
        let sheet = Timesheet::load_from_file();
        /* In case there is a sheet, there must also be a name */
        let name: Option<String> = sheet.map(|s| s.config.user_name.unwrap());

        let path = Path::new("./.trk/timesheet.json");
        if path.exists() {
            fs::remove_file(&path).unwrap_or_else(|why| {
                println!("Could not remove sessions file: {}", why.description());
            });
        }
        Timesheet::init(name.as_ref().map(|s| s.as_str()));
    }

    pub fn timesheet_status(&self) -> String {
        let mut status = format!("Sheet running for {}\n",
                sec_to_hms_string(get_seconds() - self.start));
        match self.sessions.len() {
            0 => writeln!(&mut status, "No sessions yet.").unwrap(),
            n => {
                write!(&mut status,
                       "{} session(s) so far.\nLast session:\n{}",
                       n,
                       self.sessions[n - 1].status())
                       .unwrap()
            }
        };
        status
    }

    pub fn last_session_status(&self) -> String {
        let status = self.sessions.last().map(|session| session.status());
        status.unwrap_or_else(|| String::from("No session yet."))
    }

    fn open_local_html(&self, filename: &str) {
        let file_url = match env::current_dir() {
            Ok(dir) => {
                match dir.join(&filename).to_str() {
                    Some(path) => format!("file://{}", path),
                    None => {
                        println!("Invalid filename: {}.", filename);
                        process::exit(0)
                    }
                }
            }
            Err(why) => {
                println!("Couldn't obtain current directory: {}", why.description());
                process::exit(0)
            }
        };
        match Url::parse(&file_url) {
            Ok(url) => url.open(),
            Err(why) => println!("Couldn't open file: {}", why.description()),
        }
    }

    pub fn report_last_session(&self) {
        self.write_to_html(None);
        self.open_local_html("session.html");
    }

    pub fn report_sheet(&self, ago: Option<u64>) {
        self.write_to_html(ago);
        self.open_local_html("timesheet.html");
        /* Leave complete sheet html */
        self.write_to_html(None);
    }

    pub fn show_commits(&mut self, on_off: bool) {
        self.config.show_commits = on_off;
    }

    pub fn set_repo_url(&mut self, repo: String) {
        let repo =
            if repo == "" { None } else { Some(repo) };
        self.config.repository = repo;
    }

    pub fn pause_time(&self) -> u64 {
        self.sessions.iter().fold(
            0, |total, session| total + session.pause_time())
    }

    pub fn work_time(&self) -> u64 {
        self.sessions.iter().fold(
            0, |total, session| total + session.work_time())
    }

    fn to_html(&self, ago: Option<u64>) -> String {
        let timestamp = ago.unwrap_or(self.start);
        let mut sessions_html = String::new();
        for session in &self.sessions {
            if session.start > timestamp {
                sessions_html.push_str(&format!("{}
", session.to_html()));
            }
        }

        let stylesheets = if self.config.show_commits {
            "<link rel=\"stylesheet\" type=\"text/css\" href=\".trk/style.css\">\n".to_string()
        } else {
            r#"<link rel="stylesheet" type="text/css" href=".trk/style.css">
<link rel="stylesheet" type="text/css" href=".trk/no_git_info.css">
"#.to_string()
        };

        let mut html = format!(r#"<!DOCTYPE html>

        {}
        <title>{} for {}</title>

    {}"#,
              stylesheets,
              "Timesheet",
              "Rafael Bachmann",
              sessions_html);

        write!(&mut html,
               r#"

Worked for {}

Paused for {}

"#,
               sec_to_hms_string(self.work_time()),
               sec_to_hms_string(self.pause_time()))
               .unwrap();
        write!(&mut html, "\n").unwrap();
        html
    }
}
<|endoftext|>


<commit_before>
//! Private functions and items used with the high-level library wrapper

use core::libc::*;
use core::hashmap::*;
use core::local_data::*;

use super::*;
use ll::*;

///
/// Holds data associated with a window for storage in TLS
///
pub struct WindowData {
    pos_fun:             Option<WindowPosFun>,
    size_fun:            Option<WindowSizeFun>,
    close_fun:           Option<WindowCloseFun>,
    refresh_fun:         Option<WindowRefreshFun>,
    focus_fun:           Option<WindowFocusFun>,
    iconify_fun:         Option<WindowIconifyFun>,
    mouse_button_fun:    Option<MouseButtonFun>,
    cursor_pos_fun:      Option<CursorPosFun>,
    cursor_enter_fun:    Option<CursorEnterFun>,
    scroll_fun:          Option<ScrollFun>,
    key_fun:             Option<KeyFun>,
    char_fun:            Option<CharFun>,
}

pub impl WindowData {
    /// Initialize an empty struct
    fn new() -> WindowData {
        WindowData {
            pos_fun:             None,
            size_fun:            None,
            close_fun:           None,
            refresh_fun:         None,
            focus_fun:           None,
            iconify_fun:         None,
            mouse_button_fun:    None,
            cursor_pos_fun:      None,
            cursor_enter_fun:    None,
            scroll_fun:          None,
            key_fun:             None,
            char_fun:            None,
        }
    }
}

///
/// A map of window data to be stored in TLS
///
pub struct WindowDataMap(HashMap<*GLFWwindow, @mut WindowData>);

pub impl WindowDataMap {
    /// Function stub used for retrieving a the map of window data from TLS.
    priv fn tls_key(_: @@mut WindowDataMap) {}

    /// Initializes a map of window data in TLS.
    fn init() {
        unsafe {
            local_data_set(
                WindowDataMap::tls_key,
                @@mut WindowDataMap(HashMap::new())
            )
        }
    }

    /// Retrieves a mutable pointer to the map of window data stored TLS,
    /// failing if the map could not be found.
    fn get() -> @mut WindowDataMap {
        match unsafe { local_data_get(WindowDataMap::tls_key) } {
            Some(@local_data) => local_data,
            None => fail!("Could not find a WindowDataMap in thread-local storage."),
        }
    }

    /// Removes the map of window data from TLS if it exists.
    fn remove() {
        unsafe {
            local_data_modify(WindowDataMap::tls_key, |_| None);
        }
    }
}

// Global callbacks

fn error_fun_tls_key(_: @ErrorFun) {}

pub extern "C" fn error_callback(error: c_int, description: *c_char) {
    unsafe {
        do local_data_get(error_fun_tls_key).map |&@cb| {
            cb(error, str::raw::from_c_str(description))
        };
    }
}

pub fn set_error_fun(cbfun: ErrorFun, f: &fn(GLFWerrorfun) ) {
    unsafe {
        local_data_set(error_fun_tls_key, @cbfun);
        f(error_callback);
    }
}

fn monitor_fun_tls_key(_: @MonitorFun) {}

pub extern "C" fn monitor_callback(monitor: *GLFWmonitor, event: c_int) {
    unsafe {
        do local_data_get(monitor_fun_tls_key).map |&@cb| {
            cb(&Monitor { ptr: monitor }, event)
        };
    }
}

pub fn set_monitor_fun(cbfun: MonitorFun, f: &fn(GLFWmonitorfun) ) {
    unsafe {
        local_data_set(monitor_fun_tls_key, @cbfun);
        f(monitor_callback);
    }
}

// External window callbacks

pub extern "C" fn window_pos_callback(window: *GLFWwindow, xpos: c_int, ypos: c_int) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().pos_fun.map |&cb| {
        cb(&window_, xpos as int, ypos as int)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn window_size_callback(window: *GLFWwindow, width: c_int, height: c_int) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().size_fun.map |&cb| {
        cb(&window_, width as int, height as int)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn window_close_callback(window: *GLFWwindow) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().close_fun.map |&cb| {
        cb(&window_)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn window_refresh_callback(window: *GLFWwindow) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().refresh_fun.map |&cb| {
        cb(&window_)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn window_focus_callback(window: *GLFWwindow, focused: c_int) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().focus_fun.map |&cb| {
        cb(&window_, focused as bool)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn window_iconify_callback(window: *GLFWwindow, iconified: c_int) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().iconify_fun.map |&cb| {
        cb(&window_, iconified as bool)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn mouse_button_callback(window: *GLFWwindow, button: c_int, action: c_int) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().mouse_button_fun.map |&cb| {
        cb(&window_, button, action)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn cursor_pos_callback(window: *GLFWwindow, xpos: c_double, ypos: c_double) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().cursor_pos_fun.map |&cb| {
        cb(&window_, xpos as float, ypos as float)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn cursor_enter_callback(window: *GLFWwindow, entered: c_int) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().cursor_enter_fun.map |&cb| {
        cb(&window_, entered as bool)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn scroll_callback(window: *GLFWwindow, xpos: c_double, ypos: c_double) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().scroll_fun.map |&cb| {
        cb(&window_, xpos as float, ypos as float)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn key_callback(window: *GLFWwindow, key: c_int, action: c_int) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().key_fun.map |&cb| {
        cb(&window_, key, action)
    };
    unsafe { cast::forget(window_); }
}

pub extern "C" fn char_callback(window: *GLFWwindow, character: c_uint) {
    let window_ = Window { ptr: window };
    do window_.get_local_data().char_fun.map |&cb| {
        cb(&window_, character as char)
    };
    unsafe { cast::forget(window_); }
}
<commit_msg>Generate external window callbacks using a macro<commit_after>
//! Private functions and items used with the high-level library wrapper

use core::libc::*;
use core::hashmap::*;
use core::local_data::*;

use super::*;
use ll::*;

///
/// Holds data associated with a window for storage in TLS
///
pub struct WindowData {
    pos_fun:             Option<WindowPosFun>,
    size_fun:            Option<WindowSizeFun>,
    close_fun:           Option<WindowCloseFun>,
    refresh_fun:         Option<WindowRefreshFun>,
    focus_fun:           Option<WindowFocusFun>,
    iconify_fun:         Option<WindowIconifyFun>,
    mouse_button_fun:    Option<MouseButtonFun>,
    cursor_pos_fun:      Option<CursorPosFun>,
    cursor_enter_fun:    Option<CursorEnterFun>,
    scroll_fun:          Option<ScrollFun>,
    key_fun:             Option<KeyFun>,
    char_fun:            Option<CharFun>,
}

pub impl WindowData {
    /// Initialize an empty struct
    fn new() -> WindowData {
        WindowData {
            pos_fun:             None,
            size_fun:            None,
            close_fun:           None,
            refresh_fun:         None,
            focus_fun:           None,
            iconify_fun:         None,
            mouse_button_fun:    None,
            cursor_pos_fun:      None,
            cursor_enter_fun:    None,
            scroll_fun:          None,
            key_fun:             None,
            char_fun:            None,
        }
    }
}

///
/// A map of window data to be stored in TLS
///
pub struct WindowDataMap(HashMap<*GLFWwindow, @mut WindowData>);

pub impl WindowDataMap {
    /// Function stub used for retrieving a the map of window data from TLS.
    priv fn tls_key(_: @@mut WindowDataMap) {}

    /// Initializes a map of window data in TLS.
    fn init() {
        unsafe {
            local_data_set(
                WindowDataMap::tls_key,
                @@mut WindowDataMap(HashMap::new())
            )
        }
    }

    /// Retrieves a mutable pointer to the map of window data stored TLS,
    /// failing if the map could not be found.
    fn get() -> @mut WindowDataMap {
        match unsafe { local_data_get(WindowDataMap::tls_key) } {
            Some(@local_data) => local_data,
            None => fail!("Could not find a WindowDataMap in thread-local storage."),
        }
    }

    /// Removes the map of window data from TLS if it exists.
    fn remove() {
        unsafe {
            local_data_modify(WindowDataMap::tls_key, |_| None);
        }
    }
}

// Global callbacks

fn error_fun_tls_key(_: @ErrorFun) {}

pub extern "C" fn error_callback(error: c_int, description: *c_char) {
    unsafe {
        do local_data_get(error_fun_tls_key).map |&@cb| {
            cb(error, str::raw::from_c_str(description))
        };
    }
}

pub fn set_error_fun(cbfun: ErrorFun, f: &fn(GLFWerrorfun) ) {
    unsafe {
        local_data_set(error_fun_tls_key, @cbfun);
        f(error_callback);
    }
}

fn monitor_fun_tls_key(_: @MonitorFun) {}

pub extern "C" fn monitor_callback(monitor: *GLFWmonitor, event: c_int) {
    unsafe {
        do local_data_get(monitor_fun_tls_key).map |&@cb| {
            cb(&Monitor { ptr: monitor }, event)
        };
    }
}

pub fn set_monitor_fun(cbfun: MonitorFun, f: &fn(GLFWmonitorfun) ) {
    unsafe {
        local_data_set(monitor_fun_tls_key, @cbfun);
        f(monitor_callback);
    }
}

// External window callbacks

macro_rules! window_callback(
    (fn $name:ident () => $field:ident()) => (
        pub extern "C" fn $name(window: *GLFWwindow) {
            let window_ = Window { ptr: window };
            do window_.get_local_data().$field.map |&cb| {
                cb(&window_)
            };
            unsafe { cast::forget(window_); }
        }
    );
    (fn $name:ident ($($ext_arg:ident: $ext_arg_ty:ty),*) => $field:ident($($arg_conv:expr),*)) => (
        pub extern "C" fn $name(window: *GLFWwindow $(, $ext_arg: $ext_arg_ty)*) {
            let window_ = Window { ptr: window };
            do window_.get_local_data().$field.map |&cb| {
                cb(&window_ $(, $arg_conv)*)
            };
            unsafe { cast::forget(window_); }
        }
    );
)

window_callback!(fn window_pos_callback(xpos: c_int, ypos: c_int)       => pos_fun(xpos as int, ypos as int))
window_callback!(fn window_size_callback(width: c_int, height: c_int)   => size_fun(width as int, height as int))
window_callback!(fn window_close_callback()                             => close_fun())
window_callback!(fn window_refresh_callback()                           => refresh_fun())
window_callback!(fn window_focus_callback(focused: c_int)               => focus_fun(focused as bool))
window_callback!(fn window_iconify_callback(iconified: c_int)           => iconify_fun(iconified as bool))
window_callback!(fn mouse_button_callback(button: c_int, action: c_int) => mouse_button_fun(button, action))
window_callback!(fn cursor_pos_callback(xpos: c_double, ypos: c_double) => cursor_pos_fun(xpos as float, ypos as float))
window_callback!(fn cursor_enter_callback(entered: c_int)               => cursor_enter_fun(entered as bool))
window_callback!(fn scroll_callback(xpos: c_double, ypos: c_double)     => scroll_fun(xpos as float, ypos as float))
window_callback!(fn key_callback(key: c_int, action: c_int)             => key_fun(key, action))
window_callback!(fn char_callback(character: c_uint)                    => char_fun(character as char))
<|endoftext|>


<commit_before>use std::{cmp, fmt, ptr};
#[cfg(not(target_os = "netbsd"))]
use std::os::raw::{c_int, c_short};
use std::os::unix::io::AsRawFd;
use std::os::unix::io::RawFd;
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
use std::time::Duration;

use libc::{self, time_t};

use {io, Ready, PollOpt, Token};
use event_imp::{self as event, Event};
use sys::unix::{cvt, UnixReady};
use sys::unix::io::set_cloexec;

/// Each Selector has a globally unique(ish) ID associated with it. This ID
/// gets tracked by `TcpStream`, `TcpListener`, etc... when they are first
/// registered with the `Selector`. If a type that is previously associated with
/// a `Selector` attempts to register itself with a different `Selector`, the
/// operation will return with an error. This matches windows behavior.
static NEXT_ID: AtomicUsize = ATOMIC_USIZE_INIT;

#[cfg(not(target_os = "netbsd"))]
type Filter = c_short;
#[cfg(not(target_os = "netbsd"))]
type UData = *mut ::libc::c_void;
#[cfg(not(target_os = "netbsd"))]
type Count = c_int;

#[cfg(target_os = "netbsd")]
type Filter = u32;
#[cfg(target_os = "netbsd")]
type UData = ::libc::intptr_t;
#[cfg(target_os = "netbsd")]
type Count = usize;

macro_rules! kevent {
    ($id: expr, $filter: expr, $flags: expr, $data: expr) => {
        libc::kevent {
            ident: $id as ::libc::uintptr_t,
            filter: $filter as Filter,
            flags: $flags,
            fflags: 0,
            data: 0,
            udata: $data as UData,
        }
    }
}

pub struct Selector {
    id: usize,
    kq: RawFd,
}

impl Selector {
    pub fn new() -> io::Result<Selector> {
        // offset by 1 to avoid choosing 0 as the id of a selector
        let id = NEXT_ID.fetch_add(1, Ordering::Relaxed) + 1;
        let kq = unsafe { cvt(libc::kqueue())? };
        drop(set_cloexec(kq));

        Ok(Selector {
            id,
            kq,
        })
    }

    pub fn id(&self) -> usize {
        self.id
    }

    pub fn select(&self, evts: &mut Events, awakener: Token, timeout: Option<Duration>) -> io::Result<bool> {
        let timeout = timeout.map(|to| {
            libc::timespec {
                tv_sec: cmp::min(to.as_secs(), time_t::max_value() as u64) as time_t,
                // `Duration::subsec_nanos` is guaranteed to be less than one
                // billion (the number of nanoseconds in a second), making the
                // cast to i32 safe. The cast itself is needed for platforms
                // where C's long is only 32 bits.
                tv_nsec: libc::c_long::from(to.subsec_nanos() as i32),
            }
        });
        let timeout = timeout.as_ref().map(|s| s as *const _).unwrap_or(ptr::null_mut());

        evts.clear();
        unsafe {
            let cnt = cvt(libc::kevent(self.kq,
                                            ptr::null(),
                                            0,
                                            evts.sys_events.0.as_mut_ptr(),
                                            evts.sys_events.0.capacity() as Count,
                                            timeout))?;
            evts.sys_events.0.set_len(cnt as usize);
            Ok(evts.coalesce(awakener))
        }
    }

    pub fn register(&self, fd: RawFd, token: Token, interests: Ready, opts: PollOpt) -> io::Result<()> {
        trace!("registering; token={:?}; interests={:?}", token, interests);

        let flags = if opts.contains(PollOpt::edge()) { libc::EV_CLEAR } else { 0 } |
                    if opts.contains(PollOpt::oneshot()) { libc::EV_ONESHOT } else { 0 } |
                    libc::EV_RECEIPT;

        unsafe {
            let r = if interests.contains(Ready::readable()) { libc::EV_ADD } else { libc::EV_DELETE };
            let w = if interests.contains(Ready::writable()) { libc::EV_ADD } else { libc::EV_DELETE };
            let mut changes = [
                kevent!(fd, libc::EVFILT_READ, flags | r, usize::from(token)),
                kevent!(fd, libc::EVFILT_WRITE, flags | w, usize::from(token)),
            ];

            cvt(libc::kevent(self.kq,
                             changes.as_ptr(),
                             changes.len() as Count,
                             changes.as_mut_ptr(),
                             changes.len() as Count,
                             ::std::ptr::null()))?;

            for change in changes.iter() {
                debug_assert_eq!(change.flags & libc::EV_ERROR, libc::EV_ERROR);

                // Test to see if an error happened
                if change.data == 0 {
                    continue
                }

                // Older versions of OSX (10.11 and 10.10 have been witnessed)
                // can return EPIPE when registering a pipe file descriptor
                // where the other end has already disappeared. For example code
                // that creates a pipe, closes a file descriptor, and then
                // registers the other end will see an EPIPE returned from
                // `register`.
                //
                // It also turns out that kevent will still report events on the
                // file descriptor, telling us that it's readable/hup at least
                // after we've done this registration. As a result we just
                // ignore `EPIPE` here instead of propagating it.
                //
                // More info can be found at carllerche/mio#582
                if change.data as i32 == libc::EPIPE &&
                   change.filter == libc::EVFILT_WRITE as Filter {
                    continue
                }

                // ignore ENOENT error for EV_DELETE
                let orig_flags = if change.filter == libc::EVFILT_READ as Filter { r } else { w };
                if change.data as i32 == libc::ENOENT && orig_flags & libc::EV_DELETE != 0 {
                    continue
                }

                return Err(::std::io::Error::from_raw_os_error(change.data as i32));
            }
            Ok(())
        }
    }

    pub fn reregister(&self, fd: RawFd, token: Token, interests: Ready, opts: PollOpt) -> io::Result<()> {
        // Just need to call register here since EV_ADD is a mod if already
        // registered
        self.register(fd, token, interests, opts)
    }

    pub fn deregister(&self, fd: RawFd) -> io::Result<()> {
        unsafe {
            // EV_RECEIPT is a nice way to apply changes and get back per-event results while not
            // draining the actual changes.
            let filter = libc::EV_DELETE | libc::EV_RECEIPT;
#[cfg(not(target_os = "netbsd"))]
            let mut changes = [
                kevent!(fd, libc::EVFILT_READ, filter, ptr::null_mut()),
                kevent!(fd, libc::EVFILT_WRITE, filter, ptr::null_mut()),
            ];

#[cfg(target_os = "netbsd")]
            let mut changes = [
                kevent!(fd, libc::EVFILT_READ, filter, 0),
                kevent!(fd, libc::EVFILT_WRITE, filter, 0),
            ];

            cvt(libc::kevent(self.kq,
                             changes.as_ptr(),
                             changes.len() as Count,
                             changes.as_mut_ptr(),
                             changes.len() as Count,
                             ::std::ptr::null())).map(|_| ())?;

            if changes[0].data as i32 == libc::ENOENT && changes[1].data as i32 == libc::ENOENT {
                return Err(::std::io::Error::from_raw_os_error(changes[0].data as i32));
            }
            for change in changes.iter() {
                debug_assert_eq!(libc::EV_ERROR & change.flags, libc::EV_ERROR);
                if change.data != 0 && change.data as i32 != libc::ENOENT {
                    return Err(::std::io::Error::from_raw_os_error(changes[0].data as i32));
                }
            }
            Ok(())
        }
    }
}

impl fmt::Debug for Selector {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("Selector")
            .field("id", &self.id)
            .field("kq", &self.kq)
            .finish()
    }
}

impl AsRawFd for Selector {
    fn as_raw_fd(&self) -> RawFd {
        self.kq
    }
}

impl Drop for Selector {
    fn drop(&mut self) {
        unsafe {
            let _ = libc::close(self.kq);
        }
    }
}

pub struct Events {
    sys_events: KeventList,
    events: Vec<Event>,
    event_map: HashMap<Token, usize>,
}

struct KeventList(Vec<libc::kevent>);

unsafe impl Send for KeventList {}
unsafe impl Sync for KeventList {}

impl Events {
    pub fn with_capacity(cap: usize) -> Events {
        Events {
            sys_events: KeventList(Vec::with_capacity(cap)),
            events: Vec::with_capacity(cap),
            event_map: HashMap::with_capacity(cap)
        }
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.events.len()
    }

    #[inline]
    pub fn capacity(&self) -> usize {
        self.events.capacity()
    }

    #[inline]
    pub fn is_empty(&self) -> bool {
        self.events.is_empty()
    }

    pub fn get(&self, idx: usize) -> Option<Event> {
        self.events.get(idx).cloned()
    }

    fn coalesce(&mut self, awakener: Token) -> bool {
        let mut ret = false;
        self.events.clear();
        self.event_map.clear();

        for e in self.sys_events.0.iter() {
            let token = Token(e.udata as usize);
            let len = self.events.len();

            if token == awakener {
                // TODO: Should this return an error if event is an error. It
                // is not critical as spurious wakeups are permitted.
                ret = true;
                continue;
            }

            let idx = *self.event_map.entry(token)
                .or_insert(len);

            if idx == len {
                // New entry, insert the default
                self.events.push(Event::new(Ready::empty(), token));

            }

            if e.flags & libc::EV_ERROR != 0 {
                event::kind_mut(&mut self.events[idx]).insert(*UnixReady::error());
            }

            if e.filter == libc::EVFILT_READ as Filter {
                event::kind_mut(&mut self.events[idx]).insert(Ready::readable());
            } else if e.filter == libc::EVFILT_WRITE as Filter {
                event::kind_mut(&mut self.events[idx]).insert(Ready::writable());

                // `EV_EOF` set with `EVFILT_WRITE` indicates the connection has been fully
                // disconnected (read and write), but only sockets...
                if e.flags & libc::EV_EOF != 0 {
                    event::kind_mut(&mut self.events[idx]).insert(UnixReady::hup());

                    // When the read end of the socket is closed, EV_EOF is set on
                    // flags, and fflags contains the error if there is one.
                    if e.fflags != 0 {
                        event::kind_mut(&mut self.events[idx]).insert(UnixReady::error());
                    }
                }
            }
#[cfg(any(target_os = "dragonfly",
    target_os = "freebsd", target_os = "ios", target_os = "macos"))]
            {
                if e.filter == libc::EVFILT_AIO {
                    event::kind_mut(&mut self.events[idx]).insert(UnixReady::aio());
                }
            }
#[cfg(any(target_os = "freebsd"))]
            {
                if e.filter == libc::EVFILT_LIO {
                    event::kind_mut(&mut self.events[idx]).insert(UnixReady::lio());
                }
            }
        }

        ret
    }

    pub fn push_event(&mut self, event: Event) {
        self.events.push(event);
    }

    pub fn clear(&mut self) {
        self.sys_events.0.truncate(0);
        self.events.truncate(0);
        self.event_map.clear();
    }
}

impl fmt::Debug for Events {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("Events")
            .field("len", &self.sys_events.0.len())
            .finish()
    }
}

#[test]
fn does_not_register_rw() {
    use {Poll, Ready, PollOpt, Token};
    use unix::EventedFd;

    let kq = unsafe { libc::kqueue() };
    let kqf = EventedFd(&kq);
    let poll = Poll::new().unwrap();

    // registering kqueue fd will fail if write is requested (On anything but some versions of OS
    // X)
    poll.register(&kqf, Token(1234), Ready::readable(),
                  PollOpt::edge() | PollOpt::oneshot()).unwrap();
}

#[cfg(any(target_os = "dragonfly",
    target_os = "freebsd", target_os = "ios", target_os = "macos"))]
#[test]
fn test_coalesce_aio() {
    let mut events = Events::with_capacity(1);
    events.sys_events.0.push(kevent!(0x1234, libc::EVFILT_AIO, 0, 42));
    events.coalesce(Token(0));
    assert!(events.events[0].readiness() == UnixReady::aio().into());
    assert!(events.events[0].token() == Token(42));
}
<commit_msg>Do not trigger HUP events on kqueue<commit_after>use std::{cmp, fmt, ptr};
#[cfg(not(target_os = "netbsd"))]
use std::os::raw::{c_int, c_short};
use std::os::unix::io::AsRawFd;
use std::os::unix::io::RawFd;
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
use std::time::Duration;

use libc::{self, time_t};

use {io, Ready, PollOpt, Token};
use event_imp::{self as event, Event};
use sys::unix::{cvt, UnixReady};
use sys::unix::io::set_cloexec;

/// Each Selector has a globally unique(ish) ID associated with it. This ID
/// gets tracked by `TcpStream`, `TcpListener`, etc... when they are first
/// registered with the `Selector`. If a type that is previously associated with
/// a `Selector` attempts to register itself with a different `Selector`, the
/// operation will return with an error. This matches windows behavior.
static NEXT_ID: AtomicUsize = ATOMIC_USIZE_INIT;

#[cfg(not(target_os = "netbsd"))]
type Filter = c_short;
#[cfg(not(target_os = "netbsd"))]
type UData = *mut ::libc::c_void;
#[cfg(not(target_os = "netbsd"))]
type Count = c_int;

#[cfg(target_os = "netbsd")]
type Filter = u32;
#[cfg(target_os = "netbsd")]
type UData = ::libc::intptr_t;
#[cfg(target_os = "netbsd")]
type Count = usize;

macro_rules! kevent {
    ($id: expr, $filter: expr, $flags: expr, $data: expr) => {
        libc::kevent {
            ident: $id as ::libc::uintptr_t,
            filter: $filter as Filter,
            flags: $flags,
            fflags: 0,
            data: 0,
            udata: $data as UData,
        }
    }
}

pub struct Selector {
    id: usize,
    kq: RawFd,
}

impl Selector {
    pub fn new() -> io::Result<Selector> {
        // offset by 1 to avoid choosing 0 as the id of a selector
        let id = NEXT_ID.fetch_add(1, Ordering::Relaxed) + 1;
        let kq = unsafe { cvt(libc::kqueue())? };
        drop(set_cloexec(kq));

        Ok(Selector {
            id,
            kq,
        })
    }

    pub fn id(&self) -> usize {
        self.id
    }

    pub fn select(&self, evts: &mut Events, awakener: Token, timeout: Option<Duration>) -> io::Result<bool> {
        let timeout = timeout.map(|to| {
            libc::timespec {
                tv_sec: cmp::min(to.as_secs(), time_t::max_value() as u64) as time_t,
                // `Duration::subsec_nanos` is guaranteed to be less than one
                // billion (the number of nanoseconds in a second), making the
                // cast to i32 safe. The cast itself is needed for platforms
                // where C's long is only 32 bits.
                tv_nsec: libc::c_long::from(to.subsec_nanos() as i32),
            }
        });
        let timeout = timeout.as_ref().map(|s| s as *const _).unwrap_or(ptr::null_mut());

        evts.clear();
        unsafe {
            let cnt = cvt(libc::kevent(self.kq,
                                            ptr::null(),
                                            0,
                                            evts.sys_events.0.as_mut_ptr(),
                                            evts.sys_events.0.capacity() as Count,
                                            timeout))?;
            evts.sys_events.0.set_len(cnt as usize);
            Ok(evts.coalesce(awakener))
        }
    }

    pub fn register(&self, fd: RawFd, token: Token, interests: Ready, opts: PollOpt) -> io::Result<()> {
        trace!("registering; token={:?}; interests={:?}", token, interests);

        let flags = if opts.contains(PollOpt::edge()) { libc::EV_CLEAR } else { 0 } |
                    if opts.contains(PollOpt::oneshot()) { libc::EV_ONESHOT } else { 0 } |
                    libc::EV_RECEIPT;

        unsafe {
            let r = if interests.contains(Ready::readable()) { libc::EV_ADD } else { libc::EV_DELETE };
            let w = if interests.contains(Ready::writable()) { libc::EV_ADD } else { libc::EV_DELETE };
            let mut changes = [
                kevent!(fd, libc::EVFILT_READ, flags | r, usize::from(token)),
                kevent!(fd, libc::EVFILT_WRITE, flags | w, usize::from(token)),
            ];

            cvt(libc::kevent(self.kq,
                             changes.as_ptr(),
                             changes.len() as Count,
                             changes.as_mut_ptr(),
                             changes.len() as Count,
                             ::std::ptr::null()))?;

            for change in changes.iter() {
                debug_assert_eq!(change.flags & libc::EV_ERROR, libc::EV_ERROR);

                // Test to see if an error happened
                if change.data == 0 {
                    continue
                }

                // Older versions of OSX (10.11 and 10.10 have been witnessed)
                // can return EPIPE when registering a pipe file descriptor
                // where the other end has already disappeared. For example code
                // that creates a pipe, closes a file descriptor, and then
                // registers the other end will see an EPIPE returned from
                // `register`.
                //
                // It also turns out that kevent will still report events on the
                // file descriptor, telling us that it's readable/hup at least
                // after we've done this registration. As a result we just
                // ignore `EPIPE` here instead of propagating it.
                //
                // More info can be found at carllerche/mio#582
                if change.data as i32 == libc::EPIPE &&
                   change.filter == libc::EVFILT_WRITE as Filter {
                    continue
                }

                // ignore ENOENT error for EV_DELETE
                let orig_flags = if change.filter == libc::EVFILT_READ as Filter { r } else { w };
                if change.data as i32 == libc::ENOENT && orig_flags & libc::EV_DELETE != 0 {
                    continue
                }

                return Err(::std::io::Error::from_raw_os_error(change.data as i32));
            }
            Ok(())
        }
    }

    pub fn reregister(&self, fd: RawFd, token: Token, interests: Ready, opts: PollOpt) -> io::Result<()> {
        // Just need to call register here since EV_ADD is a mod if already
        // registered
        self.register(fd, token, interests, opts)
    }

    pub fn deregister(&self, fd: RawFd) -> io::Result<()> {
        unsafe {
            // EV_RECEIPT is a nice way to apply changes and get back per-event results while not
            // draining the actual changes.
            let filter = libc::EV_DELETE | libc::EV_RECEIPT;
#[cfg(not(target_os = "netbsd"))]
            let mut changes = [
                kevent!(fd, libc::EVFILT_READ, filter, ptr::null_mut()),
                kevent!(fd, libc::EVFILT_WRITE, filter, ptr::null_mut()),
            ];

#[cfg(target_os = "netbsd")]
            let mut changes = [
                kevent!(fd, libc::EVFILT_READ, filter, 0),
                kevent!(fd, libc::EVFILT_WRITE, filter, 0),
            ];

            cvt(libc::kevent(self.kq,
                             changes.as_ptr(),
                             changes.len() as Count,
                             changes.as_mut_ptr(),
                             changes.len() as Count,
                             ::std::ptr::null())).map(|_| ())?;

            if changes[0].data as i32 == libc::ENOENT && changes[1].data as i32 == libc::ENOENT {
                return Err(::std::io::Error::from_raw_os_error(changes[0].data as i32));
            }
            for change in changes.iter() {
                debug_assert_eq!(libc::EV_ERROR & change.flags, libc::EV_ERROR);
                if change.data != 0 && change.data as i32 != libc::ENOENT {
                    return Err(::std::io::Error::from_raw_os_error(changes[0].data as i32));
                }
            }
            Ok(())
        }
    }
}

impl fmt::Debug for Selector {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("Selector")
            .field("id", &self.id)
            .field("kq", &self.kq)
            .finish()
    }
}

impl AsRawFd for Selector {
    fn as_raw_fd(&self) -> RawFd {
        self.kq
    }
}

impl Drop for Selector {
    fn drop(&mut self) {
        unsafe {
            let _ = libc::close(self.kq);
        }
    }
}

pub struct Events {
    sys_events: KeventList,
    events: Vec<Event>,
    event_map: HashMap<Token, usize>,
}

struct KeventList(Vec<libc::kevent>);

unsafe impl Send for KeventList {}
unsafe impl Sync for KeventList {}

impl Events {
    pub fn with_capacity(cap: usize) -> Events {
        Events {
            sys_events: KeventList(Vec::with_capacity(cap)),
            events: Vec::with_capacity(cap),
            event_map: HashMap::with_capacity(cap)
        }
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.events.len()
    }

    #[inline]
    pub fn capacity(&self) -> usize {
        self.events.capacity()
    }

    #[inline]
    pub fn is_empty(&self) -> bool {
        self.events.is_empty()
    }

    pub fn get(&self, idx: usize) -> Option<Event> {
        self.events.get(idx).cloned()
    }

    fn coalesce(&mut self, awakener: Token) -> bool {
        let mut ret = false;
        self.events.clear();
        self.event_map.clear();

        for e in self.sys_events.0.iter() {
            let token = Token(e.udata as usize);
            let len = self.events.len();

            if token == awakener {
                // TODO: Should this return an error if event is an error. It
                // is not critical as spurious wakeups are permitted.
                ret = true;
                continue;
            }

            let idx = *self.event_map.entry(token)
                .or_insert(len);

            if idx == len {
                // New entry, insert the default
                self.events.push(Event::new(Ready::empty(), token));

            }

            if e.flags & libc::EV_ERROR != 0 {
                event::kind_mut(&mut self.events[idx]).insert(*UnixReady::error());
            }

            if e.filter == libc::EVFILT_READ as Filter {
                event::kind_mut(&mut self.events[idx]).insert(Ready::readable());
            } else if e.filter == libc::EVFILT_WRITE as Filter {
                event::kind_mut(&mut self.events[idx]).insert(Ready::writable());
            }
#[cfg(any(target_os = "dragonfly",
    target_os = "freebsd", target_os = "ios", target_os = "macos"))]
            {
                if e.filter == libc::EVFILT_AIO {
                    event::kind_mut(&mut self.events[idx]).insert(UnixReady::aio());
                }
            }
#[cfg(any(target_os = "freebsd"))]
            {
                if e.filter == libc::EVFILT_LIO {
                    event::kind_mut(&mut self.events[idx]).insert(UnixReady::lio());
                }
            }
        }

        ret
    }

    pub fn push_event(&mut self, event: Event) {
        self.events.push(event);
    }

    pub fn clear(&mut self) {
        self.sys_events.0.truncate(0);
        self.events.truncate(0);
        self.event_map.clear();
    }
}

impl fmt::Debug for Events {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("Events")
            .field("len", &self.sys_events.0.len())
            .finish()
    }
}

#[test]
fn does_not_register_rw() {
    use {Poll, Ready, PollOpt, Token};
    use unix::EventedFd;

    let kq = unsafe { libc::kqueue() };
    let kqf = EventedFd(&kq);
    let poll = Poll::new().unwrap();

    // registering kqueue fd will fail if write is requested (On anything but some versions of OS
    // X)
    poll.register(&kqf, Token(1234), Ready::readable(),
                  PollOpt::edge() | PollOpt::oneshot()).unwrap();
}

#[cfg(any(target_os = "dragonfly",
    target_os = "freebsd", target_os = "ios", target_os = "macos"))]
#[test]
fn test_coalesce_aio() {
    let mut events = Events::with_capacity(1);
    events.sys_events.0.push(kevent!(0x1234, libc::EVFILT_AIO, 0, 42));
    events.coalesce(Token(0));
    assert!(events.events[0].readiness() == UnixReady::aio().into());
    assert!(events.events[0].token() == Token(42));
}
<|endoftext|>


<commit_before>use terrain::gfx_macros::{TileMapData};
use data::{load_map_file, get_map_tile};
use game::constants::{MAP_FILE_PATH, TILEMAP_BUF_LENGTH, TILES_PCS_W, TILES_PCS_H};

#[derive(Debug)]
pub struct Terrain {
  pub tiles: Vec<TileMapData>
}

fn calc_index(xpos: usize, ypos: usize) -> usize {
  (ypos * TILES_PCS_W) + xpos
}

fn populate_tilemap(mut tiles: Vec<TileMapData>) -> Vec<TileMapData> {
  let map = load_map_file(MAP_FILE_PATH);
  for ypos in 0..TILES_PCS_H {
    for xpos in 0..TILES_PCS_W {
      let map_val = get_map_tile(&map, 0, xpos as usize, ypos as usize);
      let tex_x = map_val % 32;
      let tex_y = (map_val - tex_x) / 32;
      let idx = calc_index(xpos, ypos);
      tiles[idx] = TileMapData::new([(tex_x-1) as f32, tex_y as f32, 0.0, 0.0]);
    }
  }
  tiles
}

pub fn generate() -> Terrain {
  let mut charmap_data = Vec::with_capacity(TILEMAP_BUF_LENGTH);

  for _ in 0..TILEMAP_BUF_LENGTH {
    charmap_data.push(TileMapData::new_empty());
  }

  Terrain {
    tiles: populate_tilemap(charmap_data)
  }
}
<commit_msg>Fix map data loading<commit_after>use terrain::gfx_macros::TileMapData;
use data::{load_map_file, get_map_tile};
use game::constants::{MAP_FILE_PATH, TILEMAP_BUF_LENGTH, TILES_PCS_W, TILES_PCS_H};

#[derive(Debug)]
pub struct Terrain {
  pub tiles: Vec<TileMapData>
}

fn calc_index(xpos: usize, ypos: usize) -> usize {
  (ypos * TILES_PCS_W) + xpos
}

fn populate_tilemap(mut tiles: Vec<TileMapData>) -> Vec<TileMapData> {
  let map = load_map_file(MAP_FILE_PATH);
  for ypos in 0..TILES_PCS_H {
    for xpos in 0..TILES_PCS_W {
      let map_val = get_map_tile(&map, 0, xpos as usize, ypos as usize);
      let tex_x = map_val % 32;
      let y = (map_val - tex_x) / 32;
      let idx = calc_index(xpos, ypos);
      let x = if tex_x < 1 { 1 } else { tex_x - 1 };
      tiles[idx] = TileMapData::new([x as f32, y as f32, 0.0, 0.0]);
    }
  }
  tiles
}

pub fn generate() -> Terrain {
  let mut charmap_data = Vec::with_capacity(TILEMAP_BUF_LENGTH);

  for _ in 0..TILEMAP_BUF_LENGTH {
    charmap_data.push(TileMapData::new_empty());
  }

  Terrain {
    tiles: populate_tilemap(charmap_data)
  }
}
<|endoftext|>


<commit_before>// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

/// This mod implemented a wrapped future pool that supports `on_tick()` which is driven by
/// tasks and is invoked no less than the specific interval.

use std::fmt;
use std::cell::{Cell, RefCell, RefMut};
use std::sync::{mpsc, Arc};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use std::time::Duration;
use futures::Future;
use futures_cpupool::{self as cpupool, CpuFuture, CpuPool};

use util;
use util::time::Instant;
use util::collections::HashMap;

pub trait Context: fmt::Debug + Send {
    /// Will be invoked periodically (no less than specified interval).
    /// When there is no task, it will NOT be invoked.
    fn on_tick(&mut self) {}
}

/// A delegator to wrap `Context` to provide `on_tick` feature.
#[derive(Debug)]
struct ContextDelegator<T: Context> {
    tick_interval: Duration,
    inner: RefCell<T>,
    last_tick: Cell<Option<Instant>>,
}

impl<T: Context> ContextDelegator<T> {
    fn new(context: T, tick_interval: Duration) -> ContextDelegator<T> {
        ContextDelegator {
            tick_interval,
            inner: RefCell::new(context),
            last_tick: Cell::new(None),
        }
    }

    fn context_mut(&self) -> RefMut<T> {
        self.inner.borrow_mut()
    }

    fn on_task_finish(&self) {
        let now = Instant::now_coarse();
        let last_tick = self.last_tick.get();
        if last_tick.is_none() {
            // set last_tick when the first future is resolved
            self.last_tick.set(Some(now));
            return;
        }
        if now.duration_since(last_tick.unwrap()) < self.tick_interval {
            return;
        }
        self.last_tick.set(Some(now));
        self.context_mut().on_tick();
    }
}

#[derive(Debug)]
pub struct ContextDelegators<T: Context> {
    delegators: Arc<HashMap<thread::ThreadId, ContextDelegator<T>>>,
}

/// Users can only retrive a Context for the current thread so that `HashMap<..>` is `Sync`.
/// Thus `ContextDelegators` is `Send` & `Sync`.
unsafe impl<T: Context> Send for ContextDelegators<T> {}
unsafe impl<T: Context> Sync for ContextDelegators<T> {}

impl<T: Context> Clone for ContextDelegators<T> {
    fn clone(&self) -> Self {
        ContextDelegators {
            delegators: Arc::clone(&self.delegators),
        }
    }
}

impl<T: Context> ContextDelegators<T> {
    fn new(delegators: HashMap<thread::ThreadId, ContextDelegator<T>>) -> Self {
        ContextDelegators {
            delegators: Arc::new(delegators),
        }
    }

    /// This function should be called in the future pool thread. Otherwise it will panic.
    pub fn current_thread_context_mut(&self) -> RefMut<T> {
        let delegator = self.get_current_thread_delegator();
        delegator.context_mut()
    }

    fn get_current_thread_delegator(&self) -> &ContextDelegator<T> {
        let thread_id = thread::current().id();
        if let Some(delegator) = self.delegators.get(&thread_id) {
            delegator
        } else {
            panic!("Called from threads out of the future thread pool");
        }
    }
}

/// A future thread pool that supports `on_tick` for each thread.
#[derive(Debug)]
pub struct FuturePool<T: Context + 'static> {
    pool: CpuPool,
    context_delegators: ContextDelegators<T>,
    running_task_count: Arc<AtomicUsize>,
}

impl<T: Context + 'static> Clone for FuturePool<T> {
    fn clone(&self) -> FuturePool<T> {
        FuturePool {
            pool: self.pool.clone(),
            context_delegators: self.context_delegators.clone(),
            running_task_count: Arc::clone(&self.running_task_count),
        }
    }
}

impl<T: Context + 'static> util::AssertSend for FuturePool<T> {}
impl<T: Context + 'static> util::AssertSync for FuturePool<T> {}

pub trait Factory<T> {
    fn build(&self) -> T;
}

impl<T, F> Factory<T> for F
where
    F: Fn() -> T,
{
    fn build(&self) -> T {
        self()
    }
}

impl<T: Context + 'static> FuturePool<T> {
    pub fn new<F>(
        pool_size: usize,
        stack_size: usize,
        name_prefix: &str,
        tick_interval: Duration,
        context_factory: F,
    ) -> FuturePool<T>
    where
        F: Factory<T>,
    {
        let (tx, rx) = mpsc::sync_channel(pool_size);
        let pool = cpupool::Builder::new()
            .pool_size(pool_size)
            .stack_size(stack_size)
            .name_prefix(name_prefix)
            .after_start(move || {
                // We only need to know each thread's id and we can build context later
                // by invoking `context_factory` in a non-concurrent way.
                let thread_id = thread::current().id();
                tx.send(thread_id).unwrap();
            })
            .create();
        let contexts = (0..pool_size)
            .map(|_| {
                let thread_id = rx.recv().unwrap();
                let context = context_factory.build();
                let context_delegator = ContextDelegator::new(context, tick_interval);
                (thread_id, context_delegator)
            })
            .collect();
        FuturePool {
            pool,
            context_delegators: ContextDelegators::new(contexts),
            running_task_count: Arc::new(AtomicUsize::new(0)),
        }
    }

    /// Get current running task count
    #[inline]
    pub fn get_running_task_count(&self) -> usize {
        self.running_task_count.load(Ordering::Acquire)
    }

    pub fn spawn<F, R>(&self, future_factory: R) -> CpuFuture<F::Item, F::Error>
    where
        R: FnOnce(ContextDelegators<T>) -> F + Send + 'static,
        F: Future + Send + 'static,
        F::Item: Send + 'static,
        F::Error: Send + 'static,
    {
        let running_task_count = Arc::clone(&self.running_task_count);
        let delegators = self.context_delegators.clone();
        let func = move || {
            future_factory(delegators.clone()).then(move |r| {
                let delegator = delegators.get_current_thread_delegator();
                delegator.on_task_finish();
                running_task_count.fetch_sub(1, Ordering::Release);
                r
            })
        };

        self.running_task_count.fetch_add(1, Ordering::Release);
        self.pool.spawn_fn(func)
    }
}

#[cfg(test)]
mod tests {
    use std::thread;
    use std::time::Duration;
    use std::sync::mpsc::{channel, Sender};
    use futures::future;

    pub use super::*;

    fn spawn_long_time_future<T: Context>(
        pool: &FuturePool<T>,
        future_duration_ms: u64,
    ) -> CpuFuture<(), ()> {
        pool.spawn(move |_| {
            thread::sleep(Duration::from_millis(future_duration_ms));
            future::ok::<(), ()>(())
        })
    }

    fn spawn_long_time_future_and_wait<T: Context>(pool: &FuturePool<T>, future_duration_ms: u64) {
        spawn_long_time_future(pool, future_duration_ms)
            .wait()
            .unwrap();
    }

    #[test]
    fn test_context() {
        #[derive(Debug)]
        struct MyContext {
            ctx_thread_id: thread::ThreadId,
        }
        impl Context for MyContext {}

        let pool = FuturePool::new(
            1,
            1024000,
            "test-pool",
            Duration::from_millis(50),
            move || MyContext {
                ctx_thread_id: thread::current().id(),
            },
        );

        let main_thread_id = thread::current().id();

        pool.spawn(move |ctxd| {
            // future_factory is executed in future pool
            let current_thread_id = thread::current().id();
            assert_ne!(main_thread_id, current_thread_id);

            // Context is created in main thread
            let ctx = ctxd.current_thread_context_mut();
            assert_eq!(ctx.ctx_thread_id, main_thread_id);
            future::ok::<(), ()>(())
        }).wait()
            .unwrap();
    }

    #[test]
    fn test_tick() {
        struct MyContext {
            tx: Sender<i32>,
            sn: i32,
        }
        impl fmt::Debug for MyContext {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                write!(f, "MyContext")
            }
        }
        impl Context for MyContext {
            fn on_tick(&mut self) {
                self.tx.send(self.sn).unwrap();
                self.sn += 1;
            }
        }

        let (tx, rx) = channel();

        let pool = FuturePool::new(
            1,
            1024000,
            "test-pool",
            Duration::from_millis(200),
            move || MyContext {
                tx: tx.clone(),
                sn: 0,
            },
        );
        assert!(rx.try_recv().is_err());

        // Tick is not emitted since there is no task
        thread::sleep(Duration::from_millis(400));
        assert!(rx.try_recv().is_err());

        // Tick is not emitted immediately for the first future
        spawn_long_time_future_and_wait(&pool, 10);
        assert!(rx.try_recv().is_err());

        spawn_long_time_future_and_wait(&pool, 1);
        spawn_long_time_future_and_wait(&pool, 1);
        spawn_long_time_future_and_wait(&pool, 1);
        spawn_long_time_future_and_wait(&pool, 1);
        assert!(rx.try_recv().is_err());

        // Even if there are tasks previously, tick is not emitted until next task arrives
        thread::sleep(Duration::from_millis(400));
        assert!(rx.try_recv().is_err());

        // Next task arrives && long time passed, tick is emitted
        spawn_long_time_future_and_wait(&pool, 400);
        assert_eq!(rx.try_recv().unwrap(), 0);
        assert!(rx.try_recv().is_err());

        // Tick is not emitted if there is no task
        thread::sleep(Duration::from_millis(400));
        assert!(rx.try_recv().is_err());

        // Tick is emitted since long enough time has passed
        spawn_long_time_future_and_wait(&pool, 1);
        assert_eq!(rx.try_recv().unwrap(), 1);
        assert!(rx.try_recv().is_err());
    }

    #[test]
    fn test_task_count() {
        #[derive(Debug)]
        struct MyContext;
        impl Context for MyContext {}

        let pool = FuturePool::new(
            2,
            1024000,
            "test-pool",
            Duration::from_millis(50),
            move || MyContext {},
        );

        assert_eq!(pool.get_running_task_count(), 0);
        let f1 = spawn_long_time_future(&pool, 100);
        assert_eq!(pool.get_running_task_count(), 1);
        let f2 = spawn_long_time_future(&pool, 200);
        assert_eq!(pool.get_running_task_count(), 2);
        let f3 = spawn_long_time_future(&pool, 300);
        assert_eq!(pool.get_running_task_count(), 3);
        f1.wait().unwrap();
        assert_eq!(pool.get_running_task_count(), 2);
        let f4 = spawn_long_time_future(&pool, 300);
        let f5 = spawn_long_time_future(&pool, 300);
        assert_eq!(pool.get_running_task_count(), 4);
        f2.join(f3).wait().unwrap();
        assert_eq!(pool.get_running_task_count(), 2);
        f4.join(f5).wait().unwrap();
        assert_eq!(pool.get_running_task_count(), 0);
    }
}
<commit_msg>Add metrics to the FuturePool (#2932)<commit_after>// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

/// This mod implemented a wrapped future pool that supports `on_tick()` which is driven by
/// tasks and is invoked no less than the specific interval.

use std::fmt;
use std::cell::{Cell, RefCell, RefMut};
use std::sync::{mpsc, Arc};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use std::time::Duration;
use futures::Future;
use futures_cpupool::{self as cpupool, CpuFuture, CpuPool};
use prometheus::{IntCounter, IntCounterVec, IntGauge, IntGaugeVec};

use util;
use util::time::Instant;
use util::collections::HashMap;

lazy_static! {
    pub static ref FUTUREPOOL_PENDING_TASK_VEC: IntGaugeVec =
        register_int_gauge_vec!(
            "tikv_futurepool_pending_task_total",
            "Current future_pool pending + running tasks.",
            &["name"]
        ).unwrap();

    pub static ref FUTUREPOOL_HANDLED_TASK_VEC: IntCounterVec =
        register_int_counter_vec!(
            "tikv_futurepool_handled_task_total",
            "Total number of future_pool handled tasks.",
            &["name"]
        ).unwrap();
}

pub trait Context: fmt::Debug + Send {
    /// Will be invoked periodically (no less than specified interval).
    /// When there is no task, it will NOT be invoked.
    fn on_tick(&mut self) {}
}

/// A delegator to wrap `Context` to provide `on_tick` feature.
#[derive(Debug)]
struct ContextDelegator<T: Context> {
    tick_interval: Duration,
    inner: RefCell<T>,
    last_tick: Cell<Option<Instant>>,
}

impl<T: Context> ContextDelegator<T> {
    fn new(context: T, tick_interval: Duration) -> ContextDelegator<T> {
        ContextDelegator {
            tick_interval,
            inner: RefCell::new(context),
            last_tick: Cell::new(None),
        }
    }

    fn context_mut(&self) -> RefMut<T> {
        self.inner.borrow_mut()
    }

    fn on_task_finish(&self) {
        let now = Instant::now_coarse();
        let last_tick = self.last_tick.get();
        if last_tick.is_none() {
            // set last_tick when the first future is resolved
            self.last_tick.set(Some(now));
            return;
        }
        if now.duration_since(last_tick.unwrap()) < self.tick_interval {
            return;
        }
        self.last_tick.set(Some(now));
        self.context_mut().on_tick();
    }
}

#[derive(Debug)]
pub struct ContextDelegators<T: Context> {
    delegators: Arc<HashMap<thread::ThreadId, ContextDelegator<T>>>,
}

/// Users can only retrive a Context for the current thread so that `HashMap<..>` is `Sync`.
/// Thus `ContextDelegators` is `Send` & `Sync`.
unsafe impl<T: Context> Send for ContextDelegators<T> {}
unsafe impl<T: Context> Sync for ContextDelegators<T> {}

impl<T: Context> Clone for ContextDelegators<T> {
    fn clone(&self) -> Self {
        ContextDelegators {
            delegators: Arc::clone(&self.delegators),
        }
    }
}

impl<T: Context> ContextDelegators<T> {
    fn new(delegators: HashMap<thread::ThreadId, ContextDelegator<T>>) -> Self {
        ContextDelegators {
            delegators: Arc::new(delegators),
        }
    }

    /// This function should be called in the future pool thread. Otherwise it will panic.
    pub fn current_thread_context_mut(&self) -> RefMut<T> {
        let delegator = self.get_current_thread_delegator();
        delegator.context_mut()
    }

    fn get_current_thread_delegator(&self) -> &ContextDelegator<T> {
        let thread_id = thread::current().id();
        if let Some(delegator) = self.delegators.get(&thread_id) {
            delegator
        } else {
            panic!("Called from threads out of the future thread pool");
        }
    }
}

/// A future thread pool that supports `on_tick` for each thread.
pub struct FuturePool<T: Context + 'static> {
    pool: CpuPool,
    context_delegators: ContextDelegators<T>,
    running_task_count: Arc<AtomicUsize>,
    metrics_pending_task_count: IntGauge,
    metrics_handled_task_count: IntCounter,
}

impl<T: Context + 'static> fmt::Debug for FuturePool<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        f.debug_struct("FuturePool")
            .field("pool", &self.pool)
            .field("context_delegators", &self.context_delegators)
            .finish()
    }
}

impl<T: Context + 'static> Clone for FuturePool<T> {
    fn clone(&self) -> FuturePool<T> {
        FuturePool {
            pool: self.pool.clone(),
            context_delegators: self.context_delegators.clone(),
            running_task_count: Arc::clone(&self.running_task_count),
            metrics_pending_task_count: self.metrics_pending_task_count.clone(),
            metrics_handled_task_count: self.metrics_handled_task_count.clone(),
        }
    }
}

impl<T: Context + 'static> util::AssertSend for FuturePool<T> {}
impl<T: Context + 'static> util::AssertSync for FuturePool<T> {}

pub trait Factory<T> {
    fn build(&self) -> T;
}

impl<T, F> Factory<T> for F
where
    F: Fn() -> T,
{
    fn build(&self) -> T {
        self()
    }
}

impl<T: Context + 'static> FuturePool<T> {
    pub fn new<F>(
        pool_size: usize,
        stack_size: usize,
        name_prefix: &str,
        tick_interval: Duration,
        context_factory: F,
    ) -> FuturePool<T>
    where
        F: Factory<T>,
    {
        let (tx, rx) = mpsc::sync_channel(pool_size);
        let pool = cpupool::Builder::new()
            .pool_size(pool_size)
            .stack_size(stack_size)
            .name_prefix(name_prefix)
            .after_start(move || {
                // We only need to know each thread's id and we can build context later
                // by invoking `context_factory` in a non-concurrent way.
                let thread_id = thread::current().id();
                tx.send(thread_id).unwrap();
            })
            .create();
        let contexts = (0..pool_size)
            .map(|_| {
                let thread_id = rx.recv().unwrap();
                let context = context_factory.build();
                let context_delegator = ContextDelegator::new(context, tick_interval);
                (thread_id, context_delegator)
            })
            .collect();
        FuturePool {
            pool,
            context_delegators: ContextDelegators::new(contexts),
            running_task_count: Arc::new(AtomicUsize::new(0)),
            metrics_pending_task_count: FUTUREPOOL_PENDING_TASK_VEC
                .with_label_values(&[name_prefix]),
            metrics_handled_task_count: FUTUREPOOL_HANDLED_TASK_VEC
                .with_label_values(&[name_prefix]),
        }
    }

    /// Get current running task count
    #[inline]
    pub fn get_running_task_count(&self) -> usize {
        self.running_task_count.load(Ordering::Acquire)
    }

    pub fn spawn<F, R>(&self, future_factory: R) -> CpuFuture<F::Item, F::Error>
    where
        R: FnOnce(ContextDelegators<T>) -> F + Send + 'static,
        F: Future + Send + 'static,
        F::Item: Send + 'static,
        F::Error: Send + 'static,
    {
        let running_task_count = Arc::clone(&self.running_task_count);
        let metrics_pending_task_count = self.metrics_pending_task_count.clone();
        let metrics_handled_task_count = self.metrics_handled_task_count.clone();
        let delegators = self.context_delegators.clone();
        let func = move || {
            future_factory(delegators.clone()).then(move |r| {
                let delegator = delegators.get_current_thread_delegator();
                delegator.on_task_finish();
                running_task_count.fetch_sub(1, Ordering::Release);
                metrics_pending_task_count.dec();
                metrics_handled_task_count.inc();
                r
            })
        };

        self.running_task_count.fetch_add(1, Ordering::Release);
        self.metrics_pending_task_count.inc();
        self.pool.spawn_fn(func)
    }
}

#[cfg(test)]
mod tests {
    use std::thread;
    use std::time::Duration;
    use std::sync::mpsc::{channel, Sender};
    use futures::future;

    pub use super::*;

    fn spawn_long_time_future<T: Context>(
        pool: &FuturePool<T>,
        future_duration_ms: u64,
    ) -> CpuFuture<(), ()> {
        pool.spawn(move |_| {
            thread::sleep(Duration::from_millis(future_duration_ms));
            future::ok::<(), ()>(())
        })
    }

    fn spawn_long_time_future_and_wait<T: Context>(pool: &FuturePool<T>, future_duration_ms: u64) {
        spawn_long_time_future(pool, future_duration_ms)
            .wait()
            .unwrap();
    }

    #[test]
    fn test_context() {
        #[derive(Debug)]
        struct MyContext {
            ctx_thread_id: thread::ThreadId,
        }
        impl Context for MyContext {}

        let pool = FuturePool::new(
            1,
            1024000,
            "test-pool",
            Duration::from_millis(50),
            move || MyContext {
                ctx_thread_id: thread::current().id(),
            },
        );

        let main_thread_id = thread::current().id();

        pool.spawn(move |ctxd| {
            // future_factory is executed in future pool
            let current_thread_id = thread::current().id();
            assert_ne!(main_thread_id, current_thread_id);

            // Context is created in main thread
            let ctx = ctxd.current_thread_context_mut();
            assert_eq!(ctx.ctx_thread_id, main_thread_id);
            future::ok::<(), ()>(())
        }).wait()
            .unwrap();
    }

    #[test]
    fn test_tick() {
        struct MyContext {
            tx: Sender<i32>,
            sn: i32,
        }
        impl fmt::Debug for MyContext {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                write!(f, "MyContext")
            }
        }
        impl Context for MyContext {
            fn on_tick(&mut self) {
                self.tx.send(self.sn).unwrap();
                self.sn += 1;
            }
        }

        let (tx, rx) = channel();

        let pool = FuturePool::new(
            1,
            1024000,
            "test-pool",
            Duration::from_millis(200),
            move || MyContext {
                tx: tx.clone(),
                sn: 0,
            },
        );
        assert!(rx.try_recv().is_err());

        // Tick is not emitted since there is no task
        thread::sleep(Duration::from_millis(400));
        assert!(rx.try_recv().is_err());

        // Tick is not emitted immediately for the first future
        spawn_long_time_future_and_wait(&pool, 10);
        assert!(rx.try_recv().is_err());

        spawn_long_time_future_and_wait(&pool, 1);
        spawn_long_time_future_and_wait(&pool, 1);
        spawn_long_time_future_and_wait(&pool, 1);
        spawn_long_time_future_and_wait(&pool, 1);
        assert!(rx.try_recv().is_err());

        // Even if there are tasks previously, tick is not emitted until next task arrives
        thread::sleep(Duration::from_millis(400));
        assert!(rx.try_recv().is_err());

        // Next task arrives && long time passed, tick is emitted
        spawn_long_time_future_and_wait(&pool, 400);
        assert_eq!(rx.try_recv().unwrap(), 0);
        assert!(rx.try_recv().is_err());

        // Tick is not emitted if there is no task
        thread::sleep(Duration::from_millis(400));
        assert!(rx.try_recv().is_err());

        // Tick is emitted since long enough time has passed
        spawn_long_time_future_and_wait(&pool, 1);
        assert_eq!(rx.try_recv().unwrap(), 1);
        assert!(rx.try_recv().is_err());
    }

    #[test]
    fn test_task_count() {
        #[derive(Debug)]
        struct MyContext;
        impl Context for MyContext {}

        let pool = FuturePool::new(
            2,
            1024000,
            "test-pool",
            Duration::from_millis(50),
            move || MyContext {},
        );

        assert_eq!(pool.get_running_task_count(), 0);
        let f1 = spawn_long_time_future(&pool, 100);
        assert_eq!(pool.get_running_task_count(), 1);
        let f2 = spawn_long_time_future(&pool, 200);
        assert_eq!(pool.get_running_task_count(), 2);
        let f3 = spawn_long_time_future(&pool, 300);
        assert_eq!(pool.get_running_task_count(), 3);
        f1.wait().unwrap();
        assert_eq!(pool.get_running_task_count(), 2);
        let f4 = spawn_long_time_future(&pool, 300);
        let f5 = spawn_long_time_future(&pool, 300);
        assert_eq!(pool.get_running_task_count(), 4);
        f2.join(f3).wait().unwrap();
        assert_eq!(pool.get_running_task_count(), 2);
        f4.join(f5).wait().unwrap();
        assert_eq!(pool.get_running_task_count(), 0);
    }
}
<|endoftext|>