<commit_before>//! Provides a portable interface to the filesystem.
//!
//! This module provides access to files in specific places:
//!
//! * The `resources/` subdirectory in the same directory as the
//! program executable,
//! * The `resources.zip` file in the same
//! directory as the program executable,
//! * The root folder of the  game's "save" directory which is in a
//! platform-dependent location,
//! such as `~/.local/share/<gameid>/` on Linux.  The `gameid`
//! is the the string passed to
//! `Context::load_from_conf()`; some platforms such as Windows also
//! incorporate the `author` string into the path.
//!
//! Files will be looked for in these locations in order, and the first one
//! found used.  That allows game assets to be easily distributed as an archive
//! file, but locally overridden for testing or modding simply by putting
//! altered copies of them in the game's `resources/` directory.
//!
//! Note that the file lookups WILL follow symlinks!  It is
//! more for convenience than absolute security, so don't treat it as
//! being secure.
//!
//! If you build `ggez` with the `cargo-resource-root` feature flag, it will
//! also look for a `resources/` subdirectory in the same directory as your
//! `Cargo.toml`, which can be very convenient for development.

// BUGGO: TODO: Also make it print out the searched directories when it
// can't find a file!

use std::env;
use std::fmt;
use std::io;
use std::path;

use app_dirs::*;

use GameError;
use GameResult;
use conf;
use vfs::{self, VFS};

const CONFIG_NAME: &'static str = "/conf.toml";

/// A structure that contains the filesystem state and cache.
#[derive(Debug)]
pub struct Filesystem {
    vfs: vfs::OverlayFS,
    resources_path: path::PathBuf,
    zip_path: path::PathBuf,
    // user_config_path: path::PathBuf,
    user_data_path: path::PathBuf,
}

/// Represents a file, either in the filesystem, or in the resources zip file,
/// or whatever.
pub enum File {
    VfsFile(Box<vfs::VFile>),
}

impl fmt::Debug for File {
    // TODO: Make this more useful.
    // But we can't seem to get a filename out of a file,
    // soooooo.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            File::VfsFile(ref _file) => write!(f, "VfsFile"),
        }
    }
}

impl io::Read for File {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match *self {
            File::VfsFile(ref mut f) => f.read(buf),
        }
    }
}

impl io::Write for File {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match *self {
            File::VfsFile(ref mut f) => f.write(buf),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match *self {
            File::VfsFile(ref mut f) => f.flush(),
        }
    }
}

impl Filesystem {
    /// Create a new Filesystem instance, using the given `id` and (on
    /// some platforms) the `author` as a portion of the user
    /// directory path.  This function is called automatically by
    /// ggez, the end user should never need to call it.
    pub fn new(id: &'static str, author: &'static str) -> GameResult<Filesystem> {
        let app_info = AppInfo {
            name: id,
            author: author,
        };
        let mut root_path = env::current_exe()?;

        // Ditch the filename (if any)
        if root_path.file_name().is_some() {
            root_path.pop();
        }

        // Set up VFS to merge resource path, root path, and zip path.
        let mut overlay = vfs::OverlayFS::new();

        let mut resources_path;
        let mut resources_zip_path;
        let user_data_path;
        // let user_config_path;
        // <game exe root>/resources/
        {
            resources_path = root_path.clone();
            resources_path.push("resources");
            let physfs = vfs::PhysicalFS::new(&resources_path, true);
            overlay.push_back(Box::new(physfs));
        }

        // <root>/resources.zip
        {
            resources_zip_path = root_path.clone();
            resources_zip_path.push("resources.zip");
            if resources_zip_path.exists() {
                let zipfs = vfs::ZipFS::new(&resources_zip_path)?;
                overlay.push_back(Box::new(zipfs));
            }
        }

        // Per-user data dir,
        // ~/.local/share/whatever/
        {
            user_data_path = app_root(AppDataType::UserData, &app_info)?;
            let physfs = vfs::PhysicalFS::new(&user_data_path, true);
            overlay.push_back(Box::new(physfs));
        }

        // // Writeable local dir, ~/.config/whatever/
        // // Save game dir is read-write
        // {
        //     user_config_path = app_root(AppDataType::UserConfig, &app_info)?;
        //     let physfs = vfs::PhysicalFS::new(&user_config_path, false);
        //     overlay.push_back(Box::new(physfs));
        // }

        // Cargo manifest dir!
        #[cfg(feature = "cargo-resource-root")]
        {
            if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
                let mut path = path::PathBuf::from(manifest_dir);
                path.push("resources");
                let physfs = vfs::PhysicalFS::new(&path, false);
                overlay.push_back(Box::new(physfs));
            }
        }

        let fs = Filesystem {
            vfs: overlay,
            resources_path: resources_path,
            zip_path: resources_zip_path,
            // user_config_path: user_config_path,
            user_data_path: user_data_path,
        };

        Ok(fs)
    }

    /// Opens the given path and returns the resulting `File`
    /// in read-only mode.
    pub fn open<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
        self.vfs
            .open(path.as_ref())
            .map(|f| File::VfsFile(f))
    }

    /// Opens a file in the user directory with the given `std::fs::OpenOptions`.
    /// Note that even if you open a file read-only, it can only access
    /// files in the user directory.
    pub fn open_options<P: AsRef<path::Path>>(&mut self,
                                              path: P,
                                              options: &vfs::OpenOptions)
                                              -> GameResult<File> {
        self.vfs
            .open_options(path.as_ref(), options)
            .map(|f| File::VfsFile(f))
            .map_err(|e| {
                GameError::ResourceLoadError(format!("Tried to open {:?} but got error: {:?}",
                                                     path.as_ref(),
                                                     e))
            })
    }

    /// Creates a new file in the user directory and opens it
    /// to be written to, truncating it if it already exists.
    pub fn create<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
        self.vfs.create(path.as_ref()).map(|f| File::VfsFile(f))
    }

    /// Create an empty directory in the user dir
    /// with the given name.  Any parents to that directory
    /// that do not exist will be created.
    pub fn create_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.mkdir(path.as_ref())
    }

    /// Deletes the specified file in the user dir.
    pub fn delete<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.rm(path.as_ref())
    }

    /// Deletes the specified directory in the user dir,
    /// and all its contents!
    pub fn delete_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.rmrf(path.as_ref())
    }

    /// Check whether a file or directory exists.
    pub fn exists<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs.exists(path.as_ref())
    }

    /// Check whether a path points at a file.
    pub fn is_file<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs
            .metadata(path.as_ref())
            .map(|m| m.is_file())
            .unwrap_or(false)
    }

    /// Check whether a path points at a directory.
    pub fn is_dir<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs
            .metadata(path.as_ref())
            .map(|m| m.is_dir())
            .unwrap_or(false)
    }

    /// Return the full path to the user data directory
    pub fn get_user_data_dir(&self) -> &path::Path {
        &self.user_data_path
    }

    /// Returns the full path to the resource directory
    /// (even if it doesn't exist)
    pub fn get_resources_dir(&self) -> &path::Path {
        &self.resources_path
    }

    /// Returns an iterator over all files and directories in the resource directory,
    /// in no particular order.
    ///
    /// Lists the base directory if an empty path is given.
    pub fn read_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<Vec<path::PathBuf>> {
        // TODO: This should return an iterator, and be called iter()
        let itr = self.vfs.read_dir(path.as_ref())?
            .map(|fname| fname.unwrap())
            .collect();
        Ok(itr)
    }

    /// Prints the contents of all data directories.
    /// Useful for debugging.
    pub fn print_all(&mut self) -> GameResult<()> {
        /// TODO: Should tell you which source the resulting files come from...
        for itm in self.read_dir("/")? {
            println!("{:?}", itm);
        }
        Ok(())
    }

    /// Looks for a file named "conf.toml" in the resources directory
    /// loads it if it finds it.
    /// If it can't read it for some reason, returns an error.
    pub fn read_config(&mut self) -> GameResult<conf::Conf> {
        let conf_path = path::Path::new(CONFIG_NAME);
        if self.is_file(conf_path) {
            let mut file = self.open(conf_path)?;
            let c = conf::Conf::from_toml_file(&mut file)?;
            Ok(c)
        } else {
            Err(GameError::ConfigError(String::from("Config file not found")))
        }
    }

    /// Takes a `conf::Conf` object and saves it to the user directory,
    /// overwriting any file already there.
    pub fn write_config(&mut self, conf: &conf::Conf) -> GameResult<()> {
        let conf_path = path::Path::new(CONFIG_NAME);
        let mut file = self.create(conf_path)?;
        let f = conf.to_toml_file(&mut file)?;
        if self.is_file(conf_path) {
            Ok(f)
        } else {
            Err(GameError::ConfigError(format!("Failed to write config file at {}", conf_path.to_string_lossy())))
        }
    }
}

#[cfg(test)]
mod tests {
    use error::*;
    use filesystem::*;
    use vfs::*;
    use std::path;
    use std::io::{Read, Write};
    use conf;

    fn get_dummy_fs_for_tests() -> Filesystem {
        let mut path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        path.push("resources");
        let physfs = vfs::PhysicalFS::new(&path, false);
        let mut ofs = vfs::OverlayFS::new();
        ofs.push_front(Box::new(physfs));
        Filesystem {
            vfs: ofs,

            resources_path: "".into(),
            zip_path: "".into(),
            // user_config_path: "".into(),
            user_data_path: "".into(),
        }

    }

    #[test]
    fn test_file_exists() {
        let mut f = get_dummy_fs_for_tests();

        let tile_file = path::Path::new("/tile.png");
        assert!(f.exists(tile_file));
        assert!(f.is_file(tile_file));

        let tile_file = path::Path::new("/oglebog.png");
        assert!(!f.exists(tile_file));
        assert!(!f.is_file(tile_file));
        assert!(!f.is_dir(tile_file));

    }

    #[test]
    fn test_read_dir() {
        let mut f = get_dummy_fs_for_tests();

        //let dir_contents_size = f.read_dir().unwrap().len();
        //assert!(dir_contents_size > 0);
    }

    #[test]
    fn test_create_delete_file() {
        let mut fs = get_dummy_fs_for_tests();
        let test_file = path::Path::new("/testfile.txt");
        let bytes = "test".as_bytes();

        {
            let mut file = fs.create(test_file).unwrap();
            file.write(bytes).unwrap();
        }
        {
            let mut buffer = Vec::new();
            let mut file = fs.open(test_file).unwrap();
            file.read_to_end(&mut buffer).unwrap();
            assert_eq!(bytes, buffer.as_slice());
        }

        fs.delete(test_file).unwrap();
    }

