<commit_before>use tokio::sync::mpsc;

// SQlite worker thread
use futures::executor::block_on_stream;

use rusqlite as rs;
use tokio::sync::oneshot;
use std::collections::HashSet;
use std::path::Path;
use unicase::UniCase;

use unicode_normalization::{
    UnicodeNormalization,
    is_nfc_quick,
    IsNormalized,
};

use chrono::prelude::{Utc, DateTime};

use crate::parser::karma::KST;
use crate::parser::karma::Karma;

// Normalize any incoming string to be stored in the database
fn normalize(input: &str) -> String {
    match is_nfc_quick(input.chars()) {
        IsNormalized::Yes => input.to_string(),
        _ => input.nfc().collect(),
    }
}

// Custom Type to handle unicase for the query users
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct KarmaName(UniCase<String>);

impl KarmaName {
    pub fn new(name: &str) -> KarmaName {
        KarmaName(UniCase::new(normalize(name)))
    }
}

impl ToString for KarmaName {
    fn to_string(&self) -> String {
        let KarmaName(n) = self;
        n.to_string()
    }
}

// TODO: should be able to replace all of these stuff maybe by some trait or some other tricks
// so that we can have a extensible query where i define the query/process/result and then send it
// to the query engine and get the result back in a good format
#[derive(Debug)]
pub enum RunQuery {
    TopNDenormalized {
        karma_col: KarmaCol,
        karma_typ: KarmaTyp,
        limit: u32,
        ord: OrdQuery
    },
    RankingDenormalized {
        karma_col: KarmaCol,
        karma_typ: KarmaTyp,
        user: KarmaName, // TODO: make it support a Set of user
    },
    Count(KarmaCol),
    Partial {
        karma_col: KarmaCol,
        users: HashSet<KarmaName>,
    },
    AddKarma {
        timestamp: DateTime<Utc>,
        user_id: String,
        username: Option<KarmaName>,
        real_name: Option<KarmaName>,
        channel_id: Option<String>,
        karma: Vec<KST>,
    },
}

#[derive(Debug, Clone, Copy)]
pub enum OrdQuery { Asc, Desc }

#[derive(Debug, Clone, Copy)]
pub enum KarmaCol { Given, Recieved }

#[derive(Debug, Clone, Copy)]
pub enum KarmaTyp { Total, Side }

#[derive(Debug)]
pub enum ResQuery {
    // RankingDenormalized
    OCount(Option<u32>),
    // Count
    Count(u32),
    // TopNDenormalized
    TopN(Vec<(String, i32)>),
    // Partial
    Partial(Vec<(String, i32, i32, i32)>),
}

fn maybe_send(
    res_tx: Option<oneshot::Sender<ResQuery>>,
    res: ResQuery,
) -> Result<(), String> {
    match res_tx {
        Some(tx) => tx.send(res).map_err(|_| "Cant send ResQuery".to_string()),
        None     => Ok(()),
    }
}

// TODO: additional check for shutting down
pub fn process_queries(
    filename: &Path,
    sql_rx: mpsc::Receiver<(RunQuery, Option<oneshot::Sender<ResQuery>>)>
) -> Result<(), Box<dyn std::error::Error>> {
    let mut block_sql_rx = block_on_stream(sql_rx);

    let mut conn = rs::Connection::open_with_flags(
        filename,
        rs::OpenFlags::SQLITE_OPEN_READ_WRITE
    ).expect(&format!("Connection error: {:?}", filename.to_str()));

    // Listen for inbound query
    while let Some((query, res_tx)) = block_sql_rx.next() {
        match query {
            RunQuery::TopNDenormalized{karma_col, karma_typ, limit, ord} => {
                let table = match karma_col {
                    KarmaCol::Given    => "karma_given_count",
                    KarmaCol::Recieved => "karma_received_count",
                };

                let t_col = match karma_typ {
                    KarmaTyp::Total => "up - down",
                    KarmaTyp::Side  => "side",
                };

                let q_ord = match ord {
                    OrdQuery::Asc  => "ASC",
                    OrdQuery::Desc => "DESC",
                };

                let mut stmt = conn.prepare(&format!(
                        "SELECT name, {t_col} as total FROM {table} ORDER BY total {q_ord} LIMIT {limit}",
                        t_col=t_col,
                        table=table,
                        q_ord=q_ord,
                        limit=limit
                )).unwrap();
                let mut rows = stmt.query(rs::NO_PARAMS).unwrap();

                let mut ret: Vec<(String, i32)> = vec![];

                while let Ok(Some(row)) = rows.next() {
                    let name: String = row.get(0).unwrap();
                    let count: i32 = row.get(1).unwrap();

                    ret.push((name.clone(), count));
                }
                maybe_send(res_tx, ResQuery::TopN(ret))?;
            },
            RunQuery::RankingDenormalized{karma_col, karma_typ, user} => {
                let table = match karma_col {
                    KarmaCol::Given    => "karma_given_count",
                    KarmaCol::Recieved => "karma_received_count",
                };

                let t_col1 = match karma_typ {
                    KarmaTyp::Total => "up - down",
                    KarmaTyp::Side  => "side",
                };

                let t_col2 = match karma_typ {
                    KarmaTyp::Total => "kcol2.up - kcol2.down",
                    KarmaTyp::Side  => "kcol2.side",
                };

                let mut stmt = conn.prepare(&format!(
                    "SELECT CASE WHEN (
                        EXISTS (SELECT TRUE FROM {table} WHERE name = ?1)
                    ) THEN (
                        SELECT (COUNT(name) + 1) FROM {table} WHERE (
                            {t_col1}
                        ) > (
                            SELECT ({t_col2}) FROM {table} AS kcol2 WHERE kcol2.name = ?2
                        )
                    ) ELSE NULL END",
                    table=table, t_col1=t_col1, t_col2=t_col2
                )).unwrap();
                let user = user.to_string();
                let mut rows = stmt.query(rs::params![user, user]).unwrap();

                if let Ok(Some(row)) = rows.next() {
                    let count: Option<u32> = row.get(0).ok();

                    maybe_send(res_tx, ResQuery::OCount(count))?;
                } else {
                    eprintln!(
                        "ERROR [Sql Worker]: RankingDenormalized - karma_col: {:?}, karma_typ: {:?}, user: {:?}",
                        karma_col, karma_typ, user
                    );
                }
            },
            RunQuery::Count(karma_col) => {
                let table = match karma_col {
                    KarmaCol::Given => "karma_given_count",
                    KarmaCol::Recieved => "karma_received_count",
                };

                let mut stmt = conn.prepare(&format!(
                    "SELECT COUNT(name) FROM {table}",
                    table=table
                )).unwrap();
                let mut rows = stmt.query(rs::NO_PARAMS).unwrap();

                if let Ok(Some(row)) = rows.next() {
                    let count: u32 = row.get(0).unwrap();

                    maybe_send(res_tx, ResQuery::Count(count))?;
                } else {
                    eprintln!("ERROR [Sql Worker]: Count - ERROR - karma_col: {:?}", karma_col);
                }
            },
            RunQuery::Partial{karma_col, users} => {
                let table = match karma_col {
                    KarmaCol::Given    => "karma_given_count",
                    KarmaCol::Recieved => "karma_received_count",
                };

                // Hack to insert enough parameterizers into the query
                let p_user = {
                    let mut p_user: String = "?1".to_string();
                    for i in 2..(users.len() + 1) {
                        p_user.push_str(&format!(", ?{}", i));
                    }
                    p_user
                };

                // Params
                let param: Vec<String> = users.iter().map(|i| i.to_string()).collect();

                let mut stmt = conn.prepare(&format!(
                    "SELECT name, up, down, side FROM {table} WHERE name in ({p_user}) ORDER BY name DESC",
                    table=table, p_user=p_user
                )).unwrap();
                let mut rows = stmt.query(&param).unwrap();

                // A bit more additional work than usual
                let mut ret: Vec<(String, i32, i32, i32)> = vec![];
                let mut has: HashSet<KarmaName> = HashSet::new();

                while let Ok(Some(row)) = rows.next() {
                    let name: String = row.get(0).unwrap();
                    let up: i32 = row.get(1).unwrap();
                    let down: i32 = row.get(2).unwrap();
                    let side: i32 = row.get(3).unwrap();

                    has.insert(KarmaName::new(&name));
                    ret.push((name, up, down, side));
                }

                // Evaulate if there's missing ones and add if so
                // TODO: this should be case insensitive (Ie database can return B and we have b)
                for n in users.difference(&has) {
                    ret.push((n.to_string(), 0, 0, 0));
                }
                maybe_send(res_tx, ResQuery::Partial(ret))?;
            },
            RunQuery::AddKarma{timestamp, user_id, username, real_name, channel_id, karma} => {
                let un = username.map(|un| un.to_string()).unwrap_or("".to_string());
                let urn = real_name.map(|rn| rn.to_string()).unwrap_or("".to_string());

                let nick_id: i64 = {
                    let mut stmt = conn.prepare("SELECT id FROM nick_metadata WHERE username = ?").unwrap();
                    let mut rows = stmt.query(rs::params![user_id]).unwrap();

                    if let Ok(Some(row)) = rows.next() {
                        row.get(0).unwrap()
                    } else {
                        let mut stmt = conn.prepare(
                            "INSERT INTO nick_metadata (cleaned_nick, full_name, username, hostmask) VALUES (?, ?, ?, ?)"
                        ).unwrap();

                        stmt.insert(rs::params![un, urn, user_id, "SlackServer"]).unwrap()
                    }
                };

                let channel_id: Option<i64> = if let Some(cid) = channel_id {
                    let mut stmt = conn.prepare("SELECT id FROM chan_metadata WHERE channel = ?").unwrap();
                    let mut rows = stmt.query(rs::params![cid]).unwrap();

                    if let Ok(Some(row)) = rows.next() {
                        row.get(0).ok()
                    } else {
                        let mut stmt = conn.prepare("INSERT INTO chan_metadata (channel) VALUES (?)").unwrap();
                        stmt.insert(rs::params![cid]).ok()
                    }
                } else {
                    None
                };

                // Shove the karma into the db now
                let tx = conn.transaction()?;
                {
                    let mut stmt = tx.prepare(
                        "INSERT INTO votes
                            (voted_at, by_whom_name, for_what_name, amount, nick_id, chan_id)
                        VALUES
                            (?, ?, ?, ?, ?, ?)"
                    ).unwrap();

                    for KST(karma_text, k) in karma.iter() {
                        let amount: i8 = match k {
                            Karma::Up   => 1,
                            Karma::Down => -1,
                            Karma::Side => 0,
                        };

                        let ts = timestamp.to_rfc3339();
                        let ts = ts.trim_end_matches("+00:00");
                        let karma_text = normalize(karma_text);

                        // TODO: better handle failure of username, maybe we should make username
                        // mandatory before inserting?
                        match channel_id {
                            Some(cid) => stmt.insert(
                                rs::params![ts, un, karma_text, amount, nick_id, cid]
                            ).unwrap(),
                            None      => stmt.insert(
                                rs::params![ts, un, karma_text, amount, nick_id, &rs::types::Null]
                            ).unwrap(),
                        };
                    }
                }
                tx.commit()?;
            },

        }
    }

    Ok(())
}
<commit_msg>initial implementation of the reacji queries<commit_after>use tokio::sync::mpsc;

