<commit_before>use super::btree::{BPlusTree, RTCursor as BPlusTreeCursor};
use super::sstable::*;
use super::*;
use client::AsyncClient;
use itertools::Itertools;
use parking_lot::RwLock;
use ram::segs::MAX_SEGMENT_SIZE;
use std::collections::BTreeSet;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use std::{mem, ptr};

const LEVEL_M_MAX_ELEMENTS_COUNT: usize = LEVEL_M * LEVEL_M * LEVEL_M;
const LEVEL_ELEMENTS_MULTIPLIER: usize = 100;
const LEVEL_DIFF_MULTIPLIER: usize = 10;
const LEVEL_M: usize = super::btree::NUM_KEYS;
const LEVEL_1: usize = LEVEL_M * LEVEL_DIFF_MULTIPLIER;
const LEVEL_2: usize = LEVEL_1 * LEVEL_DIFF_MULTIPLIER;
const LEVEL_3: usize = LEVEL_2 * LEVEL_DIFF_MULTIPLIER;
const LEVEL_4: usize = LEVEL_3 * LEVEL_DIFF_MULTIPLIER;

type LevelTrees = Vec<Box<SSLevelTree>>;

macro_rules! with_levels {
    ($($sym:ident, $level_size:ident;)*) => {
        $(
            type $sym = [EntryKey; $level_size];
            impl_sspage_slice!($sym, EntryKey, $level_size);
        )*

        fn init_lsm_level_trees(neb_client: &Arc<AsyncClient>) -> (LevelTrees, Vec<usize>, Vec<usize>) {
            let mut trees = LevelTrees::new();
            let mut max_elements_list = Vec::new();
            let mut max_elements = LEVEL_M_MAX_ELEMENTS_COUNT;
            let mut page_sizes = Vec::new();
            $(
                max_elements *= LEVEL_ELEMENTS_MULTIPLIER;
                trees.push(box LevelTree::<$sym>::new(neb_client));
                max_elements_list.push(max_elements);
                page_sizes.push($level_size);
                const_assert!($level_size * KEY_SIZE <= MAX_SEGMENT_SIZE);
            )*
            return (trees, max_elements_list, page_sizes);
        }
    };
}

with_levels!{
    L1, LEVEL_1;
    L2, LEVEL_2;
    L3, LEVEL_3;
    L4, LEVEL_4;
}

pub struct LSMTree {
    level_m: BPlusTree,
    trees: LevelTrees,
    // use Vec here for convenience
    max_sizes: Vec<usize>,
    page_sizes: Vec<usize>,
}

unsafe impl Send for LSMTree {}
unsafe impl Sync for LSMTree {}

impl LSMTree {
    pub fn new(neb_client: &Arc<AsyncClient>) -> Arc<Self> {
        let (trees, max_sizes, page_sizes) = init_lsm_level_trees(neb_client);
        let lsm_tree = Arc::new(LSMTree {
            level_m: BPlusTree::new(neb_client),
            trees,
            max_sizes,
            page_sizes,
        });
        let tree_clone = lsm_tree.clone();
        thread::spawn(move || {
            tree_clone.sentinel();
        });
        lsm_tree
    }

    pub fn insert(&self, mut key: EntryKey, id: &Id) -> Result<(), ()> {
        key_with_id(&mut key, id);
        self.level_m.insert(&key).map_err(|e| ())
    }

    pub fn remove(&self, mut key: EntryKey, id: &Id) -> Result<bool, ()> {
        key_with_id(&mut key, id);
        let m_deleted = self.level_m.remove(&key).map_err(|e| ())?;
        let levels_deleted = self
            .trees
            .iter()
            .map(|tree| tree.mark_deleted(&key))
            .collect_vec() // collect here to prevent short circuit
            .into_iter()
            .any(|d| d);
        Ok(m_deleted || levels_deleted)
    }

    pub fn seek(&self, mut key: EntryKey, ordering: Ordering) -> LSMTreeCursor {
        match ordering {
            Ordering::Forward => key_with_id(&mut key, &Id::unit_id()),
            Ordering::Backward => key_with_id(&mut key, &Id::new(::std::u64::MAX, ::std::u64::MAX)),
        };
        let mut cursors = vec![self.level_m.seek(&key, ordering).unwrap()];
        for tree in &self.trees {
            cursors.push(tree.seek(&key, ordering));
        }
        return LSMTreeCursor::new(
            cursors,
            self.trees.iter().map(|t| t.tombstones()).collect_vec(),
        );
    }

    fn sentinel(&self) {
        self.check_and_merge(
            LEVEL_M_MAX_ELEMENTS_COUNT,
            self.page_sizes[0],
            &mut BTreeSet::new(),
            &self.level_m,
            &*self.trees[0],
        );
        for i in 0..self.trees.len() - 1 {
            let upper = &*self.trees[i];
            let lower = &*self.trees[i + 1];
            let upper_tombstons_ref = upper.tombstones();
            let mut upper_tombstones = upper_tombstons_ref.write();
            self.check_and_merge(
                self.max_sizes[i],
                self.page_sizes[i + 1],
                &mut *upper_tombstones,
                upper,
                lower,
            );
        }
        thread::sleep(Duration::from_millis(100));
    }

    fn check_and_merge<U, L>(
        &self,
        max_elements: usize,
        upper_page_size: usize,
        upper_tombstones: &mut TombstonesInner,
        upper_level: &U,
        lower_level: &L,
    ) where
        U: MergeableTree + ?Sized,
        L: SSLevelTree + ?Sized,
    {
        let elements = upper_level.elements();
        if elements > max_elements {
            debug!("upper level have elements {} exceeds {}, start level merging", elements, max_elements);
            let entries_to_merge = upper_page_size;
            let guard = upper_level.prepare_level_merge();
            let mut pages = vec![guard.last_page()];
            let mut collected_entries = pages.last().unwrap().keys().len();
            while collected_entries < entries_to_merge {
                let next_page = pages.last().unwrap().next();
                collected_entries += next_page.keys().len();
                pages.push(next_page);
            }
            let mut entries = vec![];
            for page in &pages {
                entries.append(&mut Vec::from(page.keys()));
            }
            debug!("going to merge {} entries", entries.len());
            lower_level.merge(entries.as_mut_slice(), upper_tombstones);
            guard.remove_pages(pages.iter().map(|p| p.keys()).collect_vec().as_slice())
        }
    }
}

pub struct LSMTreeCursor {
    level_cursors: Vec<Box<Cursor>>,
    tombstones: Vec<Arc<Tombstones>>,
}

impl LSMTreeCursor {
    fn new(cursors: Vec<Box<Cursor>>, tombstones: Vec<Arc<Tombstones>>) -> Self {
        LSMTreeCursor {
            tombstones,
            level_cursors: cursors,
        }
    }

    fn next_candidate(&mut self) -> (bool, usize) {
        let min_tree = self
            .level_cursors
            .iter()
            .enumerate()
            .map(|(i, cursor)| (i, cursor.current()))
            .filter_map(|(i, current)| current.map(|current_val| (i, current_val)))
            .min_by_key(|(i, val)| *val)
            .map(|(id, _)| id);
        if let Some(id) = min_tree {
            let min_has_next = self.level_cursors[id].next();
            if !min_has_next {
                let next = self
                    .level_cursors
                    .iter()
                    .enumerate()
                    .filter_map(|(level, cursor)| cursor.current().map(|key| (id, key)))
                    .min_by_key(|(level, key)| *key);
                if let Some((id, _)) = next {
                    return (true, id);
                }
            } else {
                return (true, id);
            }
        }
        return (false, 0);
    }
}

impl Cursor for LSMTreeCursor {
    fn next(&mut self) -> bool {
        loop {
            let (has_next, candidate_level) = self.next_candidate();
            if !has_next {
                break;
            }
            if candidate_level == 0 {
                return has_next;
            }
            let candidate_key = self.level_cursors[candidate_level].current().unwrap();
            if self.tombstones[candidate_level + 1]
                .read()
                .contains(candidate_key)
            {
                // marked in tombstone, skip
                continue;
            }
            return true;
        }
        return false;
    }