    #[test]
    fn test_file_not_found() {
        let mut fs = get_dummy_fs_for_tests();
        {
            let rel_file = "testfile.txt";
            match fs.open(rel_file) {
                Err(GameError::ResourceNotFound(_, _)) => (),
                Err(e) => panic!("Invalid error for opening file with relative path: {:?}", e),
                Ok(f) => panic!("Should have gotten an error but instead got {:?}!", f),
            }
        }

        {
            // This absolute path should work on Windows too since we
            // completely remove filesystem roots.
            match fs.open("/ooglebooglebarg.txt") {
                Err(GameError::ResourceNotFound(_, _)) => (),
                Err(e) => panic!("Invalid error for opening nonexistent file: {}", e),
                Ok(f) => panic!("Should have gotten an error but instead got {:?}", f),
            }
        }
    }

    #[test]
    fn test_write_config() {
        let mut f = get_dummy_fs_for_tests();
        let mut conf = conf::Conf::new();
        // The config file should end up in
        // the resources directory with this
        match f.write_config(&conf) {
            Ok(f) => (),
            Err(e) => panic!("{:?}", e),
        }
        // Remove the config file!
        f.delete(CONFIG_NAME);
    }

    //#[test]
    //#fn test_app_dirs() {
    //#use app_dirs::*;
    //#use sdl2;

    //     let app_info = AppInfo{name:"test", author:"ggez"};
    //     println!("user config: {:?}", get_app_root(AppDataType::UserConfig, &app_info));
    //     println!("user cache: {:?}", get_app_root(AppDataType::UserCache, &app_info));
    //     println!("user data: {:?}", get_app_root(AppDataType::UserData, &app_info));

    //     println!("shared config: {:?}", get_app_root(AppDataType::SharedConfig, &app_info));
    //     println!("shared data: {:?}", get_app_root(AppDataType::SharedData, &app_info));

    //     println!("SDL base path: {}", sdl2::filesystem::base_path().unwrap());
    //     println!("SDL pref path: {}", sdl2::filesystem::pref_path("ggez", "id").unwrap());

    // Okay, we want user data for data, user config for config,
    // On Linux these map to:
    // ~/.local/share/test
    // ~/.config/test
    //
    // Plus we should search next to the exe path,
    // AND we should search in env!(CARGO_MANIFEST_DIR) if it exists.
    // (which is a bit hacky since we'll then end up distributing binaries
    // that check in that dir as defined at compile time...  But hmm.)
    //
    // So what we really need is to search in all these places:
    // next-to-executable
    // CARGO_MANIFEST_DIR
    // AppDataType::UserData for read-only data
    // AppDataType::UserConfig for read-write data (saved games, config files)
    // Last, zip file in ANY of the read-only data locations.
    //
    // This is getting complex.
    // We're starting to really need a full VFS layer to properly overlay
    // these things.  Look more at how physfs implements it?
    //
}
<commit_msg>Improved filesystem.print_all() method<commit_after>//! Provides a portable interface to the filesystem.
//!
//! This module provides access to files in specific places:
//!
//! * The `resources/` subdirectory in the same directory as the
//! program executable,
//! * The `resources.zip` file in the same
//! directory as the program executable,
//! * The root folder of the  game's "save" directory which is in a
//! platform-dependent location,
//! such as `~/.local/share/<gameid>/` on Linux.  The `gameid`
//! is the the string passed to
//! `Context::load_from_conf()`; some platforms such as Windows also
//! incorporate the `author` string into the path.
//!
//! Files will be looked for in these locations in order, and the first one
//! found used.  That allows game assets to be easily distributed as an archive
//! file, but locally overridden for testing or modding simply by putting
//! altered copies of them in the game's `resources/` directory.
//!
//! Note that the file lookups WILL follow symlinks!  It is
//! more for convenience than absolute security, so don't treat it as
//! being secure.
//!
//! If you build `ggez` with the `cargo-resource-root` feature flag, it will
//! also look for a `resources/` subdirectory in the same directory as your
//! `Cargo.toml`, which can be very convenient for development.

// BUGGO: TODO: Also make it print out the searched directories when it
// can't find a file!

use std::env;
use std::fmt;
use std::io;
use std::path;

use app_dirs::*;

use GameError;
use GameResult;
use conf;
use vfs::{self, VFS};

const CONFIG_NAME: &'static str = "/conf.toml";

/// A structure that contains the filesystem state and cache.
#[derive(Debug)]
pub struct Filesystem {
    vfs: vfs::OverlayFS,
    resources_path: path::PathBuf,
    zip_path: path::PathBuf,
    // user_config_path: path::PathBuf,
    user_data_path: path::PathBuf,
}

/// Represents a file, either in the filesystem, or in the resources zip file,
/// or whatever.
pub enum File {
    VfsFile(Box<vfs::VFile>),
}

impl fmt::Debug for File {
    // TODO: Make this more useful.
    // But we can't seem to get a filename out of a file,
    // soooooo.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            File::VfsFile(ref _file) => write!(f, "VfsFile"),
        }
    }
}

impl io::Read for File {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match *self {
            File::VfsFile(ref mut f) => f.read(buf),
        }
    }
}

impl io::Write for File {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match *self {
            File::VfsFile(ref mut f) => f.write(buf),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match *self {
            File::VfsFile(ref mut f) => f.flush(),
        }
    }
}

impl Filesystem {
    /// Create a new Filesystem instance, using the given `id` and (on
    /// some platforms) the `author` as a portion of the user
    /// directory path.  This function is called automatically by
    /// ggez, the end user should never need to call it.
    pub fn new(id: &'static str, author: &'static str) -> GameResult<Filesystem> {
        let app_info = AppInfo {
            name: id,
            author: author,
        };
        let mut root_path = env::current_exe()?;

        // Ditch the filename (if any)
        if root_path.file_name().is_some() {
            root_path.pop();
        }

        // Set up VFS to merge resource path, root path, and zip path.
        let mut overlay = vfs::OverlayFS::new();

        let mut resources_path;
        let mut resources_zip_path;
        let user_data_path;
        // let user_config_path;
        // <game exe root>/resources/
        {
            resources_path = root_path.clone();
            resources_path.push("resources");
            let physfs = vfs::PhysicalFS::new(&resources_path, true);
            overlay.push_back(Box::new(physfs));
        }

        // <root>/resources.zip
        {
            resources_zip_path = root_path.clone();
            resources_zip_path.push("resources.zip");
            if resources_zip_path.exists() {
                let zipfs = vfs::ZipFS::new(&resources_zip_path)?;
                overlay.push_back(Box::new(zipfs));
            }
        }

        // Per-user data dir,
        // ~/.local/share/whatever/
        {
            user_data_path = app_root(AppDataType::UserData, &app_info)?;
            let physfs = vfs::PhysicalFS::new(&user_data_path, true);
            overlay.push_back(Box::new(physfs));
        }

        // // Writeable local dir, ~/.config/whatever/
        // // Save game dir is read-write
        // {
        //     user_config_path = app_root(AppDataType::UserConfig, &app_info)?;
        //     let physfs = vfs::PhysicalFS::new(&user_config_path, false);
        //     overlay.push_back(Box::new(physfs));
        // }

        // Cargo manifest dir!
        #[cfg(feature = "cargo-resource-root")]
        {
            if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
                let mut path = path::PathBuf::from(manifest_dir);
                path.push("resources");
                let physfs = vfs::PhysicalFS::new(&path, false);
                overlay.push_back(Box::new(physfs));
            }
        }

        let fs = Filesystem {
            vfs: overlay,
            resources_path: resources_path,
            zip_path: resources_zip_path,
            // user_config_path: user_config_path,
            user_data_path: user_data_path,
        };

        Ok(fs)
    }

    /// Opens the given path and returns the resulting `File`
    /// in read-only mode.
    pub fn open<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
        self.vfs
            .open(path.as_ref())
            .map(|f| File::VfsFile(f))
    }

    /// Opens a file in the user directory with the given `std::fs::OpenOptions`.
    /// Note that even if you open a file read-only, it can only access
    /// files in the user directory.
    pub fn open_options<P: AsRef<path::Path>>(&mut self,
                                              path: P,
                                              options: &vfs::OpenOptions)
                                              -> GameResult<File> {
        self.vfs
            .open_options(path.as_ref(), options)
            .map(|f| File::VfsFile(f))
            .map_err(|e| {
                GameError::ResourceLoadError(format!("Tried to open {:?} but got error: {:?}",
                                                     path.as_ref(),
                                                     e))
            })
    }

    /// Creates a new file in the user directory and opens it
    /// to be written to, truncating it if it already exists.
    pub fn create<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
        self.vfs.create(path.as_ref()).map(|f| File::VfsFile(f))
    }

    /// Create an empty directory in the user dir
    /// with the given name.  Any parents to that directory
    /// that do not exist will be created.
    pub fn create_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.mkdir(path.as_ref())
    }

    /// Deletes the specified file in the user dir.
    pub fn delete<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.rm(path.as_ref())
    }

    /// Deletes the specified directory in the user dir,
    /// and all its contents!
    pub fn delete_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.rmrf(path.as_ref())
    }

    /// Check whether a file or directory exists.
    pub fn exists<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs.exists(path.as_ref())
    }

    /// Check whether a path points at a file.
    pub fn is_file<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs
            .metadata(path.as_ref())
            .map(|m| m.is_file())
            .unwrap_or(false)
    }

    /// Check whether a path points at a directory.
    pub fn is_dir<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs
            .metadata(path.as_ref())
            .map(|m| m.is_dir())
            .unwrap_or(false)
    }

    /// Return the full path to the user data directory
    pub fn get_user_data_dir(&self) -> &path::Path {
        &self.user_data_path
    }

    /// Returns the full path to the resource directory
    /// (even if it doesn't exist)
    pub fn get_resources_dir(&self) -> &path::Path {
        &self.resources_path
    }

    /// Returns a list of all files and directories in the resource directory,
    /// in no particular order.
    ///
    /// Lists the base directory if an empty path is given.
    pub fn read_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<Vec<path::PathBuf>> {
        // TODO: This should return an iterator, and be called iter()
        let itr = self.vfs.read_dir(path.as_ref())?
            .map(|fname| fname.unwrap())
            .collect();
        Ok(itr)
    }

    /// Prints the contents of all data directories.
    /// Useful for debugging.
    pub fn print_all(&mut self) -> GameResult<()> {
        for vfs in self.vfs.roots() {
            println!("Source {:?}", vfs);
            for itm in vfs.read_dir(path::Path::new("/"))? {
                println!("  {:?}", itm);
            }
        }
        Ok(())
    }

    /// Looks for a file named "conf.toml" in the resources directory
    /// loads it if it finds it.
    /// If it can't read it for some reason, returns an error.
    pub fn read_config(&mut self) -> GameResult<conf::Conf> {
        let conf_path = path::Path::new(CONFIG_NAME);
        if self.is_file(conf_path) {
            let mut file = self.open(conf_path)?;
            let c = conf::Conf::from_toml_file(&mut file)?;
            Ok(c)
        } else {
            Err(GameError::ConfigError(String::from("Config file not found")))
        }
    }

    /// Takes a `conf::Conf` object and saves it to the user directory,
    /// overwriting any file already there.
    pub fn write_config(&mut self, conf: &conf::Conf) -> GameResult<()> {
        let conf_path = path::Path::new(CONFIG_NAME);
        let mut file = self.create(conf_path)?;
        let f = conf.to_toml_file(&mut file)?;
        if self.is_file(conf_path) {
            Ok(f)
        } else {
            Err(GameError::ConfigError(format!("Failed to write config file at {}", conf_path.to_string_lossy())))
        }
    }
}