// SQlite worker thread
use futures::executor::block_on_stream;

use rusqlite as rs;
use tokio::sync::oneshot;
use std::collections::HashSet;
use std::path::Path;
use unicase::UniCase;

use unicode_normalization::{
    UnicodeNormalization,
    is_nfc_quick,
    IsNormalized,
};

use chrono::prelude::{Utc, DateTime};

use crate::parser::karma::KST;
use crate::parser::karma::Karma;

// Normalize any incoming string to be stored in the database
fn normalize(input: &str) -> String {
    match is_nfc_quick(input.chars()) {
        IsNormalized::Yes => input.to_string(),
        _ => input.nfc().collect(),
    }
}

// Custom Type to handle unicase for the query users
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct KarmaName(UniCase<String>);

impl KarmaName {
    pub fn new(name: &str) -> KarmaName {
        KarmaName(UniCase::new(normalize(name)))
    }
}

impl ToString for KarmaName {
    fn to_string(&self) -> String {
        let KarmaName(n) = self;
        n.to_string()
    }
}

// TODO: should be able to replace all of these stuff maybe by some trait or some other tricks
// so that we can have a extensible query where i define the query/process/result and then send it
// to the query engine and get the result back in a good format
#[derive(Debug)]
pub enum RunQuery {
    TopNDenormalized {
        karma_col: KarmaCol,
        karma_typ: KarmaTyp,
        limit: u32,
        ord: OrdQuery
    },
    RankingDenormalized {
        karma_col: KarmaCol,
        karma_typ: KarmaTyp,
        user: KarmaName, // TODO: make it support a Set of user
    },
    Count(KarmaCol),
    Partial {
        karma_col: KarmaCol,
        users: HashSet<KarmaName>,
    },
    AddKarma {
        timestamp: DateTime<Utc>,
        user_id: String,
        username: Option<KarmaName>,
        real_name: Option<KarmaName>,
        channel_id: Option<String>,
        karma: Vec<KST>,
    },
    QueryReacjiMessage{
        channel_id: String,
        message_ts: String,
    },
    AddReacjiMessage {
        user_id: String,
        username: Option<KarmaName>,
        real_name: Option<KarmaName>,
        channel_id: String,
        message_ts: String,
        message: String,
    },
    AddReacji {
        timestamp: DateTime<Utc>,
        user_id: String,
        username: Option<KarmaName>,
        real_name: Option<KarmaName>,
        action: ReacjiAction,
        message_id: i64,
        result: Karma,
    },
}

#[derive(Debug, Clone, Copy)]
pub enum ReacjiAction { Add, Del }

#[derive(Debug, Clone, Copy)]
pub enum OrdQuery { Asc, Desc }

#[derive(Debug, Clone, Copy)]
pub enum KarmaCol { Given, Recieved }

#[derive(Debug, Clone, Copy)]
pub enum KarmaTyp { Total, Side }

#[derive(Debug)]
pub enum ResQuery {
    // RankingDenormalized
    OCount(Option<u32>),
    // Count
    Count(u32),
    // TopNDenormalized
    TopN(Vec<(String, i32)>),
    // Partial
    Partial(Vec<(String, i32, i32, i32)>),
    // Message Id for query reacji message -and- add reacji message
    MessageId(Option<i64>),
}

fn maybe_send(
    res_tx: Option<oneshot::Sender<ResQuery>>,
    res: ResQuery,
) -> Result<(), String> {
    match res_tx {
        Some(tx) => tx.send(res).map_err(|_| "Cant send ResQuery".to_string()),
        None     => Ok(()),
    }
}