    fn current(&self) -> Option<&EntryKey> {
        self.level_cursors.iter().filter_map(|c| c.current()).min()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::{Cursor as StdCursor};
    use futures::prelude::*;
    use server::NebServer;
    use server::ServerOptions;
    use client;
    use std::env;
    use byteorder::BigEndian;
    use byteorder::WriteBytesExt;

    fn u64_to_slice(n: u64) -> [u8; 8] {
        let mut key_slice = [0u8; 8];
        {
            let mut cursor = StdCursor::new(&mut key_slice[..]);
            cursor.write_u64::<BigEndian>(n);
        };
        key_slice
    }

    #[test]
    pub fn insertions() {
        env_logger::init();
        let server_group = "sstable_index_init";
        let server_addr = String::from("127.0.0.1:5300");
        let server = NebServer::new_from_opts(
            &ServerOptions {
                chunk_count: 1,
                memory_size: 16 * 1024 * 1024 * 1024,
                backup_storage: None,
                wal_storage: None,
            },
            &server_addr,
            &server_group,
        );
        let client = Arc::new(
            client::AsyncClient::new(&server.rpc, &vec![server_addr], server_group).unwrap(),
        );
        client.new_schema_with_id(super::get_schema()).wait();
        let tree = LSMTree::new(&client);
        let num = env::var("LSM_TREE_TEST_ITEMS")
            // this value cannot do anything useful to the test
            // must arrange a long-term test to cover every levels
            .unwrap_or("1000".to_string())
            .parse::<u64>()
            .unwrap();

        for i in 0.. num {
            let id = Id::new(0, i);
            let key_slice = u64_to_slice(i);
            let key = SmallVec::from_slice(&key_slice);
            debug!("insert id: {}", i);
            tree.insert(key, &id).unwrap();
        }

        for i in 0.. num {
            let id = Id::new(0, i);
            let key_slice = u64_to_slice(i);
            let key = SmallVec::from_slice(&key_slice);
            debug!("insert id: {}", i);
            tree.insert(key, &id).unwrap();
        }
    }
}<commit_msg>lsm insertion validation<commit_after>use super::btree::{BPlusTree, RTCursor as BPlusTreeCursor};
use super::sstable::*;
use super::*;
use client::AsyncClient;
use itertools::Itertools;
use parking_lot::RwLock;
use ram::segs::MAX_SEGMENT_SIZE;
use std::collections::BTreeSet;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use std::{mem, ptr};

const LEVEL_M_MAX_ELEMENTS_COUNT: usize = LEVEL_M * LEVEL_M * LEVEL_M;
const LEVEL_ELEMENTS_MULTIPLIER: usize = 100;
const LEVEL_DIFF_MULTIPLIER: usize = 10;
const LEVEL_M: usize = super::btree::NUM_KEYS;
const LEVEL_1: usize = LEVEL_M * LEVEL_DIFF_MULTIPLIER;
const LEVEL_2: usize = LEVEL_1 * LEVEL_DIFF_MULTIPLIER;
const LEVEL_3: usize = LEVEL_2 * LEVEL_DIFF_MULTIPLIER;
const LEVEL_4: usize = LEVEL_3 * LEVEL_DIFF_MULTIPLIER;

type LevelTrees = Vec<Box<SSLevelTree>>;

macro_rules! with_levels {
    ($($sym:ident, $level_size:ident;)*) => {
        $(
            type $sym = [EntryKey; $level_size];
            impl_sspage_slice!($sym, EntryKey, $level_size);
        )*

        fn init_lsm_level_trees(neb_client: &Arc<AsyncClient>) -> (LevelTrees, Vec<usize>, Vec<usize>) {
            let mut trees = LevelTrees::new();
            let mut max_elements_list = Vec::new();
            let mut max_elements = LEVEL_M_MAX_ELEMENTS_COUNT;
            let mut page_sizes = Vec::new();
            $(
                max_elements *= LEVEL_ELEMENTS_MULTIPLIER;
                trees.push(box LevelTree::<$sym>::new(neb_client));
                max_elements_list.push(max_elements);
                page_sizes.push($level_size);
                const_assert!($level_size * KEY_SIZE <= MAX_SEGMENT_SIZE);
            )*
            return (trees, max_elements_list, page_sizes);
        }
    };
}

with_levels!{
    L1, LEVEL_1;
    L2, LEVEL_2;
    L3, LEVEL_3;
    L4, LEVEL_4;
}

pub struct LSMTree {
    level_m: BPlusTree,
    trees: LevelTrees,
    // use Vec here for convenience
    max_sizes: Vec<usize>,
    page_sizes: Vec<usize>,
}

unsafe impl Send for LSMTree {}
unsafe impl Sync for LSMTree {}

impl LSMTree {
    pub fn new(neb_client: &Arc<AsyncClient>) -> Arc<Self> {
        let (trees, max_sizes, page_sizes) = init_lsm_level_trees(neb_client);
        let lsm_tree = Arc::new(LSMTree {
            level_m: BPlusTree::new(neb_client),
            trees,
            max_sizes,
            page_sizes,
        });
        let tree_clone = lsm_tree.clone();
        thread::spawn(move || {
            tree_clone.sentinel();
        });
        lsm_tree
    }

    pub fn insert(&self, mut key: EntryKey, id: &Id) -> Result<(), ()> {
        key_with_id(&mut key, id);
        self.level_m.insert(&key).map_err(|e| ())
    }

    pub fn remove(&self, mut key: EntryKey, id: &Id) -> Result<bool, ()> {
        key_with_id(&mut key, id);
        let m_deleted = self.level_m.remove(&key).map_err(|e| ())?;
        let levels_deleted = self
            .trees
            .iter()
            .map(|tree| tree.mark_deleted(&key))
            .collect_vec() // collect here to prevent short circuit
            .into_iter()
            .any(|d| d);
        Ok(m_deleted || levels_deleted)
    }

    pub fn seek(&self, mut key: EntryKey, ordering: Ordering) -> LSMTreeCursor {
        match ordering {
            Ordering::Forward => key_with_id(&mut key, &Id::unit_id()),
            Ordering::Backward => key_with_id(&mut key, &Id::new(::std::u64::MAX, ::std::u64::MAX)),
        };
        let mut cursors = vec![self.level_m.seek(&key, ordering).unwrap()];
        for tree in &self.trees {
            cursors.push(tree.seek(&key, ordering));
        }
        return LSMTreeCursor::new(
            cursors,
            self.trees.iter().map(|t| t.tombstones()).collect_vec(),
        );
    }

    fn sentinel(&self) {
        self.check_and_merge(
            LEVEL_M_MAX_ELEMENTS_COUNT,
            self.page_sizes[0],
            &mut BTreeSet::new(),
            &self.level_m,
            &*self.trees[0],
        );
        for i in 0..self.trees.len() - 1 {
            let upper = &*self.trees[i];
            let lower = &*self.trees[i + 1];
            let upper_tombstons_ref = upper.tombstones();
            let mut upper_tombstones = upper_tombstons_ref.write();
            self.check_and_merge(
                self.max_sizes[i],
                self.page_sizes[i + 1],
                &mut *upper_tombstones,
                upper,
                lower,
            );
        }
        thread::sleep(Duration::from_millis(100));
    }

    fn check_and_merge<U, L>(
        &self,
        max_elements: usize,
        upper_page_size: usize,
        upper_tombstones: &mut TombstonesInner,
        upper_level: &U,
        lower_level: &L,
    ) where
        U: MergeableTree + ?Sized,
        L: SSLevelTree + ?Sized,
    {
        let elements = upper_level.elements();
        if elements > max_elements {
            debug!("upper level have elements {} exceeds {}, start level merging", elements, max_elements);
            let entries_to_merge = upper_page_size;
            let guard = upper_level.prepare_level_merge();
            let mut pages = vec![guard.last_page()];
            let mut collected_entries = pages.last().unwrap().keys().len();
            while collected_entries < entries_to_merge {
                let next_page = pages.last().unwrap().next();
                collected_entries += next_page.keys().len();
                pages.push(next_page);
            }
            let mut entries = vec![];
            for page in &pages {
                entries.append(&mut Vec::from(page.keys()));
            }
            debug!("going to merge {} entries", entries.len());
            lower_level.merge(entries.as_mut_slice(), upper_tombstones);
            guard.remove_pages(pages.iter().map(|p| p.keys()).collect_vec().as_slice())
        }
    }
}

pub struct LSMTreeCursor {
    level_cursors: Vec<Box<Cursor>>,
    tombstones: Vec<Arc<Tombstones>>,
}

impl LSMTreeCursor {
    fn new(cursors: Vec<Box<Cursor>>, tombstones: Vec<Arc<Tombstones>>) -> Self {
        LSMTreeCursor {
            tombstones,
            level_cursors: cursors,
        }
    }

    fn next_candidate(&mut self) -> (bool, usize) {
        let min_tree = self
            .level_cursors
            .iter()
            .enumerate()
            .map(|(i, cursor)| (i, cursor.current()))
            .filter_map(|(i, current)| current.map(|current_val| (i, current_val)))
            .min_by_key(|(i, val)| *val)
            .map(|(id, _)| id);
        if let Some(id) = min_tree {
            let min_has_next = self.level_cursors[id].next();
            if !min_has_next {
                let next = self
                    .level_cursors
                    .iter()
                    .enumerate()
                    .filter_map(|(level, cursor)| cursor.current().map(|key| (id, key)))
                    .min_by_key(|(level, key)| *key);
                if let Some((id, _)) = next {
                    return (true, id);
                }
            } else {
                return (true, id);
            }
        }
        return (false, 0);
    }
}

impl Cursor for LSMTreeCursor {
    fn next(&mut self) -> bool {
        loop {
            let (has_next, candidate_level) = self.next_candidate();
            if !has_next {
                break;
            }
            if candidate_level == 0 {
                return has_next;
            }
            let candidate_key = self.level_cursors[candidate_level].current().unwrap();
            if self.tombstones[candidate_level + 1]
                .read()
                .contains(candidate_key)
            {
                // marked in tombstone, skip
                continue;
            }
            return true;
        }
        return false;
    }

    fn current(&self) -> Option<&EntryKey> {
        self.level_cursors.iter().filter_map(|c| c.current()).min()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::{Cursor as StdCursor};
    use futures::prelude::*;
    use server::NebServer;
    use server::ServerOptions;
    use client;
    use std::env;
    use byteorder::BigEndian;
    use byteorder::WriteBytesExt;
    use rand::thread_rng;
    use rand::distributions::Uniform;
    use rand::Rng;

    fn u64_to_slice(n: u64) -> [u8; 8] {
        let mut key_slice = [0u8; 8];
        {
            let mut cursor = StdCursor::new(&mut key_slice[..]);
            cursor.write_u64::<BigEndian>(n);
        };
        key_slice
    }

    #[test]
    pub fn insertions() {
        env_logger::init();
        let server_group = "sstable_index_init";
        let server_addr = String::from("127.0.0.1:5300");
        let server = NebServer::new_from_opts(
            &ServerOptions {
                chunk_count: 1,
                memory_size: 16 * 1024 * 1024 * 1024,
                backup_storage: None,
                wal_storage: None,
            },
            &server_addr,
            &server_group,
        );
        let client = Arc::new(
            client::AsyncClient::new(&server.rpc, &vec![server_addr], server_group).unwrap(),
        );
        client.new_schema_with_id(super::get_schema()).wait();
        let tree = LSMTree::new(&client);
        let num = env::var("LSM_TREE_TEST_ITEMS")
            // this value cannot do anything useful to the test
            // must arrange a long-term test to cover every levels
            .unwrap_or("1000".to_string())
            .parse::<u64>()
            .unwrap();

        for i in 0.. num {
            let id = Id::new(0, i);
            let key_slice = u64_to_slice(i);
            let key = SmallVec::from_slice(&key_slice);
            debug!("insert id: {}", i);
            tree.insert(key, &id).unwrap();
        }

        debug!("Sleep for level merge");
        thread::sleep(Duration::from_millis(num));

        debug!("Start validations");
        let mut rng = thread_rng();
        let die_range = Uniform::new_inclusive(1, 6);
        let mut roll_die = rng.sample_iter(&die_range);
        for i in 0..num {
            let id = Id::new(0, i);
            let key_slice = u64_to_slice(i);
            let mut key = SmallVec::from_slice(&key_slice);
            debug!("checking: {}", i);
            let mut cursor = tree.seek(key.clone(), Ordering::Forward);
            key_with_id(&mut key, &id);
            assert_eq!(cursor.current(), Some(&key), "{}", i);
            if roll_die.next().unwrap() != 6 {
                continue;
            }
            for j in i .. num {
                let id = Id::new(0, j);
                let key_slice = u64_to_slice(j);
                let mut key = SmallVec::from_slice(&key_slice);
                key_with_id(&mut key, &id);
                assert_eq!(cursor.current(), Some(&key), "{}/{}", i, j);
                assert_eq!(cursor.next(), j != num - 1, "{}/{}", i, j);
            }
        }
    }
}<|endoftext|>


<commit_before>// Lumol, an extensible molecular simulation engine
// Copyright (C) 2015-2016 G. Fraux — BSD license

//! Input capacities for lumol
//!
//! This module provide input files reader for two types of files:
//!
//! * Configuration files uses the TOML format to define a `System` or a
//!   `Simulation` in an human-readable way, without writing any code. This
//!   type of input is further divided in potentials input files and whole
//!   simulation input files;
//! * Structures files defines the positions and the names of particles in a
//!   `System` or in a specific `Molecule`.
//!
//! # Reading configuration files
//!
//! The `read_interactions` function read interactions into a `System`, and the
//! `read_config` function reads a whole simulation (`Simulation` and `System`
//! objects).

#![warn(
    missing_docs, trivial_casts, unused_import_braces, variant_size_differences,
    unused_qualifications, unused_results
)]