#[cfg(test)]
mod tests {
    use error::*;
    use filesystem::*;
    use vfs::*;
    use std::path;
    use std::io::{Read, Write};
    use conf;

    fn get_dummy_fs_for_tests() -> Filesystem {
        let mut path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        path.push("resources");
        let physfs = vfs::PhysicalFS::new(&path, false);
        let mut ofs = vfs::OverlayFS::new();
        ofs.push_front(Box::new(physfs));
        Filesystem {
            vfs: ofs,

            resources_path: "".into(),
            zip_path: "".into(),
            // user_config_path: "".into(),
            user_data_path: "".into(),
        }

    }

    #[test]
    fn test_file_exists() {
        let mut f = get_dummy_fs_for_tests();

        let tile_file = path::Path::new("/tile.png");
        assert!(f.exists(tile_file));
        assert!(f.is_file(tile_file));

        let tile_file = path::Path::new("/oglebog.png");
        assert!(!f.exists(tile_file));
        assert!(!f.is_file(tile_file));
        assert!(!f.is_dir(tile_file));

    }

    #[test]
    fn test_read_dir() {
        let mut f = get_dummy_fs_for_tests();

        //let dir_contents_size = f.read_dir().unwrap().len();
        //assert!(dir_contents_size > 0);
    }

    #[test]
    fn test_create_delete_file() {
        let mut fs = get_dummy_fs_for_tests();
        let test_file = path::Path::new("/testfile.txt");
        let bytes = "test".as_bytes();

        {
            let mut file = fs.create(test_file).unwrap();
            file.write(bytes).unwrap();
        }
        {
            let mut buffer = Vec::new();
            let mut file = fs.open(test_file).unwrap();
            file.read_to_end(&mut buffer).unwrap();
            assert_eq!(bytes, buffer.as_slice());
        }

        fs.delete(test_file).unwrap();
    }

    #[test]
    fn test_file_not_found() {
        let mut fs = get_dummy_fs_for_tests();
        {
            let rel_file = "testfile.txt";
            match fs.open(rel_file) {
                Err(GameError::ResourceNotFound(_, _)) => (),
                Err(e) => panic!("Invalid error for opening file with relative path: {:?}", e),
                Ok(f) => panic!("Should have gotten an error but instead got {:?}!", f),
            }
        }

        {
            // This absolute path should work on Windows too since we
            // completely remove filesystem roots.
            match fs.open("/ooglebooglebarg.txt") {
                Err(GameError::ResourceNotFound(_, _)) => (),
                Err(e) => panic!("Invalid error for opening nonexistent file: {}", e),
                Ok(f) => panic!("Should have gotten an error but instead got {:?}", f),
            }
        }
    }

    #[test]
    fn test_write_config() {
        let mut f = get_dummy_fs_for_tests();
        let mut conf = conf::Conf::new();
        // The config file should end up in
        // the resources directory with this
        match f.write_config(&conf) {
            Ok(f) => (),
            Err(e) => panic!("{:?}", e),
        }
        // Remove the config file!
        f.delete(CONFIG_NAME);
    }
}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;

use serde_json::value::Value;

use priority::TaskPriority;

pub type DateTime= String; // FIXME
pub type Project = String;
pub type Status  = String;
pub type Tag     = String;
pub type UUID    = String; // FIXME
pub type Urgency = f64;

#[derive(Debug, Clone)]
pub struct Task {
    id: u64,
    desc: String,
    entry: DateTime,
    modified: Option<DateTime>,
    priority: Option<TaskPriority>,
    project: Project,
    status: Status,
    tags: Vec<Tag>,
    uuid: UUID,
    urgency: Urgency,
}

/*
 * TODO: We do not fail if the JSON parsing fails. This panics. We rely on taskwarrior to be nice
 * to us. I guess this should be fixed.
 */
impl Task {

    pub fn new(id: u64,
                desc: String,
                entry: DateTime,
                modified: Option<DateTime>,
                priority: Option<TaskPriority>,
                project: Project,
                status: Status,
                tags: Vec<Tag>,
                uuid: UUID,
                urgency: Urgency)
        -> Task
    {
        Task {
            id       : id,
            desc     : desc,
            entry    : entry,
            modified : modified,
            priority : priority,
            project  : project,
            status   : status,
            tags     : tags,
            uuid     : uuid,
            urgency  : urgency,
        }
    }

    pub fn from_value(v: Value) -> Option<Task> {
        if !v.is_object() {
            return None;
        }

        let map = v.as_object().unwrap();

        let keys = [ "id", "description", "entry", "status", "uuid", "urgency"];

        if keys.iter().any(|x| map.contains_key(*x)) {
            return None
        }

        Some(Task {
            id       : get_id(&map),
            desc     : get_desc(&map),
            entry    : get_entry(&map),
            modified : get_modified(&map),
            priority : get_priority(&map),
            project  : get_project(&map),
            status   : get_status(&map),
            tags     : get_tags(&map),
            uuid     : get_uuid(&map),
            urgency  : get_urgency(&map),
        })
    }

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

    pub fn desc(&self) -> &String {
        &self.desc
    }

    pub fn entry(&self) -> &DateTime {
        &self.entry
    }

    pub fn modified(&self) -> Option<&DateTime> {
        self.modified.as_ref()
    }

    pub fn priority(&self) -> Option<&TaskPriority> {
        self.priority.as_ref()
    }

    pub fn project(&self) -> &Project {
        &self.project
    }

    pub fn status(&self) -> &Status {
        &self.status
    }

    pub fn tags(&self) -> &Vec<Tag> {
        &self.tags
    }

    pub fn uuid(&self) -> &UUID {
        &self.uuid
    }

    pub fn urgency(&self) -> Urgency {
        self.urgency
    }

}

impl Into<Value> for Task {

    fn into(self) -> Value {
        let id       = Value::U64(self.id);
        let desc     = Value::String(self.desc);
        let entry    = Value::String(self.entry);
        let project  = Value::String(self.project);
        let status   = Value::String(self.status);
        let tags     = Value::Array(self.tags
                                        .into_iter()
                                        .map(|s| Value::String(String::from(s)))
                                        .collect());
        let uuid     = Value::String(self.uuid);
        let urgency  = Value::F64(self.urgency);

        let mut map = BTreeMap::new();
        map.insert(String::from("id")       , id);
        map.insert(String::from("desc")     , desc);
        map.insert(String::from("entry")    , entry);
        if self.modified.is_some() {
            map.insert(String::from("modified") , Value::String(self.modified.unwrap()));
        }
        if self.priority.is_some() {
            map.insert(String::from("priority") , Value::String(self.priority.unwrap().into()));
        }
        map.insert(String::from("project")  , project);
        map.insert(String::from("status")   , status);
        map.insert(String::from("tags")     , tags);
        map.insert(String::from("uuid")     , uuid);
        map.insert(String::from("urgency")  , urgency);

        Value::Object(map)
    }

}

fn get_id(map: &BTreeMap<String, Value>) -> u64 {
    map.get("id").unwrap().as_u64().unwrap()
}

fn get_desc(map: &BTreeMap<String, Value>) -> String {
    map.get("description").unwrap().as_string().map(String::from).unwrap()
}

fn get_entry(map: &BTreeMap<String, Value>) -> String {
    map.get("entry").unwrap().as_string().map(String::from).unwrap()
}

fn get_modified(map: &BTreeMap<String, Value>) -> Option<String> {
    map.get("modified")
        .map(|m| m.as_string().map(String::from).unwrap())
}

fn get_priority(map: &BTreeMap<String, Value>) -> Option<TaskPriority> {
    map.get("priority")
        .map(|m| m.as_string().map(String::from).unwrap())
        .map(|s| TaskPriority::from(&s[..]))
}

fn get_project(map: &BTreeMap<String, Value>) -> String {
    map.get("project").unwrap().as_string().map(String::from).unwrap()
}

fn get_status(map: &BTreeMap<String, Value>) -> String {
    map.get("status").unwrap().as_string().map(String::from).unwrap()
}

fn get_tags(map: &BTreeMap<String, Value>) -> Vec<Tag> {
    map.get("tags")
        .unwrap()
        .as_array()
        .unwrap()
        .clone()
        .into_iter()
        .map(|item| item.as_string().map(String::from).unwrap())
        .collect()
}

fn get_uuid(map: &BTreeMap<String, Value>) -> String {
    map.get("uuid").unwrap().as_string().map(String::from).unwrap()
}

fn get_urgency(map: &BTreeMap<String, Value>) -> f64 {
    map.get("urgency").unwrap().as_f64().unwrap()
}

#[cfg(test)]
mod test {
    use core::reader::Reader;
    use core::reader::JsonObjectReader;
    use super::Task;
    use priority::TaskPriority;
    use std::borrow::Borrow;

    #[test]
    fn test_from_json() {
        let json = String::from("{\"id\":1,\"description\":\"desc\",\"entry\":\"20150612T164806Z\",\"modified\":\"20160315T215656Z\",\"priority\":\"L\",\"project\":\"someproj\",\"status\":\"pending\",\"tags\":[\"test\",\"task\"],\"uuid\":\"93cfc5fa-2f0c-44e6-bede-c2b1ca7ceff3\",\"urgency\":1.0}");
        let bytes = json.into_bytes();
        let mut reader = JsonObjectReader::new(Reader::new(bytes.borrow()));

        let v = reader.next();
        assert!(v.is_some());
        let v = v.unwrap();

        let t = Task::from_value(v);
        assert!(t.is_some());
        let t = t.unwrap();

        assert_eq!(t.id(), 1);
        assert_eq!(t.desc().clone(), String::from("desc"));
        assert_eq!(t.entry().clone(), String::from("20150612T164806Z"));
        assert_eq!(t.modified().clone(), Some(String::from("20160315T215656Z")));
        assert_eq!(t.priority(), TaskPriority::Low);
        assert_eq!(t.project().clone(), String::from("someproj"));
        assert_eq!(t.status().clone(), String::from("pending"));
        for n in ["test", "task"].iter() {
            assert!(t.tags().contains(&String::from(*n)));
        }
        assert_eq!(t.uuid().clone(), String::from("93cfc5fa-2f0c-44e6-bede-c2b1ca7ceff3"));
        assert_eq!(t.urgency() as u64, 1.0 as u64);
    }

}
<commit_msg>Make task project optional<commit_after>use std::collections::BTreeMap;