// TODO: additional check for shutting down
pub fn process_queries(
    filename: &Path,
    sql_rx: mpsc::Receiver<(RunQuery, Option<oneshot::Sender<ResQuery>>)>
) -> Result<(), Box<dyn std::error::Error>> {
    let mut block_sql_rx = block_on_stream(sql_rx);

    let mut conn = rs::Connection::open_with_flags(
        filename,
        rs::OpenFlags::SQLITE_OPEN_READ_WRITE
    ).expect(&format!("Connection error: {:?}", filename.to_str()));

    // Listen for inbound query
    while let Some((query, res_tx)) = block_sql_rx.next() {
        match query {
            RunQuery::TopNDenormalized{karma_col, karma_typ, limit, ord} => {
                let table = match karma_col {
                    KarmaCol::Given    => "karma_given_count",
                    KarmaCol::Recieved => "karma_received_count",
                };

                let t_col = match karma_typ {
                    KarmaTyp::Total => "up - down",
                    KarmaTyp::Side  => "side",
                };

                let q_ord = match ord {
                    OrdQuery::Asc  => "ASC",
                    OrdQuery::Desc => "DESC",
                };

                let mut stmt = conn.prepare(&format!(
                        "SELECT name, {t_col} as total FROM {table} ORDER BY total {q_ord} LIMIT {limit}",
                        t_col=t_col,
                        table=table,
                        q_ord=q_ord,
                        limit=limit
                )).unwrap();
                let mut rows = stmt.query(rs::NO_PARAMS).unwrap();

                let mut ret: Vec<(String, i32)> = vec![];

                while let Ok(Some(row)) = rows.next() {
                    let name: String = row.get(0).unwrap();
                    let count: i32 = row.get(1).unwrap();

                    ret.push((name.clone(), count));
                }
                maybe_send(res_tx, ResQuery::TopN(ret))?;
            },
            RunQuery::RankingDenormalized{karma_col, karma_typ, user} => {
                let table = match karma_col {
                    KarmaCol::Given    => "karma_given_count",
                    KarmaCol::Recieved => "karma_received_count",
                };

                let t_col1 = match karma_typ {
                    KarmaTyp::Total => "up - down",
                    KarmaTyp::Side  => "side",
                };

                let t_col2 = match karma_typ {
                    KarmaTyp::Total => "kcol2.up - kcol2.down",
                    KarmaTyp::Side  => "kcol2.side",
                };

                let mut stmt = conn.prepare(&format!(
                    "SELECT CASE WHEN (
                        EXISTS (SELECT TRUE FROM {table} WHERE name = ?1)
                    ) THEN (
                        SELECT (COUNT(name) + 1) FROM {table} WHERE (
                            {t_col1}
                        ) > (
                            SELECT ({t_col2}) FROM {table} AS kcol2 WHERE kcol2.name = ?2
                        )
                    ) ELSE NULL END",
                    table=table, t_col1=t_col1, t_col2=t_col2
                )).unwrap();
                let user = user.to_string();
                let mut rows = stmt.query(rs::params![user, user]).unwrap();

                if let Ok(Some(row)) = rows.next() {
                    let count: Option<u32> = row.get(0).ok();

                    maybe_send(res_tx, ResQuery::OCount(count))?;
                } else {
                    eprintln!(
                        "ERROR [Sql Worker]: RankingDenormalized - karma_col: {:?}, karma_typ: {:?}, user: {:?}",
                        karma_col, karma_typ, user
                    );
                }
            },
            RunQuery::Count(karma_col) => {
                let table = match karma_col {
                    KarmaCol::Given => "karma_given_count",
                    KarmaCol::Recieved => "karma_received_count",
                };

                let mut stmt = conn.prepare(&format!(
                    "SELECT COUNT(name) FROM {table}",
                    table=table
                )).unwrap();
                let mut rows = stmt.query(rs::NO_PARAMS).unwrap();

                if let Ok(Some(row)) = rows.next() {
                    let count: u32 = row.get(0).unwrap();

                    maybe_send(res_tx, ResQuery::Count(count))?;
                } else {
                    eprintln!("ERROR [Sql Worker]: Count - ERROR - karma_col: {:?}", karma_col);
                }
            },
            RunQuery::Partial{karma_col, users} => {
                let table = match karma_col {
                    KarmaCol::Given    => "karma_given_count",
                    KarmaCol::Recieved => "karma_received_count",
                };

                // Hack to insert enough parameterizers into the query
                let p_user = {
                    let mut p_user: String = "?1".to_string();
                    for i in 2..(users.len() + 1) {
                        p_user.push_str(&format!(", ?{}", i));
                    }
                    p_user
                };

                // Params
                let param: Vec<String> = users.iter().map(|i| i.to_string()).collect();

                let mut stmt = conn.prepare(&format!(
                    "SELECT name, up, down, side FROM {table} WHERE name in ({p_user}) ORDER BY name DESC",
                    table=table, p_user=p_user
                )).unwrap();
                let mut rows = stmt.query(&param).unwrap();

                // A bit more additional work than usual
                let mut ret: Vec<(String, i32, i32, i32)> = vec![];
                let mut has: HashSet<KarmaName> = HashSet::new();

                while let Ok(Some(row)) = rows.next() {
                    let name: String = row.get(0).unwrap();
                    let up: i32 = row.get(1).unwrap();
                    let down: i32 = row.get(2).unwrap();
                    let side: i32 = row.get(3).unwrap();

                    has.insert(KarmaName::new(&name));
                    ret.push((name, up, down, side));
                }

                // Evaulate if there's missing ones and add if so
                // TODO: this should be case insensitive (Ie database can return B and we have b)
                for n in users.difference(&has) {
                    ret.push((n.to_string(), 0, 0, 0));
                }
                maybe_send(res_tx, ResQuery::Partial(ret))?;
            },
            RunQuery::AddKarma{timestamp, user_id, username, real_name, channel_id, karma} => {
                let un = username.map(|un| un.to_string()).unwrap_or("".to_string());
                let urn = real_name.map(|rn| rn.to_string()).unwrap_or("".to_string());

                let nick_id: i64 = {
                    let mut stmt = conn.prepare("SELECT id FROM nick_metadata WHERE username = ?").unwrap();
                    let mut rows = stmt.query(rs::params![user_id]).unwrap();

                    if let Ok(Some(row)) = rows.next() {
                        row.get(0).unwrap()
                    } else {
                        let mut stmt = conn.prepare(
                            "INSERT INTO nick_metadata (cleaned_nick, full_name, username, hostmask) VALUES (?, ?, ?, ?)"
                        ).unwrap();

                        stmt.insert(rs::params![un, urn, user_id, "SlackServer"]).unwrap()
                    }
                };

                let channel_id: Option<i64> = if let Some(cid) = channel_id {
                    let mut stmt = conn.prepare("SELECT id FROM chan_metadata WHERE channel = ?").unwrap();
                    let mut rows = stmt.query(rs::params![cid]).unwrap();

                    if let Ok(Some(row)) = rows.next() {
                        row.get(0).ok()
                    } else {
                        let mut stmt = conn.prepare("INSERT INTO chan_metadata (channel) VALUES (?)").unwrap();
                        stmt.insert(rs::params![cid]).ok()
                    }
                } else {
                    None
                };

                // Shove the karma into the db now
                let tx = conn.transaction()?;
                {
                    let mut stmt = tx.prepare(
                        "INSERT INTO votes
                            (voted_at, by_whom_name, for_what_name, amount, nick_id, chan_id)
                        VALUES
                            (?, ?, ?, ?, ?, ?)"
                    ).unwrap();

                    for KST(karma_text, k) in karma.iter() {
                        let amount: i8 = match k {
                            Karma::Up   => 1,
                            Karma::Down => -1,
                            Karma::Side => 0,
                        };

                        let ts = timestamp.to_rfc3339();
                        let ts = ts.trim_end_matches("+00:00");
                        let karma_text = normalize(karma_text);

                        // TODO: better handle failure of username, maybe we should make username
                        // mandatory before inserting?
                        match channel_id {
                            Some(cid) => stmt.insert(
                                rs::params![ts, un, karma_text, amount, nick_id, cid]
                            ).unwrap(),
                            None      => stmt.insert(
                                rs::params![ts, un, karma_text, amount, nick_id, &rs::types::Null]
                            ).unwrap(),
                        };
                    }
                }
                tx.commit()?;
            },

            RunQuery::QueryReacjiMessage{channel_id, message_ts} => {
                let channel_id: Option<i64> = {
                    let mut stmt = conn.prepare("SELECT id FROM chan_metadata WHERE channel = ?").unwrap();
                    let mut rows = stmt.query(rs::params![channel_id]).unwrap();

                    if let Ok(Some(row)) = rows.next() {
                        row.get(0).ok()
                    } else {
                        None
                    }
                };

                if let Some(channel_id) = channel_id {
                    // We good let's proceed, otherwise abort since if channel id isn't here its
                    // not going to be in reacji_message either
                    let mut stmt = conn.prepare("SELECT id FROM reacji_message WHERE ts = ? AND chan_id = ?").unwrap();
                    let mut rows = stmt.query(rs::params![message_ts, channel_id]).unwrap();

                    if let Ok(Some(row)) = rows.next() {
                        maybe_send(
                            res_tx,
                            ResQuery::MessageId(row.get(0).ok())
                        )?;
                    } else {
                        maybe_send(res_tx, ResQuery::MessageId(None))?;
                    }
                } else {
                    maybe_send(res_tx, ResQuery::MessageId(None))?;
                }
            },

            RunQuery::AddReacjiMessage{user_id, username, real_name, channel_id, message_ts, message} => {
                let un = username.map(|un| un.to_string()).unwrap_or("".to_string());
                let urn = real_name.map(|rn| rn.to_string()).unwrap_or("".to_string());

                let nick_id: i64 = {
                    let mut stmt = conn.prepare("SELECT id FROM nick_metadata WHERE username = ?").unwrap();
                    let mut rows = stmt.query(rs::params![user_id]).unwrap();

                    if let Ok(Some(row)) = rows.next() {
                        row.get(0).unwrap()
                    } else {
                        let mut stmt = conn.prepare(
                            "INSERT INTO nick_metadata (cleaned_nick, full_name, username, hostmask) VALUES (?, ?, ?, ?)"
                        ).unwrap();

                        stmt.insert(rs::params![un, urn, user_id, "SlackServer"]).unwrap()
                    }
                };

                let channel_id: i64 = {
                    let mut stmt = conn.prepare("SELECT id FROM chan_metadata WHERE channel = ?").unwrap();
                    let mut rows = stmt.query(rs::params![channel_id]).unwrap();

                    if let Ok(Some(row)) = rows.next() {
                        row.get(0).unwrap()
                    } else {
                        let mut stmt = conn.prepare("INSERT INTO chan_metadata (channel) VALUES (?)").unwrap();
                        stmt.insert(rs::params![channel_id]).unwrap()
                    }
                };

                // Insert the reacji_message content now
                let mut stmt = conn.prepare(
                    "INSERT INTO reacji_message (ts, chan_id, nick_id, message) VALUES (?, ?, ?, ?)"
                ).unwrap();

                maybe_send(
                    res_tx,
                    ResQuery::MessageId(Some(
                        stmt.insert(rs::params![message_ts, channel_id, nick_id, message]).unwrap()
                    ))
                )?;
            },

            RunQuery::AddReacji{timestamp, user_id, username, real_name, action, message_id, result} => {
                let un = username.map(|un| un.to_string()).unwrap_or("".to_string());
                let urn = real_name.map(|rn| rn.to_string()).unwrap_or("".to_string());

                let nick_id: i64 = {
                    let mut stmt = conn.prepare("SELECT id FROM nick_metadata WHERE username = ?").unwrap();
                    let mut rows = stmt.query(rs::params![user_id]).unwrap();

                    if let Ok(Some(row)) = rows.next() {
                        row.get(0).unwrap()
                    } else {
                        let mut stmt = conn.prepare(
                            "INSERT INTO nick_metadata (cleaned_nick, full_name, username, hostmask) VALUES (?, ?, ?, ?)"
                        ).unwrap();

                        stmt.insert(rs::params![un, urn, user_id, "SlackServer"]).unwrap()
                    }
                };

                // Setup the reacji bits
                let amount: i8 = match result {
                    Karma::Up   => 1,
                    Karma::Down => -1,
                    Karma::Side => 0,
                };

                let action: i8 = match action {
                    ReacjiAction::Add => 1,
                    ReacjiAction::Del => -1,
                };

                let ts = timestamp.to_rfc3339();
                let ts = ts.trim_end_matches("+00:00");

                // Insert the reacji into the database
                let mut stmt = conn.prepare(
                    "INSERT INTO reacji_votes
                        (voted_at, by_whom_name, action, reacji_message_id, amount, nick_id)
                    VALUES
                        (?, ?, ?, ?, ?, ?)"
                ).unwrap();

                stmt.insert(rs::params![ts, un, action, message_id, amount, nick_id]).unwrap();
            },
        }
    }

    Ok(())
}
<|endoftext|>