#![warn(clippy, clippy_pedantic)]
#![allow(unknown_lints)]
// List of lints we allow in this code
#![allow(
    float_arithmetic, integer_arithmetic, indexing_slicing, needless_return,
    needless_range_loop, shadow_reuse, shadow_same, shadow_unrelated,
    cast_possible_truncation, cast_precision_loss, cast_sign_loss,
    cast_possible_wrap, float_cmp, or_fun_call, string_add, non_ascii_literal,
    doc_markdown, missing_docs_in_private_items, module_inception, stutter,
    unseparated_literal_suffix
)]

#[macro_use]
extern crate log;
extern crate toml;
extern crate chemfiles;
extern crate lumol;

use toml::Table;

mod extract;
mod error;
mod interactions;
mod simulations;

#[cfg(test)]
pub mod testing;

pub use self::error::{Error, Result};
pub use self::interactions::InteractionsInput;
pub use self::simulations::Input;

/// Convert a TOML table to a Rust type.
pub trait FromToml: Sized {
    /// Do the conversion from `table` to Self.
    fn from_toml(table: &Table) -> Result<Self>;
}

/// Convert a TOML table and some additional data to a Rust type.
pub trait FromTomlWithData: Sized {
    /// The type of the additional data needed.
    type Data;
    /// Do the conversion from `table` and `data` to Self.
    fn from_toml(table: &Table, data: Self::Data) -> Result<Self>;
}

fn validate(config: &Table) -> Result<()> {
    let input = try!(config.get("input").ok_or(
        Error::from("Missing 'input' table")
    ));

    let version = try!(input.lookup("version").ok_or(
        Error::from("Missing 'version' key in 'input' table")
    ));

    let version = try!(version.as_integer().ok_or(
        Error::from("'input.version' must be an integer")
    ));

    if version != 1 {
        return Err(Error::from(
            format!("Only version 1 of input can be read, got {}", version)
        ))
    }
    Ok(())
}
<commit_msg>[doc] Improve documentation for lumol_input<commit_after>// Lumol, an extensible molecular simulation engine
// Copyright (C) 2015-2016 G. Fraux — BSD license

//! This crate provide a way to build a Lumol simulation using input files.
//!
//! Instead of building the `System` object, the `Simulation` object by hand
//! before being able to use them to run the simulation, this crate allow to
//! describe the simulation and the system in a TOML input file, using a simple
//! syntax.
//!
//! The main entry point of this crate is the `Input` struct, which allow to
//! read a whole simulation configuration. The easiest way to run a simulation
//! using Lumol is:
//!
//! ```no_run
//! extern crate lumol_input;
//! use lumol_input::Input;
//!
//! fn main() {
//!     let input = Input::new("simulation.toml").unwrap();
//!     let mut config = input.read().unwrap();
//!
//!     config.simulation.run(&mut config.system, config.nsteps);
//! }
//! ```
//!
//! This crate also provide an `InteractionsInput` for reading interactions
//! from a TOML file. It can be used to set the interactions in a system:
//!
//! ```no_run
//! extern crate lumol;
//! extern crate lumol_input;
//! use lumol::System;
//! use lumol_input::InteractionsInput;
//!
//! fn main() {
//!     let mut system = System::new();
//!
//!     // ... Build the system by hand
//!
//!     // Read the interactions
//!     let input = InteractionsInput::new("potentials.toml").unwrap();
//!     input.read(&mut system).unwrap();
//! }
//! ```
//!
#![warn(
    missing_docs, trivial_casts, unused_import_braces, variant_size_differences,
    unused_qualifications, unused_results
)]

#![warn(clippy, clippy_pedantic)]
#![allow(unknown_lints)]
// List of lints we allow in this code
#![allow(
    float_arithmetic, integer_arithmetic, indexing_slicing, needless_return,
    needless_range_loop, shadow_reuse, shadow_same, shadow_unrelated,
    cast_possible_truncation, cast_precision_loss, cast_sign_loss,
    cast_possible_wrap, float_cmp, or_fun_call, string_add, non_ascii_literal,
    doc_markdown, missing_docs_in_private_items, module_inception, stutter,
    unseparated_literal_suffix
)]

#[macro_use]
extern crate log;
extern crate toml;
extern crate chemfiles;
extern crate lumol;

use toml::Table;

mod extract;
mod error;
mod interactions;
mod simulations;

#[cfg(test)]
pub mod testing;

pub use self::error::{Error, Result};
pub use self::interactions::InteractionsInput;
pub use self::simulations::Input;

/// Convert a TOML table to a Rust type.
pub trait FromToml: Sized {
    /// Do the conversion from `table` to Self.
    fn from_toml(table: &Table) -> Result<Self>;
}

/// Convert a TOML table and some additional data to a Rust type.
pub trait FromTomlWithData: Sized {
    /// The type of the additional data needed.
    type Data;
    /// Do the conversion from `table` and `data` to Self.
    fn from_toml(table: &Table, data: Self::Data) -> Result<Self>;
}

fn validate(config: &Table) -> Result<()> {
    let input = try!(config.get("input").ok_or(
        Error::from("Missing 'input' table")
    ));

    let version = try!(input.lookup("version").ok_or(
        Error::from("Missing 'version' key in 'input' table")
    ));

    let version = try!(version.as_integer().ok_or(
        Error::from("'input.version' must be an integer")
    ));

    if version != 1 {
        return Err(Error::from(
            format!("Only version 1 of input can be read, got {}", version)
        ))
    }
    Ok(())
}
<|endoftext|>


<commit_before>mod terminal;
mod ansi;

use std::io::{self, Write};
use self::terminal::{Terminal, Event, Key};
use self::ansi::{clear, color, cursor, style};
use rayon::prelude::*;
use rff::choice::Choice;

#[derive(Debug)]
pub enum Error {
    Exit,
    Write(io::Error),
    Reset(terminal::Error)
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Error {
        Error::Write(err)
    }
}

impl From<terminal::Error> for Error {
    fn from(err: terminal::Error) -> Error {
        Error::Reset(err)
    }
}

pub struct Options {
    pub choices: Vec<String>,
    pub initial: String,
}

pub struct Interface {
    choices: Vec<String>,
    matching: Vec<Choice>,
    selected: usize,
    search: String,
    terminal: Terminal
}

impl Interface {
    /// Creates a new Interface from the provided options.
    pub fn from_opts(opts: Options) -> Interface {
        let mut term = Terminal::from("/dev/tty").unwrap();
        term.set_raw_mode().unwrap();

        Interface {
            choices: opts.choices,
            matching: Vec::new(),
            selected: 0,
            search: opts.initial,
            terminal: term
        }
    }

    // Starts the interface
    pub fn run(&mut self) -> Result<&str, Error> {
        self.filter_choices();
        self.render()?;

        for event in self.terminal.events()? {
            if let Event::Key(key) = event? {
                match key {
                    Key::Ctrl('c') | Key::Ctrl('d') => {
                        self.clear()?;
                        return Err(Error::Exit);
                    }

                    Key::Char('\n') => {
                        break;
                    },

                    Key::Ctrl('n') => {
                        self.selected += 1;
                        self.clamp_selected();
                        self.render()?;
                    },

                    Key::Ctrl('p') => {
                        self.selected = self.selected.
                            checked_sub(1).
                            unwrap_or(0);

                        self.clamp_selected();
                        self.render()?;
                    },

                    Key::Char(ch) => {
                        self.search.push(ch);
                        self.filter_existing_matches();
                        self.render()?;
                    },

                    Key::Backspace | Key::Ctrl('h') => {
                        self.search.pop();
                        self.filter_choices();
                        self.render()?;
                    }

                    Key::Ctrl('u') => {
                        self.search.clear();
                        self.filter_choices();
                        self.render()?;
                    }

                    _ => {}
                }
            };
        }

        self.clear()?;
        Ok(self.result())
    }

    fn filter_choices(&mut self) {
        let mut matches = self.choices.
            par_iter().
            cloned().
            filter_map(|choice| Choice::with_positions(&self.search, choice)).
            collect::<Vec<_>>();

        matches.sort_by(|a, b| b.partial_cmp(a).unwrap());

        self.matching = matches;
    }

    fn filter_existing_matches(&mut self) {
        let mut matches = self.matching.
            par_iter().
            map(|c| c.text().to_string()).
            filter_map(|choice| Choice::with_positions(&self.search, choice)).
            collect::<Vec<_>>();

        matches.sort_by(|a, b| b.partial_cmp(a).unwrap());

        self.matching = matches;
    }

    fn render(&mut self) -> io::Result<()> {
        write!(self.terminal, "{}{}", cursor::Column(1), clear::Screen)?;
        write!(self.terminal, "> {}", self.search)?;

        let n = self.matching.iter().take(10).len() as u16;

        self.render_matches()?;

        if n > 0 {
            let column = format!("> {}", self.search).len() as u16;
            write!(self.terminal, "{}{}", cursor::Up(n), cursor::Column(column + 1))?;
        }

        Ok(())
    }