use serde_json::value::Value;

use priority::TaskPriority;

pub type DateTime= String; // FIXME
pub type Project = String;
pub type Status  = String;
pub type Tag     = String;
pub type UUID    = String; // FIXME
pub type Urgency = f64;

#[derive(Debug, Clone)]
pub struct Task {
    id: u64,
    desc: String,
    entry: DateTime,
    modified: Option<DateTime>,
    priority: Option<TaskPriority>,
    project: Option<Project>,
    status: Status,
    tags: Vec<Tag>,
    uuid: UUID,
    urgency: Urgency,
}

/*
 * TODO: We do not fail if the JSON parsing fails. This panics. We rely on taskwarrior to be nice
 * to us. I guess this should be fixed.
 */
impl Task {

    pub fn new(id: u64,
                desc: String,
                entry: DateTime,
                modified: Option<DateTime>,
                priority: Option<TaskPriority>,
                project: Option<Project>,
                status: Status,
                tags: Vec<Tag>,
                uuid: UUID,
                urgency: Urgency)
        -> Task
    {
        Task {
            id       : id,
            desc     : desc,
            entry    : entry,
            modified : modified,
            priority : priority,
            project  : project,
            status   : status,
            tags     : tags,
            uuid     : uuid,
            urgency  : urgency,
        }
    }

    pub fn from_value(v: Value) -> Option<Task> {
        if !v.is_object() {
            return None;
        }

        let map = v.as_object().unwrap();

        let keys = [ "id", "description", "entry", "status", "uuid", "urgency"];

        if keys.iter().any(|x| map.contains_key(*x)) {
            return None
        }

        Some(Task {
            id       : get_id(&map),
            desc     : get_desc(&map),
            entry    : get_entry(&map),
            modified : get_modified(&map),
            priority : get_priority(&map),
            project  : get_project(&map),
            status   : get_status(&map),
            tags     : get_tags(&map),
            uuid     : get_uuid(&map),
            urgency  : get_urgency(&map),
        })
    }

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

    pub fn desc(&self) -> &String {
        &self.desc
    }

    pub fn entry(&self) -> &DateTime {
        &self.entry
    }

    pub fn modified(&self) -> Option<&DateTime> {
        self.modified.as_ref()
    }

    pub fn priority(&self) -> Option<&TaskPriority> {
        self.priority.as_ref()
    }

    pub fn project(&self) -> Option<&Project> {
        self.project.as_ref()
    }

    pub fn status(&self) -> &Status {
        &self.status
    }

    pub fn tags(&self) -> &Vec<Tag> {
        &self.tags
    }

    pub fn uuid(&self) -> &UUID {
        &self.uuid
    }

    pub fn urgency(&self) -> Urgency {
        self.urgency
    }

}

impl Into<Value> for Task {

    fn into(self) -> Value {
        let id       = Value::U64(self.id);
        let desc     = Value::String(self.desc);
        let entry    = Value::String(self.entry);
        let status   = Value::String(self.status);
        let tags     = Value::Array(self.tags
                                        .into_iter()
                                        .map(|s| Value::String(String::from(s)))
                                        .collect());
        let uuid     = Value::String(self.uuid);
        let urgency  = Value::F64(self.urgency);

        let mut map = BTreeMap::new();
        map.insert(String::from("id")       , id);
        map.insert(String::from("desc")     , desc);
        map.insert(String::from("entry")    , entry);
        if self.modified.is_some() {
            map.insert(String::from("modified") , Value::String(self.modified.unwrap()));
        }
        if self.priority.is_some() {
            map.insert(String::from("priority") , Value::String(self.priority.unwrap().into()));
        }
        if self.project.is_some() {
            map.insert(String::from("project"), Value::String(self.project.unwrap()));
        }
        map.insert(String::from("status")   , status);
        map.insert(String::from("tags")     , tags);
        map.insert(String::from("uuid")     , uuid);
        map.insert(String::from("urgency")  , urgency);

        Value::Object(map)
    }

}

fn get_id(map: &BTreeMap<String, Value>) -> u64 {
    map.get("id").unwrap().as_u64().unwrap()
}

fn get_desc(map: &BTreeMap<String, Value>) -> String {
    map.get("description").unwrap().as_string().map(String::from).unwrap()
}

fn get_entry(map: &BTreeMap<String, Value>) -> String {
    map.get("entry").unwrap().as_string().map(String::from).unwrap()
}

fn get_modified(map: &BTreeMap<String, Value>) -> Option<String> {
    map.get("modified")
        .map(|m| m.as_string().map(String::from).unwrap())
}

fn get_priority(map: &BTreeMap<String, Value>) -> Option<TaskPriority> {
    map.get("priority")
        .map(|m| m.as_string().map(String::from).unwrap())
        .map(|s| TaskPriority::from(&s[..]))
}

fn get_project(map: &BTreeMap<String, Value>) -> Option<String> {
    map.get("project")
        .map(|p| p.as_string().map(String::from).unwrap())
}

fn get_status(map: &BTreeMap<String, Value>) -> String {
    map.get("status").unwrap().as_string().map(String::from).unwrap()
}

fn get_tags(map: &BTreeMap<String, Value>) -> Vec<Tag> {
    map.get("tags")
        .unwrap()
        .as_array()
        .unwrap()
        .clone()
        .into_iter()
        .map(|item| item.as_string().map(String::from).unwrap())
        .collect()
}

fn get_uuid(map: &BTreeMap<String, Value>) -> String {
    map.get("uuid").unwrap().as_string().map(String::from).unwrap()
}

fn get_urgency(map: &BTreeMap<String, Value>) -> f64 {
    map.get("urgency").unwrap().as_f64().unwrap()
}

#[cfg(test)]
mod test {
    use core::reader::Reader;
    use core::reader::JsonObjectReader;
    use super::Task;
    use priority::TaskPriority;
    use std::borrow::Borrow;

    #[test]
    fn test_from_json() {
        let json = String::from("{\"id\":1,\"description\":\"desc\",\"entry\":\"20150612T164806Z\",\"modified\":\"20160315T215656Z\",\"priority\":\"L\",\"project\":\"someproj\",\"status\":\"pending\",\"tags\":[\"test\",\"task\"],\"uuid\":\"93cfc5fa-2f0c-44e6-bede-c2b1ca7ceff3\",\"urgency\":1.0}");
        let bytes = json.into_bytes();
        let mut reader = JsonObjectReader::new(Reader::new(bytes.borrow()));

        let v = reader.next();
        assert!(v.is_some());
        let v = v.unwrap();

        let t = Task::from_value(v);
        assert!(t.is_some());
        let t = t.unwrap();

        assert_eq!(t.id(), 1);
        assert_eq!(t.desc().clone(), String::from("desc"));
        assert_eq!(t.entry().clone(), String::from("20150612T164806Z"));
        assert_eq!(t.modified().clone(), Some(String::from("20160315T215656Z")));
        assert_eq!(t.priority(), TaskPriority::Low);
        assert_eq!(t.project().clone(), String::from("someproj"));
        assert_eq!(t.status().clone(), String::from("pending"));
        for n in ["test", "task"].iter() {
            assert!(t.tags().contains(&String::from(*n)));
        }
        assert_eq!(t.uuid().clone(), String::from("93cfc5fa-2f0c-44e6-bede-c2b1ca7ceff3"));
        assert_eq!(t.urgency() as u64, 1.0 as u64);
    }

}
<|endoftext|>


<commit_before>use read_token;
use range::Range;
use std::rc::Rc;

use {
    MetaData,
    MetaReader,
    ParseError,
};

/// Stores information about text.
pub struct Text {
    /// Whether to allow empty string.
    pub allow_empty: bool,
    /// Which property to set if text is read.
    pub property: Option<Rc<String>>,
}

impl Text {
    /// Parses text.
    pub fn parse<M>(
        &self,
        meta_reader: &mut M,
        state: &M::State,
        chars: &[char],
        offset: usize
    ) -> Result<(Range, M::State), (Range, ParseError)>
        where M: MetaReader
    {
        if let Some(range) = read_token::string(chars, offset) {
            if !self.allow_empty && range.length == 2 {
                Err((range, ParseError::EmptyTextNotAllowed))
            } else {
                match read_token::parse_string(
                    chars, offset, range.next_offset()) {
                    // Focus range to invalid string format.
                    Err(err) => Err((err.range(),
                        ParseError::ParseStringError(err))),
                    Ok(text) => {
                        if let Some(ref property) = self.property {
                            match meta_reader.data(
                                MetaData::String(property.clone(), text),
                                state,
                                range
                            ) {
                                Err(err) => Err((range, err)),
                                Ok(state) => Ok((range, state)),
                            }
                        } else {
                            Ok((range, state.clone()))
                        }
                    }
                }
            }
        } else {
            Err((Range::new(offset, 0), ParseError::ExpectedText))
        }
    }
}
<commit_msg>Added unit tests for `Text`<commit_after>use read_token;
use range::Range;
use std::rc::Rc;

use {
    MetaData,
    MetaReader,
    ParseError,
};

/// Stores information about text.
pub struct Text {
    /// Whether to allow empty string.
    pub allow_empty: bool,
    /// Which property to set if text is read.
    pub property: Option<Rc<String>>,
}