<commit_before>//! Buffer type's underlying data structure.

use super::Position;
use super::Range;
use std::str::from_utf8;

/// A UTF-8 string buffer designed to minimize reallocations,
/// maintaining performance amid frequent modifications.
pub struct GapBuffer {
    data: Vec<u8>,
    gap_start: usize,
    gap_length: usize,
}

/// Initializes a gap buffer with the specified data as its contents.
///
/// # Examples
///
/// ```
/// let buffer = scribe::buffer::gap_buffer::new("scribe".to_string());
/// assert_eq!(buffer.to_string(), "scribe");
/// ```
pub fn new(data: String) -> GapBuffer {
    let mut bytes = data.into_bytes();
    let capacity = bytes.capacity();
    let gap_start = bytes.len();
    let gap_length = capacity - gap_start;
    unsafe {
        bytes.set_len(capacity);
    }
    GapBuffer{ data: bytes, gap_start: gap_start, gap_length: gap_length }
}

impl GapBuffer {
    /// Inserts the specified data into the buffer at the specified position.
    /// The buffer will reallocate if there is insufficient space. If the
    /// position is out of bounds, the buffer contents will remain unchanged.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut buffer = scribe::buffer::gap_buffer::new("my buffer data".to_string());
    /// buffer.insert(" changed", &scribe::buffer::Position{ line: 0, offset: 2});
    /// assert_eq!("my changed buffer data", buffer.to_string());
    /// ```
    pub fn insert(&mut self, data: &str, position: &Position) {
        // Ensure we have the capacity to insert this data.
        if data.len() > self.gap_length {
            // We're about to add space to the end of the buffer, so move the gap
            // there beforehand so that we're essentially just increasing the
            // gap size, and preventing a split/two-segment gap.
            let offset = self.data.capacity();
            self.move_gap(offset);

            // Re-allocate the gap buffer, increasing its size.
            self.data.reserve(data.len());

            // Update the tracked gap size and tell the vector that
            // we're using all of the new space immediately.
            let capacity = self.data.capacity();
            self.gap_length = capacity - self.gap_start;
            unsafe {
                self.data.set_len(capacity);
            }
        }

        let offset = match self.find_offset(position) {
            Some(o) => o,
            None => return,
        };

        self.move_gap(offset);
        self.write_to_gap(data);
    }

    /// Returns the specified range of data from the buffer.
    ///
    /// # Examples
    ///
    /// ```
    /// let buffer = scribe::buffer::gap_buffer::new("my data".to_string());
    /// let range = scribe::buffer::Range{ start: scribe::buffer::Position{ line: 0, offset: 3 },
    ///   end: scribe::buffer::Position{ line: 0, offset: 7} };
    /// assert_eq!(buffer.read(&range).unwrap(), "data");
    /// ```
    pub fn read(&self, range: &Range) -> Option<String> {
        let start_offset = match self.find_offset(&range.start) {
            Some(offset) => {
                // If the gap is at the position we're looking for, skip over it, as
                // we don't want to include the gap contents in what we're reading.
                if offset == self.gap_start {
                    offset + self.gap_length
                } else {
                    offset
                }
            },
            None => return None,
        };

        let end_offset = match self.find_offset(&range.end) {
            Some(o) => o,
            None => return None,
        };

        Some(from_utf8(&self.data[start_offset..end_offset]).unwrap().to_string())
    }

    /// Returns a string representation of the buffer data (without gap).
    ///
    /// # Examples
    ///
    /// ```
    /// let mut buffer = scribe::buffer::gap_buffer::new("my data".to_string());
    /// assert_eq!(buffer.to_string(), "my data");
    /// ```
    pub fn to_string(&self) -> String {
        from_utf8(&self.data[..self.gap_start]).unwrap().to_string() + from_utf8(&self.data[self.gap_start+self.gap_length..]).unwrap()
    }

    /// Removes the specified range of data from the buffer.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut buffer = scribe::buffer::gap_buffer::new("my data".to_string());
    /// let range = scribe::buffer::Range{ start: scribe::buffer::Position{ line: 0, offset: 0 },
    ///   end: scribe::buffer::Position{ line: 0, offset: 3} };
    /// buffer.delete(&range);
    /// assert_eq!(buffer.to_string(), "data");
    /// ```
    pub fn delete(&mut self, range: &Range) {
        // Don't even bother if the range is invalid.
        if !range.is_valid() {
            return;
        }

        let start_offset = match self.find_offset(&range.start) {
            Some(o) => o,
            None => return,
        };
        self.move_gap(start_offset);

        let end_offset = match self.find_offset(&range.end) {
            Some(o) => o,
            None => return,
        };

        // Widen the gap to cover the deleted contents.
        self.gap_length=end_offset-self.gap_start;
    }

    /// Checks whether or not the specified position is in bounds of the buffer data.
    ///
    /// # Examples
    ///
    /// ```
    /// let buffer = scribe::buffer::gap_buffer::new("scribe".to_string());
    /// let in_bounds = scribe::buffer::Position{ line: 0, offset: 0 };
    /// let out_of_bounds = scribe::buffer::Position{ line: 1, offset: 3 };
    ///
    /// assert_eq!(buffer.in_bounds(&in_bounds), true);
    /// assert_eq!(buffer.in_bounds(&out_of_bounds), false);
    /// ```
    pub fn in_bounds(&self, position: &Position) -> bool {
        self.find_offset(position) != None
    }

    // Maps a position to its offset equivalent in the data.
    fn find_offset(&self, position: &Position) -> Option<usize> {
        let first_half = from_utf8(&self.data[..self.gap_start]).unwrap();
        let mut line = 0;
        let mut line_offset = 0;

        for char_index in first_half.char_indices() {
            let (offset, character) = char_index;

            // Check to see if we've found the position yet.
            if line == position.line && line_offset == position.offset {
                return Some(offset);
            }

            // Advance the line and offset characters.
            if character == '\n' {
                line+=1;
                line_offset = 0;
            } else {
                line_offset+=1;
            }
        }

        // We didn't find the position *within* the first half, but it could
        // be right after it, which means it's right at the start of the gap.
        if line == position.line && line_offset == position.offset {
            return Some(self.gap_start);
        }

        // We haven't reached the position yet, so we'll move on to the other half.
        let second_half = from_utf8(&self.data[self.gap_start+self.gap_length..]).unwrap();
        for char_index in second_half.char_indices() {
            let (offset, character) = char_index;

            // Check to see if we've found the position yet.
            if line == position.line && line_offset == position.offset {
                return Some(self.gap_start + self.gap_length + offset);
            }

            // Advance the line and offset characters.
            if character == '\n' {
                line+=1;
                line_offset = 0;
            } else {
                line_offset+=1;
            }
        }

        // We didn't find the position *within* the second half, but it could
        // be right after it, which means it's at the end of the buffer.
        if line == position.line && line_offset == position.offset {
            return Some(self.data.len());
        }

        None
    }

    fn move_gap(&mut self, offset: usize) {
        // We don't need to move any data if the buffer is at capacity.
        if self.gap_length == 0 {
            self.gap_start = offset;
            return;
        }

        if offset < self.gap_start {
            // Shift the gap to the left one byte at a time.
            for index in (offset..self.gap_start).rev() {
                self.data[index + self.gap_length] = self.data[index];
                self.data[index] = 0;
            }

            self.gap_start = offset;
        } else if offset > self.gap_start {
            // Shift the gap to the right one byte at a time.
            for index in (self.gap_start+self.gap_length..offset) {
                self.data[index-self.gap_length] = self.data[index];
                self.data[index] = 0;
            }

            // Because the offset was after the gap, its value included the
            // gap length. We must remove it to determine the starting point.
            self.gap_start = offset - self.gap_length;
        }
    }

