<commit_before>// Copyright (C) 2016 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use helpers::*;

use yaml_rust::Yaml;
use console;
use crowbook::{Result, Book, BookOptions};
use crowbook_intl_runtime::set_lang;
use crowbook::Stats;
use tempdir::TempDir;
use clap::ArgMatches;
use std::process::exit;
use std::io;
use std::io::Read;
use std::env;
use std::fs::File;
use simplelog::{Config, TermLogger, LogLevel, LogLevelFilter, SimpleLogger, WriteLogger};

/// Render a book to specific format
fn render_format(book: &mut Book, emoji: bool, matches: &ArgMatches, format: &str) {
    let mut key = String::from("output.");
    key.push_str(format);

    let mut stdout = false;
    let mut file = None;

    if let Some(f) = matches.value_of("output") {
        if f == "-" {
            stdout = true;
        } else {
            file = Some(String::from(f));
        }
    }

    let res = book.options.get_path(&key);

    let result = match(file, res, stdout) {
        (Some(file), _, _) |
        (None, Ok(file), false) => book.render_format_to_file(format, file),

        (None, Err(_), _) |
        (None, _, true)
        => book.render_format_to(format, &mut io::stdout()),
    };

    match result {
        Err(err) => print_error(&format!("{}", err), emoji),
        Ok(_) => {}
    }
}

pub fn try_main() -> Result<()> {
    let lang = get_lang()
        .or_else(|| {
            match env::var("LANG") {
                Ok(val) => {
                    Some(val)
                },
                Err(_) => None,
            }
        });
    if let Some(val) = lang {
        if val.starts_with("fr") {
            set_lang("fr");
        } else {
            set_lang("en");
        }
    }

    let mut fancy_ui = true;
    let mut emoji = console::Term::stderr().want_emoji();

    let (matches, help, version) = create_matches();

    if matches.is_present("force-emoji") {
        emoji = true;
    }

    display_header(emoji);

    if matches.is_present("list-options") {
        println!("{}", BookOptions::description(false));
        exit(0);
    }

    if matches.is_present("no-fancy") || matches.is_present("stats") {
        fancy_ui = false;
        emoji = false;
    }

    if matches.is_present("list-options-md") {
        println!("{}", BookOptions::description(true));
        exit(0);
    }

    if matches.is_present("print-template") {
        let template = matches.value_of("print-template").unwrap();
        let mut book = Book::new();
        set_book_options(&mut book, &matches);
        let result = book.get_template(template.as_ref());
        match result {
            Ok(s) => {
                println!("{}", s);
                exit(0);
            }
            Err(_) => print_error_and_exit(&lformat!("{} is not a valid template name.",
                                                     template),
                                           emoji),
        }
    }

    if matches.is_present("help") {
        println!("{}", help);
        exit(0);
    }

    if matches.is_present("version") {
        println!("{}", version);
        exit(0);
    }

    if matches.is_present("create") {
        create_book(&matches);
    }

    if !matches.is_present("BOOK") {
        print_error_and_exit(&lformat!("You must pass the file of a book configuration \
                               file.\n\n{}\n\nFor more information try --help.",
                                       matches.usage()),
                             emoji);
    }

    // ok to unwrap since clap checks it's there
    let s = matches.value_of("BOOK").unwrap();

    // Initalize logger
    let mut log_config = Config::default();
    log_config.target = None;
    log_config.location = None;
    log_config.time = None;
    let verbosity = if matches.is_present("verbose") {
        log_config.time = Some(LogLevel::Error);
        log_config.target = Some(LogLevel::Error);
        fancy_ui = false;
        LogLevelFilter::Debug
    } else if matches.is_present("quiet") {
        fancy_ui = false;
        LogLevelFilter::Error
    } else if fancy_ui {
        LogLevelFilter::Warn
    } else {
        LogLevelFilter::Info
    };

    let error_dir = TempDir::new("crowbook").unwrap();
    let error_path = "error.log";
    if fancy_ui {
        let errors = File::create(error_dir.path().join(error_path)).unwrap();
        let _ = WriteLogger::init(verbosity, log_config, errors);
    } else {
        if TermLogger::init(verbosity, log_config).is_err() {
            // If it failed, not much we can do, we just won't display log
            let _ = SimpleLogger::init(verbosity, log_config);
        }
    }

    {
        let mut book = Book::new();
        if matches.is_present("autograph") {
            println!("{}", &lformat!("Enter autograph: "));
            let mut autograph = String::new();
            match io::stdin().read_to_string(&mut autograph) {
                Ok(_) => {
                    println!("{}", autograph);
                    book.options.set_yaml(Yaml::String("autograph".to_string()), Yaml::String(autograph)).unwrap();
                },
                Err(_) => print_error(&lformat!("could not read autograph from stdin"), emoji),
            }
        }

        if fancy_ui {
            book.add_progress_bar(emoji);
        }
        book.set_options(&get_book_options(&matches));

        {
            let res = if matches.is_present("single") {
                if s != "-" {
                    book.load_markdown_file(s)
                } else {
                    book.read_markdown_config(io::stdin())
                }
            } else if s != "-" {
                book.load_file(s)
            } else {
                book.read_config(io::stdin())
            }.map(|_| ());

            match res {
                Ok(..) => {},
                Err(err) => {
                    book.set_error(&format!("{}", err));
                    return Err(err);
                }
            }
        }

        set_book_options(&mut book, &matches);

        if matches.is_present("stats") {
            let stats = Stats::new(&book);
            println!("{}", stats);
            exit(0);
        }

        if let Some(format) = matches.value_of("to") {
            render_format(&mut book, emoji,& matches, format);
        } else {
            book.render_all();
        }
    }
    if fancy_ui {
        let mut errors = String::new();
        let mut file = File::open(error_dir.path().join(error_path)).unwrap();
        file.read_to_string(&mut errors).unwrap();
        if !errors.is_empty() {
            print_warning(&lformat!("Crowbook exited successfully, but the following errors occurred:"),
                          emoji);
            let mut lines: Vec<_> = errors.lines().collect();
            lines.sort();
            lines.dedup();
            for line in &lines {
                if line.starts_with("[ERROR]") {
                    let line = &line[8..];
                    print_error(line, emoji);
                } else if line.starts_with("[WARN]") {
                    let line = &line[7..];
                    print_warning(line, emoji);
                }
            }
        }
    }

    Ok(())
}

pub fn real_main() {
    if let Err(err) = try_main() {
        print_error_and_exit(&format!("{}", err), false);
    }
}
<commit_msg>Remove debug println<commit_after>// Copyright (C) 2016 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use helpers::*;

use yaml_rust::Yaml;
use console;
use crowbook::{Result, Book, BookOptions};
use crowbook_intl_runtime::set_lang;
use crowbook::Stats;
use tempdir::TempDir;
use clap::ArgMatches;
use std::process::exit;
use std::io;
use std::io::Read;
use std::env;
use std::fs::File;
use simplelog::{Config, TermLogger, LogLevel, LogLevelFilter, SimpleLogger, WriteLogger};

/// Render a book to specific format
fn render_format(book: &mut Book, emoji: bool, matches: &ArgMatches, format: &str) {
    let mut key = String::from("output.");
    key.push_str(format);

    let mut stdout = false;
    let mut file = None;

    if let Some(f) = matches.value_of("output") {
        if f == "-" {
            stdout = true;
        } else {
            file = Some(String::from(f));
        }
    }

    let res = book.options.get_path(&key);

    let result = match(file, res, stdout) {
        (Some(file), _, _) |
        (None, Ok(file), false) => book.render_format_to_file(format, file),

        (None, Err(_), _) |
        (None, _, true)
        => book.render_format_to(format, &mut io::stdout()),
    };

    match result {
        Err(err) => print_error(&format!("{}", err), emoji),
        Ok(_) => {}
    }
}

pub fn try_main() -> Result<()> {
    let lang = get_lang()
        .or_else(|| {
            match env::var("LANG") {
                Ok(val) => {
                    Some(val)
                },
                Err(_) => None,
            }
        });
    if let Some(val) = lang {
        if val.starts_with("fr") {
            set_lang("fr");
        } else {
            set_lang("en");
        }
    }

    let mut fancy_ui = true;
    let mut emoji = console::Term::stderr().want_emoji();

    let (matches, help, version) = create_matches();

    if matches.is_present("force-emoji") {
        emoji = true;
    }

    display_header(emoji);

    if matches.is_present("list-options") {
        println!("{}", BookOptions::description(false));
        exit(0);
    }

    if matches.is_present("no-fancy") || matches.is_present("stats") {
        fancy_ui = false;
        emoji = false;
    }

    if matches.is_present("list-options-md") {
        println!("{}", BookOptions::description(true));
        exit(0);
    }

    if matches.is_present("print-template") {
        let template = matches.value_of("print-template").unwrap();
        let mut book = Book::new();
        set_book_options(&mut book, &matches);
        let result = book.get_template(template.as_ref());
        match result {
            Ok(s) => {
                println!("{}", s);
                exit(0);
            }
            Err(_) => print_error_and_exit(&lformat!("{} is not a valid template name.",
                                                     template),
                                           emoji),
        }
    }

    if matches.is_present("help") {
        println!("{}", help);
        exit(0);
    }

    if matches.is_present("version") {
        println!("{}", version);
        exit(0);
    }

    if matches.is_present("create") {
        create_book(&matches);
    }

    if !matches.is_present("BOOK") {
        print_error_and_exit(&lformat!("You must pass the file of a book configuration \
                               file.\n\n{}\n\nFor more information try --help.",
                                       matches.usage()),
                             emoji);
    }

    // ok to unwrap since clap checks it's there
    let s = matches.value_of("BOOK").unwrap();

    // Initalize logger
    let mut log_config = Config::default();
    log_config.target = None;
    log_config.location = None;
    log_config.time = None;
    let verbosity = if matches.is_present("verbose") {
        log_config.time = Some(LogLevel::Error);
        log_config.target = Some(LogLevel::Error);
        fancy_ui = false;
        LogLevelFilter::Debug
    } else if matches.is_present("quiet") {
        fancy_ui = false;
        LogLevelFilter::Error
    } else if fancy_ui {
        LogLevelFilter::Warn
    } else {
        LogLevelFilter::Info
    };

    let error_dir = TempDir::new("crowbook").unwrap();
    let error_path = "error.log";
    if fancy_ui {
        let errors = File::create(error_dir.path().join(error_path)).unwrap();
        let _ = WriteLogger::init(verbosity, log_config, errors);
    } else {
        if TermLogger::init(verbosity, log_config).is_err() {
            // If it failed, not much we can do, we just won't display log
            let _ = SimpleLogger::init(verbosity, log_config);
        }
    }

    {
        let mut book = Book::new();
        if matches.is_present("autograph") {
            println!("{}", &lformat!("Enter autograph: "));
            let mut autograph = String::new();
            match io::stdin().read_to_string(&mut autograph) {
                Ok(_) => {
                    book.options.set_yaml(Yaml::String("autograph".to_string()), Yaml::String(autograph)).unwrap();
                },
                Err(_) => print_error(&lformat!("could not read autograph from stdin"), emoji),
            }
        }

        if fancy_ui {
            book.add_progress_bar(emoji);
        }
        book.set_options(&get_book_options(&matches));

        {
            let res = if matches.is_present("single") {
                if s != "-" {
                    book.load_markdown_file(s)
                } else {
                    book.read_markdown_config(io::stdin())
                }
            } else if s != "-" {
                book.load_file(s)
            } else {
                book.read_config(io::stdin())
            }.map(|_| ());

            match res {
                Ok(..) => {},
                Err(err) => {
                    book.set_error(&format!("{}", err));
                    return Err(err);
                }
            }
        }

        set_book_options(&mut book, &matches);

        if matches.is_present("stats") {
            let stats = Stats::new(&book);
            println!("{}", stats);
            exit(0);
        }

        if let Some(format) = matches.value_of("to") {
            render_format(&mut book, emoji,& matches, format);
        } else {
            book.render_all();
        }
    }
    if fancy_ui {
        let mut errors = String::new();
        let mut file = File::open(error_dir.path().join(error_path)).unwrap();
        file.read_to_string(&mut errors).unwrap();
        if !errors.is_empty() {
            print_warning(&lformat!("Crowbook exited successfully, but the following errors occurred:"),
                          emoji);
            let mut lines: Vec<_> = errors.lines().collect();
            lines.sort();
            lines.dedup();
            for line in &lines {
                if line.starts_with("[ERROR]") {
                    let line = &line[8..];
                    print_error(line, emoji);
                } else if line.starts_with("[WARN]") {
                    let line = &line[7..];
                    print_warning(line, emoji);
                }
            }
        }
    }

    Ok(())
}