impl Text {
    /// Parses text.
    pub fn parse<M>(
        &self,
        meta_reader: &mut M,
        state: &M::State,
        chars: &[char],
        offset: usize
    ) -> Result<(Range, M::State), (Range, ParseError)>
        where M: MetaReader
    {
        if let Some(range) = read_token::string(chars, offset) {
            if !self.allow_empty && range.length == 2 {
                Err((range, ParseError::EmptyTextNotAllowed))
            } else {
                match read_token::parse_string(
                    chars, offset, range.next_offset()) {
                    // Focus range to invalid string format.
                    Err(err) => Err((err.range(),
                        ParseError::ParseStringError(err))),
                    Ok(text) => {
                        if let Some(ref property) = self.property {
                            match meta_reader.data(
                                MetaData::String(property.clone(), text),
                                state,
                                range
                            ) {
                                Err(err) => Err((range, err)),
                                Ok(state) => Ok((range, state)),
                            }
                        } else {
                            Ok((range, state.clone()))
                        }
                    }
                }
            }
        } else {
            Err((Range::new(offset, 0), ParseError::ExpectedText))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::super::*;
    use range::Range;
    use std::rc::Rc;

    #[test]
    fn expected_text() {
        let text = "23";
        let chars: Vec<char> = text.chars().collect();
        let mut tokenizer = Tokenizer::new();
        let s = TokenizerState::new();
        let text = Text {
            allow_empty: true,
            property: None
        };
        let res = text.parse(&mut tokenizer, &s, &chars, 0);
        assert_eq!(res, Err((Range::new(0, 0), ParseError::ExpectedText)));
    }

    #[test]
    fn empty_string() {
        let text = "\"\"";
        let chars: Vec<char> = text.chars().collect();
        let mut tokenizer = Tokenizer::new();
        let s = TokenizerState::new();
        let text = Text {
            allow_empty: false,
            property: None
        };
        let res = text.parse(&mut tokenizer, &s, &chars, 0);
        assert_eq!(res, Err((Range::new(0, 2), ParseError::EmptyTextNotAllowed)));
    }

    #[test]
    fn successful() {
        let text = "foo \"hello\"";
        let chars: Vec<char> = text.chars().collect();
        let mut tokenizer = Tokenizer::new();
        let s = TokenizerState::new();
        let foo: Rc<String> = Rc::new("foo".into());
        let text = Text {
            allow_empty: true,
            property: Some(foo.clone())
        };
        let res = text.parse(&mut tokenizer, &s, &chars[4..], 4);
        assert_eq!(res, Ok((Range::new(4, 7), TokenizerState(1))));
        assert_eq!(tokenizer.tokens.len(), 1);
        assert_eq!(&tokenizer.tokens[0].0,
            &MetaData::String(foo.clone(), "hello".into()));
    }
}
<|endoftext|>


<commit_before>pub struct Trie {
    vec: Vec<(char, Box<Trie>)>,
    word: bool,
}

impl Trie {
    pub fn new() -> Trie {
        Trie {
            vec: vec![],
            word: false,
        }
    }

    pub fn clear(&mut self) {
        self.vec.clear();
        self.word = false;
    }

    pub fn insert(&mut self, str: &str) {
        let mut trie_ptr: *mut Trie = &mut *self;
        for char in str.chars() {
            trie_ptr = get_char_node_for_insert(trie_ptr, char);
        }

        unsafe {
            (*trie_ptr).word = true;
        }
    }

    pub fn contains(&self, str: &str) -> bool {
        let mut trie = self;
        for char in str.chars() {
            if let Some(trie_) = get_char_node_for_lookup(trie, char) {
                trie = trie_;
            } else {
                return false;
            }
        }
        trie.word
    }

    // TODO: This needs garbage collection. We may want to trim the branches
    // that doesn't have any words.
    pub fn remove(&mut self, str: &str) {
        let mut chars = str.chars();
        if let Some(char) = chars.next() {
            if let Ok(idx) = self.vec.binary_search_by(|&(char_, _)| char_.cmp(&char)) {
                self.vec[idx].1.remove(chars.as_str());
            }
        } else {
            self.word = false;
        }
    }

    // TODO: We need an Iterator instance instead.
    pub fn to_strings(&self, prefix: &str) -> Vec<String> {
        let mut ret = {
            if self.word {
                vec![prefix.to_owned()]
            } else {
                vec![]
            }
        };

        for &(c, ref t) in &self.vec {
            let mut prefix_ = prefix.to_owned();
            prefix_.push(c);
            ret.extend(t.to_strings(&prefix_));
        }

        ret
    }

    // TODO: We need an Iterator instance instead.
    pub fn drop_pfx(&self, prefix: &mut Iterator<Item = char>) -> Vec<String> {
        let mut trie = self;
        for char in prefix {
            if let Some(trie_) = get_char_node_for_lookup(trie, char) {
                trie = trie_;
            } else {
                return vec![];
            }
        }
        trie.to_strings("")
    }
}

fn get_char_node_for_insert(trie: *mut Trie, char: char) -> *mut Trie {
    let trie_ref: &mut Trie = unsafe { &mut *trie };
    match trie_ref
        .vec
        .binary_search_by(|&(char_, _)| char_.cmp(&char))
    {
        Ok(idx) =>
            &mut *trie_ref.vec[idx].1,
        Err(idx) => {
            trie_ref.vec.insert(
                idx,
                (
                    char,
                    Box::new(Trie {
                        vec: vec![],
                        word: false,
                    }),
                ),
            );
            &mut *trie_ref.vec[idx].1
        }
    }
}

fn get_char_node_for_lookup(trie: &Trie, char: char) -> Option<&Trie> {
    match trie.vec.binary_search_by(|&(char_, _)| char_.cmp(&char)) {
        Ok(idx) =>
            Some(&trie.vec[idx].1),
        Err(_) =>
            None,
    }
}

#[cfg(test)]
mod tests {

    extern crate test;

    use super::*;

    #[test]
    fn trie_test_1() {
        let mut trie = Trie::new();
        trie.insert("yada yada");
        assert_eq!(vec!["yada yada"], trie.to_strings(""));
    }

    #[test]
    fn trie_test_2() {
        let mut trie = Trie::new();
        trie.insert("foo");
        assert!(trie.contains("foo"));
        trie.insert("bar");
        assert!(trie.contains("foo"));
        assert!(trie.contains("bar"));
        trie.insert("baz");
        assert!(trie.contains("foo"));
        assert!(trie.contains("bar"));
        assert!(trie.contains("baz"));
        assert_eq!(vec!["bar", "baz", "foo"], trie.to_strings(""));
    }

    #[test]
    fn trie_test_3() {
        let mut trie = Trie::new();
        trie.insert("foo");
        trie.insert("bar");
        trie.insert("baz");
        assert_eq!(vec!["ar", "az"], trie.drop_pfx(&mut "b".chars()));
    }

    #[test]
    fn trie_test_insert_remove() {
        let mut trie = Trie::new();
        trie.insert("foo");
        trie.insert("bar");
        trie.insert("baz");
        trie.remove("bar");
        assert!(trie.contains("foo"));
        assert!(!trie.contains("bar"));
        assert!(trie.contains("baz"));
        assert_eq!(vec!["az"], trie.drop_pfx(&mut "b".chars()));
    }

} // tests

#[cfg(test)]
mod benchs {

    extern crate test;

    use self::test::Bencher;
    use std::fs::File;
    use std::io::Read;
    use super::*;

    #[bench]
    fn bench_trie_build(b: &mut Bencher) {
        // Total words: 305,089
        // 117,701,680 ns (0.1 seconds)
        // (before reversing the list: 116,795,268 ns (0.1 seconds))

        let mut contents = String::new();
        let mut words: Vec<&str> = vec![];
        {
            match File::open("/usr/share/dict/american") {
                Err(_) => {
                    println!("Can't open dictionary file, aborting benchmark.");
                    return;
                }
                Ok(mut file) => {
                    file.read_to_string(&mut contents).unwrap();
                    words.extend(contents.lines());
                }
            }
        }

        b.iter(|| {
            let mut trie = Trie::new();
            // Note that we insert the words in reverse order here. Since the
            // dictionary is already sorted, we end up benchmarking the best case.
            // Since that best case is never really happens in practice, the number
            // is practically useless. Worst case is at least giving an upper bound.
            for word in words.iter().rev() {
                trie.insert(word);
            }
            trie
        });
    }

    /*
#[bench]
fn bench_hashset_build(b : &mut Bencher) {

    // Total words: 305,089
    // 40,292,006 ns (0.04 seconds)

    use std::collections::HashSet;

    let mut contents = String::new();
    let mut words : Vec<&str> = vec![];
    {
        let mut file = File::open("/usr/share/dict/american").unwrap();
        file.read_to_string(&mut contents).unwrap();
        words.extend(contents.lines());
    }

    b.iter(|| {
        let mut set = HashSet::new();
        for word in words.iter() {
            set.insert(word);
        }
        set
    });
}
*/

    #[bench]
    fn bench_trie_lookup(b: &mut Bencher) {
        // Total:     305,089 words
        // Returning:     235 words
        // 140,717 ns (0.14 ms)

        let mut contents = String::new();
        let mut words: Vec<&str> = vec![];
        {
            match File::open("/usr/share/dict/american") {
                Err(_) => {
                    println!("Can't open dictionary file, aborting benchmark.");
                    return;
                }
                Ok(mut file) => {
                    file.read_to_string(&mut contents).unwrap();
                    words.extend(contents.lines());
                }
            }
        }

        let mut trie = Trie::new();
        for word in words {
            trie.insert(word);
        }

        b.iter(|| trie.drop_pfx(&mut "abs".chars()));
    }

    #[bench]
    fn bench_trie_list_all(b: &mut Bencher) {
        // Total:     305,089 words
        // Returning: 305,089 words
        // 205,946,060 ns (0.2 s)

        let mut contents = String::new();
        let mut words: Vec<&str> = vec![];
        {
            match File::open("/usr/share/dict/american") {
                Err(_) => {
                    println!("Can't open dictionary file, aborting benchmark.");
                    return;
                }
                Ok(mut file) => {
                    file.read_to_string(&mut contents).unwrap();
                    words.extend(contents.lines());
                }
            }
        }

        let mut trie = Trie::new();
        for word in words {
            trie.insert(word);
        }

        b.iter(|| trie.drop_pfx(&mut "".chars()));
    }

} // benchs
<commit_msg>Trim brnaches that does not have any words<commit_after>pub struct Trie {
    vec: Vec<(char, Box<Trie>)>,
    word: bool,
}

impl Trie {
    pub fn new() -> Trie {
        Trie {
            vec: vec![],
            word: false,
        }
    }

    pub fn clear(&mut self) {
        self.vec.clear();
        self.word = false;
    }

    pub fn insert(&mut self, str: &str) {
        let mut trie_ptr: *mut Trie = &mut *self;
        for char in str.chars() {
            trie_ptr = get_char_node_for_insert(trie_ptr, char);
        }

        unsafe {
            (*trie_ptr).word = true;
        }
    }

    pub fn contains(&self, str: &str) -> bool {
        let mut trie = self;
        for char in str.chars() {
            if let Some(trie_) = get_char_node_for_lookup(trie, char) {
                trie = trie_;
            } else {
                return false;
            }
        }
        trie.word
    }

    pub fn remove(&mut self, str: &str) {
        let mut chars = str.chars();
        if let Some(char) = chars.next() {
            if let Ok(idx) = self.vec.binary_search_by(|&(char_, _)| char_.cmp(&char)) {
                let del = {
                    let trie = &mut self.vec[idx].1;
                    trie.remove(chars.as_str());
                    !trie.word && trie.vec.len() == 0
                };
                if del {
                    self.vec.remove(idx);
                };
            }
        } else {
            self.word = false;
        }
    }

    // TODO: We need an Iterator instance instead.
    pub fn to_strings(&self, prefix: &str) -> Vec<String> {
        let mut ret = {
            if self.word {
                vec![prefix.to_owned()]
            } else {
                vec![]
            }
        };

        for &(c, ref t) in &self.vec {
            let mut prefix_ = prefix.to_owned();
            prefix_.push(c);
            ret.extend(t.to_strings(&prefix_));
        }

        ret
    }

    // TODO: We need an Iterator instance instead.
    pub fn drop_pfx(&self, prefix: &mut Iterator<Item = char>) -> Vec<String> {
        let mut trie = self;
        for char in prefix {
            if let Some(trie_) = get_char_node_for_lookup(trie, char) {
                trie = trie_;
            } else {
                return vec![];
            }
        }
        trie.to_strings("")
    }
}

fn get_char_node_for_insert(trie: *mut Trie, char: char) -> *mut Trie {
    let trie_ref: &mut Trie = unsafe { &mut *trie };
    match trie_ref
        .vec
        .binary_search_by(|&(char_, _)| char_.cmp(&char))
    {
        Ok(idx) =>
            &mut *trie_ref.vec[idx].1,
        Err(idx) => {
            trie_ref.vec.insert(
                idx,
                (
                    char,
                    Box::new(Trie {
                        vec: vec![],
                        word: false,
                    }),
                ),
            );
            &mut *trie_ref.vec[idx].1
        }
    }
}

fn get_char_node_for_lookup(trie: &Trie, char: char) -> Option<&Trie> {
    match trie.vec.binary_search_by(|&(char_, _)| char_.cmp(&char)) {
        Ok(idx) =>
            Some(&trie.vec[idx].1),
        Err(_) =>
            None,
    }
}

#[cfg(test)]
mod tests {

    extern crate test;

    use super::*;

    #[test]
    fn trie_test_1() {
        let mut trie = Trie::new();
        trie.insert("yada yada");
        assert_eq!(vec!["yada yada"], trie.to_strings(""));
    }

    #[test]
    fn trie_test_2() {
        let mut trie = Trie::new();
        trie.insert("foo");
        assert!(trie.contains("foo"));
        trie.insert("bar");
        assert!(trie.contains("foo"));
        assert!(trie.contains("bar"));
        trie.insert("baz");
        assert!(trie.contains("foo"));
        assert!(trie.contains("bar"));
        assert!(trie.contains("baz"));
        assert_eq!(vec!["bar", "baz", "foo"], trie.to_strings(""));
    }

    #[test]
    fn trie_test_3() {
        let mut trie = Trie::new();
        trie.insert("foo");
        trie.insert("bar");
        trie.insert("baz");
        assert_eq!(vec!["ar", "az"], trie.drop_pfx(&mut "b".chars()));
    }

    #[test]
    fn trie_test_insert_remove() {
        let mut trie = Trie::new();
        trie.insert("foo");
        trie.insert("bar");
        trie.insert("baz");
        trie.remove("bar");
        assert!(trie.contains("foo"));
        assert!(!trie.contains("bar"));
        assert!(trie.contains("baz"));
        assert_eq!(vec!["az"], trie.drop_pfx(&mut "b".chars()));
    }

} // tests

#[cfg(test)]
mod benchs {

    extern crate test;

    use self::test::Bencher;
    use std::fs::File;
    use std::io::Read;
    use super::*;

    #[bench]
    fn bench_trie_build(b: &mut Bencher) {
        // Total words: 305,089
        // 117,701,680 ns (0.1 seconds)
        // (before reversing the list: 116,795,268 ns (0.1 seconds))

        let mut contents = String::new();
        let mut words: Vec<&str> = vec![];
        {
            match File::open("/usr/share/dict/american") {
                Err(_) => {
                    println!("Can't open dictionary file, aborting benchmark.");
                    return;
                }
                Ok(mut file) => {
                    file.read_to_string(&mut contents).unwrap();
                    words.extend(contents.lines());
                }
            }
        }

        b.iter(|| {
            let mut trie = Trie::new();
            // Note that we insert the words in reverse order here. Since the
            // dictionary is already sorted, we end up benchmarking the best case.
            // Since that best case is never really happens in practice, the number
            // is practically useless. Worst case is at least giving an upper bound.
            for word in words.iter().rev() {
                trie.insert(word);
            }
            trie
        });
    }

    /*
#[bench]
fn bench_hashset_build(b : &mut Bencher) {

    // Total words: 305,089
    // 40,292,006 ns (0.04 seconds)

    use std::collections::HashSet;

    let mut contents = String::new();
    let mut words : Vec<&str> = vec![];
    {
        let mut file = File::open("/usr/share/dict/american").unwrap();
        file.read_to_string(&mut contents).unwrap();
        words.extend(contents.lines());
    }

    b.iter(|| {
        let mut set = HashSet::new();
        for word in words.iter() {
            set.insert(word);
        }
        set
    });
}
*/

    #[bench]
    fn bench_trie_lookup(b: &mut Bencher) {
        // Total:     305,089 words
        // Returning:     235 words
        // 140,717 ns (0.14 ms)

        let mut contents = String::new();
        let mut words: Vec<&str> = vec![];
        {
            match File::open("/usr/share/dict/american") {
                Err(_) => {
                    println!("Can't open dictionary file, aborting benchmark.");
                    return;
                }
                Ok(mut file) => {
                    file.read_to_string(&mut contents).unwrap();
                    words.extend(contents.lines());
                }
            }
        }

        let mut trie = Trie::new();
        for word in words {
            trie.insert(word);
        }

        b.iter(|| trie.drop_pfx(&mut "abs".chars()));
    }

    #[bench]
    fn bench_trie_list_all(b: &mut Bencher) {
        // Total:     305,089 words
        // Returning: 305,089 words
        // 205,946,060 ns (0.2 s)

        let mut contents = String::new();
        let mut words: Vec<&str> = vec![];
        {
            match File::open("/usr/share/dict/american") {
                Err(_) => {
                    println!("Can't open dictionary file, aborting benchmark.");
                    return;
                }
                Ok(mut file) => {
                    file.read_to_string(&mut contents).unwrap();
                    words.extend(contents.lines());
                }
            }
        }

        let mut trie = Trie::new();
        for word in words {
            trie.insert(word);
        }

        b.iter(|| trie.drop_pfx(&mut "".chars()));
    }

} // benchs
<|endoftext|>


<commit_before>use std::ptr::null_mut;
use {
    ffi,
    Bitmap,
    FtResult,
    Glyph,
    GlyphMetrics,
    Outline,
    RenderMode,
    Vector
};

#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct GlyphSlot {
    library_raw: ffi::FT_Library,
    raw: ffi::FT_GlyphSlot
}

impl GlyphSlot {
    pub fn from_raw(library_raw: ffi::FT_Library, raw: ffi::FT_GlyphSlot) -> Self {
        GlyphSlot {
            library_raw: library_raw,
            raw: raw
        }
    }