    fn write_to_gap(&mut self, data: &str) {
        for byte in data.bytes() {
            self.data[self.gap_start] = byte;
            self.gap_start+=1;
            self.gap_length-=1;
        }
    }
}

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

    #[test]
    fn move_gap_works() {
        let mut gb = new("This is a test.".to_string());
        gb.move_gap(0);
        assert_eq!(gb.to_string(), "This is a test.");
    }

    #[test]
    fn inserting_at_the_start_works() {
        let mut gb = new("toolkit".to_string());

        // This insert serves to move the gap to the start of the buffer.
        gb.insert(" ", &Position { line: 0, offset: 0 });
        assert_eq!(gb.to_string(), " toolkit");

        // We insert enough data (more than twice original capacity) to force
        // a re-allocation, which, with the gap at the start and when handled
        // incorrectly, will create a split/two-segment gap. Bad news.
        gb.insert("scribe text", &Position { line: 0, offset: 0 });
        assert_eq!(gb.to_string(), "scribe text toolkit");
    }

    #[test]
    fn inserting_in_the_middle_works() {
        let mut gb = new("    editor".to_string());

        // Same deal as above "at the start" test, where we want to move
        // the gap into the middle and then force a reallocation to check
        // that pre-allocation gap shifting is working correctly.
        gb.insert(" ", &Position { line: 0, offset: 4 });
        gb.insert("scribe", &Position { line: 0, offset: 4 });
        assert_eq!(gb.to_string(), "    scribe editor");
    }

    #[test]
    fn inserting_at_the_end_works() {
        let mut gb = new("This is a test.".to_string());
        gb.insert(" Seriously.", &Position { line: 0, offset: 15 });
        assert_eq!(gb.to_string(), "This is a test. Seriously.");
    }

    #[test]
    fn inserting_in_different_spots_twice_works() {
        let mut gb = new("This is a test.".to_string());
        gb.insert("Hi. ", &Position { line: 0, offset: 0 });
        gb.insert(" Thank you.", &Position { line: 0, offset: 19 });
        assert_eq!(gb.to_string(), "Hi. This is a test. Thank you.");
    }

    #[test]
    fn inserting_at_an_invalid_position_does_nothing() {
        let mut gb = new("This is a test.".to_string());
        gb.insert(" Seriously.", &Position { line: 0, offset: 35 });
        assert_eq!(gb.to_string(), "This is a test.");
    }

    #[test]
    fn deleting_works() {
        let mut gb = new("This is a test.\nSee what happens.".to_string());
        let start = Position{ line: 0, offset: 8 };
        let end = Position{ line: 1, offset: 4 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "This is what happens.");
    }

    #[test]
    fn deleting_with_a_reversed_range_does_nothing() {
        let mut gb = new("This is a test.".to_string());
        let start = Position{ line: 0, offset: 10 };
        let end = Position{ line: 0, offset: 0 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "This is a test.");
    }

    #[test]
    fn inserting_then_deleting_at_the_start_works() {
        let mut gb = new(String::new());
        gb.insert("This is a test.", &Position{ line: 0, offset: 0});
        let start = Position{ line: 0, offset: 0 };
        let end = Position{ line: 0, offset: 1 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "his is a test.");
    }

    #[test]
    fn deleting_immediately_after_the_end_of_the_gap_widens_the_gap() {
        let mut gb = new("This is a test.".to_string());
        let mut start = Position{ line: 0, offset: 8 };
        let mut end = Position{ line: 0, offset: 9 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "This is  test.");
        assert_eq!(gb.gap_length, 1);

        start = Position{ line: 0, offset: 9 };
        end = Position{ line: 0, offset: 10 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "This is  est.");
        assert_eq!(gb.gap_length, 2);
    }

    #[test]
    fn read_does_not_include_gap_contents_when_gap_is_at_start_of_range() {
        // Create a buffer and a range that captures the first character.
        let mut gb = new("scribe".to_string());
        let range = Range{
            start: Position{ line: 0, offset: 0 },
            end: Position{ line: 0, offset: 1 }
        };

        // Delete the first character, which will move the gap buffer to the start.
        gb.delete(&range);
        assert_eq!(gb.to_string(), "cribe");

        // Ask for the first character, which would include the deleted
        // value, if the read function isn't smart enough to skip it.
        assert_eq!(gb.read(&range).unwrap(), "c");
    }
}
<commit_msg>Update gap buffer delete to support out-of-bound range endpoints.<commit_after>//! Buffer type's underlying data structure.

use super::Position;
use super::Range;
use std::str::from_utf8;

/// A UTF-8 string buffer designed to minimize reallocations,
/// maintaining performance amid frequent modifications.
pub struct GapBuffer {
    data: Vec<u8>,
    gap_start: usize,
    gap_length: usize,
}

/// Initializes a gap buffer with the specified data as its contents.
///
/// # Examples
///
/// ```
/// let buffer = scribe::buffer::gap_buffer::new("scribe".to_string());
/// assert_eq!(buffer.to_string(), "scribe");
/// ```
pub fn new(data: String) -> GapBuffer {
    let mut bytes = data.into_bytes();
    let capacity = bytes.capacity();
    let gap_start = bytes.len();
    let gap_length = capacity - gap_start;
    unsafe {
        bytes.set_len(capacity);
    }
    GapBuffer{ data: bytes, gap_start: gap_start, gap_length: gap_length }
}

impl GapBuffer {
    /// Inserts the specified data into the buffer at the specified position.
    /// The buffer will reallocate if there is insufficient space. If the
    /// position is out of bounds, the buffer contents will remain unchanged.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut buffer = scribe::buffer::gap_buffer::new("my buffer data".to_string());
    /// buffer.insert(" changed", &scribe::buffer::Position{ line: 0, offset: 2});
    /// assert_eq!("my changed buffer data", buffer.to_string());
    /// ```
    pub fn insert(&mut self, data: &str, position: &Position) {
        // Ensure we have the capacity to insert this data.
        if data.len() > self.gap_length {
            // We're about to add space to the end of the buffer, so move the gap
            // there beforehand so that we're essentially just increasing the
            // gap size, and preventing a split/two-segment gap.
            let offset = self.data.capacity();
            self.move_gap(offset);

            // Re-allocate the gap buffer, increasing its size.
            self.data.reserve(data.len());

            // Update the tracked gap size and tell the vector that
            // we're using all of the new space immediately.
            let capacity = self.data.capacity();
            self.gap_length = capacity - self.gap_start;
            unsafe {
                self.data.set_len(capacity);
            }
        }

        let offset = match self.find_offset(position) {
            Some(o) => o,
            None => return,
        };

        self.move_gap(offset);
        self.write_to_gap(data);
    }

    /// Returns the specified range of data from the buffer.
    ///
    /// # Examples
    ///
    /// ```
    /// let buffer = scribe::buffer::gap_buffer::new("my data".to_string());
    /// let range = scribe::buffer::Range{ start: scribe::buffer::Position{ line: 0, offset: 3 },
    ///   end: scribe::buffer::Position{ line: 0, offset: 7} };
    /// assert_eq!(buffer.read(&range).unwrap(), "data");
    /// ```
    pub fn read(&self, range: &Range) -> Option<String> {
        let start_offset = match self.find_offset(&range.start) {
            Some(offset) => {
                // If the gap is at the position we're looking for, skip over it, as
                // we don't want to include the gap contents in what we're reading.
                if offset == self.gap_start {
                    offset + self.gap_length
                } else {
                    offset
                }
            },
            None => return None,
        };

        let end_offset = match self.find_offset(&range.end) {
            Some(o) => o,
            None => return None,
        };

        Some(from_utf8(&self.data[start_offset..end_offset]).unwrap().to_string())
    }

    /// Returns a string representation of the buffer data (without gap).
    ///
    /// # Examples
    ///
    /// ```
    /// let mut buffer = scribe::buffer::gap_buffer::new("my data".to_string());
    /// assert_eq!(buffer.to_string(), "my data");
    /// ```
    pub fn to_string(&self) -> String {
        from_utf8(&self.data[..self.gap_start]).unwrap().to_string() + from_utf8(&self.data[self.gap_start+self.gap_length..]).unwrap()
    }

    /// Removes the specified range of data from the buffer.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut buffer = scribe::buffer::gap_buffer::new("my data".to_string());
    /// let range = scribe::buffer::Range{ start: scribe::buffer::Position{ line: 0, offset: 0 },
    ///   end: scribe::buffer::Position{ line: 0, offset: 3} };
    /// buffer.delete(&range);
    /// assert_eq!(buffer.to_string(), "data");
    /// ```
    pub fn delete(&mut self, range: &Range) {
        // Don't even bother if the range is invalid.
        if !range.is_valid() {
            return;
        }

        let start_offset = match self.find_offset(&range.start) {
            Some(o) => o,
            None => return,
        };
        self.move_gap(start_offset);

        match self.find_offset(&range.end) {
            Some(offset) => {
                // Widen the gap to cover the deleted contents.
                self.gap_length = offset - self.gap_start;
            },
            None => {
                // The end of the range doesn't exist; check
                // if it's on the last line in the file.
                let start_of_next_line = Position{ line: range.end.line + 1, offset: 0 };

                match self.find_offset(&start_of_next_line) {
                    Some(offset) => {
                        // There are other lines below this range.
                        // Just remove up until the end of the line.
                        self.gap_length = offset - self.gap_start;
                    },
                    None => {
                        // We're on the last line, just get rid of the rest
                        // by extending the gap right to the end of the buffer.
                        self.gap_length = self.data.len() - self.gap_start;
                    }
                }
            }
        };
    }

    /// Checks whether or not the specified position is in bounds of the buffer data.
    ///
    /// # Examples
    ///
    /// ```
    /// let buffer = scribe::buffer::gap_buffer::new("scribe".to_string());
    /// let in_bounds = scribe::buffer::Position{ line: 0, offset: 0 };
    /// let out_of_bounds = scribe::buffer::Position{ line: 1, offset: 3 };
    ///
    /// assert_eq!(buffer.in_bounds(&in_bounds), true);
    /// assert_eq!(buffer.in_bounds(&out_of_bounds), false);
    /// ```
    pub fn in_bounds(&self, position: &Position) -> bool {
        self.find_offset(position) != None
    }