pub fn real_main() {
    if let Err(err) = try_main() {
        print_error_and_exit(&format!("{}", err), false);
    }
}
<|endoftext|>


<commit_before>#![feature(exit_status)]
extern crate octobuild;
extern crate petgraph;
extern crate tempdir;

use octobuild::common::BuildTask;
use octobuild::cache::Cache;
use octobuild::wincmd;
use octobuild::xg;
use octobuild::utils;
use octobuild::version;
use octobuild::vs::compiler::VsCompiler;
use octobuild::compiler::*;

use petgraph::{Graph, EdgeDirection};
use petgraph::graph::NodeIndex;
use tempdir::TempDir;

use std::fs::File;
use std::env;
use std::io::{BufReader, Error, ErrorKind, Write};
use std::io;
use std::iter::FromIterator;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{channel, Sender, Receiver};
use std::thread;

#[derive(Debug)]
struct TaskMessage {
	index: NodeIndex,
	task: BuildTask
}

#[derive(Debug)]
struct ResultMessage {
	index: NodeIndex,
	task: BuildTask,
	worker: usize,
	result: Result<OutputInfo, Error>
}

fn main() {
	println!("XGConsole ({}):", version::full_version());
	let args = Vec::from_iter(env::args());
	for arg in args.iter() {
		println!("  {}", arg);
	}
	match execute(&args[1..]) {
		Ok(result) => {
			env::set_exit_status(match result {
				Some(r) => r,
				None => 500
			});
		}
		Err(e) => {
			println!("FATAL ERROR: {:?}", e);
			env::set_exit_status(500);
		}
	}
}

fn execute(args: &[String]) -> Result<Option<i32>, Error> {
	let cache = Cache::new();
	let temp_dir = try! (TempDir::new("octobuild"));
	for arg in args.iter() {
		if arg.starts_with("/") {continue}

		let (tx_result, rx_result): (Sender<ResultMessage>, Receiver<ResultMessage>) = channel();
		let (tx_task, rx_task): (Sender<TaskMessage>, Receiver<TaskMessage>) = channel();

		let mutex_rx_task = create_threads(rx_task, tx_result, utils::num_cpus(), |worker_id:usize| {
			let temp_path = temp_dir.path().to_path_buf();
			let temp_cache = cache.clone();
			move |task:TaskMessage| -> ResultMessage {
				execute_task(&temp_cache, &temp_path, worker_id, task)
			}
		});

		let path = Path::new(arg);
		let file = try! (File::open(&path));
		let graph = try! (xg::parser::parse(BufReader::new(file)));
		let validated_graph = try! (validate_graph(graph));
		match try! (execute_graph(&validated_graph, tx_task, mutex_rx_task, rx_result)) {
			Some(v) if v == 0 => {}
			v => {return Ok(v)}
		}
	}
	cache.cleanup(16 * 1024 * 1024 * 1024);
	Ok(Some(0))
}

fn create_threads<R: 'static + Send, T: 'static + Send, Worker:'static + Fn(T) -> R + Send, Factory:Fn(usize) -> Worker>(rx_task: Receiver<T>, tx_result: Sender<R>, num_cpus: usize, factory: Factory) ->  Arc<Mutex<Receiver<T>>> {
	let mutex_rx_task = Arc::new(Mutex::new(rx_task));
	for cpu_id in 0..num_cpus {
		let local_rx_task = mutex_rx_task.clone();
		let local_tx_result = tx_result.clone();
		let worker = factory(cpu_id);
		thread::spawn(move || {
			loop {
				let task: T;
				match local_rx_task.lock().unwrap().recv() {
					Ok(v) => {task = v;}
					Err(_) => {break;}
				}
				match local_tx_result.send(worker(task)) {
					Ok(_) => {}
					Err(_) => {break;}
				}
			}
		});
	}
	mutex_rx_task
}

fn validate_graph(graph: Graph<BuildTask, ()>) -> Result<Graph<BuildTask, ()>, Error> {
	let mut completed:Vec<bool> = Vec::new();
	let mut queue:Vec<NodeIndex> = Vec::new();
	for index in 0 .. graph.node_count() {
		completed.push(false);
		queue.push(NodeIndex::new(index));
	}
	let mut count:usize = 0;
	let mut i:usize = 0;
	while i < queue.len() {
		let index = queue[i];
		if (!completed[index.index()]) && (is_ready(&graph, &completed, &index)) {
			completed[index.index()] = true;
			for neighbor in graph.neighbors_directed(index, EdgeDirection::Incoming) {
				queue.push(neighbor);
			}
			count += 1;
			if count ==completed.len() {
				return Ok(graph);
			}
		}
		i = i + 1;
	}
	Err(Error::new(ErrorKind::InvalidInput, "Found cycles in build dependencies"))
}

fn execute_task(cache: &Cache, temp_dir: &Path, worker: usize, message: TaskMessage) -> ResultMessage {
	let args = wincmd::expand_args(&message.task.args, &|name:&str|->Option<String>{env::var(name).ok()});
	let output = execute_compiler(cache, temp_dir, &message.task.exec, &Path::new(&message.task.working_dir), &args);
	ResultMessage {
		index: message.index,
		task: message.task,
		worker: worker,
		result: output,
	}
}

fn execute_compiler(cache: &Cache, temp_dir: &Path, program: &str, cwd: &Path, args: &[String]) -> Result<OutputInfo, Error> {
	let command = CommandInfo {
		program: Path::new(program).to_path_buf(),
		current_dir: Some(cwd.to_path_buf()),
	};
	if Path::new(program).ends_with("cl.exe") {
		let compiler = VsCompiler::new(cache, temp_dir);
		compiler.compile(command, args)
	} else {
		command.to_command()
			.args(&args)
			.output()
			.map(|o| OutputInfo::new(o))
	}
}

fn execute_graph(graph: &Graph<BuildTask, ()>, tx_task: Sender<TaskMessage>, mutex_rx_task: Arc<Mutex<Receiver<TaskMessage>>>, rx_result: Receiver<ResultMessage>) -> Result<Option<i32>, Error> {
	// Run all tasks.
	let result = execute_until_failed(graph, tx_task, &rx_result);
	// Cleanup task queue.
	for _ in mutex_rx_task.lock().unwrap().iter() {
	}
	// Wait for in progress task completion.
	for _ in rx_result.iter() {
	}
	result
}

fn execute_until_failed(graph: &Graph<BuildTask, ()>, tx_task: Sender<TaskMessage>, rx_result: &Receiver<ResultMessage>) -> Result<Option<i32>, Error> {
	let mut completed:Vec<bool> = Vec::new();
	for _ in 0 .. graph.node_count() {
		completed.push(false);
	}
	for index in graph.without_edges(EdgeDirection::Incoming) {
		try! (tx_task.send(TaskMessage{
			index: index,
			task: graph.node_weight(index).unwrap().clone(),
		}).map_err(|e| Error::new(ErrorKind::Other, e)));
	}

	let mut count:usize = 0;
	for message in rx_result.iter() {
		assert!(!completed[message.index.index()]);
		count += 1;
		println!("#{} {}/{}: {}", message.worker, count, completed.len(), message.task.title);
		let result = try! (message.result);
		try! (io::stdout().write_all(&result.stdout));
		try! (io::stderr().write_all(&result.stderr));
		if !result.success() {
			return Ok(result.status);
		}
		completed[message.index.index()] = true;

		for source in graph.neighbors_directed(message.index, EdgeDirection::Incoming) {
			if !completed[source.index()] {
				if is_ready(graph, &completed, &source) {
					try! (tx_task.send(TaskMessage{
						index: source,
						task: graph.node_weight(source).unwrap().clone(),
					}).map_err(|e| Error::new(ErrorKind::Other, e)));
				}
			}
		}

		if count == completed.len() {
			return Ok(Some(0));
		}
	}
	panic! ("Unexpected end of result pipe");
}