    pub fn render_glyph(&self, render_mode: RenderMode) -> FtResult<()> {
        let err = unsafe {
            ffi::FT_Render_Glyph(self.raw, render_mode as u32)
        };
        if err == ffi::FT_Err_Ok {
            Ok(())
        } else {
            Err(err.into())
        }
    }

    pub fn get_subglyph_info(&self, sub_index: u32) -> FtResult<(i32, u32, i32, i32, ffi::FT_Matrix)> {
        let mut index = 0;
        let mut flags = 0;
        let mut arg1 = 0;
        let mut arg2 = 0;
        let mut transfrom = ffi::FT_Matrix {
            xx: 0, xy: 0,
            yx: 0, yy: 0
        };
        let err = unsafe {
            ffi::FT_Get_SubGlyph_Info(self.raw, sub_index, &mut index, &mut flags,
                                      &mut arg1, &mut arg2, &mut transfrom)
        };
        if err == ffi::FT_Err_Ok {
            Ok((index, flags, arg1, arg2, transfrom))
        } else {
            Err(err.into())
        }
    }

    pub fn get_glyph(&self) -> FtResult<Glyph> {
        let mut aglyph = null_mut();

        let err = unsafe {
            ffi::FT_Get_Glyph(self.raw, &mut aglyph)
        };
        if err == ffi::FT_Err_Ok {
            Ok(Glyph::from_raw(self.library_raw, aglyph))
        } else {
            Err(err.into())
        }
    }

    pub fn outline(&self) -> Option<Outline> {
        let outline = unsafe { &(*self.raw).outline };
        let format = unsafe { (*self.raw).format };

        if format == ffi::FT_GLYPH_FORMAT_OUTLINE {
            let outline = unsafe {
                Outline::from_raw(outline)
            };
            Some(outline)
        } else {
            None
        }
    }

    #[inline(always)]
    pub fn bitmap(&self) -> Bitmap {
        let bitmap = unsafe { &(*self.raw).bitmap };

        Bitmap::from_raw(bitmap)
    }

    #[inline(always)]
    pub fn bitmap_left(&self) -> i32 {
        unsafe {
            (*self.raw).bitmap_left
        }
    }

    #[inline(always)]
    pub fn bitmap_top(&self) -> i32 {
        unsafe {
            (*self.raw).bitmap_top
        }
    }

    #[inline(always)]
    pub fn advance(&self) -> Vector {
        unsafe {
            (*self.raw).advance
        }
    }

    #[inline(always)]
    pub fn linear_hori_advance(&self) -> ffi::FT_Fixed {
        unsafe {
            (*self.raw).linearHoriAdvance
        }
    }

    #[inline(always)]
    pub fn linear_vert_advance(&self) -> ffi::FT_Fixed {
        unsafe {
            (*self.raw).linearVertAdvance
        }
    }

    #[inline(always)]
    pub fn metrics(&self) -> GlyphMetrics {
        unsafe {
            (*self.raw).metrics
        }
    }

    #[inline(always)]
    pub fn raw(&self) -> &ffi::FT_GlyphSlotRec {
        unsafe {
            &*self.raw
        }
    }
}
<commit_msg>Return 'SubGlyphInfo' struct instead of tuple.<commit_after>use std::ptr::null_mut;
use {
    ffi,
    Bitmap,
    FtResult,
    Glyph,
    GlyphMetrics,
    Outline,
    RenderMode,
    Vector
};

/// A description of a given subglyph returned by `GlyphSlot::get_subglyph_info`
/// function.
#[derive(Copy, Clone)]
pub struct SubGlyphInfo {
    /// The glyph index of the subglyph.
    pub index: i32,
    /// The subglyph flags, see FT_SUBGLYPH_FLAG_XXX.
    pub flags: u32,
    /// The subglyph's first argument (if any).
    pub arg1: i32,
    /// The subglyph's second argument (if any).
    pub arg2: i32,
    /// The subglyph transformation (if any).
    pub transfrom: ffi::FT_Matrix
}

impl Default for SubGlyphInfo {
    fn default() -> Self {
        SubGlyphInfo {
            index: 0,
            flags: 0,
            arg1: 0,
            arg2: 0,
            transfrom: ffi::FT_Matrix { xx: 0, xy: 0, yx: 0, yy: 0 }
        }
    }
}

#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct GlyphSlot {
    library_raw: ffi::FT_Library,
    raw: ffi::FT_GlyphSlot
}