    // Maps a position to its offset equivalent in the data.
    fn find_offset(&self, position: &Position) -> Option<usize> {
        let first_half = from_utf8(&self.data[..self.gap_start]).unwrap();
        let mut line = 0;
        let mut line_offset = 0;

        for char_index in first_half.char_indices() {
            let (offset, character) = char_index;

            // Check to see if we've found the position yet.
            if line == position.line && line_offset == position.offset {
                return Some(offset);
            }

            // Advance the line and offset characters.
            if character == '\n' {
                line+=1;
                line_offset = 0;
            } else {
                line_offset+=1;
            }
        }

        // We didn't find the position *within* the first half, but it could
        // be right after it, which means it's right at the start of the gap.
        if line == position.line && line_offset == position.offset {
            return Some(self.gap_start);
        }

        // We haven't reached the position yet, so we'll move on to the other half.
        let second_half = from_utf8(&self.data[self.gap_start+self.gap_length..]).unwrap();
        for char_index in second_half.char_indices() {
            let (offset, character) = char_index;

            // Check to see if we've found the position yet.
            if line == position.line && line_offset == position.offset {
                return Some(self.gap_start + self.gap_length + offset);
            }

            // Advance the line and offset characters.
            if character == '\n' {
                line+=1;
                line_offset = 0;
            } else {
                line_offset+=1;
            }
        }

        // We didn't find the position *within* the second half, but it could
        // be right after it, which means it's at the end of the buffer.
        if line == position.line && line_offset == position.offset {
            return Some(self.data.len());
        }

        None
    }

    fn move_gap(&mut self, offset: usize) {
        // We don't need to move any data if the buffer is at capacity.
        if self.gap_length == 0 {
            self.gap_start = offset;
            return;
        }

        if offset < self.gap_start {
            // Shift the gap to the left one byte at a time.
            for index in (offset..self.gap_start).rev() {
                self.data[index + self.gap_length] = self.data[index];
                self.data[index] = 0;
            }

            self.gap_start = offset;
        } else if offset > self.gap_start {
            // Shift the gap to the right one byte at a time.
            for index in (self.gap_start+self.gap_length..offset) {
                self.data[index-self.gap_length] = self.data[index];
                self.data[index] = 0;
            }

            // Because the offset was after the gap, its value included the
            // gap length. We must remove it to determine the starting point.
            self.gap_start = offset - self.gap_length;
        }
    }

    fn write_to_gap(&mut self, data: &str) {
        for byte in data.bytes() {
            self.data[self.gap_start] = byte;
            self.gap_start+=1;
            self.gap_length-=1;
        }
    }
}

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

    #[test]
    fn move_gap_works() {
        let mut gb = new("This is a test.".to_string());
        gb.move_gap(0);
        assert_eq!(gb.to_string(), "This is a test.");
    }

    #[test]
    fn inserting_at_the_start_works() {
        let mut gb = new("toolkit".to_string());

        // This insert serves to move the gap to the start of the buffer.
        gb.insert(" ", &Position { line: 0, offset: 0 });
        assert_eq!(gb.to_string(), " toolkit");

        // We insert enough data (more than twice original capacity) to force
        // a re-allocation, which, with the gap at the start and when handled
        // incorrectly, will create a split/two-segment gap. Bad news.
        gb.insert("scribe text", &Position { line: 0, offset: 0 });
        assert_eq!(gb.to_string(), "scribe text toolkit");
    }

    #[test]
    fn inserting_in_the_middle_works() {
        let mut gb = new("    editor".to_string());

        // Same deal as above "at the start" test, where we want to move
        // the gap into the middle and then force a reallocation to check
        // that pre-allocation gap shifting is working correctly.
        gb.insert(" ", &Position { line: 0, offset: 4 });
        gb.insert("scribe", &Position { line: 0, offset: 4 });
        assert_eq!(gb.to_string(), "    scribe editor");
    }

    #[test]
    fn inserting_at_the_end_works() {
        let mut gb = new("This is a test.".to_string());
        gb.insert(" Seriously.", &Position { line: 0, offset: 15 });
        assert_eq!(gb.to_string(), "This is a test. Seriously.");
    }

    #[test]
    fn inserting_in_different_spots_twice_works() {
        let mut gb = new("This is a test.".to_string());
        gb.insert("Hi. ", &Position { line: 0, offset: 0 });
        gb.insert(" Thank you.", &Position { line: 0, offset: 19 });
        assert_eq!(gb.to_string(), "Hi. This is a test. Thank you.");
    }

    #[test]
    fn inserting_at_an_invalid_position_does_nothing() {
        let mut gb = new("This is a test.".to_string());
        gb.insert(" Seriously.", &Position { line: 0, offset: 35 });
        assert_eq!(gb.to_string(), "This is a test.");
    }

    #[test]
    fn deleting_works() {
        let mut gb = new("This is a test.\nSee what happens.".to_string());
        let start = Position{ line: 0, offset: 8 };
        let end = Position{ line: 1, offset: 4 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "This is what happens.");
    }

    #[test]
    fn deleting_with_a_reversed_range_does_nothing() {
        let mut gb = new("This is a test.".to_string());
        let start = Position{ line: 0, offset: 10 };
        let end = Position{ line: 0, offset: 0 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "This is a test.");
    }

    #[test]
    fn inserting_then_deleting_at_the_start_works() {
        let mut gb = new(String::new());
        gb.insert("This is a test.", &Position{ line: 0, offset: 0});
        let start = Position{ line: 0, offset: 0 };
        let end = Position{ line: 0, offset: 1 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "his is a test.");
    }

    #[test]
    fn deleting_immediately_after_the_end_of_the_gap_widens_the_gap() {
        let mut gb = new("This is a test.".to_string());
        let mut start = Position{ line: 0, offset: 8 };
        let mut end = Position{ line: 0, offset: 9 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "This is  test.");
        assert_eq!(gb.gap_length, 1);

        start = Position{ line: 0, offset: 9 };
        end = Position{ line: 0, offset: 10 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "This is  est.");
        assert_eq!(gb.gap_length, 2);
    }

    #[test]
    fn deleting_to_an_out_of_range_line_deletes_to_the_end_of_the_buffer() {
        let mut gb = new("scribe\nlibrary".to_string());
        let start = Position{ line: 0, offset: 6 };
        let end = Position{ line: 2, offset: 10 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "scribe");
    }

    #[test]
    fn deleting_to_an_out_of_range_column_deletes_to_the_end_of_the_buffer() {
        let mut gb = new("scribe\nlibrary".to_string());
        let start = Position{ line: 0, offset: 0 };
        let end = Position{ line: 0, offset: 100 };
        gb.delete(&Range{ start: start, end: end });
        assert_eq!(gb.to_string(), "library");
    }

    #[test]
    fn read_does_not_include_gap_contents_when_gap_is_at_start_of_range() {
        // Create a buffer and a range that captures the first character.
        let mut gb = new("scribe".to_string());
        let range = Range{
            start: Position{ line: 0, offset: 0 },
            end: Position{ line: 0, offset: 1 }
        };

        // Delete the first character, which will move the gap buffer to the start.
        gb.delete(&range);
        assert_eq!(gb.to_string(), "cribe");

        // Ask for the first character, which would include the deleted
        // value, if the read function isn't smart enough to skip it.
        assert_eq!(gb.read(&range).unwrap(), "c");
    }
}
<|endoftext|>


<commit_before>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use target::Target;

pub fn target() -> Target {
    Target {
        data_layout: "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16\
                      -i32:32:32-i64:32:64\
                      -f32:32:32-f64:32:64-v64:64:64\
                      -v128:128:128-a0:0:64-f80:128:128\
                      -n8:16:32".to_string(),
        llvm_target: "i686-apple-darwin".to_string(),
        target_endian: "little".to_string(),
        target_word_size: "32".to_string(),
        arch: "x86".to_string(),
        target_os: "macos".to_string(),

        options: super::apple_base::opts()
    }
}
<commit_msg>Same fix for mac32<commit_after>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use target::Target;

pub fn target() -> Target {
    let mut base = super::apple_base::opts();
    base.pre_link_args.push("-m32".to_string());

    Target {
        data_layout: "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16\
                      -i32:32:32-i64:32:64\
                      -f32:32:32-f64:32:64-v64:64:64\
                      -v128:128:128-a0:0:64-f80:128:128\
                      -n8:16:32".to_string(),
        llvm_target: "i686-apple-darwin".to_string(),
        target_endian: "little".to_string(),
        target_word_size: "32".to_string(),
        arch: "x86".to_string(),
        target_os: "macos".to_string(),
        options: base,
    }
}
<|endoftext|>


<commit_before>use num::Float;
use na::{self, Bounded};
use ncollide::math::Scalar;
use ncollide::query::Contact;
use volumetric::InertiaTensor;
use resolution::constraint::velocity_constraint::VelocityConstraint;
use object::RigidBody;
use math::{Point, Vector, Orientation};

/// The correction coefficient used by the constraint solver.
pub enum CorrectionMode<N: Scalar> {
    /// Penetration are solved by the penalty method.
    Velocity(N),
    /// Penetration are solved by the penalty method together with a hard repositioning.
    VelocityAndPosition(N, N, N),
    /// Penetration are solved by the penalty method together with a hard repositioning.
    ///
    /// The amount of velocity correction is bounded by threshold.
    VelocityAndPositionThresold(N, N, N)
}

impl<N: Scalar> CorrectionMode<N> {
    #[inline]
    /// The velocity correction coefficient.
    pub fn vel_corr_factor(&self) -> N {
        match *self {
            CorrectionMode::Velocity(ref v)                          => v.clone(),
            CorrectionMode::VelocityAndPosition(ref v, _, _)         => v.clone(),
            CorrectionMode::VelocityAndPositionThresold(ref v, _, _) => v.clone()
        }
    }