    fn render_matches(&mut self) -> io::Result<()> {
        let max_width = self.terminal.max_width;
        let matches = self.matching.iter().take(10);

        for (i, choice) in matches.enumerate() {
            let selected = i == self.selected;

            write!(self.terminal, "\r\n")?;

            if selected {
                write!(self.terminal, "{}", style::Invert)?;
            }

            if let Some(positions) = choice.positions() {
                let chars = choice.text().chars().take(max_width);

                for (i, ch) in chars.enumerate() {
                    let match_position = positions.iter().any(|p| *p == i);

                    if match_position {
                        let color = color::Fg(color::Colors::Magenta);
                        let reset = color::Fg(color::Reset);
                        write!(self.terminal, "{}{}{}", color, ch, reset)?;
                    } else {
                        write!(self.terminal, "{}", ch)?;
                    }
                }

            } else {
                let text = choice.text()
                    .chars()
                    .take(max_width)
                    .collect::<String>();

                write!(self.terminal, "{}", text)?;
            }

            if selected {
                write!(self.terminal, "{}", style::NoInvert)?;
            }
        }

        Ok(())
    }

    #[inline(always)]
    fn clamp_selected(&mut self) {
        let mut max = self.matching.len();
        if max > 10 { max = 10; }

        if self.selected >= max {
            self.selected = max - 1;
        }
    }

    fn clear(&mut self) -> Result<(), Error> {
        write!(self.terminal, "{}{}", cursor::Column(1), clear::Screen)?;
        self.terminal.reset()?;
        Ok(())
    }

    fn result(&mut self) -> &str {
        self.matching.iter().
            map(|c| c.text()).
            nth(self.selected).
            unwrap_or(&self.search)
    }
}
<commit_msg>Clean up render_matches loop<commit_after>mod terminal;
mod ansi;

use std::io::{self, Write};
use self::terminal::{Terminal, Event, Key};
use self::ansi::{clear, color, cursor, style};
use rayon::prelude::*;
use rff::choice::Choice;

#[derive(Debug)]
pub enum Error {
    Exit,
    Write(io::Error),
    Reset(terminal::Error)
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Error {
        Error::Write(err)
    }
}

impl From<terminal::Error> for Error {
    fn from(err: terminal::Error) -> Error {
        Error::Reset(err)
    }
}

pub struct Options {
    pub choices: Vec<String>,
    pub initial: String,
}

pub struct Interface {
    choices: Vec<String>,
    matching: Vec<Choice>,
    selected: usize,
    search: String,
    terminal: Terminal
}

impl Interface {
    /// Creates a new Interface from the provided options.
    pub fn from_opts(opts: Options) -> Interface {
        let mut term = Terminal::from("/dev/tty").unwrap();
        term.set_raw_mode().unwrap();

        Interface {
            choices: opts.choices,
            matching: Vec::new(),
            selected: 0,
            search: opts.initial,
            terminal: term
        }
    }

    // Starts the interface
    pub fn run(&mut self) -> Result<&str, Error> {
        self.filter_choices();
        self.render()?;

        for event in self.terminal.events()? {
            if let Event::Key(key) = event? {
                match key {
                    Key::Ctrl('c') | Key::Ctrl('d') => {
                        self.clear()?;
                        return Err(Error::Exit);
                    }

                    Key::Char('\n') => {
                        break;
                    },

                    Key::Ctrl('n') => {
                        self.selected += 1;
                        self.clamp_selected();
                        self.render()?;
                    },

                    Key::Ctrl('p') => {
                        self.selected = self.selected.
                            checked_sub(1).
                            unwrap_or(0);

                        self.clamp_selected();
                        self.render()?;
                    },

                    Key::Char(ch) => {
                        self.search.push(ch);
                        self.filter_existing_matches();
                        self.render()?;
                    },

                    Key::Backspace | Key::Ctrl('h') => {
                        self.search.pop();
                        self.filter_choices();
                        self.render()?;
                    }

                    Key::Ctrl('u') => {
                        self.search.clear();
                        self.filter_choices();
                        self.render()?;
                    }

                    _ => {}
                }
            };
        }

        self.clear()?;
        Ok(self.result())
    }

    fn filter_choices(&mut self) {
        let mut matches = self.choices.
            par_iter().
            cloned().
            filter_map(|choice| Choice::with_positions(&self.search, choice)).
            collect::<Vec<_>>();

        matches.sort_by(|a, b| b.partial_cmp(a).unwrap());

        self.matching = matches;
    }

    fn filter_existing_matches(&mut self) {
        let mut matches = self.matching.
            par_iter().
            map(|c| c.text().to_string()).
            filter_map(|choice| Choice::with_positions(&self.search, choice)).
            collect::<Vec<_>>();

        matches.sort_by(|a, b| b.partial_cmp(a).unwrap());

        self.matching = matches;
    }

    fn render(&mut self) -> io::Result<()> {
        write!(self.terminal, "{}{}", cursor::Column(1), clear::Screen)?;
        write!(self.terminal, "> {}", self.search)?;

        let n = self.matching.iter().take(10).len() as u16;

        self.render_matches()?;

        if n > 0 {
            let column = format!("> {}", self.search).len() as u16;
            write!(self.terminal, "{}{}", cursor::Up(n), cursor::Column(column + 1))?;
        }

        Ok(())
    }

    fn render_matches(&mut self) -> io::Result<()> {
        let max_width = self.terminal.max_width;
        let matches = self.matching.iter().take(10);

        for (i, choice) in matches.enumerate() {
            let selected = i == self.selected;
            let chars = choice.text().chars().take(max_width);

            write!(self.terminal, "\r\n")?;

            if selected {
                write!(self.terminal, "{}", style::Invert)?;
            }

            if let Some(positions) = choice.positions() {
                for (i, ch) in chars.enumerate() {
                    let match_position = positions.iter().any(|p| *p == i);

                    if match_position {
                        let color = color::Fg(color::Colors::Magenta);
                        let reset = color::Fg(color::Reset);
                        write!(self.terminal, "{}{}{}", color, ch, reset)?;
                    } else {
                        write!(self.terminal, "{}", ch)?;
                    }
                }

            } else {
                write!(self.terminal, "{}", chars.collect::<String>())?;
            }

            if selected {
                write!(self.terminal, "{}", style::NoInvert)?;
            }
        }

        Ok(())
    }

    #[inline(always)]
    fn clamp_selected(&mut self) {
        let mut max = self.matching.len();
        if max > 10 { max = 10; }

        if self.selected >= max {
            self.selected = max - 1;
        }
    }

    fn clear(&mut self) -> Result<(), Error> {
        write!(self.terminal, "{}{}", cursor::Column(1), clear::Screen)?;
        self.terminal.reset()?;
        Ok(())
    }

    fn result(&mut self) -> &str {
        self.matching.iter().
            map(|c| c.text()).
            nth(self.selected).
            unwrap_or(&self.search)
    }
}
<|endoftext|>


<commit_before>use std::rc::Rc;

use gtk;
use gtk::prelude::*;
use gtk::{Menu, MenuItem};

use app::AppContextPointer;
use gui::Gui;

mod menu_callbacks;

pub fn layout(gui: &Gui, ac: &AppContextPointer) {
    // Build the menu bar
    build_menu_bar(ac, gui);
    configure_main_window(gui, ac);
}

fn build_menu_bar(ac: &AppContextPointer, gui: &Gui) {
    //
    // The file menu.
    //

    // The open item
    let open_item = MenuItem::new_with_label("Open");
    let win1 = gui.get_window().clone();
    let ac1 = Rc::clone(ac);
    open_item.connect_activate(move |mi| menu_callbacks::open_callback(mi, &ac1, &win1));

    // The quit item
    let quit_item = MenuItem::new_with_label("Quit");
    quit_item.connect_activate(|_| {
        gtk::main_quit();
    });

    // Build the file menu
    let file_menu: Menu = gui.get_builder().get_object("main_menu_file").unwrap();
    file_menu.append(&open_item);
    file_menu.append(&quit_item);

    //
    // End the file menu
    //
}

fn configure_main_window(gui: &Gui, ac: &AppContextPointer) {
    let window = gui.get_window();

    let (width, height) = {
        let cfg = ac.config.borrow();
        (cfg.window_width, cfg.window_height)
    };

    if width > 0 || height > 0 {
        window.set_default_size(width, height);
        println!("Set default size with: {}x{}", width, height);
    }

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    let ac1 = Rc::clone(ac);
    window.connect_configure_event(move |win, _evt| {
        let (width, height) = win.get_size();
        let mut config = ac1.config.borrow_mut();
        config.window_width = width;
        config.window_height = height;
        println!("Configure window called with: {}x{}", width, height);
        false
    });

    window.show_all();
}
<commit_msg>Fixed window sizing.<commit_after>use std::rc::Rc;

use gtk;
use gtk::prelude::*;
use gtk::{Menu, MenuItem};

use app::AppContextPointer;
use gui::Gui;

mod menu_callbacks;

pub fn layout(gui: &Gui, ac: &AppContextPointer) {
    // Build the menu bar
    build_menu_bar(ac, gui);
    configure_main_window(gui, ac);
}

fn build_menu_bar(ac: &AppContextPointer, gui: &Gui) {
    //
    // The file menu.
    //

    // The open item
    let open_item = MenuItem::new_with_label("Open");
    let win1 = gui.get_window().clone();
    let ac1 = Rc::clone(ac);
    open_item.connect_activate(move |mi| menu_callbacks::open_callback(mi, &ac1, &win1));

    // The quit item
    let quit_item = MenuItem::new_with_label("Quit");
    quit_item.connect_activate(|_| {
        gtk::main_quit();
    });

    // Build the file menu
    let file_menu: Menu = gui.get_builder().get_object("main_menu_file").unwrap();
    file_menu.append(&open_item);
    file_menu.append(&quit_item);

    //
    // End the file menu
    //
}

fn configure_main_window(gui: &Gui, ac: &AppContextPointer) {
    let window = gui.get_window();

    let (width, height) = {
        let cfg = ac.config.borrow();
        (cfg.window_width, cfg.window_height)
    };

    if width > 0 || height > 0 {
        window.resize(width, height);
    }

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    let ac1 = Rc::clone(ac);
    window.connect_configure_event(move |win, _evt| {
        let (width, height) = win.get_size();
        let mut config = ac1.config.borrow_mut();
        config.window_width = width;
        config.window_height = height;
        false
    });

    window.show_all();
}
<|endoftext|>