impl GlyphSlot {
    pub fn from_raw(library_raw: ffi::FT_Library, raw: ffi::FT_GlyphSlot) -> Self {
        GlyphSlot {
            library_raw: library_raw,
            raw: raw
        }
    }

    pub fn render_glyph(&self, render_mode: RenderMode) -> FtResult<()> {
        let err = unsafe {
            ffi::FT_Render_Glyph(self.raw, render_mode as u32)
        };
        if err == ffi::FT_Err_Ok {
            Ok(())
        } else {
            Err(err.into())
        }
    }

    pub fn get_subglyph_info(&self, sub_index: u32) -> FtResult<SubGlyphInfo> {
        let mut info = SubGlyphInfo::default();
        let err = unsafe {
            ffi::FT_Get_SubGlyph_Info(self.raw, sub_index, &mut info.index, &mut info.flags,
                                      &mut info.arg1, &mut info.arg2, &mut info.transfrom)
        };
        if err == ffi::FT_Err_Ok {
            Ok(info)
        } else {
            Err(err.into())
        }
    }

    pub fn get_glyph(&self) -> FtResult<Glyph> {
        let mut aglyph = null_mut();

        let err = unsafe {
            ffi::FT_Get_Glyph(self.raw, &mut aglyph)
        };
        if err == ffi::FT_Err_Ok {
            Ok(Glyph::from_raw(self.library_raw, aglyph))
        } else {
            Err(err.into())
        }
    }

    pub fn outline(&self) -> Option<Outline> {
        let outline = unsafe { &(*self.raw).outline };
        let format = unsafe { (*self.raw).format };

        if format == ffi::FT_GLYPH_FORMAT_OUTLINE {
            let outline = unsafe {
                Outline::from_raw(outline)
            };
            Some(outline)
        } else {
            None
        }
    }

    #[inline(always)]
    pub fn bitmap(&self) -> Bitmap {
        let bitmap = unsafe { &(*self.raw).bitmap };

        Bitmap::from_raw(bitmap)
    }

    #[inline(always)]
    pub fn bitmap_left(&self) -> i32 {
        unsafe {
            (*self.raw).bitmap_left
        }
    }

    #[inline(always)]
    pub fn bitmap_top(&self) -> i32 {
        unsafe {
            (*self.raw).bitmap_top
        }
    }

    #[inline(always)]
    pub fn advance(&self) -> Vector {
        unsafe {
            (*self.raw).advance
        }
    }

    #[inline(always)]
    pub fn linear_hori_advance(&self) -> ffi::FT_Fixed {
        unsafe {
            (*self.raw).linearHoriAdvance
        }
    }

    #[inline(always)]
    pub fn linear_vert_advance(&self) -> ffi::FT_Fixed {
        unsafe {
            (*self.raw).linearVertAdvance
        }
    }

    #[inline(always)]
    pub fn metrics(&self) -> GlyphMetrics {
        unsafe {
            (*self.raw).metrics
        }
    }

    #[inline(always)]
    pub fn raw(&self) -> &ffi::FT_GlyphSlotRec {
        unsafe {
            &*self.raw
        }
    }
}
<|endoftext|>


<commit_before>use camera;
use camera::Camera;
use common::*;
use gl;
use gl::types::*;
use id_allocator::IdAllocator;
use mob;
use nalgebra::Vec3;
use shaders::Shaders;
use terrain::terrain_vram_buffers::TerrainVRAMBuffers;
use yaglw::gl_context::GLContext;
use yaglw::vertex_buffer::{GLArray, GLBuffer, GLType, DrawMode, VertexAttribData};
use yaglw::texture::{Texture2D, TextureUnit};
use vertex::{ColoredVertex, TextureVertex};

pub struct View<'a> {
  pub gl: GLContext,
  pub shaders: Shaders<'a>,

  pub terrain_buffers: TerrainVRAMBuffers<'a>,
  pub mob_buffers: mob::MobBuffers<'a>,
  pub hud_triangles: GLArray<'a, ColoredVertex>,
  pub text_triangles: GLArray<'a, TextureVertex>,

  pub misc_texture_unit: TextureUnit,
  pub text_textures: Vec<Texture2D<'a>>,
  pub camera: Camera,
  pub lateral_rotation: f32,
  pub vertical_rotation: f32,
}

impl<'a> View<'a> {
  pub fn new(mut gl: GLContext) -> View<'a> {
    let mut texture_unit_alloc = IdAllocator::new();

    let mut shaders = Shaders::new(&mut gl);

    let terrain_buffers = TerrainVRAMBuffers::new(&mut gl);
    terrain_buffers.bind_glsl_uniforms(
      &mut gl,
      &mut texture_unit_alloc,
      &mut shaders.terrain_shader,
    );

    let mob_buffers = mob::MobBuffers::new(&mut gl, &shaders.mob_shader);

    let buffer = GLBuffer::new(&mut gl, 16 * VERTICES_PER_TRIANGLE as usize);
    let hud_triangles = {
      GLArray::new(
        &mut gl,
        &shaders.hud_color_shader.shader,
        &[
          VertexAttribData { name: "position", size: 3, unit: GLType::Float },
          VertexAttribData { name: "in_color", size: 4, unit: GLType::Float },
        ],
        DrawMode::Triangles,
        buffer,
      )
    };

    let buffer = GLBuffer::new(&mut gl, 8 * VERTICES_PER_TRIANGLE as usize);
    let text_triangles =
      GLArray::new(
        &mut gl,
        &shaders.hud_texture_shader.shader,
        &[
          VertexAttribData { name: "position", size: 3, unit: GLType::Float },
          VertexAttribData { name: "texture_position", size: 2, unit: GLType::Float },
        ],
        DrawMode::Triangles,
        buffer,
      );

    let text_textures = Vec::new();

    let misc_texture_unit = texture_unit_alloc.allocate();

    unsafe {
      gl::FrontFace(gl::CCW);
      gl::CullFace(gl::BACK);
      gl::Enable(gl::CULL_FACE);

      gl::Enable(gl::BLEND);
      gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);

      gl::Enable(gl::LINE_SMOOTH);
      gl::LineWidth(2.5);

      gl::Enable(gl::DEPTH_TEST);
      gl::DepthFunc(gl::LESS);
      gl::ClearDepth(1.0);
    }

    unsafe {
      gl::ActiveTexture(misc_texture_unit.gl_id());
    }

    let texture_in =
      shaders.hud_texture_shader.shader.get_uniform_location("texture_in");
    shaders.hud_texture_shader.shader.use_shader(&mut gl);
    unsafe {
      gl::Uniform1i(texture_in, misc_texture_unit.glsl_id as GLint);
    }

    View {
      gl: gl,
      shaders: shaders,

      terrain_buffers: terrain_buffers,
      mob_buffers: mob_buffers,
      hud_triangles: hud_triangles,
      text_triangles: text_triangles,

      misc_texture_unit: misc_texture_unit,
      text_textures: text_textures,
      camera: {
        let mut camera = Camera::unit();
        // Initialize the projection matrix.
        camera.fov = camera::perspective(3.14/3.0, 4.0/3.0, 0.1, 2048.0);
        camera
      },

      lateral_rotation: 0.0,
      vertical_rotation: 0.0,
    }
  }

  /// Rotate the camera around the y axis, by `r` radians. Positive is counterclockwise.
  pub fn rotate_lateral(&mut self, r: GLfloat) {
    self.lateral_rotation = self.lateral_rotation + r;
    self.camera.rotate(Vec3::new(0.0, 1.0, 0.0), r);
  }

  /// Changes the camera pitch by `r` radians. Positive is up.
  /// Angles that "flip around" (i.e. looking too far up or down)
  /// are sliently rejected.
  pub fn rotate_vertical(&mut self, r: GLfloat) {
    let axis =
      camera::from_axis_angle3(Vec3::new(0.0, 1.0, 0.0), self.lateral_rotation) *
      Vec3::new(1.0, 0.0, 0.0);
    self.camera.rotate(axis, r);
  }
}
<commit_msg>Fixed camera over-rotating vertically.<commit_after>use camera;
use camera::Camera;
use common::*;
use gl;
use gl::types::*;
use id_allocator::IdAllocator;
use mob;
use nalgebra::Vec3;
use shaders::Shaders;
use std::f32::consts::PI;
use terrain::terrain_vram_buffers::TerrainVRAMBuffers;
use yaglw::gl_context::GLContext;
use yaglw::vertex_buffer::{GLArray, GLBuffer, GLType, DrawMode, VertexAttribData};
use yaglw::texture::{Texture2D, TextureUnit};
use vertex::{ColoredVertex, TextureVertex};

pub struct View<'a> {
  pub gl: GLContext,
  pub shaders: Shaders<'a>,

  pub terrain_buffers: TerrainVRAMBuffers<'a>,
  pub mob_buffers: mob::MobBuffers<'a>,
  pub hud_triangles: GLArray<'a, ColoredVertex>,
  pub text_triangles: GLArray<'a, TextureVertex>,

  pub misc_texture_unit: TextureUnit,
  pub text_textures: Vec<Texture2D<'a>>,
  pub camera: Camera,
  pub lateral_rotation: f32,
  pub vertical_rotation: f32,
}

impl<'a> View<'a> {
  pub fn new(mut gl: GLContext) -> View<'a> {
    let mut texture_unit_alloc = IdAllocator::new();

    let mut shaders = Shaders::new(&mut gl);

    let terrain_buffers = TerrainVRAMBuffers::new(&mut gl);
    terrain_buffers.bind_glsl_uniforms(
      &mut gl,
      &mut texture_unit_alloc,
      &mut shaders.terrain_shader,
    );

    let mob_buffers = mob::MobBuffers::new(&mut gl, &shaders.mob_shader);

    let buffer = GLBuffer::new(&mut gl, 16 * VERTICES_PER_TRIANGLE as usize);
    let hud_triangles = {
      GLArray::new(
        &mut gl,
        &shaders.hud_color_shader.shader,
        &[
          VertexAttribData { name: "position", size: 3, unit: GLType::Float },
          VertexAttribData { name: "in_color", size: 4, unit: GLType::Float },
        ],
        DrawMode::Triangles,
        buffer,
      )
    };