fn is_ready(graph: &Graph<BuildTask, ()>, completed: &Vec<bool>, source: &NodeIndex) -> bool {
	for neighbor in graph.neighbors_directed(*source, EdgeDirection::Outgoing) {
		if !completed[neighbor.index()]{
			return false
		}
	}
	true
}
<commit_msg>Fix build graph processing<commit_after>#![feature(exit_status)]
extern crate octobuild;
extern crate petgraph;
extern crate tempdir;

use octobuild::common::BuildTask;
use octobuild::cache::Cache;
use octobuild::wincmd;
use octobuild::xg;
use octobuild::utils;
use octobuild::version;
use octobuild::vs::compiler::VsCompiler;
use octobuild::compiler::*;

use petgraph::{Graph, EdgeDirection};
use petgraph::graph::NodeIndex;
use tempdir::TempDir;

use std::fs::File;
use std::env;
use std::io::{BufReader, Error, ErrorKind, Write};
use std::io;
use std::iter::FromIterator;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{channel, Sender, Receiver};
use std::thread;

#[derive(Debug)]
struct TaskMessage {
	index: NodeIndex,
	task: BuildTask
}

#[derive(Debug)]
struct ResultMessage {
	index: NodeIndex,
	task: BuildTask,
	worker: usize,
	result: Result<OutputInfo, Error>
}

fn main() {
	println!("XGConsole ({}):", version::full_version());
	let args = Vec::from_iter(env::args());
	for arg in args.iter() {
		println!("  {}", arg);
	}
	match execute(&args[1..]) {
		Ok(result) => {
			env::set_exit_status(match result {
				Some(r) => r,
				None => 500
			});
		}
		Err(e) => {
			println!("FATAL ERROR: {:?}", e);
			env::set_exit_status(500);
		}
	}
}

fn execute(args: &[String]) -> Result<Option<i32>, Error> {
	let cache = Cache::new();
	let temp_dir = try! (TempDir::new("octobuild"));
	for arg in args.iter() {
		if arg.starts_with("/") {continue}

		let (tx_result, rx_result): (Sender<ResultMessage>, Receiver<ResultMessage>) = channel();
		let (tx_task, rx_task): (Sender<TaskMessage>, Receiver<TaskMessage>) = channel();

		let mutex_rx_task = create_threads(rx_task, tx_result, utils::num_cpus(), |worker_id:usize| {
			let temp_path = temp_dir.path().to_path_buf();
			let temp_cache = cache.clone();
			move |task:TaskMessage| -> ResultMessage {
				execute_task(&temp_cache, &temp_path, worker_id, task)
			}
		});

		let path = Path::new(arg);
		let file = try! (File::open(&path));
		let graph = try! (xg::parser::parse(BufReader::new(file)));
		let validated_graph = try! (validate_graph(graph));
		match try! (execute_graph(&validated_graph, tx_task, mutex_rx_task, rx_result)) {
			Some(v) if v == 0 => {}
			v => {return Ok(v)}
		}
	}
	cache.cleanup(16 * 1024 * 1024 * 1024);
	Ok(Some(0))
}

fn create_threads<R: 'static + Send, T: 'static + Send, Worker:'static + Fn(T) -> R + Send, Factory:Fn(usize) -> Worker>(rx_task: Receiver<T>, tx_result: Sender<R>, num_cpus: usize, factory: Factory) ->  Arc<Mutex<Receiver<T>>> {
	let mutex_rx_task = Arc::new(Mutex::new(rx_task));
	for cpu_id in 0..num_cpus {
		let local_rx_task = mutex_rx_task.clone();
		let local_tx_result = tx_result.clone();
		let worker = factory(cpu_id);
		thread::spawn(move || {
			loop {
				let task: T;
				match local_rx_task.lock().unwrap().recv() {
					Ok(v) => {task = v;}
					Err(_) => {break;}
				}
				match local_tx_result.send(worker(task)) {
					Ok(_) => {}
					Err(_) => {break;}
				}
			}
		});
	}
	mutex_rx_task
}

fn validate_graph(graph: Graph<BuildTask, ()>) -> Result<Graph<BuildTask, ()>, Error> {
	let mut completed:Vec<bool> = Vec::new();
	let mut queue:Vec<NodeIndex> = Vec::new();
	for index in 0 .. graph.node_count() {
		completed.push(false);
		queue.push(NodeIndex::new(index));
	}
	let mut count:usize = 0;
	let mut i:usize = 0;
	while i < queue.len() {
		let index = queue[i];
		if (!completed[index.index()]) && (is_ready(&graph, &completed, &index)) {
			completed[index.index()] = true;
			for neighbor in graph.neighbors_directed(index, EdgeDirection::Incoming) {
				queue.push(neighbor);
			}
			count += 1;
			if count ==completed.len() {
				return Ok(graph);
			}
		}
		i = i + 1;
	}
	Err(Error::new(ErrorKind::InvalidInput, "Found cycles in build dependencies"))
}

fn execute_task(cache: &Cache, temp_dir: &Path, worker: usize, message: TaskMessage) -> ResultMessage {
	let args = wincmd::expand_args(&message.task.args, &|name:&str|->Option<String>{env::var(name).ok()});
	let output = execute_compiler(cache, temp_dir, &message.task.exec, &Path::new(&message.task.working_dir), &args);
	ResultMessage {
		index: message.index,
		task: message.task,
		worker: worker,
		result: output,
	}
}

fn execute_compiler(cache: &Cache, temp_dir: &Path, program: &str, cwd: &Path, args: &[String]) -> Result<OutputInfo, Error> {
	let command = CommandInfo {
		program: Path::new(program).to_path_buf(),
		current_dir: Some(cwd.to_path_buf()),
	};
	if Path::new(program).ends_with("cl.exe") {
		let compiler = VsCompiler::new(cache, temp_dir);
		compiler.compile(command, args)
	} else {
		command.to_command()
			.args(&args)
			.output()
			.map(|o| OutputInfo::new(o))
	}
}

fn execute_graph(graph: &Graph<BuildTask, ()>, tx_task: Sender<TaskMessage>, mutex_rx_task: Arc<Mutex<Receiver<TaskMessage>>>, rx_result: Receiver<ResultMessage>) -> Result<Option<i32>, Error> {
	// Run all tasks.
	let result = execute_until_failed(graph, tx_task, &rx_result);
	// Cleanup task queue.
	for _ in mutex_rx_task.lock().unwrap().iter() {
	}
	// Wait for in progress task completion.
	for _ in rx_result.iter() {
	}
	result
}

fn execute_until_failed(graph: &Graph<BuildTask, ()>, tx_task: Sender<TaskMessage>, rx_result: &Receiver<ResultMessage>) -> Result<Option<i32>, Error> {
	let mut completed:Vec<bool> = Vec::new();
	for _ in 0 .. graph.node_count() {
		completed.push(false);
	}
	for index in graph.without_edges(EdgeDirection::Outgoing) {
		try! (tx_task.send(TaskMessage{
			index: index,
			task: graph.node_weight(index).unwrap().clone(),
		}).map_err(|e| Error::new(ErrorKind::Other, e)));
	}

	let mut count:usize = 0;
	for message in rx_result.iter() {
		assert!(!completed[message.index.index()]);
		count += 1;
		println!("#{} {}/{}: {}", message.worker, count, completed.len(), message.task.title);
		let result = try! (message.result);
		try! (io::stdout().write_all(&result.stdout));
		try! (io::stderr().write_all(&result.stderr));
		if !result.success() {
			return Ok(result.status);
		}
		completed[message.index.index()] = true;

		for source in graph.neighbors_directed(message.index, EdgeDirection::Incoming) {
			if !completed[source.index()] {
				if is_ready(graph, &completed, &source) {
					try! (tx_task.send(TaskMessage{
						index: source,
						task: graph.node_weight(source).unwrap().clone(),
					}).map_err(|e| Error::new(ErrorKind::Other, e)));
				}
			}
		}

		if count == completed.len() {
			return Ok(Some(0));
		}
	}
	panic! ("Unexpected end of result pipe");
}

fn is_ready(graph: &Graph<BuildTask, ()>, completed: &Vec<bool>, source: &NodeIndex) -> bool {
	for neighbor in graph.neighbors_directed(*source, EdgeDirection::Outgoing) {
		if !completed[neighbor.index()]{
			return false
		}
	}
	true
}
<|endoftext|>


<commit_before>use std::ops;

use std::cell::Cell;

pub trait Buffer<I>: ops::Deref<Target=[I]> {
    /// Attempts to fill the buffer using the closure `F`, the successful return from `F`
    /// should contain the number of items successfully written to the slice.
    ///
    /// # Note
    ///
    /// The returned value must *NOT* be larger than the length of the given slice.
    ///
    /// Return `0` if no more data is available or if the slice is of zero length.
    fn fill<F, E>(&mut self, f: F) -> Result<usize, E>
      where F: FnOnce(&mut [I]) -> Result<usize, E>;

    /// Buffer attempts to clear space.
    fn request_space(&mut self, usize);

    /// Consumes the given amount of bytes, must be less than or equal to len.
    ///
    /// Does not invalidate any borrow of data from self.
    fn consume(&self, items: usize);

    /// Returns the number of bytes left in the buffer.
    fn len(&self) -> usize;

    /// Returns the maximum amount of data which can be stored
    fn capacity(&self) -> usize;
}

/// TODO: Tests
#[derive(Debug, Eq, PartialEq)]
pub struct FixedSizeBuffer<I: Default + Clone> {
    buffer:    Vec<I>,
    populated: usize,
    /// The number of bytes from the start of the buffer which are used.
    ///
    /// As long as used <= populated it is safe.
    used:      Cell<usize>,
}

impl<I: Default + Clone> FixedSizeBuffer<I> {
    pub fn new(size: usize) -> Self {
        FixedSizeBuffer {
            buffer:    vec![Default::default(); size],
            populated: 0,
            used:      Cell::new(0),
        }
    }

    /// Removes used data and moves the unused remainder to the front of self.buffer.
    pub fn drop_used(&mut self) {
        use std::ptr;

        assert!(self.populated >= self.used.get());

        unsafe {
            ptr::copy(self.buffer.as_ptr().offset(self.used.get() as isize), self.buffer.as_mut_ptr(), self.populated - self.used.get());
        }

        self.populated -= self.used.get();
        self.used.set(0);
    }
}

impl<I: Default + Clone> ops::Deref for FixedSizeBuffer<I> {
    type Target = [I];

    fn deref(&self) -> &[I] {
        &self.buffer[self.used.get()..self.populated]
    }
}