    #[inline]
    /// The position correction coefficient.
    pub fn pos_corr_factor(&self) -> N {
        match *self {
            CorrectionMode::VelocityAndPosition(_, ref p, _)         => p.clone(),
            CorrectionMode::VelocityAndPositionThresold(_, ref p, _) => p.clone(),
            CorrectionMode::Velocity(_)                              => na::zero()
        }
    }

    #[inline]
    /// The minimum penetration depth required to switch on the hard repositioning based method.
    pub fn min_depth_for_pos_corr(&self) -> N {
        match *self {
            CorrectionMode::VelocityAndPosition(_, _, ref t)         => t.clone(),
            CorrectionMode::VelocityAndPositionThresold(_, _, ref t) => t.clone(),
            CorrectionMode::Velocity(_)                              => Bounded::max_value()
        }
    }

    #[inline]
    /// The max penetration depth the velocity correction will attempt to correct.
    pub fn max_depth_for_vel_corr(&self) -> N {
        match *self {
            CorrectionMode::VelocityAndPosition(_, _, _)             => Bounded::max_value(),
            CorrectionMode::VelocityAndPositionThresold(_, _, ref t) => t.clone(),
            CorrectionMode::Velocity(_)                              => Bounded::max_value()
        }
    }
}

pub struct CorrectionParameters<N: Scalar> {
    pub corr_mode:       CorrectionMode<N>,
    pub joint_corr:      N,
    pub rest_eps:        N
}

pub fn reinit_to_first_order_equation<N: Scalar>(dt:         N,
                                                 coll:       &Contact<Point<N>>,
                                                 constraint: &mut VelocityConstraint<N>,
                                                 correction: &CorrectionParameters<N>) {
    /*
     * Fill b
     */
    if coll.depth >= correction.corr_mode.min_depth_for_pos_corr() {
        constraint.objective = correction.corr_mode.pos_corr_factor() * coll.depth.max(na::zero()) / dt;
    }
    else {
        constraint.objective = na::zero();
    }

    /*
     * Reset forces
     */
    constraint.impulse = na::zero();
}

pub fn fill_second_order_equation<N: Scalar>(dt:           N,
                                             coll:         &Contact<Point<N>>,
                                             rb1:          &RigidBody<N>,
                                             rb2:          &RigidBody<N>,
                                             rconstraint:  &mut VelocityConstraint<N>,
                                             idr:          usize,
                                             fconstraints: &mut [VelocityConstraint<N>],
                                             idf:          usize,
                                             cache:        &[N],
                                             correction:   &CorrectionParameters<N>) {
    let restitution = rb1.restitution() * rb2.restitution();

    let center = na::center(&coll.world1, &coll.world2);

    fill_velocity_constraint(dt.clone(),
                             coll.normal.clone(),
                             center.clone(),
                             restitution,
                             coll.depth.clone(),
                             cache[0].clone(), // coll.impulses[0].clone(),
                             na::zero(),
                             Bounded::max_value(),
                             rb1,
                             rb2,
                             rconstraint,
                             correction);

    let friction  = rb1.friction() * rb2.friction();
    // To bound the friction we use the last frame normal impulse.
    // That means we have to make a special case for the first time the contact appears.
    // In that case, we estimate the impulse by the derired normal correction.

    let mut i = 0;

    na::orthonormal_subspace_basis(&coll.normal, |friction_axis| {
        let constraint = &mut fconstraints[idf + i];

        fill_velocity_constraint(dt.clone(),
                                 friction_axis,
                                 center.clone(),
                                 na::zero(),
                                 na::zero(),
                                 cache[i + 1].clone(), // coll.impulses[i].clone(),
                                 na::zero(), // dont setup the limit now
                                 na::zero(), // dont setup the limit now
                                 rb1,
                                 rb2,
                                 constraint,
                                 correction);

        constraint.friction_coeff    = friction.clone();
        constraint.friction_limit_id = idr;
        i = i + 1;

        true
    })
}

pub fn fill_constraint_geometry<N: Scalar>(normal:     Vector<N>,
                                           rot_axis1:  Orientation<N>,
                                           rot_axis2:  Orientation<N>,
                                           rb1:        &Option<&RigidBody<N>>,
                                           rb2:        &Option<&RigidBody<N>>,
                                           constraint: &mut VelocityConstraint<N>) {
    constraint.normal             = normal;
    constraint.inv_projected_mass = na::zero();

    match *rb1 {
        Some(ref rb) => {
            // rotation axis
            constraint.weighted_normal1   = constraint.normal * rb.inv_mass();
            constraint.rot_axis1          = rot_axis1;

            constraint.weighted_rot_axis1 = rb.inv_inertia().apply(&constraint.rot_axis1);

            constraint.inv_projected_mass = constraint.inv_projected_mass +
                na::dot(&constraint.normal, &constraint.weighted_normal1) +
                na::dot(&constraint.rot_axis1, &constraint.weighted_rot_axis1);
        },
        None => { }
    }

    match *rb2 {
        Some(ref rb) => {
            // rotation axis
            constraint.weighted_normal2   = constraint.normal * rb.inv_mass();
            constraint.rot_axis2          = rot_axis2;

            constraint.weighted_rot_axis2 = rb.inv_inertia().apply(&constraint.rot_axis2);

            constraint.inv_projected_mass = constraint.inv_projected_mass +
                na::dot(&constraint.normal, &constraint.weighted_normal2) +
                na::dot(&constraint.rot_axis2, &constraint.weighted_rot_axis2);
        },
        None => { }
    }

    let _1: N = na::one();
    constraint.inv_projected_mass = _1 / constraint.inv_projected_mass;
}

fn fill_velocity_constraint<N: Scalar>(dt:              N,
                                       normal:          Vector<N>,
                                       center:          Point<N>,
                                       restitution:     N,
                                       depth:           N,
                                       initial_impulse: N,
                                       lobound:         N,
                                       hibound:         N,
                                       rb1:             &RigidBody<N>,
                                       rb2:             &RigidBody<N>,
                                       constraint:      &mut VelocityConstraint<N>,
                                       correction:      &CorrectionParameters<N>) {
    let rot_axis1 = na::cross(&(center - *rb1.center_of_mass()), &-normal);
    let rot_axis2 = na::cross(&(center - *rb2.center_of_mass()), &normal);

    let opt_rb1 = if rb1.can_move() { Some(rb1) } else { None };
    let opt_rb2 = if rb2.can_move() { Some(rb2) } else { None };
    fill_constraint_geometry(normal, rot_axis1, rot_axis2, &opt_rb1, &opt_rb2, constraint);

    /*
     * Fill indice
     */
    constraint.id1 = rb1.index();
    constraint.id2 = rb2.index();

    /*
     * correction amount
     */
    constraint.objective = relative_velocity(
        &opt_rb1,
        &opt_rb2,
        &constraint.normal,
        &constraint.rot_axis1,
        &constraint.rot_axis2,
        &dt);

    if constraint.objective < -correction.rest_eps {
        constraint.objective = constraint.objective + restitution * constraint.objective
    }

    constraint.objective = -constraint.objective;

    if depth < na::zero() {
        constraint.objective = constraint.objective + depth / dt
    }
    else if depth < correction.corr_mode.max_depth_for_vel_corr() {
        constraint.objective = constraint.objective + depth * correction.corr_mode.vel_corr_factor() / dt
    }

    // for warm-starting
    constraint.impulse = if depth < na::zero() { na::zero() } else { initial_impulse };

    /*
     * constraint bounds
     */
    constraint.lobound = lobound;
    constraint.hibound = hibound;
}

pub fn relative_velocity<N: Scalar>(rb1:       &Option<&RigidBody<N>>,
                                    rb2:       &Option<&RigidBody<N>>,
                                    normal:    &Vector<N>,
                                    rot_axis1: &Orientation<N>,
                                    rot_axis2: &Orientation<N>,
                                    dt:        &N)
                                    -> N {
    let mut dvel: N = na::zero();

    match *rb1 {
        Some(ref rb) => {
            dvel = dvel - na::dot(&(rb.lin_vel() + rb.lin_acc() * *dt), normal)
                        + na::dot(&(rb.ang_vel() + rb.ang_acc() * *dt), rot_axis1);
        },
        None => { }
    }

    match *rb2 {
        Some(ref rb) => {
            dvel = dvel + na::dot(&(rb.lin_vel() + rb.lin_acc() * *dt), normal)
                        + na::dot(&(rb.ang_vel() + rb.ang_acc() * *dt), rot_axis2);
        },
        None => { }
    }

    dvel
}
<commit_msg>Remove overly-aggressive damping.<commit_after>use num::Float;
use na::{self, Bounded};
use ncollide::math::Scalar;
use ncollide::query::Contact;
use volumetric::InertiaTensor;
use resolution::constraint::velocity_constraint::VelocityConstraint;
use object::RigidBody;
use math::{Point, Vector, Orientation};