    let buffer = GLBuffer::new(&mut gl, 8 * VERTICES_PER_TRIANGLE as usize);
    let text_triangles =
      GLArray::new(
        &mut gl,
        &shaders.hud_texture_shader.shader,
        &[
          VertexAttribData { name: "position", size: 3, unit: GLType::Float },
          VertexAttribData { name: "texture_position", size: 2, unit: GLType::Float },
        ],
        DrawMode::Triangles,
        buffer,
      );

    let text_textures = Vec::new();

    let misc_texture_unit = texture_unit_alloc.allocate();

    unsafe {
      gl::FrontFace(gl::CCW);
      gl::CullFace(gl::BACK);
      gl::Enable(gl::CULL_FACE);

      gl::Enable(gl::BLEND);
      gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);

      gl::Enable(gl::LINE_SMOOTH);
      gl::LineWidth(2.5);

      gl::Enable(gl::DEPTH_TEST);
      gl::DepthFunc(gl::LESS);
      gl::ClearDepth(1.0);
    }

    unsafe {
      gl::ActiveTexture(misc_texture_unit.gl_id());
    }

    let texture_in =
      shaders.hud_texture_shader.shader.get_uniform_location("texture_in");
    shaders.hud_texture_shader.shader.use_shader(&mut gl);
    unsafe {
      gl::Uniform1i(texture_in, misc_texture_unit.glsl_id as GLint);
    }

    View {
      gl: gl,
      shaders: shaders,

      terrain_buffers: terrain_buffers,
      mob_buffers: mob_buffers,
      hud_triangles: hud_triangles,
      text_triangles: text_triangles,

      misc_texture_unit: misc_texture_unit,
      text_textures: text_textures,
      camera: {
        let mut camera = Camera::unit();
        // Initialize the projection matrix.
        camera.fov = camera::perspective(3.14/3.0, 4.0/3.0, 0.1, 2048.0);
        camera
      },

      lateral_rotation: 0.0,
      vertical_rotation: 0.0,
    }
  }

  /// Rotate the camera around the y axis, by `r` radians. Positive is counterclockwise.
  pub fn rotate_lateral(&mut self, r: GLfloat) {
    self.lateral_rotation = self.lateral_rotation + r;
    self.camera.rotate(Vec3::new(0.0, 1.0, 0.0), r);
  }

  /// Changes the camera pitch by `r` radians. Positive is up.
  /// Angles that "flip around" (i.e. looking too far up or down)
  /// are sliently rejected.
  pub fn rotate_vertical(&mut self, r: GLfloat) {
    let new_rotation = self.vertical_rotation + r;

    if new_rotation < -PI / 2.0
    || new_rotation >  PI / 2.0 {
      return
    }

    self.vertical_rotation = new_rotation;

    let axis =
      camera::from_axis_angle3(Vec3::new(0.0, 1.0, 0.0), self.lateral_rotation) *
      Vec3::new(1.0, 0.0, 0.0);
    self.camera.rotate(axis, r);
  }
}
<|endoftext|>


<commit_before>use scene::{BoxedScene, Scene, SceneChangeEvent, SceneName};
use engine::EngineData;
use text_renderer::TextRenderer;
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::surface::Surface;
use std::io::Result;
use std::path::Path;
use std::fs::File;

use uorustlibs::gump::{GumpReader, Gump};

use sdl2::render::{Renderer, Texture, TextureQuery};
use sdl2::rect::Rect;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;

pub struct GumpScene {
    reader: Result<GumpReader<File>>,
    index: u32,
    texture: Option<Texture>,
}

impl GumpScene {
    pub fn new<'a>(renderer: &mut Renderer, engine_data: &mut EngineData<'a>) -> BoxedScene<SceneName, EngineData<'a>> {
        let reader = GumpReader::new(&Path::new("./assets/gumpidx.mul"), &Path::new("./assets/gumpart.mul"));
        let mut scene = Box::new(GumpScene {
            reader: reader,
            index: 0,
            texture: None,
        });
        scene.create_slice(renderer, engine_data);

        scene
    }

    fn create_slice(&mut self, renderer: &mut Renderer, engine_data: &mut EngineData) {
        match self.reader {
            Ok(ref mut reader) => {
                match reader.read_gump(self.index) {
                    Ok(gump) => {
                        let mut dest = Surface::new(1024, 768, PixelFormatEnum::RGBA8888).unwrap();
                        dest.fill_rect(None, Color::RGB(0, 0, 0)).unwrap();
                        let surface = gump.to_surface();
                        surface.blit(None, &mut dest, Some(Rect::new(0, 0, surface.width(), surface.height())));
                        let label = engine_data.text_renderer.create_text(&format!("{}", self.index), Color::RGBA(255, 255, 255, 255));
                        label.blit(None, &mut dest, Some(Rect::new(9, surface.height() as i32 + 16, label.width(), label.height())));
                        self.texture = Some(renderer.create_texture_from_surface(&dest).unwrap());
                    },
                    _ => {
                        let texture = engine_data.text_renderer.create_text_texture(renderer, "Could not create gump", Color::RGBA(255, 255, 255, 255));
                        self.texture = Some(texture);
                    }
                }

                /*for y in 0..MAX_Y {
                    for x in 0..MAX_X {
                        let maybe_tile = reader.read_tile(start + x + (y * MAX_X));
                        let index = start + x + (y * MAX_X);
                        match maybe_tile {
                            Ok(tile) => {
                                let surface = tile.to_surface();
                                surface.blit(None, &mut dest, Some(Rect::new(44 * x as i32, (44 + 16) * y as i32, 44, 44)));
                            },
                            _ => ()
                        };
                        let label = engine_data.text_renderer.create_text(&format!("{}", index), Color::RGBA(255, 255, 255, 255));
                        label.blit(None, &mut dest, Some(Rect::new(44 * x as i32, ((44 + 16) * y as i32) + 44, label.width(), label.height())));
                    }
                }*/

            },
            _ => {
                let texture = engine_data.text_renderer.create_text_texture(renderer, "Could not create slice", Color::RGBA(255, 255, 255, 255));
                self.texture = Some(texture);
            }
        }
    }
}

impl<'a> Scene<SceneName, EngineData<'a>> for GumpScene {
    fn render(&self, renderer: &mut Renderer, engine_data: &mut EngineData) {
        renderer.clear();
        match self.texture {
            Some(ref texture) => {
                renderer.copy(texture, None, None).unwrap();
            },
            None => ()
        };
        renderer.present();
    }

    fn handle_event(&mut self, event: &Event, renderer: &mut Renderer, engine_data: &mut EngineData) -> Option<SceneChangeEvent<SceneName>> {
        match *event {
            Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                Some(SceneChangeEvent::PopScene)
            },
            Event::KeyDown { keycode: Some(Keycode::Left), .. } => {
                if self.index > 0 {
                    self.index -= 1;
                    self.create_slice(renderer, engine_data);
                }
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Right), .. } => {
                self.index += 1;
                self.create_slice(renderer, engine_data);
                None
            },
             _ => None
        }
    }
}
<commit_msg>Easier to read errors for gumps<commit_after>use scene::{BoxedScene, Scene, SceneChangeEvent, SceneName};
use engine::EngineData;
use text_renderer::TextRenderer;
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::surface::Surface;
use std::io::Result;
use std::path::Path;
use std::fs::File;

use uorustlibs::gump::{GumpReader, Gump};

use sdl2::render::{Renderer, Texture, TextureQuery};
use sdl2::rect::Rect;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;

pub struct GumpScene {
    reader: Result<GumpReader<File>>,
    index: u32,
    texture: Option<Texture>,
}

impl GumpScene {
    pub fn new<'a>(renderer: &mut Renderer, engine_data: &mut EngineData<'a>) -> BoxedScene<SceneName, EngineData<'a>> {
        let reader = GumpReader::new(&Path::new("./assets/gumpidx.mul"), &Path::new("./assets/gumpart.mul"));
        let mut scene = Box::new(GumpScene {
            reader: reader,
            index: 0,
            texture: None,
        });
        scene.create_slice(renderer, engine_data);

        scene
    }

    fn create_slice(&mut self, renderer: &mut Renderer, engine_data: &mut EngineData) {
        let mut dest = Surface::new(1024, 768, PixelFormatEnum::RGBA8888).unwrap();
        match self.reader {
            Ok(ref mut reader) => {
                match reader.read_gump(self.index) {
                    Ok(gump) => {
                        dest.fill_rect(None, Color::RGB(0, 0, 0)).unwrap();
                        let surface = gump.to_surface();
                        surface.blit(None, &mut dest, Some(Rect::new(0, 0, surface.width(), surface.height())));
                        let label = engine_data.text_renderer.create_text(&format!("{}", self.index), Color::RGBA(255, 255, 255, 255));
                        label.blit(None, &mut dest, Some(Rect::new(9, surface.height() as i32 + 16, label.width(), label.height())));
                    },
                    _ => {
                        let error = engine_data.text_renderer.create_text("Invalid gump", Color::RGBA(255, 255, 255, 255));
                        error.blit(None, &mut dest, Some(Rect::new(0, 0, error.width(), error.height())));
                        let label = engine_data.text_renderer.create_text(&format!("{}", self.index), Color::RGBA(255, 255, 255, 255));
                        label.blit(None, &mut dest, Some(Rect::new(0, 16, label.width(), label.height())));
                    }
                }
            },
            _ => {
                let error = engine_data.text_renderer.create_text("Could not create slice", Color::RGBA(255, 255, 255, 255));
            }
        }
        self.texture = Some(renderer.create_texture_from_surface(&dest).unwrap());
    }
}

impl<'a> Scene<SceneName, EngineData<'a>> for GumpScene {
    fn render(&self, renderer: &mut Renderer, engine_data: &mut EngineData) {
        renderer.clear();
        match self.texture {
            Some(ref texture) => {
                renderer.copy(texture, None, None).unwrap();
            },
            None => ()
        };
        renderer.present();
    }

    fn handle_event(&mut self, event: &Event, renderer: &mut Renderer, engine_data: &mut EngineData) -> Option<SceneChangeEvent<SceneName>> {
        match *event {
            Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                Some(SceneChangeEvent::PopScene)
            },
            Event::KeyDown { keycode: Some(Keycode::Left), .. } => {
                if self.index > 0 {
                    self.index -= 1;
                    self.create_slice(renderer, engine_data);
                }
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Right), .. } => {
                self.index += 1;
                self.create_slice(renderer, engine_data);
                None
            },
             _ => None
        }
    }
}
<|endoftext|>