impl<I: Default + Clone> ops::DerefMut for FixedSizeBuffer<I> {
    fn deref_mut(&mut self) -> &mut [I] {
        &mut self.buffer[self.used.get()..self.populated]
    }
}

impl<I: Default + Clone> Buffer<I> for FixedSizeBuffer<I> {
    fn fill<F, E>(&mut self, f: F) -> Result<usize, E>
      where F: FnOnce(&mut [I]) -> Result<usize, E> {
        f(&mut self.buffer[self.populated..]).map(|n| {
            self.populated += n;

            n
        })
    }

    fn request_space(&mut self, _items: usize) {
        self.drop_used();
    }

    fn consume(&self, items: usize) {
        debug_assert!(self.used.get() + items <= self.populated);

        self.used.set(self.used.get() + items)
    }

    fn len(&self) -> usize {
        self.populated - self.used.get()
    }

    fn capacity(&self) -> usize {
        self.buffer.len()
    }
}
<commit_msg>FixedSizeBuffer: Only move the data if space needs to be freed<commit_after>use std::ops;
use std::ptr;

use std::cell::Cell;

pub trait Buffer<I>: ops::Deref<Target=[I]> {
    /// Attempt to fill the buffer using the closure `F`.
    ///
    /// The successful return from `F` should contain the number of items successfully
    /// written to the slice.
    ///
    /// # Notes
    ///
    /// * The returned value must *NOT* be larger than the length of the given slice.
    ///
    /// * Return `0` if no more data is available or if the slice is of zero length.
    ///
    /// * The slice might contain uninitialized memory, do not read from the slice.
    fn fill<F, E>(&mut self, f: F) -> Result<usize, E>
      where F: FnOnce(&mut [I]) -> Result<usize, E>;

    /// Buffer attempts to clear space for additional items.
    fn request_space(&mut self, usize);

    /// Consumes the given amount of bytes, must be less than or equal to len.
    ///
    /// Does not invalidate any borrow of data from self.
    fn consume(&self, items: usize);

    /// Returns the number of bytes left in the buffer.
    fn len(&self) -> usize;

    /// Returns the maximum amount of data which can be stored
    fn capacity(&self) -> usize;
}

/// A fixed size buffer.
///
/// Only allocates when created.
// TODO: Tests
#[derive(Debug, Eq, PartialEq)]
pub struct FixedSizeBuffer<I: Default + Clone> {
    buffer:    Vec<I>,
    populated: usize,
    /// The number of bytes from the start of the buffer which are used.
    ///
    /// As long as used <= populated it is safe.
    used:      Cell<usize>,
}

impl<I: Default + Clone> FixedSizeBuffer<I> {
    pub fn new(size: usize) -> Self {
        assert!(size > 0);

        FixedSizeBuffer {
            buffer:    vec![Default::default(); size],
            populated: 0,
            used:      Cell::new(0),
        }
    }
}

impl<I: Default + Clone> ops::Deref for FixedSizeBuffer<I> {
    type Target = [I];

    fn deref(&self) -> &[I] {
        &self.buffer[self.used.get()..self.populated]
    }
}

impl<I: Default + Clone> ops::DerefMut for FixedSizeBuffer<I> {
    fn deref_mut(&mut self) -> &mut [I] {
        &mut self.buffer[self.used.get()..self.populated]
    }
}

impl<I: Default + Clone> Buffer<I> for FixedSizeBuffer<I> {
    fn fill<F, E>(&mut self, f: F) -> Result<usize, E>
      where F: FnOnce(&mut [I]) -> Result<usize, E> {
        f(&mut self.buffer[self.populated..]).map(|n| {
            self.populated += n;

            n
        })
    }

    fn request_space(&mut self, items: usize) {
        use std::ptr;

        assert!(self.populated >= self.used.get());

        // Only copy if we actually need to free the space
        if self.buffer.len() - self.populated < items {
            unsafe {
                ptr::copy(self.buffer.as_ptr().offset(self.used.get() as isize), self.buffer.as_mut_ptr(), self.populated - self.used.get());
            }

            self.populated -= self.used.get();
            self.used.set(0);
        }
    }

    fn consume(&self, items: usize) {
        debug_assert!(self.used.get() + items <= self.populated);

        self.used.set(self.used.get() + items)
    }

    fn len(&self) -> usize {
        self.populated - self.used.get()
    }

    fn capacity(&self) -> usize {
        self.buffer.len()
    }
}
<|endoftext|>


<commit_before>use super::Options;
use toml::value::{Table, Value};

mod elaborate_workspace;
mod pkg_status;
mod temp_project;
pub use self::elaborate_workspace::ElaborateWorkspace;
pub use self::pkg_status::*;
pub use self::temp_project::TempProject;

/// A continent struct for quick parsing and manipulating manifest
#[derive(Debug, serde_derive::Serialize, serde_derive::Deserialize)]
struct Manifest {
    #[serde(serialize_with = "::toml::ser::tables_last")]
    pub package: Table,
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "opt_tables_last"
    )]
    pub dependencies: Option<Table>,
    #[serde(
        rename = "dev-dependencies",
        skip_serializing_if = "Option::is_none",
        serialize_with = "opt_tables_last"
    )]
    pub dev_dependencies: Option<Table>,
    #[serde(
        rename = "build-dependencies",
        skip_serializing_if = "Option::is_none",
        serialize_with = "opt_tables_last"
    )]
    pub build_dependencies: Option<Table>,
    pub lib: Option<Table>,
    pub bin: Option<Vec<Table>>,
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "opt_tables_last"
    )]
    pub workspace: Option<Table>,
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "opt_tables_last"
    )]
    pub target: Option<Table>,
    pub features: Option<Value>,
}

impl Manifest {
    pub fn name(&self) -> String {
        match self.package["name"] {
            Value::String(ref name) => name.clone(),
            _ => unreachable!(),
        }
    }
}

pub fn opt_tables_last<S>(data: &Option<Table>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: ::serde::ser::Serializer,
{
    match *data {
        Some(ref d) => ::toml::ser::tables_last(d, serializer),
        None => unreachable!(),
    }
}
<commit_msg>Add the missing `cargo-features` entry in `Manifest`.<commit_after>use super::Options;
use toml::value::{Table, Value};

mod elaborate_workspace;
mod pkg_status;
mod temp_project;
pub use self::elaborate_workspace::ElaborateWorkspace;
pub use self::pkg_status::*;
pub use self::temp_project::TempProject;

/// A continent struct for quick parsing and manipulating manifest
#[derive(Debug, serde_derive::Serialize, serde_derive::Deserialize)]
struct Manifest {
    #[serde(
        rename = "cargo-features",
        skip_serializing_if = "Option::is_none"
    )]
    pub cargo_features: Option<Value>,
    #[serde(serialize_with = "::toml::ser::tables_last")]
    pub package: Table,
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "opt_tables_last"
    )]
    pub dependencies: Option<Table>,
    #[serde(
        rename = "dev-dependencies",
        skip_serializing_if = "Option::is_none",
        serialize_with = "opt_tables_last"
    )]
    pub dev_dependencies: Option<Table>,
    #[serde(
        rename = "build-dependencies",
        skip_serializing_if = "Option::is_none",
        serialize_with = "opt_tables_last"
    )]
    pub build_dependencies: Option<Table>,
    pub lib: Option<Table>,
    pub bin: Option<Vec<Table>>,
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "opt_tables_last"
    )]
    pub workspace: Option<Table>,
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "opt_tables_last"
    )]
    pub target: Option<Table>,
    pub features: Option<Value>,
}

impl Manifest {
    pub fn name(&self) -> String {
        match self.package["name"] {
            Value::String(ref name) => name.clone(),
            _ => unreachable!(),
        }
    }
}