/// The correction coefficient used by the constraint solver.
pub enum CorrectionMode<N: Scalar> {
    /// Penetration are solved by the penalty method.
    Velocity(N),
    /// Penetration are solved by the penalty method together with a hard repositioning.
    VelocityAndPosition(N, N, N),
    /// Penetration are solved by the penalty method together with a hard repositioning.
    ///
    /// The amount of velocity correction is bounded by threshold.
    VelocityAndPositionThresold(N, N, N)
}

impl<N: Scalar> CorrectionMode<N> {
    #[inline]
    /// The velocity correction coefficient.
    pub fn vel_corr_factor(&self) -> N {
        match *self {
            CorrectionMode::Velocity(ref v)                          => v.clone(),
            CorrectionMode::VelocityAndPosition(ref v, _, _)         => v.clone(),
            CorrectionMode::VelocityAndPositionThresold(ref v, _, _) => v.clone()
        }
    }

    #[inline]
    /// The position correction coefficient.
    pub fn pos_corr_factor(&self) -> N {
        match *self {
            CorrectionMode::VelocityAndPosition(_, ref p, _)         => p.clone(),
            CorrectionMode::VelocityAndPositionThresold(_, ref p, _) => p.clone(),
            CorrectionMode::Velocity(_)                              => na::zero()
        }
    }

    #[inline]
    /// The minimum penetration depth required to switch on the hard repositioning based method.
    pub fn min_depth_for_pos_corr(&self) -> N {
        match *self {
            CorrectionMode::VelocityAndPosition(_, _, ref t)         => t.clone(),
            CorrectionMode::VelocityAndPositionThresold(_, _, ref t) => t.clone(),
            CorrectionMode::Velocity(_)                              => Bounded::max_value()
        }
    }

    #[inline]
    /// The max penetration depth the velocity correction will attempt to correct.
    pub fn max_depth_for_vel_corr(&self) -> N {
        match *self {
            CorrectionMode::VelocityAndPosition(_, _, _)             => Bounded::max_value(),
            CorrectionMode::VelocityAndPositionThresold(_, _, ref t) => t.clone(),
            CorrectionMode::Velocity(_)                              => Bounded::max_value()
        }
    }
}

pub struct CorrectionParameters<N: Scalar> {
    pub corr_mode:       CorrectionMode<N>,
    pub joint_corr:      N,
    pub rest_eps:        N
}

pub fn reinit_to_first_order_equation<N: Scalar>(dt:         N,
                                                 coll:       &Contact<Point<N>>,
                                                 constraint: &mut VelocityConstraint<N>,
                                                 correction: &CorrectionParameters<N>) {
    /*
     * Fill b
     */
    if coll.depth >= correction.corr_mode.min_depth_for_pos_corr() {
        constraint.objective = correction.corr_mode.pos_corr_factor() * coll.depth.max(na::zero()) / dt;
    }
    else {
        constraint.objective = na::zero();
    }

    /*
     * Reset forces
     */
    constraint.impulse = na::zero();
}

pub fn fill_second_order_equation<N: Scalar>(dt:           N,
                                             coll:         &Contact<Point<N>>,
                                             rb1:          &RigidBody<N>,
                                             rb2:          &RigidBody<N>,
                                             rconstraint:  &mut VelocityConstraint<N>,
                                             idr:          usize,
                                             fconstraints: &mut [VelocityConstraint<N>],
                                             idf:          usize,
                                             cache:        &[N],
                                             correction:   &CorrectionParameters<N>) {
    let restitution = rb1.restitution() * rb2.restitution();

    let center = na::center(&coll.world1, &coll.world2);

    fill_velocity_constraint(dt.clone(),
                             coll.normal.clone(),
                             center.clone(),
                             restitution,
                             coll.depth.clone(),
                             cache[0].clone(), // coll.impulses[0].clone(),
                             na::zero(),
                             Bounded::max_value(),
                             rb1,
                             rb2,
                             rconstraint,
                             correction);

    let friction  = rb1.friction() * rb2.friction();
    // To bound the friction we use the last frame normal impulse.
    // That means we have to make a special case for the first time the contact appears.
    // In that case, we estimate the impulse by the derired normal correction.

    let mut i = 0;

    na::orthonormal_subspace_basis(&coll.normal, |friction_axis| {
        let constraint = &mut fconstraints[idf + i];

        fill_velocity_constraint(dt.clone(),
                                 friction_axis,
                                 center.clone(),
                                 na::zero(),
                                 na::zero(),
                                 cache[i + 1].clone(), // coll.impulses[i].clone(),
                                 na::zero(), // dont setup the limit now
                                 na::zero(), // dont setup the limit now
                                 rb1,
                                 rb2,
                                 constraint,
                                 correction);

        constraint.friction_coeff    = friction.clone();
        constraint.friction_limit_id = idr;
        i = i + 1;

        true
    })
}

pub fn fill_constraint_geometry<N: Scalar>(normal:     Vector<N>,
                                           rot_axis1:  Orientation<N>,
                                           rot_axis2:  Orientation<N>,
                                           rb1:        &Option<&RigidBody<N>>,
                                           rb2:        &Option<&RigidBody<N>>,
                                           constraint: &mut VelocityConstraint<N>) {
    constraint.normal             = normal;
    constraint.inv_projected_mass = na::zero();

    match *rb1 {
        Some(ref rb) => {
            // rotation axis
            constraint.weighted_normal1   = constraint.normal * rb.inv_mass();
            constraint.rot_axis1          = rot_axis1;

            constraint.weighted_rot_axis1 = rb.inv_inertia().apply(&constraint.rot_axis1);

            constraint.inv_projected_mass = constraint.inv_projected_mass +
                na::dot(&constraint.normal, &constraint.weighted_normal1) +
                na::dot(&constraint.rot_axis1, &constraint.weighted_rot_axis1);
        },
        None => { }
    }

    match *rb2 {
        Some(ref rb) => {
            // rotation axis
            constraint.weighted_normal2   = constraint.normal * rb.inv_mass();
            constraint.rot_axis2          = rot_axis2;

            constraint.weighted_rot_axis2 = rb.inv_inertia().apply(&constraint.rot_axis2);

            constraint.inv_projected_mass = constraint.inv_projected_mass +
                na::dot(&constraint.normal, &constraint.weighted_normal2) +
                na::dot(&constraint.rot_axis2, &constraint.weighted_rot_axis2);
        },
        None => { }
    }

    let _1: N = na::one();
    constraint.inv_projected_mass = _1 / constraint.inv_projected_mass;
}

fn fill_velocity_constraint<N: Scalar>(dt:              N,
                                       normal:          Vector<N>,
                                       center:          Point<N>,
                                       restitution:     N,
                                       depth:           N,
                                       initial_impulse: N,
                                       lobound:         N,
                                       hibound:         N,
                                       rb1:             &RigidBody<N>,
                                       rb2:             &RigidBody<N>,
                                       constraint:      &mut VelocityConstraint<N>,
                                       correction:      &CorrectionParameters<N>) {
    let rot_axis1 = na::cross(&(center - *rb1.center_of_mass()), &-normal);
    let rot_axis2 = na::cross(&(center - *rb2.center_of_mass()), &normal);

    let opt_rb1 = if rb1.can_move() { Some(rb1) } else { None };
    let opt_rb2 = if rb2.can_move() { Some(rb2) } else { None };
    fill_constraint_geometry(normal, rot_axis1, rot_axis2, &opt_rb1, &opt_rb2, constraint);

    /*
     * Fill indice
     */
    constraint.id1 = rb1.index();
    constraint.id2 = rb2.index();

    /*
     * correction amount
     */
    constraint.objective = relative_velocity(
        &opt_rb1,
        &opt_rb2,
        &constraint.normal,
        &constraint.rot_axis1,
        &constraint.rot_axis2,
        &dt);

    if constraint.objective < -correction.rest_eps {
        constraint.objective = constraint.objective + restitution * constraint.objective
    }

    constraint.objective = -constraint.objective;

    if false { // depth < na::zero() {
        constraint.objective = constraint.objective + depth / dt
    }
    else if depth < correction.corr_mode.max_depth_for_vel_corr() {
        constraint.objective = constraint.objective + depth * correction.corr_mode.vel_corr_factor() / dt
    }

    // for warm-starting
    constraint.impulse = if depth < na::zero() { na::zero() } else { initial_impulse };

    /*
     * constraint bounds
     */
    constraint.lobound = lobound;
    constraint.hibound = hibound;
}

pub fn relative_velocity<N: Scalar>(rb1:       &Option<&RigidBody<N>>,
                                    rb2:       &Option<&RigidBody<N>>,
                                    normal:    &Vector<N>,
                                    rot_axis1: &Orientation<N>,
                                    rot_axis2: &Orientation<N>,
                                    dt:        &N)
                                    -> N {
    let mut dvel: N = na::zero();

    match *rb1 {
        Some(ref rb) => {
            dvel = dvel - na::dot(&(rb.lin_vel() + rb.lin_acc() * *dt), normal)
                        + na::dot(&(rb.ang_vel() + rb.ang_acc() * *dt), rot_axis1);
        },
        None => { }
    }

    match *rb2 {
        Some(ref rb) => {
            dvel = dvel + na::dot(&(rb.lin_vel() + rb.lin_acc() * *dt), normal)
                        + na::dot(&(rb.ang_vel() + rb.ang_acc() * *dt), rot_axis2);
        },
        None => { }
    }

    dvel
}
<|endoftext|>