<commit_before>use std::borrow::Borrow;
use std::ops::Range;
use pso;
use {Backend, IndexCount, InstanceCount, VertexCount, VertexOffset};
use buffer::IndexBufferView;
use image::{ImageLayout, SubresourceRange};
use memory::Barrier;
use query::{Query, QueryControl, QueryId};
use super::{
    ColorValue, StencilValue, Rect, Viewport,
    AttachmentClear, BufferCopy, BufferImageCopy,
    ClearColor, ClearDepthStencil, ClearValue,
    ImageCopy, ImageResolve, SubpassContents,
};

///
pub trait RawCommandBuffer<B: Backend>: Clone + Send {
    ///
    fn begin(&mut self);

    ///
    fn finish(&mut self);

    ///
    fn reset(&mut self, release_resources: bool);

    ///
    fn pipeline_barrier<'a, T>(
        &mut self,
        stages: Range<pso::PipelineStage>,
        barriers: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<Barrier<'a, B>>;

    ///
    fn fill_buffer(
        &mut self,
        buffer: &B::Buffer,
        range: Range<u64>,
        data: u32,
    );

    ///
    fn update_buffer(
        &mut self,
        buffer: &B::Buffer,
        offset: u64,
        data: &[u8],
    );

    /// Clear color image
    fn clear_color_image(
        &mut self,
        &B::Image,
        ImageLayout,
        SubresourceRange,
        ClearColor,
    );

    /// Clear depth-stencil image
    fn clear_depth_stencil_image(
        &mut self,
        &B::Image,
        ImageLayout,
        SubresourceRange,
        ClearDepthStencil,
    );

    ///
    fn clear_attachments<T, U>(&mut self, clears: T, rects: U)
    where
        T: IntoIterator,
        T::Item: Borrow<AttachmentClear>,
        U: IntoIterator,
        U::Item: Borrow<Rect>;