pub fn opt_tables_last<S>(data: &Option<Table>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: ::serde::ser::Serializer,
{
    match *data {
        Some(ref d) => ::toml::ser::tables_last(d, serializer),
        None => unreachable!(),
    }
}
<|endoftext|>


<commit_before>use glib::object::Cast;
use glib::Type;
use gtk;
use gtk::prelude::{
    BoxExt, ButtonExt, CellLayoutExt, CellRendererExt, ContainerExt, EntryExt, GridExt,
    GtkListStoreExtManual, GtkWindowExt, OverlayExt, SearchBarExt, TreeModelExt,
    TreeModelFilterExt, TreeSelectionExt, TreeViewColumnExt, TreeViewExt, WidgetExt,
};

use sysinfo::{AsU32, Pid, Process, ProcessExt};

use notebook::NoteBook;

use std::cell::Cell;
use std::collections::HashMap;
use std::rc::Rc;

use utils::{create_button_with_image, format_number};

#[allow(dead_code)]
pub struct Procs {
    pub left_tree: gtk::TreeView,
    pub scroll: gtk::ScrolledWindow,
    pub current_pid: Rc<Cell<Option<Pid>>>,
    pub kill_button: gtk::Button,
    pub info_button: gtk::Button,
    pub vertical_layout: gtk::Box,
    pub list_store: gtk::ListStore,
    pub columns: Vec<gtk::TreeViewColumn>,
    pub filter_entry: gtk::Entry,
    pub search_bar: gtk::SearchBar,
    pub filter_button: gtk::Button,
}

impl Procs {
    pub fn new(
        proc_list: &HashMap<Pid, Process>,
        note: &mut NoteBook,
        window: &gtk::ApplicationWindow,
    ) -> Procs {
        let left_tree = gtk::TreeView::new();
        let scroll = gtk::ScrolledWindow::new(None::<&gtk::Adjustment>, None::<&gtk::Adjustment>);
        let current_pid = Rc::new(Cell::new(None));
        let kill_button = gtk::Button::with_label("End task");
        let info_button = gtk::Button::with_label("More information");

        let filter_button =
            create_button_with_image(include_bytes!("../assets/magnifier.png"), "Filter");

        // TODO: maybe add an 'X' button to close search as well?
        let overlay = gtk::Overlay::new();
        let filter_entry = gtk::Entry::new();
        let search_bar = gtk::SearchBar::new();

        // We put the filter entry at the right bottom.
        filter_entry.set_halign(gtk::Align::End);
        filter_entry.set_valign(gtk::Align::End);
        filter_entry.hide(); // By default, we don't show it.
        search_bar.connect_entry(&filter_entry);
        search_bar.set_show_close_button(true);

        overlay.add_overlay(&filter_entry);

        let mut columns: Vec<gtk::TreeViewColumn> = Vec::new();

        let list_store = gtk::ListStore::new(&[
            // The first four columns of the model are going to be visible in the view.
            Type::U32,    // pid
            Type::String, // name
            Type::String, // CPU
            Type::String, // mem
            Type::String, // disk I/O
            // These two will serve as keys when sorting by process name and CPU usage.
            Type::String, // name_lowercase
            Type::F32,    // CPU_f32
            Type::U64,    // mem
            Type::U64,    // disk I/O
        ]);

        for pro in proc_list.values() {
            create_and_fill_model(
                &list_store,
                pro.pid().as_u32(),
                &pro.cmd(),
                &pro.name(),
                pro.cpu_usage(),
                pro.memory() * 1_000,
            );
        }

        left_tree.set_headers_visible(true);
        scroll.add(&left_tree);
        overlay.add(&scroll);
        let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
        let horizontal_layout = gtk::Grid::new();

        left_tree.connect_cursor_changed(
            clone!(@weak current_pid, @weak kill_button, @weak info_button => move |tree_view| {
                let selection = tree_view.get_selection();
                let (pid, ret) = if let Some((model, iter)) = selection.get_selected() {
                    if let Ok(Some(x)) = model.get_value(&iter, 0).get::<u32>() {
                        (Some(x as Pid), true)
                    } else {
                        (None, false)
                    }
                } else {
                    (None, false)
                };
                current_pid.set(pid);
                kill_button.set_sensitive(ret);
                info_button.set_sensitive(ret);
            }),
        );
        kill_button.set_sensitive(false);
        info_button.set_sensitive(false);

        vertical_layout.pack_start(&overlay, true, true, 0);
        horizontal_layout.attach(&info_button, 0, 0, 4, 1);
        horizontal_layout.attach_next_to(
            &kill_button,
            Some(&info_button),
            gtk::PositionType::Right,
            4,
            1,
        );
        horizontal_layout.attach_next_to(
            &filter_button,
            Some(&kill_button),
            gtk::PositionType::Right,
            1,
            1,
        );
        horizontal_layout.set_column_homogeneous(true);
        vertical_layout.pack_start(&horizontal_layout, false, true, 0);

        // The filter part.
        let filter_model = gtk::TreeModelFilter::new(&list_store, None);
        filter_model.set_visible_func(
            clone!(@weak filter_entry => @default-return false, move |model, iter| {
                if !filter_entry.get_visible() || filter_entry.get_text_length() < 1 {
                    return true;
                }
                let text = filter_entry.get_text();
                    if text.is_empty() {
                        return true;
                    }
                    let text: &str = text.as_ref();
                    // TODO: Maybe add an option to make searches case sensitive?
                    let pid = model.get_value(iter, 0)
                                   .get::<u32>()
                                   .unwrap_or(None)
                                   .map(|p| p.to_string())
                                   .unwrap_or_else(String::new);
                    let name = model.get_value(iter, 1)
                                    .get::<String>()
                                    .unwrap_or(None)
                                    .map(|s| s.to_lowercase())
                                    .unwrap_or_else(String::new);
                    pid.contains(text) ||
                    text.contains(&pid) ||
                    name.contains(text) ||
                    text.contains(&name)
            }),
        );
        // For the filtering to be taken into account, we need to add it directly into the
        // "global" model.
        let sort_model = gtk::TreeModelSort::new(&filter_model);
        left_tree.set_model(Some(&sort_model));

        append_column("pid", &mut columns, &left_tree, None);
        append_column("process name", &mut columns, &left_tree, Some(200));
        append_column("cpu usage", &mut columns, &left_tree, None);
        append_column("memory usage", &mut columns, &left_tree, None);
        #[cfg(not(windows))]
        {
            append_column("disk I/O usage", &mut columns, &left_tree, None);
        }
        #[cfg(windows)]
        {
            append_column("I/O usage", &mut columns, &left_tree, None);
        }

        // When we click the "name" column the order is defined by the
        // "name_lowercase" effectively making the built-in comparator ignore case.
        columns[1].set_sort_column_id(5);
        // Likewise clicking the "CPU" column sorts by the "CPU_f32" one because
        // we want the order to be numerical not lexicographical.
        columns[2].set_sort_column_id(6);
        // The memory usage display has been improved, so to make efficient sort,
        // we have to separate the display and the actual number.
        columns[3].set_sort_column_id(7);
        // The disk I/O usage display has been improved, so to make efficient sort,
        // we have to separate the display and the actual number.
        columns[4].set_sort_column_id(8);

        filter_entry.connect_property_text_length_notify(move |_| {
            filter_model.refilter();
        });

        note.create_tab("Process list", &vertical_layout);

        filter_button.connect_clicked(clone!(@weak filter_entry, @weak window => move |_| {
            if filter_entry.get_visible() {
                filter_entry.hide();
            } else {
                filter_entry.show_all();
                window.set_focus(Some(&filter_entry));
            }
        }));

        Procs {
            left_tree,
            scroll,
            current_pid,
            kill_button,
            info_button,
            vertical_layout: vertical_layout
                .downcast::<gtk::Box>()
                .expect("downcast failed"),
            list_store,
            columns,
            filter_entry,
            search_bar,
            filter_button,
        }
    }

    pub fn hide_filter(&self) {
        self.filter_entry.hide();
        self.filter_entry.set_text("");
        self.search_bar.set_search_mode(false);
    }
}

fn append_column(
    title: &str,
    v: &mut Vec<gtk::TreeViewColumn>,
    left_tree: &gtk::TreeView,
    max_width: Option<i32>,
) {
    let id = v.len() as i32;
    let renderer = gtk::CellRendererText::new();

    if title != "process name" {
        renderer.set_property_xalign(1.0);
    }

    let column = gtk::TreeViewColumn::new();
    column.set_title(title);
    column.set_resizable(true);
    if let Some(max_width) = max_width {
        column.set_max_width(max_width);
        column.set_expand(true);
    }
    column.set_min_width(10);
    column.pack_start(&renderer, true);
    column.add_attribute(&renderer, "text", id);
    column.set_clickable(true);
    column.set_sort_column_id(id);
    left_tree.append_column(&column);
    v.push(column);
}

pub fn create_and_fill_model(
    list_store: &gtk::ListStore,
    pid: u32,
    cmdline: &[String],
    name: &str,
    cpu: f32,
    memory: u64,
) {
    if cmdline.is_empty() || name.is_empty() {
        return;
    }
    list_store.insert_with_values(
        None,
        &[0, 1, 2, 3, 4, 5, 6, 7, 8],
        &[
            &pid,
            &name,
            &format!("{:.1}", cpu),
            &format_number(memory),
            &String::new(),
            &name.to_lowercase(),
            &cpu,
            &memory,
            &0,
        ],
    );
}
<commit_msg>Rework process name a bit<commit_after>use glib::object::Cast;
use glib::Type;
use gtk;
use gtk::prelude::{
    BoxExt, ButtonExt, CellLayoutExt, CellRendererExt, ContainerExt, EntryExt, GridExt,
    GtkListStoreExtManual, GtkWindowExt, OverlayExt, SearchBarExt, TreeModelExt,
    TreeModelFilterExt, TreeSelectionExt, TreeViewColumnExt, TreeViewExt, WidgetExt,
};

use sysinfo::{AsU32, Pid, Process, ProcessExt};

use notebook::NoteBook;

use std::cell::Cell;
use std::collections::HashMap;
use std::rc::Rc;

use utils::{create_button_with_image, format_number};

#[allow(dead_code)]
pub struct Procs {
    pub left_tree: gtk::TreeView,
    pub scroll: gtk::ScrolledWindow,
    pub current_pid: Rc<Cell<Option<Pid>>>,
    pub kill_button: gtk::Button,
    pub info_button: gtk::Button,
    pub vertical_layout: gtk::Box,
    pub list_store: gtk::ListStore,
    pub columns: Vec<gtk::TreeViewColumn>,
    pub filter_entry: gtk::Entry,
    pub search_bar: gtk::SearchBar,
    pub filter_button: gtk::Button,
}

impl Procs {
    pub fn new(
        proc_list: &HashMap<Pid, Process>,
        note: &mut NoteBook,
        window: &gtk::ApplicationWindow,
    ) -> Procs {
        let left_tree = gtk::TreeView::new();
        let scroll = gtk::ScrolledWindow::new(None::<&gtk::Adjustment>, None::<&gtk::Adjustment>);
        let current_pid = Rc::new(Cell::new(None));
        let kill_button = gtk::Button::with_label("End task");
        let info_button = gtk::Button::with_label("More information");

        let filter_button =
            create_button_with_image(include_bytes!("../assets/magnifier.png"), "Filter");

        // TODO: maybe add an 'X' button to close search as well?
        let overlay = gtk::Overlay::new();
        let filter_entry = gtk::Entry::new();
        let search_bar = gtk::SearchBar::new();

        // We put the filter entry at the right bottom.
        filter_entry.set_halign(gtk::Align::End);
        filter_entry.set_valign(gtk::Align::End);
        filter_entry.hide(); // By default, we don't show it.
        search_bar.connect_entry(&filter_entry);
        search_bar.set_show_close_button(true);

        overlay.add_overlay(&filter_entry);

        let mut columns: Vec<gtk::TreeViewColumn> = Vec::new();

        let list_store = gtk::ListStore::new(&[
            // The first four columns of the model are going to be visible in the view.
            Type::U32,    // pid
            Type::String, // name
            Type::String, // CPU
            Type::String, // mem
            Type::String, // disk I/O
            // These two will serve as keys when sorting by process name and CPU usage.
            Type::String, // name_lowercase
            Type::F32,    // CPU_f32
            Type::U64,    // mem
            Type::U64,    // disk I/O
        ]);

        for pro in proc_list.values() {
            if let Some(exe) = pro
                .exe()
                .file_name()
                .and_then(|f| f.to_str())
                .or_else(|| Some(pro.name()))
            {
                create_and_fill_model(
                    &list_store,
                    pro.pid().as_u32(),
                    &pro.cmd(),
                    exe,
                    pro.cpu_usage(),
                    pro.memory() * 1_000,
                );
            }
        }

        left_tree.set_headers_visible(true);
        scroll.add(&left_tree);
        overlay.add(&scroll);
        let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
        let horizontal_layout = gtk::Grid::new();

        left_tree.connect_cursor_changed(
            clone!(@weak current_pid, @weak kill_button, @weak info_button => move |tree_view| {
                let selection = tree_view.get_selection();
                let (pid, ret) = if let Some((model, iter)) = selection.get_selected() {
                    if let Ok(Some(x)) = model.get_value(&iter, 0).get::<u32>() {
                        (Some(x as Pid), true)
                    } else {
                        (None, false)
                    }
                } else {
                    (None, false)
                };
                current_pid.set(pid);
                kill_button.set_sensitive(ret);
                info_button.set_sensitive(ret);
            }),
        );
        kill_button.set_sensitive(false);
        info_button.set_sensitive(false);

        vertical_layout.pack_start(&overlay, true, true, 0);
        horizontal_layout.attach(&info_button, 0, 0, 4, 1);
        horizontal_layout.attach_next_to(
            &kill_button,
            Some(&info_button),
            gtk::PositionType::Right,
            4,
            1,
        );
        horizontal_layout.attach_next_to(
            &filter_button,
            Some(&kill_button),
            gtk::PositionType::Right,
            1,
            1,
        );
        horizontal_layout.set_column_homogeneous(true);
        vertical_layout.pack_start(&horizontal_layout, false, true, 0);

        // The filter part.
        let filter_model = gtk::TreeModelFilter::new(&list_store, None);
        filter_model.set_visible_func(
            clone!(@weak filter_entry => @default-return false, move |model, iter| {
                if !filter_entry.get_visible() || filter_entry.get_text_length() < 1 {
                    return true;
                }
                let text = filter_entry.get_text();
                    if text.is_empty() {
                        return true;
                    }
                    let text: &str = text.as_ref();
                    // TODO: Maybe add an option to make searches case sensitive?
                    let pid = model.get_value(iter, 0)
                                   .get::<u32>()
                                   .unwrap_or(None)
                                   .map(|p| p.to_string())
                                   .unwrap_or_else(String::new);
                    let name = model.get_value(iter, 1)
                                    .get::<String>()
                                    .unwrap_or(None)
                                    .map(|s| s.to_lowercase())
                                    .unwrap_or_else(String::new);
                    pid.contains(text) ||
                    text.contains(&pid) ||
                    name.contains(text) ||
                    text.contains(&name)
            }),
        );
        // For the filtering to be taken into account, we need to add it directly into the
        // "global" model.
        let sort_model = gtk::TreeModelSort::new(&filter_model);
        left_tree.set_model(Some(&sort_model));

        append_column("pid", &mut columns, &left_tree, None);
        append_column("process name", &mut columns, &left_tree, Some(200));
        append_column("cpu usage", &mut columns, &left_tree, None);
        append_column("memory usage", &mut columns, &left_tree, None);
        #[cfg(not(windows))]
        {
            append_column("disk I/O usage", &mut columns, &left_tree, None);
        }
        #[cfg(windows)]
        {
            append_column("I/O usage", &mut columns, &left_tree, None);
        }

        // When we click the "name" column the order is defined by the
        // "name_lowercase" effectively making the built-in comparator ignore case.
        columns[1].set_sort_column_id(5);
        // Likewise clicking the "CPU" column sorts by the "CPU_f32" one because
        // we want the order to be numerical not lexicographical.
        columns[2].set_sort_column_id(6);
        // The memory usage display has been improved, so to make efficient sort,
        // we have to separate the display and the actual number.
        columns[3].set_sort_column_id(7);
        // The disk I/O usage display has been improved, so to make efficient sort,
        // we have to separate the display and the actual number.
        columns[4].set_sort_column_id(8);

        filter_entry.connect_property_text_length_notify(move |_| {
            filter_model.refilter();
        });

        note.create_tab("Process list", &vertical_layout);

        filter_button.connect_clicked(clone!(@weak filter_entry, @weak window => move |_| {
            if filter_entry.get_visible() {
                filter_entry.hide();
            } else {
                filter_entry.show_all();
                window.set_focus(Some(&filter_entry));
            }
        }));

        Procs {
            left_tree,
            scroll,
            current_pid,
            kill_button,
            info_button,
            vertical_layout: vertical_layout
                .downcast::<gtk::Box>()
                .expect("downcast failed"),
            list_store,
            columns,
            filter_entry,
            search_bar,
            filter_button,
        }
    }

    pub fn hide_filter(&self) {
        self.filter_entry.hide();
        self.filter_entry.set_text("");
        self.search_bar.set_search_mode(false);
    }
}

fn append_column(
    title: &str,
    v: &mut Vec<gtk::TreeViewColumn>,
    left_tree: &gtk::TreeView,
    max_width: Option<i32>,
) {
    let id = v.len() as i32;
    let renderer = gtk::CellRendererText::new();

    if title != "process name" {
        renderer.set_property_xalign(1.0);
    }

    let column = gtk::TreeViewColumn::new();
    column.set_title(title);
    column.set_resizable(true);
    if let Some(max_width) = max_width {
        column.set_max_width(max_width);
        column.set_expand(true);
    }
    column.set_min_width(10);
    column.pack_start(&renderer, true);
    column.add_attribute(&renderer, "text", id);
    column.set_clickable(true);
    column.set_sort_column_id(id);
    left_tree.append_column(&column);
    v.push(column);
}

pub fn create_and_fill_model(
    list_store: &gtk::ListStore,
    pid: u32,
    cmdline: &[String],
    name: &str,
    cpu: f32,
    memory: u64,
) {
    if cmdline.is_empty() || name.is_empty() {
        return;
    }
    list_store.insert_with_values(
        None,
        &[0, 1, 2, 3, 4, 5, 6, 7, 8],
        &[
            &pid,
            &name,
            &format!("{:.1}", cpu),
            &format_number(memory),
            &String::new(),
            &name.to_lowercase(),
            &cpu,
            &memory,
            &0,
        ],
    );
}
<|endoftext|>


<commit_before>use bvh::*;
use bounding_hierarchy::BHShape;
use aabb::AABB;
use std::collections::HashSet;

// TODO Consider: Instead of getting the scene's shapes passed, let leaf nodes store an AABB
// that is updated from the outside, perhaps by passing not only the indices of the changed
// shapes, but also their new AABBs into optimize().

impl BVH {
    /// Optimizes the `BVH` by batch-reorganizing updated nodes.
    /// Based on https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Util/ssBVH/ssBVH.cs
    ///
    /// Needs all the scene's shapes, plus the indices of the shapes that were updated.
    ///
    pub fn optimize<Shape: BHShape>(&mut self,
                                    refit_shape_indices: &HashSet<usize>,
                                    shapes: &[Shape]) {
        let mut refit_node_indices: HashSet<usize> = refit_shape_indices.iter()
            .map(|x| shapes[*x].bh_node_index())
            .collect();

        // As long as we have refit nodes left, take the list of refit nodes
        // with the highest depth (sweep nodes) and try to rotate them all
        while refit_node_indices.len() > 0 {
            let mut max_depth = 0;
            let mut sweep_node_indices: Vec<usize> = Vec::new();

            // Find max_depth and sweep_node_indices in one iteration
            for refit_node_index in refit_node_indices.iter() {
                let depth = match self.nodes[*refit_node_index] {
                    BVHNode::Node { depth, .. } => depth,
                    BVHNode::Leaf { depth, .. } => depth,
                    BVHNode::Dummy => panic!("Dummy node found during BVH optimization!"),
                };

                if depth > max_depth {
                    max_depth = depth;
                    sweep_node_indices.clear();
                    sweep_node_indices.push(*refit_node_index);
                } else if depth == max_depth {
                    sweep_node_indices.push(*refit_node_index);
                }
            }

            // Try to find a useful tree rotation with all nodes previously found
            for sweep_node_index in sweep_node_indices.iter() {
                // This node does not need to be checked again
                refit_node_indices.remove(&sweep_node_index);

                let new_refit_node_index = self.try_rotate(*sweep_node_index, shapes);

                // Instead of finding a useful tree rotation, we found another node
                // that we should check, so we add its index to the refit_node_indices.
                if let Some(index) = new_refit_node_index {
                    refit_node_indices.insert(index);
                }
            }
        }
    }

    /// Checks if there is a way to rotate a child and a grandchild (or two grandchildren) of
    /// the given node (specified by `node_index`) that would improve the `BVH`.
    /// If there is, the best rotation found is performed.
    /// Relies on the children and transitive children of the given node having correct AABBs.
    ///
    /// Returns Some(index_of_node) if a new node was found that should be used for optimization.
    ///
    fn try_rotate<Shape: BHShape>(&mut self, node_index: usize, shapes: &[Shape]) -> Option<usize> {
        let mut nodes = &mut self.nodes;

        let node_clone = nodes[node_index].clone();

        // Contains the surface area that would result from applying the currently favored rotation.
        // The rotation with the smallest SA will be applied in the end.
        // The value is calculated by child_l_aabb.surface_area() + child_r_aabb.surface_area()
        let mut best_surface_area = 0f32;

        // TODO Re-implement without mutability
        let mut parent_index: usize = 0;

        #[derive(Debug, Copy, Clone)]
        struct NodeData {
            index: usize,
            aabb: AABB,
        }

        macro_rules! get_children_node_data {
            ($node_index:expr) => ({
                let node = &nodes[$node_index];
                match *node {
                    BVHNode::Node { child_l, child_r, child_l_aabb, child_r_aabb, .. } => {
                        Some((NodeData{ index: child_l, aabb: child_l_aabb},
                            NodeData{ index: child_r, aabb: child_r_aabb}))
                    }
                    BVHNode::Leaf { .. } => {
                        None
                    }
                    BVHNode::Dummy => panic!("Dummy node found during BVH optimization!"),
                }
            });
        }

        // If this node is not a grandparent, update the AABB,
        // queue the parent for refitting, and bail out.
        // If it is a grandparent, calculate the current best_surface_area.
        let (child_l, child_r) = match node_clone {
            BVHNode::Node { parent, child_l, child_r, child_l_aabb, child_r_aabb, .. } => {
                if let BVHNode::Leaf { shape, .. } = nodes[child_l] {
                    let shape_l_index = shape;
                    if let BVHNode::Leaf { shape, .. } = nodes[child_r] {
                        let shape_r_index = shape;

                        // Update the AABBs saved for the children
                        // since at least one of them changed
                        let mut node = &mut nodes[node_index];
                        match node {
                            &mut BVHNode::Node { ref mut child_l_aabb,
                                                 ref mut child_r_aabb,
                                                 .. } => {
                                *child_l_aabb = shapes[shape_l_index].aabb();
                                *child_r_aabb = shapes[shape_r_index].aabb();
                            }
                            _ => unreachable!(),
                        }

                        return Some(parent);
                    }
                }

                let left_children = match get_children_node_data!(child_l) {
                    Some(x) => x,
                    _ => unreachable!(),
                };
                let right_children = match get_children_node_data!(child_r) {
                    Some(x) => x,
                    _ => unreachable!(),
                };

                // Update the AABBs saved for the children
                // since at least one of them changed
                let aabb_l = left_children.0.aabb.join(&left_children.1.aabb);
                let aabb_r = right_children.0.aabb.join(&right_children.1.aabb);

                parent_index = parent;
                best_surface_area = child_l_aabb.surface_area() + child_r_aabb.surface_area();
                (NodeData {
                     index: child_l,
                     aabb: aabb_l,
                 },
                 NodeData {
                     index: child_r,
                     aabb: aabb_r,
                 })
            }
            BVHNode::Leaf { parent, .. } => {
                return Some(parent);
            }
            BVHNode::Dummy => panic!("Dummy node found during BVH optimization!"),
        };

        // Stores the Rotation that would result in the surface area best_surface_area,
        // thus being the favored rotation that will be executed after considering all rotations.
        let mut best_rotation: Option<(usize, usize)> = None;

        macro_rules! consider_rotation {
            ($a:expr, $b:expr, $sa:expr) => {
                if $sa < best_surface_area {
                    best_surface_area = $sa;
                    best_rotation = Some(($a.index, $b.index));
                }
            };
        }

        // Get indices and AABBs of all grandchildren
        let left_children_nodes = {
            let node_data: NodeData = child_l;
            let index = node_data.index;
            get_children_node_data!(index)
        };
        let right_children_nodes = {
            let node_data: NodeData = child_r;
            let index = node_data.index;
            get_children_node_data!(index)
        };

        // Child to grandchild rotations
        if let Some((child_rl, child_rr)) = right_children_nodes {
            consider_rotation!(child_l,
                               child_rl,
                               child_rl.aabb.surface_area() +
                               child_l.aabb.join(&child_rr.aabb).surface_area());
            consider_rotation!(child_l,
                               child_rr,
                               child_rr.aabb.surface_area() +
                               child_l.aabb.join(&child_rl.aabb).surface_area());
        }
        if let Some((child_ll, child_lr)) = left_children_nodes {
            consider_rotation!(child_r,
                               child_ll,
                               child_ll.aabb.surface_area() +
                               child_r.aabb.join(&child_lr.aabb).surface_area());
            consider_rotation!(child_r,
                               child_lr,
                               child_lr.aabb.surface_area() +
                               child_r.aabb.join(&child_ll.aabb).surface_area());

            // Grandchild to grandchild rotations
            if let Some((child_rl, child_rr)) = right_children_nodes {
                consider_rotation!(child_ll,
                                   child_rl,
                                   child_rl.aabb.join(&child_lr.aabb).surface_area() +
                                   child_ll.aabb.join(&child_rr.aabb).surface_area());
                consider_rotation!(child_ll,
                                   child_rr,
                                   child_ll.aabb.join(&child_rl.aabb).surface_area() +
                                   child_lr.aabb.join(&child_rr.aabb).surface_area());
            }
        }

        let new_refit_node_index = if parent_index > 0 {
            Some(parent_index)
        } else {
            None
        };

        if let Some(rotation) = best_rotation {
            BVH::rotate(nodes, rotation.0, rotation.1);

            // TODO Update all changed node AABBs

            // Return parent node's index for upcoming refitting,
            // since this node just changed its AABB
            new_refit_node_index
        } else {
            // Update this node's AABBs (child_l_aabb, child_r_aabb)
            // according to the children nodes' AABBs.
            let mut node = &mut nodes[node_index];
            match node {
                &mut BVHNode::Node { ref mut child_l_aabb, ref mut child_r_aabb, .. } => {
                    *child_l_aabb = child_l.aabb;
                    *child_r_aabb = child_r.aabb;
                }
                _ => unreachable!(),
            }

            // Even with no rotation being useful for this node, a parent node's rotation
            // could be beneficial, so queue the parent sometimes.
            // TODO Return None most of the time, randomly
            // (see https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Util/ssBVH/ssBVH_Node.cs#L307)
            new_refit_node_index
        }
    }

    /// Switch two nodes by rewiring the involved indices (not by moving them in the nodes slice).
    fn rotate(nodes: &mut Vec<BVHNode>, node_a_index: usize, node_b_index: usize) {
        // This function is not defined with a self parameter to make it easier to call
        // without running into borrow checker issues.
        macro_rules! should_not_happen {
            () => ( panic!("While rotating BVH nodes, something unexpected happened."); );
            ($s:expr) => ( panic!("While rotating BVH nodes, something unexpected happened: {}", $s); );
        }

        #[allow(dead_code)] // The compiler falsely detects dead code here
        fn get_parent_index(nodes: &Vec<BVHNode>, node_index: usize) -> usize {
            let node = &nodes[node_index];

            match *node {
                BVHNode::Node { parent, .. } |
                BVHNode::Leaf { parent, .. } => parent,
                _ => should_not_happen!(),
            }
        }

        // Get parent indices
        let node_a_parent_index = get_parent_index(nodes, node_a_index);
        let node_b_parent_index = get_parent_index(nodes, node_b_index);

        #[allow(dead_code)] // The compiler falsely detects dead code here
        fn get_is_left_child(nodes: &Vec<BVHNode>,
                             node_index: usize,
                             node_parent_index: usize)
                             -> bool {
            let node_parent = &nodes[node_parent_index];

            match *node_parent {
                BVHNode::Node { child_l, .. } => child_l == node_index,
                _ => should_not_happen!(),
            }
        }

        // Get info about the nodes being a left or right child
        let node_a_is_left_child = get_is_left_child(nodes, node_a_index, node_a_parent_index);
        let node_b_is_left_child = get_is_left_child(nodes, node_b_index, node_b_parent_index);

        #[allow(dead_code)] // The compiler falsely detects dead code here
        fn connect_nodes(nodes: &mut Vec<BVHNode>,
                         child_index: usize,
                         parent_index: usize,
                         left_child: bool) {
            // Set parent's child and get its depth
            let parent_depth = {
                let mut parent = &mut nodes[parent_index];
                match parent {
                    &mut BVHNode::Node { ref mut child_l, ref mut child_r, depth, .. } => {
                        if left_child {
                            *child_l = child_index;
                        } else {
                            *child_r = child_index;
                        }
                        depth
                    }
                    _ => should_not_happen!(),
                }
            };

            // Set child's parent and depth
            {
                let mut child = &mut nodes[child_index];
                match child {
                    &mut BVHNode::Node { ref mut parent, ref mut depth, .. } |
                    &mut BVHNode::Leaf { ref mut parent, ref mut depth, .. } => {
                        *parent = parent_index;
                        *depth = parent_depth + 1;
                    }
                    _ => should_not_happen!(),
                };
            }
        }

        // Perform the switch
        connect_nodes(nodes,
                      node_a_index,
                      node_b_parent_index,
                      node_b_is_left_child);
        connect_nodes(nodes,
                      node_b_index,
                      node_a_parent_index,
                      node_a_is_left_child);
    }
}

#[cfg(test)]
pub mod tests {
    use bvh::BVH;
    use std::collections::HashSet;
    use testbase::build_some_bh;

    #[test]
    /// Tests if the optimize function tries to change a fresh BVH even though it shouldn't
    fn test_optimizing_new_bvh() {
        let (mut shapes, mut bvh) = build_some_bh::<BVH>();

        let refit_shape_indices: HashSet<usize> = (0..shapes.len()).collect();
        bvh.optimize(&refit_shape_indices, &mut shapes);
    }

    // TODO Add tests for:
    // * correct parent
    // * correct depth
    // * correct BVH after optimizing
    // * correct parent and depth after optimizing
}
<commit_msg>Add Todo<commit_after>use bvh::*;
use bounding_hierarchy::BHShape;
use aabb::AABB;
use std::collections::HashSet;

// TODO Consider: Instead of getting the scene's shapes passed, let leaf nodes store an AABB
// that is updated from the outside, perhaps by passing not only the indices of the changed
// shapes, but also their new AABBs into optimize().

impl BVH {
    /// Optimizes the `BVH` by batch-reorganizing updated nodes.
    /// Based on https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Util/ssBVH/ssBVH.cs
    ///
    /// Needs all the scene's shapes, plus the indices of the shapes that were updated.
    ///
    pub fn optimize<Shape: BHShape>(&mut self,
                                    refit_shape_indices: &HashSet<usize>,
                                    shapes: &[Shape]) {
        let mut refit_node_indices: HashSet<usize> = refit_shape_indices.iter()
            .map(|x| shapes[*x].bh_node_index())
            .collect();

        // As long as we have refit nodes left, take the list of refit nodes
        // with the highest depth (sweep nodes) and try to rotate them all
        while refit_node_indices.len() > 0 {
            let mut max_depth = 0;
            let mut sweep_node_indices: Vec<usize> = Vec::new();

            // Find max_depth and sweep_node_indices in one iteration
            for refit_node_index in refit_node_indices.iter() {
                let depth = match self.nodes[*refit_node_index] {
                    BVHNode::Node { depth, .. } => depth,
                    BVHNode::Leaf { depth, .. } => depth,
                    BVHNode::Dummy => panic!("Dummy node found during BVH optimization!"),
                };

                if depth > max_depth {
                    max_depth = depth;
                    sweep_node_indices.clear();
                    sweep_node_indices.push(*refit_node_index);
                } else if depth == max_depth {
                    sweep_node_indices.push(*refit_node_index);
                }
            }

            // Try to find a useful tree rotation with all nodes previously found
            for sweep_node_index in sweep_node_indices.iter() {
                // This node does not need to be checked again
                refit_node_indices.remove(&sweep_node_index);

                let new_refit_node_index = self.try_rotate(*sweep_node_index, shapes);

                // Instead of finding a useful tree rotation, we found another node
                // that we should check, so we add its index to the refit_node_indices.
                if let Some(index) = new_refit_node_index {
                    refit_node_indices.insert(index);
                }
            }
        }
    }

    /// Checks if there is a way to rotate a child and a grandchild (or two grandchildren) of
    /// the given node (specified by `node_index`) that would improve the `BVH`.
    /// If there is, the best rotation found is performed.
    /// Relies on the children and transitive children of the given node having correct AABBs.
    ///
    /// Returns Some(index_of_node) if a new node was found that should be used for optimization.
    ///
    fn try_rotate<Shape: BHShape>(&mut self, node_index: usize, shapes: &[Shape]) -> Option<usize> {
        let mut nodes = &mut self.nodes;

        let node_clone = nodes[node_index].clone();

        // Contains the surface area that would result from applying the currently favored rotation.
        // The rotation with the smallest SA will be applied in the end.
        // The value is calculated by child_l_aabb.surface_area() + child_r_aabb.surface_area()
        let mut best_surface_area = 0f32;

        // TODO Re-implement without mutability
        let mut parent_index: usize = 0;

        #[derive(Debug, Copy, Clone)]
        struct NodeData {
            index: usize,
            aabb: AABB,
        }

        macro_rules! get_children_node_data {
            ($node_index:expr) => ({
                let node = &nodes[$node_index];
                match *node {
                    BVHNode::Node { child_l, child_r, child_l_aabb, child_r_aabb, .. } => {
                        Some((NodeData{ index: child_l, aabb: child_l_aabb},
                            NodeData{ index: child_r, aabb: child_r_aabb}))
                    }
                    BVHNode::Leaf { .. } => {
                        None
                    }
                    BVHNode::Dummy => panic!("Dummy node found during BVH optimization!"),
                }
            });
        }

        // If this node is not a grandparent, update the AABB,
        // queue the parent for refitting, and bail out.
        // If it is a grandparent, calculate the current best_surface_area.
        let (child_l, child_r) = match node_clone {
            BVHNode::Node { parent, child_l, child_r, child_l_aabb, child_r_aabb, .. } => {
                if let BVHNode::Leaf { shape, .. } = nodes[child_l] {
                    let shape_l_index = shape;
                    if let BVHNode::Leaf { shape, .. } = nodes[child_r] {
                        let shape_r_index = shape;

                        // Update the AABBs saved for the children
                        // since at least one of them changed
                        let mut node = &mut nodes[node_index];
                        match node {
                            &mut BVHNode::Node { ref mut child_l_aabb,
                                                 ref mut child_r_aabb,
                                                 .. } => {
                                *child_l_aabb = shapes[shape_l_index].aabb();
                                *child_r_aabb = shapes[shape_r_index].aabb();
                            }
                            _ => unreachable!(),
                        }

                        return Some(parent);
                    }
                }

                // TODO There are nodes with only one leaf node attached.
                // The following code assumes this to be impossible.

                let left_children = match get_children_node_data!(child_l) {
                    Some(x) => x,
                    _ => unreachable!(),
                };
                let right_children = match get_children_node_data!(child_r) {
                    Some(x) => x,
                    _ => unreachable!(),
                };

                // Update the AABBs saved for the children
                // since at least one of them changed
                let aabb_l = left_children.0.aabb.join(&left_children.1.aabb);
                let aabb_r = right_children.0.aabb.join(&right_children.1.aabb);

                parent_index = parent;
                best_surface_area = child_l_aabb.surface_area() + child_r_aabb.surface_area();
                (NodeData {
                     index: child_l,
                     aabb: aabb_l,
                 },
                 NodeData {
                     index: child_r,
                     aabb: aabb_r,
                 })
            }
            BVHNode::Leaf { parent, .. } => {
                return Some(parent);
            }
            BVHNode::Dummy => panic!("Dummy node found during BVH optimization!"),
        };

        // Stores the Rotation that would result in the surface area best_surface_area,
        // thus being the favored rotation that will be executed after considering all rotations.
        let mut best_rotation: Option<(usize, usize)> = None;

        macro_rules! consider_rotation {
            ($a:expr, $b:expr, $sa:expr) => {
                if $sa < best_surface_area {
                    best_surface_area = $sa;
                    best_rotation = Some(($a.index, $b.index));
                }
            };
        }

        // Get indices and AABBs of all grandchildren
        let left_children_nodes = {
            let node_data: NodeData = child_l;
            let index = node_data.index;
            get_children_node_data!(index)
        };
        let right_children_nodes = {
            let node_data: NodeData = child_r;
            let index = node_data.index;
            get_children_node_data!(index)
        };

        // Child to grandchild rotations
        if let Some((child_rl, child_rr)) = right_children_nodes {
            consider_rotation!(child_l,
                               child_rl,
                               child_rl.aabb.surface_area() +
                               child_l.aabb.join(&child_rr.aabb).surface_area());
            consider_rotation!(child_l,
                               child_rr,
                               child_rr.aabb.surface_area() +
                               child_l.aabb.join(&child_rl.aabb).surface_area());
        }
        if let Some((child_ll, child_lr)) = left_children_nodes {
            consider_rotation!(child_r,
                               child_ll,
                               child_ll.aabb.surface_area() +
                               child_r.aabb.join(&child_lr.aabb).surface_area());
            consider_rotation!(child_r,
                               child_lr,
                               child_lr.aabb.surface_area() +
                               child_r.aabb.join(&child_ll.aabb).surface_area());

            // Grandchild to grandchild rotations
            if let Some((child_rl, child_rr)) = right_children_nodes {
                consider_rotation!(child_ll,
                                   child_rl,
                                   child_rl.aabb.join(&child_lr.aabb).surface_area() +
                                   child_ll.aabb.join(&child_rr.aabb).surface_area());
                consider_rotation!(child_ll,
                                   child_rr,
                                   child_ll.aabb.join(&child_rl.aabb).surface_area() +
                                   child_lr.aabb.join(&child_rr.aabb).surface_area());
            }
        }

        let new_refit_node_index = if parent_index > 0 {
            Some(parent_index)
        } else {
            None
        };

        if let Some(rotation) = best_rotation {
            BVH::rotate(nodes, rotation.0, rotation.1);

            // TODO Update all changed node AABBs

            // Return parent node's index for upcoming refitting,
            // since this node just changed its AABB
            new_refit_node_index
        } else {
            // Update this node's AABBs (child_l_aabb, child_r_aabb)
            // according to the children nodes' AABBs.
            let mut node = &mut nodes[node_index];
            match node {
                &mut BVHNode::Node { ref mut child_l_aabb, ref mut child_r_aabb, .. } => {
                    *child_l_aabb = child_l.aabb;
                    *child_r_aabb = child_r.aabb;
                }
                _ => unreachable!(),
            }

            // Even with no rotation being useful for this node, a parent node's rotation
            // could be beneficial, so queue the parent sometimes.
            // TODO Return None most of the time, randomly
            // (see https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Util/ssBVH/ssBVH_Node.cs#L307)
            new_refit_node_index
        }
    }

    /// Switch two nodes by rewiring the involved indices (not by moving them in the nodes slice).
    fn rotate(nodes: &mut Vec<BVHNode>, node_a_index: usize, node_b_index: usize) {
        // This function is not defined with a self parameter to make it easier to call
        // without running into borrow checker issues.
        macro_rules! should_not_happen {
            () => ( panic!("While rotating BVH nodes, something unexpected happened."); );
            ($s:expr) => ( panic!("While rotating BVH nodes, something unexpected happened: {}", $s); );
        }

        #[allow(dead_code)] // The compiler falsely detects dead code here
        fn get_parent_index(nodes: &Vec<BVHNode>, node_index: usize) -> usize {
            let node = &nodes[node_index];

            match *node {
                BVHNode::Node { parent, .. } |
                BVHNode::Leaf { parent, .. } => parent,
                _ => should_not_happen!(),
            }
        }

        // Get parent indices
        let node_a_parent_index = get_parent_index(nodes, node_a_index);
        let node_b_parent_index = get_parent_index(nodes, node_b_index);

        #[allow(dead_code)] // The compiler falsely detects dead code here
        fn get_is_left_child(nodes: &Vec<BVHNode>,
                             node_index: usize,
                             node_parent_index: usize)
                             -> bool {
            let node_parent = &nodes[node_parent_index];

            match *node_parent {
                BVHNode::Node { child_l, .. } => child_l == node_index,
                _ => should_not_happen!(),
            }
        }

        // Get info about the nodes being a left or right child
        let node_a_is_left_child = get_is_left_child(nodes, node_a_index, node_a_parent_index);
        let node_b_is_left_child = get_is_left_child(nodes, node_b_index, node_b_parent_index);

        #[allow(dead_code)] // The compiler falsely detects dead code here
        fn connect_nodes(nodes: &mut Vec<BVHNode>,
                         child_index: usize,
                         parent_index: usize,
                         left_child: bool) {
            // Set parent's child and get its depth
            let parent_depth = {
                let mut parent = &mut nodes[parent_index];
                match parent {
                    &mut BVHNode::Node { ref mut child_l, ref mut child_r, depth, .. } => {
                        if left_child {
                            *child_l = child_index;
                        } else {
                            *child_r = child_index;
                        }
                        depth
                    }
                    _ => should_not_happen!(),
                }
            };

            // Set child's parent and depth
            {
                let mut child = &mut nodes[child_index];
                match child {
                    &mut BVHNode::Node { ref mut parent, ref mut depth, .. } |
                    &mut BVHNode::Leaf { ref mut parent, ref mut depth, .. } => {
                        *parent = parent_index;
                        *depth = parent_depth + 1;
                    }
                    _ => should_not_happen!(),
                };
            }
        }

        // Perform the switch
        connect_nodes(nodes,
                      node_a_index,
                      node_b_parent_index,
                      node_b_is_left_child);
        connect_nodes(nodes,
                      node_b_index,
                      node_a_parent_index,
                      node_a_is_left_child);
    }
}

#[cfg(test)]
pub mod tests {
    use bvh::BVH;
    use std::collections::HashSet;
    use testbase::build_some_bh;

    #[test]
    /// Tests if the optimize function tries to change a fresh BVH even though it shouldn't
    fn test_optimizing_new_bvh() {
        let (mut shapes, mut bvh) = build_some_bh::<BVH>();

        let refit_shape_indices: HashSet<usize> = (0..shapes.len()).collect();
        bvh.optimize(&refit_shape_indices, &mut shapes);
    }

    // TODO Add tests for:
    // * correct parent
    // * correct depth
    // * correct BVH after optimizing
    // * correct parent and depth after optimizing
}
<|endoftext|>