    ///
    fn resolve_image<T>(
        &mut self,
        src: &B::Image,
        src_layout: ImageLayout,
        dst: &B::Image,
        dst_layout: ImageLayout,
        regions: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<ImageResolve>;

    /// Bind index buffer view.
    fn bind_index_buffer(&mut self, IndexBufferView<B>);

    /// Bind vertex buffers.
    fn bind_vertex_buffers(&mut self, pso::VertexBufferSet<B>);

    /// Set the viewport parameters for the rasterizer.
    ///
    /// Every other viewport, which is not specified in this call,
    /// will be disabled.
    ///
    /// Ensure that the number of set viewports at draw time is equal
    /// (or higher) to the number specified in the bound pipeline.
    ///
    /// # Errors
    ///
    /// This function does not return an error. Invalid usage of this function
    /// will result in an error on `finish`.
    ///
    /// - Command buffer must be in recording state.
    /// - Number of viewports must be between 1 and `max_viewports`.
    /// - Only queues with graphics capability support this function.
    fn set_viewports<T>(&mut self, viewports: T)
    where
        T: IntoIterator,
        T::Item: Borrow<Viewport>;

    /// Set the scissor rectangles for the rasterizer.
    ///
    /// Every other scissor, which is not specified in this call,
    /// will be disabled.
    ///
    /// Each scissor corresponds to the viewport with the same index.
    ///
    /// Ensure that the number of set scissors at draw time is equal (or higher)
    /// to the number of viewports specified in the bound pipeline.
    ///
    /// # Errors
    ///
    /// This function does not return an error. Invalid usage of this function
    /// will result in an error on `finish`.
    ///
    /// - Command buffer must be in recording state.
    /// - Number of scissors must be between 1 and `max_viewports`.
    /// - Only queues with graphics capability support this function.
    fn set_scissors<T>(&mut self, rects: T)
    where
        T: IntoIterator,
        T::Item: Borrow<Rect>;

    ///
    fn set_stencil_reference(&mut self, front: StencilValue, back: StencilValue);

    ///
    fn set_blend_constants(&mut self, ColorValue);

    ///
    fn begin_renderpass<T>(
        &mut self,
        render_pass: &B::RenderPass,
        frame_buffer: &B::Framebuffer,
        render_area: Rect,
        clear_values: T,
        first_subpass: SubpassContents,
    ) where
        T: IntoIterator,
        T::Item: Borrow<ClearValue>;
    ///
    fn next_subpass(&mut self, contents: SubpassContents);

    ///
    fn end_renderpass(&mut self);

    /// Bind a graphics pipeline.
    ///
    /// # Errors
    ///
    /// This function does not return an error. Invalid usage of this function
    /// will result in an error on `finish`.
    ///
    /// - Command buffer must be in recording state.
    /// - Only queues with graphics capability support this function.
    fn bind_graphics_pipeline(&mut self, &B::GraphicsPipeline);

    ///
    fn bind_graphics_descriptor_sets<'a, T>(
        &mut self,
        layout: &B::PipelineLayout,
        first_set: usize,
        sets: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<B::DescriptorSet>;

    /// Bind a compute pipeline.
    ///
    /// # Errors
    ///
    /// This function does not return an error. Invalid usage of this function
    /// will result in an error on `finish`.
    ///
    /// - Command buffer must be in recording state.
    /// - Only queues with compute capability support this function.
    fn bind_compute_pipeline(&mut self, &B::ComputePipeline);

    ///
    fn bind_compute_descriptor_sets<T>(
        &mut self,
        layout: &B::PipelineLayout,
        first_set: usize,
        sets: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<B::DescriptorSet>;

    /// Execute a workgroup in the compute pipeline.
    ///
    /// # Errors
    ///
    /// This function does not return an error. Invalid usage of this function
    /// will result in an error on `finish`.
    ///
    /// - Command buffer must be in recording state.
    /// - A compute pipeline must be bound using `bind_compute_pipeline`.
    /// - Only queues with compute capability support this function.
    /// - This function must be called outside of a renderpass.
    /// - `(x, y, z)` must be less than or equal to `Limits::max_compute_group_size`
    ///
    /// TODO:
    fn dispatch(&mut self, x: u32, y: u32, z: u32);

    ///
    fn dispatch_indirect(&mut self, buffer: &B::Buffer, offset: u64);

    ///
    fn copy_buffer<T>(
        &mut self,
        src: &B::Buffer,
        dst: &B::Buffer,
        regions: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<BufferCopy>;

    ///
    fn copy_image<T>(
        &mut self,
        src: &B::Image,
        src_layout: ImageLayout,
        dst: &B::Image,
        dst_layout: ImageLayout,
        regions: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<ImageCopy>;

    ///
    fn copy_buffer_to_image<T>(
        &mut self,
        src: &B::Buffer,
        dst: &B::Image,
        dst_layout: ImageLayout,
        regions: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<BufferImageCopy>;

    ///
    fn copy_image_to_buffer<T>(
        &mut self,
        src: &B::Image,
        src_layout: ImageLayout,
        dst: &B::Buffer,
        regions: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<BufferImageCopy>;

    ///
    fn draw(
        &mut self,
        vertices: Range<VertexCount>,
        instances: Range<InstanceCount>,
    );

    ///
    fn draw_indexed(
        &mut self,
        indices: Range<IndexCount>,
        base_vertex: VertexOffset,
        instances: Range<InstanceCount>,
    );

    ///
    fn draw_indirect(
        &mut self,
        buffer: &B::Buffer,
        offset: u64,
        draw_count: u32,
        stride: u32,
    );

    ///
    fn draw_indexed_indirect(
        &mut self,
        buffer: &B::Buffer,
        offset: u64,
        draw_count: u32,
        stride: u32,
    );

    ///
    fn begin_query(&mut self, query: Query<B>, flags: QueryControl);

    ///
    fn end_query(&mut self, query: Query<B>);

    ///
    fn reset_query_pool(&mut self, pool: &B::QueryPool, queries: Range<QueryId>);

    ///
    fn write_timestamp(&mut self, pso::PipelineStage, Query<B>);

    ///
    fn push_graphics_constants(
        &mut self,
        layout: &B::PipelineLayout,
        stages: pso::ShaderStageFlags,
        offset: u32,
        constants: &[u32],
    );

    ///
    fn push_compute_constants(
        &mut self,
        layout: &B::PipelineLayout,
        offset: u32,
        constants: &[u32],
    );
}
<commit_msg>hal: Expose raw, union-based clearing API for portability<commit_after>use std::borrow::Borrow;
use std::ops::Range;
use pso;
use {Backend, IndexCount, InstanceCount, VertexCount, VertexOffset};
use buffer::IndexBufferView;
use image::{ImageLayout, SubresourceRange};
use memory::Barrier;
use query::{Query, QueryControl, QueryId};
use super::{
    ColorValue, StencilValue, Rect, Viewport,
    AttachmentClear, BufferCopy, BufferImageCopy,
    ClearColor, ClearDepthStencil, ClearValue,
    ImageCopy, ImageResolve, SubpassContents,
};

/// Unsafe variant of `ClearColor`.
#[repr(C)]
#[derive(Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub union ClearColorRaw {
    ///
    pub float32: [f32; 4],
    ///
    pub int32: [i32; 4],
    ///
    pub uint32: [u32; 4],
    _align: [u32; 4],
}
///
#[repr(C)]
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ClearDepthStencilRaw {
    ///
    pub depth: f32,
    ///
    pub stencil: u32,
}
/// Unsafe variant of `ClearValue`.
#[repr(C)]
#[derive(Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub union ClearValueRaw {
    ///
    pub color: ClearColorRaw,
    ///
    pub depth_stencil: ClearDepthStencilRaw,
    _align: [u32; 4],
}

///
pub trait RawCommandBuffer<B: Backend>: Clone + Send {
    ///
    fn begin(&mut self);

    ///
    fn finish(&mut self);

    ///
    fn reset(&mut self, release_resources: bool);

    ///
    fn pipeline_barrier<'a, T>(
        &mut self,
        stages: Range<pso::PipelineStage>,
        barriers: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<Barrier<'a, B>>;

    ///
    fn fill_buffer(
        &mut self,
        buffer: &B::Buffer,
        range: Range<u64>,
        data: u32,
    );

    ///
    fn update_buffer(
        &mut self,
        buffer: &B::Buffer,
        offset: u64,
        data: &[u8],
    );

    /// Clear color image
    fn clear_color_image(
        &mut self,
        image: &B::Image,
        layout: ImageLayout,
        range: SubresourceRange,
        cv: ClearColor,
    ) {
        self.clear_color_image_raw(
            image,
            layout,
            range,
            match cv {
                ClearColor::Float(cv) => ClearColorRaw { float32: cv },
                ClearColor::Int(cv) => ClearColorRaw { int32: cv },
                ClearColor::Uint(cv) => ClearColorRaw { uint32: cv },
            },
        )
    }

    /// Clear color image
    fn clear_color_image_raw(
        &mut self,
        &B::Image,
        ImageLayout,
        SubresourceRange,
        ClearColorRaw,
    );

    /// Clear depth-stencil image
    fn clear_depth_stencil_image(
        &mut self,
        image: &B::Image,
        layout: ImageLayout,
        range: SubresourceRange,
        cv: ClearDepthStencil,
    ) {
        let cv = ClearDepthStencilRaw {
            depth: cv.0,
            stencil: cv.1,
        };
        self.clear_depth_stencil_image_raw(image, layout, range, cv)
    }

    /// Clear depth-stencil image
    fn clear_depth_stencil_image_raw(
        &mut self,
        &B::Image,
        ImageLayout,
        SubresourceRange,
        ClearDepthStencilRaw,
    );

    ///
    fn clear_attachments<T, U>(&mut self, clears: T, rects: U)
    where
        T: IntoIterator,
        T::Item: Borrow<AttachmentClear>,
        U: IntoIterator,
        U::Item: Borrow<Rect>;

    ///
    fn resolve_image<T>(
        &mut self,
        src: &B::Image,
        src_layout: ImageLayout,
        dst: &B::Image,
        dst_layout: ImageLayout,
        regions: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<ImageResolve>;

    /// Bind index buffer view.
    fn bind_index_buffer(&mut self, IndexBufferView<B>);

    /// Bind vertex buffers.
    fn bind_vertex_buffers(&mut self, pso::VertexBufferSet<B>);

    /// Set the viewport parameters for the rasterizer.
    ///
    /// Every other viewport, which is not specified in this call,
    /// will be disabled.
    ///
    /// Ensure that the number of set viewports at draw time is equal
    /// (or higher) to the number specified in the bound pipeline.
    ///
    /// # Errors
    ///
    /// This function does not return an error. Invalid usage of this function
    /// will result in an error on `finish`.
    ///
    /// - Command buffer must be in recording state.
    /// - Number of viewports must be between 1 and `max_viewports`.
    /// - Only queues with graphics capability support this function.
    fn set_viewports<T>(&mut self, viewports: T)
    where
        T: IntoIterator,
        T::Item: Borrow<Viewport>;

    /// Set the scissor rectangles for the rasterizer.
    ///
    /// Every other scissor, which is not specified in this call,
    /// will be disabled.
    ///
    /// Each scissor corresponds to the viewport with the same index.
    ///
    /// Ensure that the number of set scissors at draw time is equal (or higher)
    /// to the number of viewports specified in the bound pipeline.
    ///
    /// # Errors
    ///
    /// This function does not return an error. Invalid usage of this function
    /// will result in an error on `finish`.
    ///
    /// - Command buffer must be in recording state.
    /// - Number of scissors must be between 1 and `max_viewports`.
    /// - Only queues with graphics capability support this function.
    fn set_scissors<T>(&mut self, rects: T)
    where
        T: IntoIterator,
        T::Item: Borrow<Rect>;

    ///
    fn set_stencil_reference(&mut self, front: StencilValue, back: StencilValue);

    ///
    fn set_blend_constants(&mut self, ColorValue);

    ///
    fn begin_renderpass<T>(
        &mut self,
        render_pass: &B::RenderPass,
        framebuffer: &B::Framebuffer,
        render_area: Rect,
        clear_values: T,
        first_subpass: SubpassContents,
    ) where
        T: IntoIterator,
        T::Item: Borrow<ClearValue>
    {
        let clear_values = clear_values
            .into_iter()
            .map(|cv| {
                match *cv.borrow() {
                    ClearValue::Color(ClearColor::Float(cv)) =>
                        ClearValueRaw { color: ClearColorRaw { float32: cv }},
                    ClearValue::Color(ClearColor::Int(cv)) =>
                        ClearValueRaw { color: ClearColorRaw { int32: cv }},
                    ClearValue::Color(ClearColor::Uint(cv)) =>
                        ClearValueRaw { color: ClearColorRaw { uint32: cv }},
                    ClearValue::DepthStencil(ClearDepthStencil(depth, stencil)) =>
                        ClearValueRaw { depth_stencil: ClearDepthStencilRaw { depth, stencil }},
                }
            });

        self.begin_renderpass_raw(
            render_pass,
            framebuffer,
            render_area,
            clear_values,
            first_subpass,
        )
    }

    ///
    fn begin_renderpass_raw<T>(
        &mut self,
        render_pass: &B::RenderPass,
        framebuffer: &B::Framebuffer,
        render_area: Rect,
        clear_values: T,
        first_subpass: SubpassContents,
    ) where
        T: IntoIterator,
        T::Item: Borrow<ClearValueRaw>;

    ///
    fn next_subpass(&mut self, contents: SubpassContents);

    ///
    fn end_renderpass(&mut self);

    /// Bind a graphics pipeline.
    ///
    /// # Errors
    ///
    /// This function does not return an error. Invalid usage of this function
    /// will result in an error on `finish`.
    ///
    /// - Command buffer must be in recording state.
    /// - Only queues with graphics capability support this function.
    fn bind_graphics_pipeline(&mut self, &B::GraphicsPipeline);

    ///
    fn bind_graphics_descriptor_sets<'a, T>(
        &mut self,
        layout: &B::PipelineLayout,
        first_set: usize,
        sets: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<B::DescriptorSet>;

    /// Bind a compute pipeline.
    ///
    /// # Errors
    ///
    /// This function does not return an error. Invalid usage of this function
    /// will result in an error on `finish`.
    ///
    /// - Command buffer must be in recording state.
    /// - Only queues with compute capability support this function.
    fn bind_compute_pipeline(&mut self, &B::ComputePipeline);

    ///
    fn bind_compute_descriptor_sets<T>(
        &mut self,
        layout: &B::PipelineLayout,
        first_set: usize,
        sets: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<B::DescriptorSet>;

    /// Execute a workgroup in the compute pipeline.
    ///
    /// # Errors
    ///
    /// This function does not return an error. Invalid usage of this function
    /// will result in an error on `finish`.
    ///
    /// - Command buffer must be in recording state.
    /// - A compute pipeline must be bound using `bind_compute_pipeline`.
    /// - Only queues with compute capability support this function.
    /// - This function must be called outside of a renderpass.
    /// - `(x, y, z)` must be less than or equal to `Limits::max_compute_group_size`
    ///
    /// TODO:
    fn dispatch(&mut self, x: u32, y: u32, z: u32);

    ///
    fn dispatch_indirect(&mut self, buffer: &B::Buffer, offset: u64);

    ///
    fn copy_buffer<T>(
        &mut self,
        src: &B::Buffer,
        dst: &B::Buffer,
        regions: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<BufferCopy>;

    ///
    fn copy_image<T>(
        &mut self,
        src: &B::Image,
        src_layout: ImageLayout,
        dst: &B::Image,
        dst_layout: ImageLayout,
        regions: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<ImageCopy>;

    ///
    fn copy_buffer_to_image<T>(
        &mut self,
        src: &B::Buffer,
        dst: &B::Image,
        dst_layout: ImageLayout,
        regions: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<BufferImageCopy>;

    ///
    fn copy_image_to_buffer<T>(
        &mut self,
        src: &B::Image,
        src_layout: ImageLayout,
        dst: &B::Buffer,
        regions: T,
    ) where
        T: IntoIterator,
        T::Item: Borrow<BufferImageCopy>;

    ///
    fn draw(
        &mut self,
        vertices: Range<VertexCount>,
        instances: Range<InstanceCount>,
    );

    ///
    fn draw_indexed(
        &mut self,
        indices: Range<IndexCount>,
        base_vertex: VertexOffset,
        instances: Range<InstanceCount>,
    );

    ///
    fn draw_indirect(
        &mut self,
        buffer: &B::Buffer,
        offset: u64,
        draw_count: u32,
        stride: u32,
    );

    ///
    fn draw_indexed_indirect(
        &mut self,
        buffer: &B::Buffer,
        offset: u64,
        draw_count: u32,
        stride: u32,
    );

    ///
    fn begin_query(&mut self, query: Query<B>, flags: QueryControl);

    ///
    fn end_query(&mut self, query: Query<B>);

    ///
    fn reset_query_pool(&mut self, pool: &B::QueryPool, queries: Range<QueryId>);

    ///
    fn write_timestamp(&mut self, pso::PipelineStage, Query<B>);

    ///
    fn push_graphics_constants(
        &mut self,
        layout: &B::PipelineLayout,
        stages: pso::ShaderStageFlags,
        offset: u32,
        constants: &[u32],
    );

    ///
    fn push_compute_constants(
        &mut self,
        layout: &B::PipelineLayout,
        offset: u32,
        constants: &[u32],
    );
}
<|endoftext|>


<commit_before>//! A model for spaced repetition system learning.
//!
//! This crate maintains a database of problems, and some notions of intervals and a bit of history
//! in order to allow them to be used for spaced repetition learning.
//!
//! The key type is the `Store` which is associated with a single database file.
//!
//! A given `Problem` is simply two text strings, a question and an answer.  It is up to the user
//! of this crate to determine what these mean.  It could be as simple as text to display to the
//! user, or some encoded data to determine the correct answer.
//!
//! The client of this library should be able to ask questions, determine if the answer is correct,
//! and return a 1-4 rating of how well the user answered the question.  In some cases, it may only
//! make sense to return either a 1 for an incorrect answer, or a 4 for a correct answer.

#![deny(missing_docs)]

extern crate rand;
extern crate rusqlite;

use rand::{Rng, thread_rng};
use rusqlite::{Connection, Transaction};
use std::error;
use std::path::Path;
use std::result;
use std::time::{SystemTime, UNIX_EPOCH};

/// A wrapper around the result type for all results returned.  Currently, the errors are just
/// boxed, and this should be improved.
pub type Result<T> = result::Result<T, Box<error::Error + Send + Sync>>;

/// A Store holds problems in a database (and holds the handle to the database).
pub struct Store {
    /// The connection to the database.
    conn: Connection,
}

impl Store {
    /// Create a new store at the given path.  Will return an error if the database has already
    /// been created.
    pub fn create<P: AsRef<Path>>(path: P) -> Result<Store> {
        let mut conn = Connection::open(path)?;

        {
            let tx = conn.transaction()?;
            tx.execute("CREATE TABLE probs (id INTEGER PRIMARY KEY,
                question TEXT UNIQUE,
                answer TEXT NOT NULL)",
                         &[])?;
            tx.execute("CREATE TABLE learning (probid INTEGER PRIMARY KEY REFERENCES probs (id),
                next REAL NOT NULL,
                interval REAL NOT NULL)",
                         &[])?;
            tx.execute("CREATE INDEX learning_next ON learning (next)", &[])?;
            tx.execute("CREATE TABLE schema_version (version TEXT NOT NULL)", &[])?;
            tx.execute("INSERT INTO schema_version VALUES (?)", &[&"20170408B"])?;
            tx.commit()?;
        }

        conn.execute("PRAGMA foreign_keys = ON", &[])?;

        Ok(Store { conn: conn })
    }

    /// Open an existing (and ideally already populated) `Store`.
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Store> {
        let conn = Connection::open(path)?;
        {
            let mut stmt = conn.prepare(" SELECT version FROM schema_version")?;
            let mut rows = stmt.query_map(&[], |row| {
                    let vers: String = row.get(0);
                    vers
                })?;
            match rows.next() {
                Some(text) => {
                    let text = text?;
                    if text != "20170408B" {
                        panic!("schema version mismatch {}", text);
                    }
                }
                None => panic!("No schema present"),
            }
            match rows.next() {
                Some(_) => panic!("Multiple rows in schema_version"),
                None => (),
            }
        }
        Ok(Store { conn: conn })
    }

    /// Return a populator that can be used to more rapidly populate the data.  The population will
    /// be done within the context of a single sqlite3 database transaction.
    pub fn populate(&mut self) -> Result<Populator> {
        let tx = self.conn.transaction()?;
        Ok(Populator { tx: tx })
    }

    /// Query for the next problem that has expired.  If Some, then this is the next problem that
    /// should be asked.
    pub fn get_next(&mut self) -> Result<Option<Problem>> {
        let mut stmt = self.conn
            .prepare("
            SELECT id, question, answer, next, interval
            FROM probs JOIN learning
            WHERE probs.id = learning.probid
                AND next <= ?
            ORDER BY next
            LIMIT 1")?;
        let mut rows = stmt.query_map(&[&now()], |row| {
                Problem {
                    id: row.get(0),
                    question: row.get(1),
                    answer: row.get(2),
                    next: row.get(3),
                    interval: row.get(4),
                }
            })?;
        match rows.next() {
            Some(row) => Ok(Some(row?)),
            None => Ok(None),
        }
    }

    /// Get a problem that hasn't started being learned.  The interval and "next" will be set
    /// appropriately for a new word.
    pub fn get_new(&mut self) -> Result<Option<Problem>> {
        let mut stmt = self.conn
            .prepare("
            SELECT id, question, answer
            FROM probs
            WHERE ID NOT IN (SELECT probid FROM learning)
            ORDER BY id
            LIMIT 1")?;
        let mut rows = stmt.query_map(&[], |row| {
                Problem {
                    id: row.get(0),
                    question: row.get(1),
                    answer: row.get(2),
                    next: now(),
                    interval: 5.0,
                }
            })?;
        match rows.next() {
            Some(row) => Ok(Some(row?)),
            None => Ok(None),
        }
    }

    /// Update a word, based on a learning factor.  The scale is 1..4, with 1 being totally
    /// incorrect, and 4 being totally correct.
    pub fn update(&mut self, prob: Problem, factor: u8) -> Result<()> {
        let factor = match factor {
            1 => 0.25,
            2 => 0.9,
            3 => 1.2,
            4 => 2.2,
            _ => panic!("Invalid factor: {}", factor),
        };

        let mut prob = prob;

        let mut rng = thread_rng();
        let interval = prob.interval;
        // Compute the interval, capping with a minimum of 5 seconds.
        prob.interval = (interval * factor * rng.gen_range(0.75, 1.25)).max(5.0);
        prob.next = now() + prob.interval;

        let tx = self.conn.transaction()?;
        tx.execute("INSERT OR REPLACE INTO learning VALUES (?, ?, ?)",
                     &[&prob.id, &prob.next, &prob.interval])?;
        tx.commit()?;

        Ok(())
    }

    /// Retrieve statistics about the words available.
    pub fn get_counts(&self) -> Result<Counts> {
        let unlearned: i64 = self.conn
            .query_row("
                SELECT COUNT(*)
                FROM probs
                WHERE id NOT IN (SELECT probid FROM learning)",
                       &[],
                       |row| row.get(0))?;

        let cur = now();

        let active: i64 = self.conn
            .query_row("
                SELECT COUNT(*)
                FROM probs JOIN learning
                WHERE probs.id = learning.probid
                    AND next <= ?",
                       &[&cur],
                       |row| row.get(0))?;

        let later: i64 = self.conn
            .query_row("
                SELECT COUNT(*)
                FROM probs JOIN learning
                WHERE probs.id = learning.probid
                    and next > ?",
                       &[&cur],
                       |row| row.get(0))?;

        let mut interval = 1.0;
        let mut prior = 0.0;
        let buckets: Vec<_> = COUNT_BUCKETS
            .iter()
            .map(|buk| {
                interval *= buk.limit;
                let count: i64 = self.conn
                    .query_row("
                    SELECT COUNT(*)
                    FROM probs JOIN learning
                    WHERE probs.id = learning.probid
                        AND interval <= ? AND interval > ?",
                               &[&interval, &prior],
                               |row| row.get(0))
                    .unwrap();
                prior = interval;
                Bucket {
                    name: buk.name,
                    count: count as usize,
                }
            })
            .collect();

        Ok(Counts {
               active: active as usize,
               later: later as usize,
               unlearned: unlearned as usize,
               buckets: buckets,
           })
    }
}

/// Statistics about the current state of the problems.
pub struct Counts {
    /// The number of "Active" problems.  Something is considered active if it is due for learning.
    pub active: usize,
    /// The number of problems that are being learned, but aren't ready to be asked again.
    pub later: usize,
    /// The number of problems the user has never been shown.
    pub unlearned: usize,

    /// Counts of all of the problems, grouped into histogram buckets based on the learning
    /// interval of the problem.  The bucket names are a short description of the interval.
    pub buckets: Vec<Bucket>,
}

/// A single histogram bucket describing a number of problems of a given category.
pub struct Bucket {
    /// A short description of this bucket.
    pub name: &'static str,
    /// The number of problems in this bucket.
    pub count: usize,
}

struct BucketBin {
    name: &'static str,
    limit: f64,
}

static COUNT_BUCKETS: &'static [BucketBin] = &[BucketBin {
     name: "sec",
     limit: 60.0,
 },
 BucketBin {
     name: "min",
     limit: 60.0,
 },
 BucketBin {
     name: "hr",
     limit: 24.0,
 },
 BucketBin {
     name: "day",
     limit: 30.0,
 },
 BucketBin {
     name: "mon",
     limit: 1.0e30,
 }];

/// A single problem retrieved.  `next` and `interval` are currently public, but should not be
/// modified by outside code.  Updates only happen when the `update` method is called on the store.
pub struct Problem {
    id: i64,
    /// The text of the question.  Can be an arbitrary string, encoded in a way that is meaningful
    /// to the client of this crate.
    pub question: String,
    /// Likewise, the correct answer to the question.
    pub answer: String,
    next: f64, // TODO: Make these private, and provide a query.
    interval: f64,
}

impl Problem {
    /// Get the Posix timestamp for when we will next ask this Problem.
    pub fn get_next(&self) -> f64 {
        self.next
    }

    /// Get the time interval, in seconds, between asks.  This will be adjusted by `update` each
    /// time a question is answered.
    pub fn get_interval(&self) -> f64 {
        self.interval
    }
}

/// A helper to populate a `Store` with `Problem`s.
pub struct Populator<'a> {
    tx: Transaction<'a>,
}

impl<'a> Populator<'a> {
    /// Add a single unlearned problem to the store.
    pub fn add_problem(&mut self, question: &str, answer: &str) -> Result<()> {
        self.tx
            .execute("INSERT INTO probs (question, answer) VALUES (?, ?)",
                     &[&question, &answer])?;
        Ok(())
    }

    /// Add a problem that is in the process of being learned.  The 'next' value is the unix time
    /// that the question should be asked again, and 'interval' is the current interval.
    ///
    /// This is used when importing problems from another system, where some of the problems may
    /// already be learned.
    pub fn add_learning_problem(&mut self,
                                question: &str,
                                answer: &str,
                                next: f64,
                                interval: f64)
                                -> Result<()> {
        self.tx
            .execute("INSERT INTO probs (question, answer) VALUES (?, ?)",
                     &[&question, &answer])?;
        self.tx
            .execute("INSERT INTO learning VALUES (?, ?, ?)",
                     &[&self.tx.last_insert_rowid(), &next, &interval])?;
        Ok(())
    }

    /// Consume the `Populator` and commit.  If the Populator is dropped without calling `commit`,
    /// any changes made by it will be rolled back.
    pub fn commit(self) -> Result<()> {
        self.tx.commit()?;
        Ok(())
    }
}

/// Get the current time in the Posix timestamp format.  This is the same time value used by the
/// 'next' field of the Problems, and can be used, for example, during population to set already
/// partially-learned problems.
pub fn now() -> f64 {
    let stamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
    let sec = stamp.as_secs();
    let nsec = stamp.subsec_nanos();

    sec as f64 + (nsec as f64 / 1.0e9)
}
<commit_msg>timelearn: Fix comment<commit_after>//! A model for spaced repetition system learning.
//!
//! This crate maintains a database of problems, and some notions of intervals and a bit of history
//! in order to allow them to be used for spaced repetition learning.
//!
//! The key type is the `Store` which is associated with a single database file.
//!
//! A given `Problem` is simply two text strings, a question and an answer.  It is up to the user
//! of this crate to determine what these mean.  It could be as simple as text to display to the
//! user, or some encoded data to determine the correct answer.
//!
//! The client of this library should be able to ask questions, determine if the answer is correct,
//! and return a 1-4 rating of how well the user answered the question.  In some cases, it may only
//! make sense to return either a 1 for an incorrect answer, or a 4 for a correct answer.

#![deny(missing_docs)]

extern crate rand;
extern crate rusqlite;

use rand::{Rng, thread_rng};
use rusqlite::{Connection, Transaction};
use std::error;
use std::path::Path;
use std::result;
use std::time::{SystemTime, UNIX_EPOCH};

/// A wrapper around the result type for all results returned.  Currently, the errors are just
/// boxed, and this should be improved.
pub type Result<T> = result::Result<T, Box<error::Error + Send + Sync>>;

/// A Store holds problems in a database (and holds the handle to the database).
pub struct Store {
    /// The connection to the database.
    conn: Connection,
}

impl Store {
    /// Create a new store at the given path.  Will return an error if the database has already
    /// been created.
    pub fn create<P: AsRef<Path>>(path: P) -> Result<Store> {
        let mut conn = Connection::open(path)?;

        {
            let tx = conn.transaction()?;
            tx.execute("CREATE TABLE probs (id INTEGER PRIMARY KEY,
                question TEXT UNIQUE,
                answer TEXT NOT NULL)",
                         &[])?;
            tx.execute("CREATE TABLE learning (probid INTEGER PRIMARY KEY REFERENCES probs (id),
                next REAL NOT NULL,
                interval REAL NOT NULL)",
                         &[])?;
            tx.execute("CREATE INDEX learning_next ON learning (next)", &[])?;
            tx.execute("CREATE TABLE schema_version (version TEXT NOT NULL)", &[])?;
            tx.execute("INSERT INTO schema_version VALUES (?)", &[&"20170408B"])?;
            tx.commit()?;
        }

        conn.execute("PRAGMA foreign_keys = ON", &[])?;

        Ok(Store { conn: conn })
    }

    /// Open an existing (and ideally already populated) `Store`.
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Store> {
        let conn = Connection::open(path)?;
        {
            let mut stmt = conn.prepare(" SELECT version FROM schema_version")?;
            let mut rows = stmt.query_map(&[], |row| {
                    let vers: String = row.get(0);
                    vers
                })?;
            match rows.next() {
                Some(text) => {
                    let text = text?;
                    if text != "20170408B" {
                        panic!("schema version mismatch {}", text);
                    }
                }
                None => panic!("No schema present"),
            }
            match rows.next() {
                Some(_) => panic!("Multiple rows in schema_version"),
                None => (),
            }
        }
        Ok(Store { conn: conn })
    }

    /// Return a populator that can be used to more rapidly populate the data.  The population will
    /// be done within the context of a single sqlite3 database transaction.
    pub fn populate(&mut self) -> Result<Populator> {
        let tx = self.conn.transaction()?;
        Ok(Populator { tx: tx })
    }

    /// Query for the next problem that has expired.  If Some, then this is the next problem that
    /// should be asked.
    pub fn get_next(&mut self) -> Result<Option<Problem>> {
        let mut stmt = self.conn
            .prepare("
            SELECT id, question, answer, next, interval
            FROM probs JOIN learning
            WHERE probs.id = learning.probid
                AND next <= ?
            ORDER BY next
            LIMIT 1")?;
        let mut rows = stmt.query_map(&[&now()], |row| {
                Problem {
                    id: row.get(0),
                    question: row.get(1),
                    answer: row.get(2),
                    next: row.get(3),
                    interval: row.get(4),
                }
            })?;
        match rows.next() {
            Some(row) => Ok(Some(row?)),
            None => Ok(None),
        }
    }

    /// Get a problem that hasn't started being learned.  The interval and "next" will be set
    /// appropriately for a new word.
    pub fn get_new(&mut self) -> Result<Option<Problem>> {
        let mut stmt = self.conn
            .prepare("
            SELECT id, question, answer
            FROM probs
            WHERE ID NOT IN (SELECT probid FROM learning)
            ORDER BY id
            LIMIT 1")?;
        let mut rows = stmt.query_map(&[], |row| {
                Problem {
                    id: row.get(0),
                    question: row.get(1),
                    answer: row.get(2),
                    next: now(),
                    interval: 5.0,
                }
            })?;
        match rows.next() {
            Some(row) => Ok(Some(row?)),
            None => Ok(None),
        }
    }

    /// Update a word, based on a learning factor.  The scale is 1..4, with 1 being totally
    /// incorrect, and 4 being totally correct.
    pub fn update(&mut self, prob: Problem, factor: u8) -> Result<()> {
        let factor = match factor {
            1 => 0.25,
            2 => 0.9,
            3 => 1.2,
            4 => 2.2,
            _ => panic!("Invalid factor: {}", factor),
        };

        let mut prob = prob;

        let mut rng = thread_rng();
        let interval = prob.interval;
        // Compute the interval, capping with a minimum of 5 seconds.
        prob.interval = (interval * factor * rng.gen_range(0.75, 1.25)).max(5.0);
        prob.next = now() + prob.interval;

        let tx = self.conn.transaction()?;
        tx.execute("INSERT OR REPLACE INTO learning VALUES (?, ?, ?)",
                     &[&prob.id, &prob.next, &prob.interval])?;
        tx.commit()?;

        Ok(())
    }

    /// Retrieve statistics about the words available.
    pub fn get_counts(&self) -> Result<Counts> {
        let unlearned: i64 = self.conn
            .query_row("
                SELECT COUNT(*)
                FROM probs
                WHERE id NOT IN (SELECT probid FROM learning)",
                       &[],
                       |row| row.get(0))?;

        let cur = now();

        let active: i64 = self.conn
            .query_row("
                SELECT COUNT(*)
                FROM probs JOIN learning
                WHERE probs.id = learning.probid
                    AND next <= ?",
                       &[&cur],
                       |row| row.get(0))?;

        let later: i64 = self.conn
            .query_row("
                SELECT COUNT(*)
                FROM probs JOIN learning
                WHERE probs.id = learning.probid
                    and next > ?",
                       &[&cur],
                       |row| row.get(0))?;

        let mut interval = 1.0;
        let mut prior = 0.0;
        let buckets: Vec<_> = COUNT_BUCKETS
            .iter()
            .map(|buk| {
                interval *= buk.limit;
                let count: i64 = self.conn
                    .query_row("
                    SELECT COUNT(*)
                    FROM probs JOIN learning
                    WHERE probs.id = learning.probid
                        AND interval <= ? AND interval > ?",
                               &[&interval, &prior],
                               |row| row.get(0))
                    .unwrap();
                prior = interval;
                Bucket {
                    name: buk.name,
                    count: count as usize,
                }
            })
            .collect();

        Ok(Counts {
               active: active as usize,
               later: later as usize,
               unlearned: unlearned as usize,
               buckets: buckets,
           })
    }
}

/// Statistics about the current state of the problems.
pub struct Counts {
    /// The number of "Active" problems.  Something is considered active if it is due for learning.
    pub active: usize,
    /// The number of problems that are being learned, but aren't ready to be asked again.
    pub later: usize,
    /// The number of problems the user has never been shown.
    pub unlearned: usize,

    /// Counts of all of the problems, grouped into histogram buckets based on the learning
    /// interval of the problem.  The bucket names are a short description of the interval.
    pub buckets: Vec<Bucket>,
}

/// A single histogram bucket describing a number of problems of a given category.
pub struct Bucket {
    /// A short description of this bucket.
    pub name: &'static str,
    /// The number of problems in this bucket.
    pub count: usize,
}

struct BucketBin {
    name: &'static str,
    limit: f64,
}

static COUNT_BUCKETS: &'static [BucketBin] = &[BucketBin {
     name: "sec",
     limit: 60.0,
 },
 BucketBin {
     name: "min",
     limit: 60.0,
 },
 BucketBin {
     name: "hr",
     limit: 24.0,
 },
 BucketBin {
     name: "day",
     limit: 30.0,
 },
 BucketBin {
     name: "mon",
     limit: 1.0e30,
 }];

/// A single problem retrieved.
pub struct Problem {
    id: i64,
    /// The text of the question.  Can be an arbitrary string, encoded in a way that is meaningful
    /// to the client of this crate.
    pub question: String,
    /// Likewise, the correct answer to the question.
    pub answer: String,
    next: f64, // TODO: Make these private, and provide a query.
    interval: f64,
}

impl Problem {
    /// Get the Posix timestamp for when we will next ask this Problem.
    pub fn get_next(&self) -> f64 {
        self.next
    }

    /// Get the time interval, in seconds, between asks.  This will be adjusted by `update` each
    /// time a question is answered.
    pub fn get_interval(&self) -> f64 {
        self.interval
    }
}

/// A helper to populate a `Store` with `Problem`s.
pub struct Populator<'a> {
    tx: Transaction<'a>,
}

impl<'a> Populator<'a> {
    /// Add a single unlearned problem to the store.
    pub fn add_problem(&mut self, question: &str, answer: &str) -> Result<()> {
        self.tx
            .execute("INSERT INTO probs (question, answer) VALUES (?, ?)",
                     &[&question, &answer])?;
        Ok(())
    }

    /// Add a problem that is in the process of being learned.  The 'next' value is the unix time
    /// that the question should be asked again, and 'interval' is the current interval.
    ///
    /// This is used when importing problems from another system, where some of the problems may
    /// already be learned.
    pub fn add_learning_problem(&mut self,
                                question: &str,
                                answer: &str,
                                next: f64,
                                interval: f64)
                                -> Result<()> {
        self.tx
            .execute("INSERT INTO probs (question, answer) VALUES (?, ?)",
                     &[&question, &answer])?;
        self.tx
            .execute("INSERT INTO learning VALUES (?, ?, ?)",
                     &[&self.tx.last_insert_rowid(), &next, &interval])?;
        Ok(())
    }

    /// Consume the `Populator` and commit.  If the Populator is dropped without calling `commit`,
    /// any changes made by it will be rolled back.
    pub fn commit(self) -> Result<()> {
        self.tx.commit()?;
        Ok(())
    }
}

/// Get the current time in the Posix timestamp format.  This is the same time value used by the
/// 'next' field of the Problems, and can be used, for example, during population to set already
/// partially-learned problems.
pub fn now() -> f64 {
    let stamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
    let sec = stamp.as_secs();
    let nsec = stamp.subsec_nanos();

    sec as f64 + (nsec as f64 / 1.0e9)
}
<|endoftext|>