<commit_before>

extern crate "snowmew-core" as core;
extern crate "snowmew-position" as position;
extern crate "snowmew-graphics" as graphics;
extern crate "snowmew-render" as render;
extern crate wire;
extern crate bchannel;
extern crate bincode;
extern crate "rustc-serialize" as rustc_serialize;

use core::common::{Common, CommonData};
use position::{Positions, PositionData};
use graphics::{Graphics, GraphicsData};
use render::{Renderable, RenderData};

use std::sync::mpsc;
use std::thread::Thread;
use std::ops::{Deref, DerefMut};
use std::io::net::tcp::{
    TcpStream,
    TcpListener,
    TcpAcceptor
};
use std::io::{
    IoError,
};
use wire::SizeLimit;
use wire::tcp::OutTcpStream;
use rustc_serialize::{Decodable, Encodable};

use core::game::Game;

#[derive(RustcEncodable, RustcDecodable)]
pub enum ServerMessage<T, E> {
    Image(T),
    Event(E),
    Sync
}

#[derive(RustcEncodable, RustcDecodable)]
pub enum ClientMessage<E> {
    Event(E)
}

pub struct Server<Game, GameData> {
    game: Game,
    data: GameData
}

unsafe impl<T:Send, E:Send> Send for ServerClient<T, E> {}

struct ServerClient<T, E> {
    to_client: OutTcpStream<ServerMessage<T, E>>,
    from_client: bchannel::Receiver<ClientMessage<E>, bincode::DecodingError>
}

impl<G, GD:Clone+Send+Decodable+Encodable> Server<G, GD> {
    pub fn new(game: G, data: GD) -> Server<G, GD> {
        Server {
            game: game,
            data: data
        }
    }

    pub fn serve<E:Send+Encodable+Clone+Decodable>(mut self, iface: &str, port: u16)
        where G: Game<GD, E> {

        let (listener, _) = wire::listen_tcp(iface, port).unwrap();

        let (tx, rx): (mpsc::Sender<ServerClient<GD, E>>,
                       mpsc::Receiver<ServerClient<GD, E>>) = mpsc::channel();
        Thread::spawn(move || {
            let (read_limit, write_limit) = (SizeLimit::Infinite, SizeLimit::Infinite);

            for connection in listener.into_blocking_iter() {
                let (i, mut o) = wire::upgrade_tcp(connection, read_limit, write_limit);

                let client = ServerClient {
                    to_client: o,
                    from_client: i
                };

                tx.send(client);
            }
        });

        let mut clients = Vec::new();

        loop {
            // add clients to game
            while clients.len() == 0 {
                let mut res = rx.recv().ok().expect("failed to get message");
                res.to_client.send(&ServerMessage::Image(self.data.clone()));
                clients.push(res);
            }

            while let Ok(mut res) = rx.try_recv() {
                res.to_client.send(&ServerMessage::Image(self.data.clone()));
                clients.push(res)
            }

            let mut events = Vec::new();

            for c in clients.iter() {
                events.push(c.from_client.recv_block().expect("client sucks man"));
            }

            for ClientMessage::Event(e) in events.drain() {
                for c in clients.iter_mut() {
                    c.to_client.send(&ServerMessage::Event(e.clone()));
                }
                self.data = self.game.step(e, self.data)
            }
            for c in clients.iter_mut() {
                c.to_client.send(&ServerMessage::Sync);
            }

        }
    }
}

#[derive(Clone)]
pub struct ClientState<T, E> {
    predict_frame: u32,
    predict: T,
    predict_delta: Vec<(u32, E)>,
    server_frame: u32,
    server: T
}

impl<T, E> Deref for ClientState<T, E> {
    type Target = T;

    fn deref<'a>(&'a self) -> &'a T {
        &self.predict
    }
}

impl<T, E> DerefMut for ClientState<T, E> {
    fn deref_mut<'a>(&'a mut self) -> &'a mut T {
        &mut self.predict
    }
}

impl<E, T: Common> Common for ClientState<T, E> {
    fn get_common<'a>(&'a self) -> &'a CommonData { self.predict.get_common() }
    fn get_common_mut<'a>(&'a mut self) -> &'a mut CommonData { self.predict.get_common_mut() }
}

impl<E, T: Positions> Positions for ClientState<T, E> {
    fn get_position<'a>(&'a self) -> &'a PositionData { self.predict.get_position() }
    fn get_position_mut<'a>(&'a mut self) -> &'a mut PositionData { self.predict.get_position_mut() }
}

impl<E, T: Graphics> Graphics for ClientState<T, E> {
    fn get_graphics<'a>(&'a self) -> &'a GraphicsData { self.predict.get_graphics() }
    fn get_graphics_mut<'a>(&'a mut self) -> &'a mut GraphicsData { self.predict.get_graphics_mut() }
}

impl<E, T: Renderable+Common+Positions+Graphics> Renderable for ClientState<T, E> {
    fn get_render_data<'a>(&'a self) -> &'a RenderData { self.predict.get_render_data() }
    fn get_render_data_mut<'a>(&'a mut self) -> &'a mut RenderData { self.predict.get_render_data_mut() }
}

pub struct Client<G, T, E> {
    game: G,
    to_server: OutTcpStream<ClientMessage<E>>,
    from_server: bchannel::Receiver<ServerMessage<T, E>, bincode::DecodingError>
}

impl<G, T:Send+Encodable+Decodable+Clone, E:Send+Encodable+Decodable> Client<G, T, E> {
    pub fn new(game: G, server: &str, port: u16) -> Client<G, T, E> {
        let (o, i) = wire::connect_tcp(server, port, SizeLimit::Infinite, SizeLimit::Infinite).unwrap();

        Client {
            game: game,
            to_server: i,
            from_server: o
        }
    }

    pub fn gamedata(&mut self) -> ClientState<T, E> {
        match self.from_server.recv_block().expect("failed to get state") {
            ServerMessage::Image(state) => {
                println!("download done");
                ClientState {
                    predict_frame: 0,
                    predict: state.clone(),
                    predict_delta: Vec::new(),
                    server_frame: 0,
                    server: state.clone()
                }
            },
            _ => panic!("expected game data")
        }
    }
}

impl<G: Game<T, E>, T:Send+Encodable+Decodable+Clone, E:Send+Encodable+Decodable+Clone>
    Game<ClientState<T, E>, E> for Client<G, T, E> {

    fn step(&mut self, event: E, mut gd: ClientState<T, E>) -> ClientState<T, E> {
        self.to_server.send(&ClientMessage::Event(event.clone()));

        let mut frame = gd.predict.clone();
        let mut index = gd.predict_frame;

        gd.predict_delta.push((gd.predict_frame, event));
        gd.predict_frame += 1;

        loop {
            match self.from_server.recv() {
                Some(ServerMessage::Image(_)) => panic!("unexpected game data"),
                Some(ServerMessage::Event(e)) => {
                    gd.server = self.game.step(e, gd.server);
                }
                Some(ServerMessage::Sync) => {
                    gd.server_frame += 1;
                    let camera = gd.camera().expect("no camera found");
                    frame = gd.server.clone();
                    index = gd.server_frame;
                },
                None => break
            }
        }

        let mut predict = Vec::new();
        for (idx, e) in gd.predict_delta.drain() {
            if idx > index {
                println!("{} > {}", idx, index);
                frame = self.game.step(e.clone(), frame);
            }

            if idx > gd.server_frame {
                predict.push((idx, e));
            }
        }

        gd.predict = frame;
        gd.predict_delta = predict;
        gd
    }
}

<commit_msg>Added logic to stop client from getting to far ahead of the server<commit_after>

extern crate "snowmew-core" as core;
extern crate "snowmew-position" as position;
extern crate "snowmew-graphics" as graphics;
extern crate "snowmew-render" as render;
extern crate wire;
extern crate bchannel;
extern crate bincode;
extern crate "rustc-serialize" as rustc_serialize;

use core::common::{Common, CommonData};
use position::{Positions, PositionData};
use graphics::{Graphics, GraphicsData};
use render::{Renderable, RenderData};

use std::sync::mpsc;
use std::thread::Thread;
use std::ops::{Deref, DerefMut};
use std::io::net::tcp::{
    TcpStream,
    TcpListener,
    TcpAcceptor
};
use std::io::{
    IoError,
};
use wire::SizeLimit;
use wire::tcp::OutTcpStream;
use rustc_serialize::{Decodable, Encodable};

use core::game::Game;

#[derive(RustcEncodable, RustcDecodable)]
pub enum ServerMessage<T, E> {
    Image(T),
    Event(E),
    Sync
}

#[derive(RustcEncodable, RustcDecodable)]
pub enum ClientMessage<E> {
    Event(E)
}

pub struct Server<Game, GameData> {
    game: Game,
    data: GameData
}

unsafe impl<T:Send, E:Send> Send for ServerClient<T, E> {}

struct ServerClient<T, E> {
    to_client: OutTcpStream<ServerMessage<T, E>>,
    from_client: bchannel::Receiver<ClientMessage<E>, bincode::DecodingError>
}

impl<G, GD:Clone+Send+Decodable+Encodable> Server<G, GD> {
    pub fn new(game: G, data: GD) -> Server<G, GD> {
        Server {
            game: game,
            data: data
        }
    }

    pub fn serve<E:Send+Encodable+Clone+Decodable>(mut self, iface: &str, port: u16)
        where G: Game<GD, E> {

        let (listener, _) = wire::listen_tcp(iface, port).unwrap();

        let (tx, rx): (mpsc::Sender<ServerClient<GD, E>>,
                       mpsc::Receiver<ServerClient<GD, E>>) = mpsc::channel();
        Thread::spawn(move || {
            let (read_limit, write_limit) = (SizeLimit::Infinite, SizeLimit::Infinite);

            for connection in listener.into_blocking_iter() {
                let (i, mut o) = wire::upgrade_tcp(connection, read_limit, write_limit);

                let client = ServerClient {
                    to_client: o,
                    from_client: i
                };

                tx.send(client);
            }
        });

        let mut clients = Vec::new();

        loop {
            // add clients to game
            while clients.len() == 0 {
                let mut res = rx.recv().ok().expect("failed to get message");
                res.to_client.send(&ServerMessage::Image(self.data.clone()));
                clients.push(res);
            }

            while let Ok(mut res) = rx.try_recv() {
                res.to_client.send(&ServerMessage::Image(self.data.clone()));
                clients.push(res)
            }

            let mut events = Vec::new();

            for c in clients.iter() {
                events.push(c.from_client.recv_block().expect("client sucks man"));
            }

            for ClientMessage::Event(e) in events.drain() {
                for c in clients.iter_mut() {
                    c.to_client.send(&ServerMessage::Event(e.clone()));
                }
                self.data = self.game.step(e, self.data)
            }
            for c in clients.iter_mut() {
                c.to_client.send(&ServerMessage::Sync);
            }

        }
    }
}

#[derive(Clone)]
pub struct ClientState<T, E> {
    sync: bool,
    predict_frame: u32,
    predict: T,
    predict_delta: Vec<(u32, E)>,
    server_frame: u32,
    server: T
}

impl<T, E> Deref for ClientState<T, E> {
    type Target = T;

    fn deref<'a>(&'a self) -> &'a T {
        &self.predict
    }
}

impl<T, E> DerefMut for ClientState<T, E> {
    fn deref_mut<'a>(&'a mut self) -> &'a mut T {
        &mut self.predict
    }
}

impl<E, T: Common> Common for ClientState<T, E> {
    fn get_common<'a>(&'a self) -> &'a CommonData { self.predict.get_common() }
    fn get_common_mut<'a>(&'a mut self) -> &'a mut CommonData { self.predict.get_common_mut() }
}

impl<E, T: Positions> Positions for ClientState<T, E> {
    fn get_position<'a>(&'a self) -> &'a PositionData { self.predict.get_position() }
    fn get_position_mut<'a>(&'a mut self) -> &'a mut PositionData { self.predict.get_position_mut() }
}

impl<E, T: Graphics> Graphics for ClientState<T, E> {
    fn get_graphics<'a>(&'a self) -> &'a GraphicsData { self.predict.get_graphics() }
    fn get_graphics_mut<'a>(&'a mut self) -> &'a mut GraphicsData { self.predict.get_graphics_mut() }
}

impl<E, T: Renderable+Common+Positions+Graphics> Renderable for ClientState<T, E> {
    fn get_render_data<'a>(&'a self) -> &'a RenderData { self.predict.get_render_data() }
    fn get_render_data_mut<'a>(&'a mut self) -> &'a mut RenderData { self.predict.get_render_data_mut() }
}

pub struct Client<G, T, E> {
    game: G,
    to_server: OutTcpStream<ClientMessage<E>>,
    from_server: bchannel::Receiver<ServerMessage<T, E>, bincode::DecodingError>
}

impl<G, T:Send+Encodable+Decodable+Clone, E:Send+Encodable+Decodable> Client<G, T, E> {
    pub fn new(game: G, server: &str, port: u16) -> Client<G, T, E> {
        let (o, i) = wire::connect_tcp(server, port, SizeLimit::Infinite, SizeLimit::Infinite).unwrap();

        Client {
            game: game,
            to_server: i,
            from_server: o
        }
    }

    pub fn gamedata(&mut self) -> ClientState<T, E> {
        match self.from_server.recv_block().expect("failed to get state") {
            ServerMessage::Image(state) => {
                println!("download done");
                ClientState {
                    sync: false,
                    predict_frame: 0,
                    predict: state.clone(),
                    predict_delta: Vec::new(),
                    server_frame: 0,
                    server: state.clone()
                }
            },
            _ => panic!("expected game data")
        }
    }
}

impl<G: Game<T, E>, T:Send+Encodable+Decodable+Clone, E:Send+Encodable+Decodable+Clone>
    Game<ClientState<T, E>, E> for Client<G, T, E> {

    fn step(&mut self, event: E, mut gd: ClientState<T, E>) -> ClientState<T, E> {
        let mut frame = gd.predict.clone();
        let mut index = gd.predict_frame;

        loop {
            match self.from_server.recv() {
                Some(ServerMessage::Image(_)) => panic!("unexpected game data"),
                Some(ServerMessage::Event(e)) => {
                    gd.server = self.game.step(e, gd.server);
                }
                Some(ServerMessage::Sync) => {
                    gd.server_frame += 1;
                    frame = gd.server.clone();
                    index = gd.server_frame;
                },
                None => break
            }
        }

        if gd.sync {
            gd.sync = gd.predict_frame > gd.server_frame + 1;
            if gd.sync {
                return gd
            }
        } else if gd.predict_frame > gd.server_frame + 5 {
            gd.sync = true;
            return gd;
        }

        self.to_server.send(&ClientMessage::Event(event.clone()));
        gd.predict_delta.push((gd.predict_frame, event));
        gd.predict_frame += 1;

        let mut predict = Vec::new();
        for (idx, e) in gd.predict_delta.drain() {
            if idx > index {
                println!("{} > {}", idx, index);
                frame = self.game.step(e.clone(), frame);
            }

            if idx > gd.server_frame {
                predict.push((idx, e));
            }
        }

        gd.predict = frame;
        gd.predict_delta = predict;
        gd
    }
}

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use devtools_traits;
use devtools_traits::{EvaluateJSReply, NodeInfo, Modification};
use dom::bindings::conversions;
use dom::bindings::conversions::FromJSValConvertible;
use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast};
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::DOMRectBinding::{DOMRectMethods};
use dom::bindings::codegen::Bindings::ElementBinding::{ElementMethods};
use dom::node::{Node, NodeHelpers};
use dom::window::{WindowHelpers};
use dom::element::Element;
use dom::document::DocumentHelpers;
use page::Page;
use servo_msg::constellation_msg::PipelineId;
use script_task::get_page;
use std::rc::Rc;

pub fn handle_evaluate_js(page: &Rc<Page>, pipeline: PipelineId, eval: String, reply: Sender<EvaluateJSReply>){
    let page = get_page(&*page, pipeline);
    let frame = page.frame();
    let window = frame.as_ref().unwrap().window.root();
    let cx = window.get_cx();
    let rval = window.evaluate_js_with_result(eval.as_slice());

    reply.send(if rval.is_undefined() {
        devtools_traits::VoidValue
    } else if rval.is_boolean() {
        devtools_traits::BooleanValue(rval.to_boolean())
    } else if rval.is_double() {
        devtools_traits::NumberValue(FromJSValConvertible::from_jsval(cx, rval, ()).unwrap())
    } else if rval.is_string() {
        //FIXME: use jsstring_to_str when jsval grows to_jsstring
        devtools_traits::StringValue(FromJSValConvertible::from_jsval(cx, rval, conversions::Default).unwrap())
    } else {
        //FIXME: jsvals don't have an is_int32/is_number yet
        assert!(rval.is_object_or_null());
        panic!("object values unimplemented")
    });
}

pub fn handle_get_root_node(page: &Rc<Page>, pipeline: PipelineId, reply: Sender<NodeInfo>) {
    let page = get_page(&*page, pipeline);
    let frame = page.frame();
    let document = frame.as_ref().unwrap().document.root();

    let node: JSRef<Node> = NodeCast::from_ref(*document);
    reply.send(node.summarize());
}

pub fn handle_get_document_element(page: &Rc<Page>, pipeline: PipelineId, reply: Sender<NodeInfo>) {
    let page = get_page(&*page, pipeline);
    let frame = page.frame();
    let document = frame.as_ref().unwrap().document.root();
    let document_element = document.GetDocumentElement().root().unwrap();

    let node: JSRef<Node> = NodeCast::from_ref(*document_element);
    reply.send(node.summarize());
}

fn find_node_by_unique_id(page: &Rc<Page>, pipeline: PipelineId, node_id: String) -> Temporary<Node> {
    let page = get_page(&*page, pipeline);
    let frame = page.frame();
    let document = frame.as_ref().unwrap().document.root();
    let node: JSRef<Node> = NodeCast::from_ref(*document);

    for candidate in node.traverse_preorder() {
        if candidate.get_unique_id().as_slice() == node_id.as_slice() {
            return Temporary::from_rooted(candidate);
        }
    }

    panic!("couldn't find node with unique id {:s}", node_id)
}

pub fn handle_get_children(page: &Rc<Page>, pipeline: PipelineId, node_id: String, reply: Sender<Vec<NodeInfo>>) {
    let parent = find_node_by_unique_id(&*page, pipeline, node_id).root();
    let children = parent.children().map(|child| child.summarize()).collect();
    reply.send(children);
}

pub fn handle_get_layout(page: &Rc<Page>, pipeline: PipelineId, node_id: String, reply: Sender<(f32, f32)>) {
    let node = find_node_by_unique_id(&*page, pipeline, node_id).root();
    let elem: JSRef<Element> = ElementCast::to_ref(*node).expect("should be getting layout of element");
    let rect = elem.GetBoundingClientRect().root();
    reply.send((rect.Width(), rect.Height()));
}

pub fn handle_modify_attribute(page: &Rc<Page>, pipeline: PipelineId, node_id: String, modifications: Vec<Modification>) {
    let node = find_node_by_unique_id(&*page, pipeline, node_id).root();
    let elem: JSRef<Element> = ElementCast::to_ref(*node).expect("should be getting layout of element");

    for modification in modifications.iter(){
        match modification.newValue {
            Some(ref string) => {
                let _ = elem.SetAttribute(modification.attributeName.clone(), string.clone());
            },
            None => elem.RemoveAttribute(modification.attributeName.clone()),
        }
    }
}
<commit_msg>auto merge of #4397 : jdm/servo/issue_4235, r=jdm<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use devtools_traits;
use devtools_traits::{EvaluateJSReply, NodeInfo, Modification};
use dom::bindings::conversions;
use dom::bindings::conversions::FromJSValConvertible;
use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast};
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::DOMRectBinding::{DOMRectMethods};
use dom::bindings::codegen::Bindings::ElementBinding::{ElementMethods};
use dom::node::{Node, NodeHelpers};
use dom::window::{WindowHelpers};
use dom::element::Element;
use dom::document::DocumentHelpers;
use page::Page;
use servo_msg::constellation_msg::PipelineId;
use script_task::get_page;
use std::rc::Rc;

pub fn handle_evaluate_js(page: &Rc<Page>, pipeline: PipelineId, eval: String, reply: Sender<EvaluateJSReply>){
    let page = get_page(&*page, pipeline);
    let frame = page.frame();
    let window = frame.as_ref().unwrap().window.root();
    let cx = window.get_cx();
    let rval = window.evaluate_js_with_result(eval.as_slice());

    reply.send(if rval.is_undefined() {
        devtools_traits::VoidValue
    } else if rval.is_boolean() {
        devtools_traits::BooleanValue(rval.to_boolean())
    } else if rval.is_double() {
        devtools_traits::NumberValue(FromJSValConvertible::from_jsval(cx, rval, ()).unwrap())
    } else if rval.is_string() {
        //FIXME: use jsstring_to_str when jsval grows to_jsstring
        devtools_traits::StringValue(FromJSValConvertible::from_jsval(cx, rval, conversions::Default).unwrap())
    } else if rval.is_null() {
        devtools_traits::NullValue
    } else {
        //FIXME: jsvals don't have an is_int32/is_number yet
        assert!(rval.is_object());
        panic!("object values unimplemented")
    });
}

pub fn handle_get_root_node(page: &Rc<Page>, pipeline: PipelineId, reply: Sender<NodeInfo>) {
    let page = get_page(&*page, pipeline);
    let frame = page.frame();
    let document = frame.as_ref().unwrap().document.root();

    let node: JSRef<Node> = NodeCast::from_ref(*document);
    reply.send(node.summarize());
}

pub fn handle_get_document_element(page: &Rc<Page>, pipeline: PipelineId, reply: Sender<NodeInfo>) {
    let page = get_page(&*page, pipeline);
    let frame = page.frame();
    let document = frame.as_ref().unwrap().document.root();
    let document_element = document.GetDocumentElement().root().unwrap();

    let node: JSRef<Node> = NodeCast::from_ref(*document_element);
    reply.send(node.summarize());
}

fn find_node_by_unique_id(page: &Rc<Page>, pipeline: PipelineId, node_id: String) -> Temporary<Node> {
    let page = get_page(&*page, pipeline);
    let frame = page.frame();
    let document = frame.as_ref().unwrap().document.root();
    let node: JSRef<Node> = NodeCast::from_ref(*document);

    for candidate in node.traverse_preorder() {
        if candidate.get_unique_id().as_slice() == node_id.as_slice() {
            return Temporary::from_rooted(candidate);
        }
    }

    panic!("couldn't find node with unique id {:s}", node_id)
}

pub fn handle_get_children(page: &Rc<Page>, pipeline: PipelineId, node_id: String, reply: Sender<Vec<NodeInfo>>) {
    let parent = find_node_by_unique_id(&*page, pipeline, node_id).root();
    let children = parent.children().map(|child| child.summarize()).collect();
    reply.send(children);
}

pub fn handle_get_layout(page: &Rc<Page>, pipeline: PipelineId, node_id: String, reply: Sender<(f32, f32)>) {
    let node = find_node_by_unique_id(&*page, pipeline, node_id).root();
    let elem: JSRef<Element> = ElementCast::to_ref(*node).expect("should be getting layout of element");
    let rect = elem.GetBoundingClientRect().root();
    reply.send((rect.Width(), rect.Height()));
}

pub fn handle_modify_attribute(page: &Rc<Page>, pipeline: PipelineId, node_id: String, modifications: Vec<Modification>) {
    let node = find_node_by_unique_id(&*page, pipeline, node_id).root();
    let elem: JSRef<Element> = ElementCast::to_ref(*node).expect("should be getting layout of element");

    for modification in modifications.iter(){
        match modification.newValue {
            Some(ref string) => {
                let _ = elem.SetAttribute(modification.attributeName.clone(), string.clone());
            },
            None => elem.RemoveAttribute(modification.attributeName.clone()),
        }
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

pub mod data;
pub mod restyle_damage;
pub mod snapshot;
pub mod snapshot_helpers;
pub mod traversal;
pub mod wrapper;

pub mod conversions;
pub mod selector_parser;
pub mod values;
<commit_msg>style: Describe the gecko module.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Gecko-specific style-system bits.

pub mod data;
pub mod restyle_damage;
pub mod snapshot;
pub mod snapshot_helpers;
pub mod traversal;
pub mod wrapper;

pub mod conversions;
pub mod selector_parser;
pub mod values;
<|endoftext|>


<commit_before>use std::borrow::Cow;
use std::collections::{hash_map::Entry::Vacant, BTreeMap, HashMap};
use std::{self, fmt};

use serde::ser::Serialize;
use serde_derive::{Deserialize, Serialize};
use serde_json::{to_value, Value};

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct ValidationError {
    pub code: Cow<'static, str>,
    pub message: Option<Cow<'static, str>>,
    pub params: HashMap<Cow<'static, str>, Value>,
}

impl ValidationError {
    pub fn new(code: &'static str) -> ValidationError {
        ValidationError { code: Cow::from(code), message: None, params: HashMap::new() }
    }

    pub fn add_param<T: Serialize>(&mut self, name: Cow<'static, str>, val: &T) {
        self.params.insert(name, to_value(val).unwrap());
    }
}

impl fmt::Display for ValidationError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "Validation error: {} [{:?}]", self.code, self.params)
    }
}

impl std::error::Error for ValidationError {
    fn description(&self) -> &str {
        &self.code
    }
    fn cause(&self) -> Option<&dyn std::error::Error> {
        None
    }
}

#[derive(Debug, Serialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum ValidationErrorsKind {
    Struct(Box<ValidationErrors>),
    List(BTreeMap<usize, Box<ValidationErrors>>),
    Field(Vec<ValidationError>),
}

#[derive(Default, Debug, Serialize, Clone, PartialEq)]
pub struct ValidationErrors(HashMap<&'static str, ValidationErrorsKind>);

impl ValidationErrors {
    pub fn new() -> ValidationErrors {
        ValidationErrors(HashMap::new())
    }

    /// Returns a boolean indicating whether a validation result includes validation errors for a
    /// given field. May be used as a condition for performing nested struct validations on a field
    /// in the absence of field-level validation errors.
    pub fn has_error(result: &Result<(), ValidationErrors>, field: &'static str) -> bool {
        match result {
            Ok(()) => false,
            Err(ref errs) => errs.contains_key(field),
        }
    }

    /// Returns the combined outcome of a struct's validation result along with the nested
    /// validation result for one of its fields.
    pub fn merge(
        parent: Result<(), ValidationErrors>,
        field: &'static str,
        child: Result<(), ValidationErrors>,
    ) -> Result<(), ValidationErrors> {
        match child {
            Ok(()) => parent,
            Err(errors) => {
                parent.and_then(|_| Err(ValidationErrors::new())).map_err(|mut parent_errors| {
                    parent_errors.add_nested(field, ValidationErrorsKind::Struct(Box::new(errors)));
                    parent_errors
                })
            }
        }
    }

    /// Returns the combined outcome of a struct's validation result along with the nested
    /// validation result for one of its fields where that field is a vector of validating structs.
    pub fn merge_all(
        parent: Result<(), ValidationErrors>,
        field: &'static str,
        children: Vec<Result<(), ValidationErrors>>,
    ) -> Result<(), ValidationErrors> {
        let errors = children
            .into_iter()
            .enumerate()
            .filter_map(|(i, res)| res.err().map(|mut err| (i, err.remove(field))))
            .filter_map(|(i, entry)| match entry {
                Some(ValidationErrorsKind::Struct(errors)) => Some((i, errors)),
                _ => None,
            })
            .collect::<BTreeMap<_, _>>();

        if errors.is_empty() {
            parent
        } else {
            parent.and_then(|_| Err(ValidationErrors::new())).map_err(|mut parent_errors| {
                parent_errors.add_nested(field, ValidationErrorsKind::List(errors));
                parent_errors
            })
        }
    }

    /// Returns a map of field-level validation errors found for the struct that was validated and
    /// any of it's nested structs that are tagged for validation.
    pub fn errors(&self) -> &HashMap<&'static str, ValidationErrorsKind> {
        &self.0
    }

    /// Consume the struct, returning the validation errors found
    pub fn into_errors(self) -> HashMap<&'static str, ValidationErrorsKind> {
        self.0
    }

    /// Returns a map of only field-level validation errors found for the struct that was validated.
    pub fn field_errors(&self) -> HashMap<&str, &Vec<ValidationError>> {
        self.0
            .iter()
            .filter_map(|(k, v)| {
                if let ValidationErrorsKind::Field(errors) = v {
                    Some((*k, errors))
                } else {
                    None
                }
            })
            .collect::<HashMap<_, _>>()
    }

    pub fn add(&mut self, field: &'static str, error: ValidationError) {
        if let ValidationErrorsKind::Field(ref mut vec) =
            self.0.entry(field).or_insert_with(|| ValidationErrorsKind::Field(vec![]))
        {
            vec.push(error);
        } else {
            panic!("Attempt to add field validation to a non-Field ValidationErrorsKind instance");
        }
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    fn add_nested(&mut self, field: &'static str, errors: ValidationErrorsKind) {
        if let Vacant(entry) = self.0.entry(field) {
            entry.insert(errors);
        } else {
            panic!("Attempt to replace non-empty ValidationErrors entry");
        }
    }

    fn contains_key(&self, field: &'static str) -> bool {
        self.0.contains_key(field)
    }

    fn remove(&mut self, field: &'static str) -> Option<ValidationErrorsKind> {
        self.0.remove(field)
    }
}

impl std::error::Error for ValidationErrors {
    fn description(&self) -> &str {
        "Validation failed"
    }
    fn cause(&self) -> Option<&dyn std::error::Error> {
        None
    }
}

impl fmt::Display for ValidationErrors {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, fmt)
    }
}
<commit_msg>Specify static lifetime for field_errors<commit_after>use std::borrow::Cow;
use std::collections::{hash_map::Entry::Vacant, BTreeMap, HashMap};
use std::{self, fmt};

use serde::ser::Serialize;
use serde_derive::{Deserialize, Serialize};
use serde_json::{to_value, Value};

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct ValidationError {
    pub code: Cow<'static, str>,
    pub message: Option<Cow<'static, str>>,
    pub params: HashMap<Cow<'static, str>, Value>,
}

impl ValidationError {
    pub fn new(code: &'static str) -> ValidationError {
        ValidationError { code: Cow::from(code), message: None, params: HashMap::new() }
    }

    pub fn add_param<T: Serialize>(&mut self, name: Cow<'static, str>, val: &T) {
        self.params.insert(name, to_value(val).unwrap());
    }
}

impl fmt::Display for ValidationError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "Validation error: {} [{:?}]", self.code, self.params)
    }
}

impl std::error::Error for ValidationError {
    fn description(&self) -> &str {
        &self.code
    }
    fn cause(&self) -> Option<&dyn std::error::Error> {
        None
    }
}

#[derive(Debug, Serialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum ValidationErrorsKind {
    Struct(Box<ValidationErrors>),
    List(BTreeMap<usize, Box<ValidationErrors>>),
    Field(Vec<ValidationError>),
}

#[derive(Default, Debug, Serialize, Clone, PartialEq)]
pub struct ValidationErrors(HashMap<&'static str, ValidationErrorsKind>);

impl ValidationErrors {
    pub fn new() -> ValidationErrors {
        ValidationErrors(HashMap::new())
    }

    /// Returns a boolean indicating whether a validation result includes validation errors for a
    /// given field. May be used as a condition for performing nested struct validations on a field
    /// in the absence of field-level validation errors.
    pub fn has_error(result: &Result<(), ValidationErrors>, field: &'static str) -> bool {
        match result {
            Ok(()) => false,
            Err(ref errs) => errs.contains_key(field),
        }
    }

    /// Returns the combined outcome of a struct's validation result along with the nested
    /// validation result for one of its fields.
    pub fn merge(
        parent: Result<(), ValidationErrors>,
        field: &'static str,
        child: Result<(), ValidationErrors>,
    ) -> Result<(), ValidationErrors> {
        match child {
            Ok(()) => parent,
            Err(errors) => {
                parent.and_then(|_| Err(ValidationErrors::new())).map_err(|mut parent_errors| {
                    parent_errors.add_nested(field, ValidationErrorsKind::Struct(Box::new(errors)));
                    parent_errors
                })
            }
        }
    }

    /// Returns the combined outcome of a struct's validation result along with the nested
    /// validation result for one of its fields where that field is a vector of validating structs.
    pub fn merge_all(
        parent: Result<(), ValidationErrors>,
        field: &'static str,
        children: Vec<Result<(), ValidationErrors>>,
    ) -> Result<(), ValidationErrors> {
        let errors = children
            .into_iter()
            .enumerate()
            .filter_map(|(i, res)| res.err().map(|mut err| (i, err.remove(field))))
            .filter_map(|(i, entry)| match entry {
                Some(ValidationErrorsKind::Struct(errors)) => Some((i, errors)),
                _ => None,
            })
            .collect::<BTreeMap<_, _>>();

        if errors.is_empty() {
            parent
        } else {
            parent.and_then(|_| Err(ValidationErrors::new())).map_err(|mut parent_errors| {
                parent_errors.add_nested(field, ValidationErrorsKind::List(errors));
                parent_errors
            })
        }
    }

    /// Returns a map of field-level validation errors found for the struct that was validated and
    /// any of it's nested structs that are tagged for validation.
    pub fn errors(&self) -> &HashMap<&'static str, ValidationErrorsKind> {
        &self.0
    }

    /// Consume the struct, returning the validation errors found
    pub fn into_errors(self) -> HashMap<&'static str, ValidationErrorsKind> {
        self.0
    }

    /// Returns a map of only field-level validation errors found for the struct that was validated.
    pub fn field_errors(&self) -> HashMap<&'static str, &Vec<ValidationError>> {
        self.0
            .iter()
            .filter_map(|(k, v)| {
                if let ValidationErrorsKind::Field(errors) = v {
                    Some((*k, errors))
                } else {
                    None
                }
            })
            .collect::<HashMap<_, _>>()
    }

    pub fn add(&mut self, field: &'static str, error: ValidationError) {
        if let ValidationErrorsKind::Field(ref mut vec) =
            self.0.entry(field).or_insert_with(|| ValidationErrorsKind::Field(vec![]))
        {
            vec.push(error);
        } else {
            panic!("Attempt to add field validation to a non-Field ValidationErrorsKind instance");
        }
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    fn add_nested(&mut self, field: &'static str, errors: ValidationErrorsKind) {
        if let Vacant(entry) = self.0.entry(field) {
            entry.insert(errors);
        } else {
            panic!("Attempt to replace non-empty ValidationErrors entry");
        }
    }

    fn contains_key(&self, field: &'static str) -> bool {
        self.0.contains_key(field)
    }

    fn remove(&mut self, field: &'static str) -> Option<ValidationErrorsKind> {
        self.0.remove(field)
    }
}

impl std::error::Error for ValidationErrors {
    fn description(&self) -> &str {
        "Validation failed"
    }
    fn cause(&self) -> Option<&dyn std::error::Error> {
        None
    }
}

impl fmt::Display for ValidationErrors {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, fmt)
    }
}
<|endoftext|>


<commit_before>extern crate site;
#[macro_use]
extern crate errors;
extern crate content;
extern crate front_matter;

use std::path::{Path, Component};

use errors::Result;
use site::Site;
use content::{Page, Section};
use front_matter::{PageFrontMatter, SectionFrontMatter};

/// Finds the section that contains the page given if there is one
pub fn find_parent_section<'a>(site: &'a Site, page: &Page) -> Option<&'a Section> {
    for section in site.sections.values() {
        if section.is_child_page(&page.file.path) {
            return Some(section)
        }
    }

    None
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PageChangesNeeded {
    /// Editing `tags`
    Tags,
    /// Editing `categories`
    Categories,
    /// Editing `date`, `order` or `weight`
    Sort,
    /// Editing anything causes a re-render of the page
    Render,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SectionChangesNeeded {
    /// Editing `sort_by`
    Sort,
    /// Editing `title`, `description`, `extra`, `template` or setting `render` to true
    Render,
    /// Editing `paginate_by`, `paginate_path` or `insert_anchor_links`
    RenderWithPages,
    /// Setting `render` to false
    Delete,
}

/// Evaluates all the params in the front matter that changed so we can do the smallest
/// delta in the serve command
/// Order matters as the actions will be done in insertion order
fn find_section_front_matter_changes(current: &SectionFrontMatter, new: &SectionFrontMatter) -> Vec<SectionChangesNeeded> {
    let mut changes_needed = vec![];

    if current.sort_by != new.sort_by {
        changes_needed.push(SectionChangesNeeded::Sort);
    }

    // We want to hide the section
    // TODO: what to do on redirect_path change?
    if current.should_render() && !new.should_render() {
        changes_needed.push(SectionChangesNeeded::Delete);
        // Nothing else we can do
        return changes_needed;
    }

    if current.paginate_by != new.paginate_by
        || current.paginate_path != new.paginate_path
        || current.insert_anchor_links != new.insert_anchor_links {
        changes_needed.push(SectionChangesNeeded::RenderWithPages);
        // Nothing else we can do
        return changes_needed;
    }

    // Any new change will trigger a re-rendering of the section page only
    changes_needed.push(SectionChangesNeeded::Render);
    changes_needed
}

/// Evaluates all the params in the front matter that changed so we can do the smallest
/// delta in the serve command
/// Order matters as the actions will be done in insertion order
fn find_page_front_matter_changes(current: &PageFrontMatter, other: &PageFrontMatter) -> Vec<PageChangesNeeded> {
    let mut changes_needed = vec![];

    if current.tags != other.tags {
        changes_needed.push(PageChangesNeeded::Tags);
    }

    if current.category != other.category {
        changes_needed.push(PageChangesNeeded::Categories);
    }

    if current.date != other.date || current.order != other.order || current.weight != other.weight {
        changes_needed.push(PageChangesNeeded::Sort);
    }

    changes_needed.push(PageChangesNeeded::Render);
    changes_needed
}

/// Handles a path deletion: could be a page, a section, a folder
fn delete_element(site: &mut Site, path: &Path, is_section: bool) -> Result<()> {
    // Ignore the event if this path was not known
    if !site.sections.contains_key(path) && !site.pages.contains_key(path) {
        return Ok(());
    }

    if is_section {
        if let Some(s) = site.pages.remove(path) {
            site.permalinks.remove(&s.file.relative);
            site.populate_sections();
        }
    } else {
        if let Some(p) = site.pages.remove(path) {
            site.permalinks.remove(&p.file.relative);

            if p.meta.has_tags() || p.meta.category.is_some() {
                site.populate_tags_and_categories();
            }

            // if there is a parent section, we will need to re-render it
            // most likely
            if find_parent_section(site, &p).is_some() {
                site.populate_sections();
            }
        };
    }

    // Ensure we have our fn updated so it doesn't contain the permalink(s)/section/page deleted
    site.register_tera_global_fns();
    // Deletion is something that doesn't happen all the time so we
    // don't need to optimise it too much
    return site.build();
}

/// Handles a `_index.md` (a section) being edited in some ways
fn handle_section_editing(site: &mut Site, path: &Path) -> Result<()> {
    let section = Section::from_file(path, &site.config)?;
    match site.add_section(section, true)? {
        // Updating a section
        Some(prev) => {
            if site.sections[path].meta == prev.meta {
                // Front matter didn't change, only content did
                // so we render only the section page, not its pages
                return site.render_section(&site.sections[path], false);
            }

            // Front matter changed
            for changes in find_section_front_matter_changes(&site.sections[path].meta, &prev.meta) {
                // Sort always comes first if present so the rendering will be fine
                match changes {
                    SectionChangesNeeded::Sort => {
                        site.sort_sections_pages(Some(path));
                        site.register_tera_global_fns();
                    },
                    SectionChangesNeeded::Render => site.render_section(&site.sections[path], false)?,
                    SectionChangesNeeded::RenderWithPages => site.render_section(&site.sections[path], true)?,
                    // not a common enough operation to make it worth optimizing
                    SectionChangesNeeded::Delete => {
                        site.populate_sections();
                        site.build()?;
                    },
                };
            }
            return Ok(());
        },
        // New section, only render that one
        None => {
            site.populate_sections();
            site.register_tera_global_fns();
            return site.render_section(&site.sections[path], true);
        }
    };
}

macro_rules! render_parent_section {
    ($site: expr, $path: expr) => {
        match find_parent_section($site, &$site.pages[$path]) {
            Some(s) => {
                $site.render_section(s, false)?;
            },
            None => (),
        };
    }
}

/// Handles a page being edited in some ways
fn handle_page_editing(site: &mut Site, path: &Path) -> Result<()> {
    let page = Page::from_file(path, &site.config)?;
    match site.add_page(page, true)? {
        // Updating a page
        Some(prev) => {
            // Front matter didn't change, only content did
            if site.pages[path].meta == prev.meta {
                // Other than the page itself, the summary might be seen
                // on a paginated list for a blog for example
                if site.pages[path].summary.is_some() {
                    render_parent_section!(site, path);
                }
                // TODO: register_tera_global_fns is expensive as it involves lots of cloning
                // I can't think of a valid usecase where you would need the content
                // of a page through a global fn so it's commented out for now
                // site.register_tera_global_fns();
                return site.render_page(& site.pages[path]);
            }

            // Front matter changed
            let mut taxonomies_populated = false;
            let mut sections_populated = false;
            for changes in find_page_front_matter_changes(&site.pages[path].meta, &prev.meta) {
                // Sort always comes first if present so the rendering will be fine
                match changes {
                    PageChangesNeeded::Tags => {
                        if !taxonomies_populated {
                            site.populate_tags_and_categories();
                            taxonomies_populated = true;
                        }
                        site.register_tera_global_fns();
                        site.render_tags()?;
                    },
                    PageChangesNeeded::Categories => {
                        if !taxonomies_populated {
                            site.populate_tags_and_categories();
                            taxonomies_populated = true;
                        }
                        site.register_tera_global_fns();
                        site.render_categories()?;
                    },
                    PageChangesNeeded::Sort => {
                        let section_path = match find_parent_section(site, &site.pages[path]) {
                            Some(s) => s.file.path.clone(),
                            None => continue  // Do nothing if it's an orphan page
                        };
                        if !sections_populated {
                            site.populate_sections();
                            sections_populated = true;
                        }
                        site.sort_sections_pages(Some(&section_path));
                        site.register_tera_global_fns();
                        site.render_index()?;
                    },
                    PageChangesNeeded::Render => {
                        if !sections_populated {
                            site.populate_sections();
                            sections_populated = true;
                        }
                        site.register_tera_global_fns();
                        render_parent_section!(site, path);
                        site.render_page(&site.pages[path])?;
                    },
                };
            }
            Ok(())
        },
        // It's a new page!
        None => {
            site.populate_sections();
            site.populate_tags_and_categories();
            site.register_tera_global_fns();
            // No need to optimise that yet, we can revisit if it becomes an issue
            site.build()
        }
    }
}

/// What happens when a section or a page is changed
pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
    let is_section = path.file_name().unwrap() == "_index.md";
    let is_md = path.extension().unwrap() == "md";
    let index = path.parent().unwrap().join("index.md");

    // A few situations can happen:
    // 1. Change on .md files
    //    a. Is there an `index.md`? Return an error if it's something other than delete
    //    b. Deleted? remove the element
    //    c. Edited?
    //       1. filename is `_index.md`, this is a section
    //       1. it's a page otherwise
    // 2. Change on non .md files
    //    a. Try to find a corresponding `_index.md`
    //       1. Nothing? Return Ok
    //       2. Something? Update the page
    if is_md {
        // only delete if it was able to be added in the first place
        if !index.exists() && !path.exists() {
            delete_element(site, path, is_section)?;
        }

        // Added another .md in a assets directory
        if index.exists() && path.exists() {
            bail!(
                "Change on {:?} detected but there is already an `index.md` in the same folder",
                path.display()
            );
        } else if index.exists() && !path.exists() {
            // deleted the wrong .md, do nothing
            return Ok(());
        }

        if is_section {
            handle_section_editing(site, path)
        } else {
            handle_page_editing(site, path)
        }
    } else {
        if index.exists()  {
            handle_page_editing(site, &index)
        } else {
            Ok(())
        }
    }
}

/// What happens when a template is changed
pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
    site.tera.full_reload()?;
    let filename = path.file_name().unwrap().to_str().unwrap();

    match filename {
        "sitemap.xml" => site.render_sitemap(),
        "rss.xml" => site.render_rss_feed(),
        "robots.txt" => site.render_robots(),
        "categories.html" | "category.html" => site.render_categories(),
        "tags.html" | "tag.html" => site.render_tags(),
        "page.html" => {
            site.render_sections()?;
            site.render_orphan_pages()
        },
        "section.html" => site.render_sections(),
        // Either the index or some unknown template changed
        // We can't really know what this change affects so rebuild all
        // the things
        _ => {
            // If we are updating a shortcode, re-render the markdown of all pages/site
            // because we have no clue which one needs rebuilding
            // TODO: look if there the shortcode is used in the markdown instead of re-rendering
            // everything
            if path.components().collect::<Vec<_>>().contains(&Component::Normal("shortcodes".as_ref())) {
                site.render_markdown()?;
            }
            site.populate_sections();
            site.render_sections()?;
            site.render_orphan_pages()?;
            site.render_categories()?;
            site.render_tags()
        },
    }
}

#[cfg(test)]
mod tests {
    use front_matter::{PageFrontMatter, SectionFrontMatter, SortBy};
    use super::{
        find_page_front_matter_changes, find_section_front_matter_changes,
        PageChangesNeeded, SectionChangesNeeded
    };

    #[test]
    fn can_find_tag_changes_in_page_frontmatter() {
        let new = PageFrontMatter { tags: Some(vec!["a tag".to_string()]), ..PageFrontMatter::default() };
        let changes = find_page_front_matter_changes(&PageFrontMatter::default(), &new);
        assert_eq!(changes, vec![PageChangesNeeded::Tags, PageChangesNeeded::Render]);
    }

    #[test]
    fn can_find_category_changes_in_page_frontmatter() {
        let current = PageFrontMatter { category: Some("a category".to_string()), ..PageFrontMatter::default() };
        let changes = find_page_front_matter_changes(&current, &PageFrontMatter::default());
        assert_eq!(changes, vec![PageChangesNeeded::Categories, PageChangesNeeded::Render]);
    }

    #[test]
    fn can_find_multiple_changes_in_page_frontmatter() {
        let current = PageFrontMatter { category: Some("a category".to_string()), order: Some(1), ..PageFrontMatter::default() };
        let changes = find_page_front_matter_changes(&current, &PageFrontMatter::default());
        assert_eq!(changes, vec![PageChangesNeeded::Categories, PageChangesNeeded::Sort, PageChangesNeeded::Render]);
    }

    #[test]
    fn can_find_sort_changes_in_section_frontmatter() {
        let new = SectionFrontMatter { sort_by: Some(SortBy::Date), ..SectionFrontMatter::default() };
        let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new);
        assert_eq!(changes, vec![SectionChangesNeeded::Sort, SectionChangesNeeded::Render]);
    }

    #[test]
    fn can_find_render_changes_in_section_frontmatter() {
        let new = SectionFrontMatter { render: Some(false), ..SectionFrontMatter::default() };
        let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new);
        assert_eq!(changes, vec![SectionChangesNeeded::Delete]);
    }

    #[test]
    fn can_find_paginate_by_changes_in_section_frontmatter() {
        let new = SectionFrontMatter { paginate_by: Some(10), ..SectionFrontMatter::default() };
        let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new);
        assert_eq!(changes, vec![SectionChangesNeeded::RenderWithPages]);
    }
}
<commit_msg>Let people edit their index.md<commit_after>extern crate site;
#[macro_use]
extern crate errors;
extern crate content;
extern crate front_matter;

use std::path::{Path, Component};

use errors::Result;
use site::Site;
use content::{Page, Section};
use front_matter::{PageFrontMatter, SectionFrontMatter};

/// Finds the section that contains the page given if there is one
pub fn find_parent_section<'a>(site: &'a Site, page: &Page) -> Option<&'a Section> {
    for section in site.sections.values() {
        if section.is_child_page(&page.file.path) {
            return Some(section)
        }
    }

    None
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PageChangesNeeded {
    /// Editing `tags`
    Tags,
    /// Editing `categories`
    Categories,
    /// Editing `date`, `order` or `weight`
    Sort,
    /// Editing anything causes a re-render of the page
    Render,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SectionChangesNeeded {
    /// Editing `sort_by`
    Sort,
    /// Editing `title`, `description`, `extra`, `template` or setting `render` to true
    Render,
    /// Editing `paginate_by`, `paginate_path` or `insert_anchor_links`
    RenderWithPages,
    /// Setting `render` to false
    Delete,
}

/// Evaluates all the params in the front matter that changed so we can do the smallest
/// delta in the serve command
/// Order matters as the actions will be done in insertion order
fn find_section_front_matter_changes(current: &SectionFrontMatter, new: &SectionFrontMatter) -> Vec<SectionChangesNeeded> {
    let mut changes_needed = vec![];

    if current.sort_by != new.sort_by {
        changes_needed.push(SectionChangesNeeded::Sort);
    }

    // We want to hide the section
    // TODO: what to do on redirect_path change?
    if current.should_render() && !new.should_render() {
        changes_needed.push(SectionChangesNeeded::Delete);
        // Nothing else we can do
        return changes_needed;
    }

    if current.paginate_by != new.paginate_by
        || current.paginate_path != new.paginate_path
        || current.insert_anchor_links != new.insert_anchor_links {
        changes_needed.push(SectionChangesNeeded::RenderWithPages);
        // Nothing else we can do
        return changes_needed;
    }

    // Any new change will trigger a re-rendering of the section page only
    changes_needed.push(SectionChangesNeeded::Render);
    changes_needed
}

/// Evaluates all the params in the front matter that changed so we can do the smallest
/// delta in the serve command
/// Order matters as the actions will be done in insertion order
fn find_page_front_matter_changes(current: &PageFrontMatter, other: &PageFrontMatter) -> Vec<PageChangesNeeded> {
    let mut changes_needed = vec![];

    if current.tags != other.tags {
        changes_needed.push(PageChangesNeeded::Tags);
    }

    if current.category != other.category {
        changes_needed.push(PageChangesNeeded::Categories);
    }

    if current.date != other.date || current.order != other.order || current.weight != other.weight {
        changes_needed.push(PageChangesNeeded::Sort);
    }

    changes_needed.push(PageChangesNeeded::Render);
    changes_needed
}

/// Handles a path deletion: could be a page, a section, a folder
fn delete_element(site: &mut Site, path: &Path, is_section: bool) -> Result<()> {
    // Ignore the event if this path was not known
    if !site.sections.contains_key(path) && !site.pages.contains_key(path) {
        return Ok(());
    }

    if is_section {
        if let Some(s) = site.pages.remove(path) {
            site.permalinks.remove(&s.file.relative);
            site.populate_sections();
        }
    } else {
        if let Some(p) = site.pages.remove(path) {
            site.permalinks.remove(&p.file.relative);

            if p.meta.has_tags() || p.meta.category.is_some() {
                site.populate_tags_and_categories();
            }

            // if there is a parent section, we will need to re-render it
            // most likely
            if find_parent_section(site, &p).is_some() {
                site.populate_sections();
            }
        };
    }

    // Ensure we have our fn updated so it doesn't contain the permalink(s)/section/page deleted
    site.register_tera_global_fns();
    // Deletion is something that doesn't happen all the time so we
    // don't need to optimise it too much
    return site.build();
}

/// Handles a `_index.md` (a section) being edited in some ways
fn handle_section_editing(site: &mut Site, path: &Path) -> Result<()> {
    let section = Section::from_file(path, &site.config)?;
    match site.add_section(section, true)? {
        // Updating a section
        Some(prev) => {
            if site.sections[path].meta == prev.meta {
                // Front matter didn't change, only content did
                // so we render only the section page, not its pages
                return site.render_section(&site.sections[path], false);
            }

            // Front matter changed
            for changes in find_section_front_matter_changes(&site.sections[path].meta, &prev.meta) {
                // Sort always comes first if present so the rendering will be fine
                match changes {
                    SectionChangesNeeded::Sort => {
                        site.sort_sections_pages(Some(path));
                        site.register_tera_global_fns();
                    },
                    SectionChangesNeeded::Render => site.render_section(&site.sections[path], false)?,
                    SectionChangesNeeded::RenderWithPages => site.render_section(&site.sections[path], true)?,
                    // not a common enough operation to make it worth optimizing
                    SectionChangesNeeded::Delete => {
                        site.populate_sections();
                        site.build()?;
                    },
                };
            }
            return Ok(());
        },
        // New section, only render that one
        None => {
            site.populate_sections();
            site.register_tera_global_fns();
            return site.render_section(&site.sections[path], true);
        }
    };
}

macro_rules! render_parent_section {
    ($site: expr, $path: expr) => {
        match find_parent_section($site, &$site.pages[$path]) {
            Some(s) => {
                $site.render_section(s, false)?;
            },
            None => (),
        };
    }
}

/// Handles a page being edited in some ways
fn handle_page_editing(site: &mut Site, path: &Path) -> Result<()> {
    let page = Page::from_file(path, &site.config)?;
    match site.add_page(page, true)? {
        // Updating a page
        Some(prev) => {
            // Front matter didn't change, only content did
            if site.pages[path].meta == prev.meta {
                // Other than the page itself, the summary might be seen
                // on a paginated list for a blog for example
                if site.pages[path].summary.is_some() {
                    render_parent_section!(site, path);
                }
                // TODO: register_tera_global_fns is expensive as it involves lots of cloning
                // I can't think of a valid usecase where you would need the content
                // of a page through a global fn so it's commented out for now
                // site.register_tera_global_fns();
                return site.render_page(& site.pages[path]);
            }

            // Front matter changed
            let mut taxonomies_populated = false;
            let mut sections_populated = false;
            for changes in find_page_front_matter_changes(&site.pages[path].meta, &prev.meta) {
                // Sort always comes first if present so the rendering will be fine
                match changes {
                    PageChangesNeeded::Tags => {
                        if !taxonomies_populated {
                            site.populate_tags_and_categories();
                            taxonomies_populated = true;
                        }
                        site.register_tera_global_fns();
                        site.render_tags()?;
                    },
                    PageChangesNeeded::Categories => {
                        if !taxonomies_populated {
                            site.populate_tags_and_categories();
                            taxonomies_populated = true;
                        }
                        site.register_tera_global_fns();
                        site.render_categories()?;
                    },
                    PageChangesNeeded::Sort => {
                        let section_path = match find_parent_section(site, &site.pages[path]) {
                            Some(s) => s.file.path.clone(),
                            None => continue  // Do nothing if it's an orphan page
                        };
                        if !sections_populated {
                            site.populate_sections();
                            sections_populated = true;
                        }
                        site.sort_sections_pages(Some(&section_path));
                        site.register_tera_global_fns();
                        site.render_index()?;
                    },
                    PageChangesNeeded::Render => {
                        if !sections_populated {
                            site.populate_sections();
                            sections_populated = true;
                        }
                        site.register_tera_global_fns();
                        render_parent_section!(site, path);
                        site.render_page(&site.pages[path])?;
                    },
                };
            }
            Ok(())
        },
        // It's a new page!
        None => {
            site.populate_sections();
            site.populate_tags_and_categories();
            site.register_tera_global_fns();
            // No need to optimise that yet, we can revisit if it becomes an issue
            site.build()
        }
    }
}

/// What happens when a section or a page is changed
pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
    let is_section = path.file_name().unwrap() == "_index.md";
    let is_md = path.extension().unwrap() == "md";
    let index = path.parent().unwrap().join("index.md");

    // A few situations can happen:
    // 1. Change on .md files
    //    a. Is there an `index.md`? Return an error if it's something other than delete
    //    b. Deleted? remove the element
    //    c. Edited?
    //       1. filename is `_index.md`, this is a section
    //       1. it's a page otherwise
    // 2. Change on non .md files
    //    a. Try to find a corresponding `_index.md`
    //       1. Nothing? Return Ok
    //       2. Something? Update the page
    if is_md {
        // only delete if it was able to be added in the first place
        if !index.exists() && !path.exists() {
            delete_element(site, path, is_section)?;
        }

        // Added another .md in a assets directory
        if index.exists() && path.exists() && path != index {
            bail!(
                "Change on {:?} detected but there is already an `index.md` in the same folder",
                path.display()
            );
        } else if index.exists() && !path.exists() {
            // deleted the wrong .md, do nothing
            return Ok(());
        }

        if is_section {
            handle_section_editing(site, path)
        } else {
            handle_page_editing(site, path)
        }
    } else {
        if index.exists()  {
            handle_page_editing(site, &index)
        } else {
            Ok(())
        }
    }
}

/// What happens when a template is changed
pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
    site.tera.full_reload()?;
    let filename = path.file_name().unwrap().to_str().unwrap();

    match filename {
        "sitemap.xml" => site.render_sitemap(),
        "rss.xml" => site.render_rss_feed(),
        "robots.txt" => site.render_robots(),
        "categories.html" | "category.html" => site.render_categories(),
        "tags.html" | "tag.html" => site.render_tags(),
        "page.html" => {
            site.render_sections()?;
            site.render_orphan_pages()
        },
        "section.html" => site.render_sections(),
        // Either the index or some unknown template changed
        // We can't really know what this change affects so rebuild all
        // the things
        _ => {
            // If we are updating a shortcode, re-render the markdown of all pages/site
            // because we have no clue which one needs rebuilding
            // TODO: look if there the shortcode is used in the markdown instead of re-rendering
            // everything
            if path.components().collect::<Vec<_>>().contains(&Component::Normal("shortcodes".as_ref())) {
                site.render_markdown()?;
            }
            site.populate_sections();
            site.render_sections()?;
            site.render_orphan_pages()?;
            site.render_categories()?;
            site.render_tags()
        },
    }
}

#[cfg(test)]
mod tests {
    use front_matter::{PageFrontMatter, SectionFrontMatter, SortBy};
    use super::{
        find_page_front_matter_changes, find_section_front_matter_changes,
        PageChangesNeeded, SectionChangesNeeded
    };

    #[test]
    fn can_find_tag_changes_in_page_frontmatter() {
        let new = PageFrontMatter { tags: Some(vec!["a tag".to_string()]), ..PageFrontMatter::default() };
        let changes = find_page_front_matter_changes(&PageFrontMatter::default(), &new);
        assert_eq!(changes, vec![PageChangesNeeded::Tags, PageChangesNeeded::Render]);
    }

    #[test]
    fn can_find_category_changes_in_page_frontmatter() {
        let current = PageFrontMatter { category: Some("a category".to_string()), ..PageFrontMatter::default() };
        let changes = find_page_front_matter_changes(&current, &PageFrontMatter::default());
        assert_eq!(changes, vec![PageChangesNeeded::Categories, PageChangesNeeded::Render]);
    }

    #[test]
    fn can_find_multiple_changes_in_page_frontmatter() {
        let current = PageFrontMatter { category: Some("a category".to_string()), order: Some(1), ..PageFrontMatter::default() };
        let changes = find_page_front_matter_changes(&current, &PageFrontMatter::default());
        assert_eq!(changes, vec![PageChangesNeeded::Categories, PageChangesNeeded::Sort, PageChangesNeeded::Render]);
    }

    #[test]
    fn can_find_sort_changes_in_section_frontmatter() {
        let new = SectionFrontMatter { sort_by: Some(SortBy::Date), ..SectionFrontMatter::default() };
        let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new);
        assert_eq!(changes, vec![SectionChangesNeeded::Sort, SectionChangesNeeded::Render]);
    }

    #[test]
    fn can_find_render_changes_in_section_frontmatter() {
        let new = SectionFrontMatter { render: Some(false), ..SectionFrontMatter::default() };
        let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new);
        assert_eq!(changes, vec![SectionChangesNeeded::Delete]);
    }

    #[test]
    fn can_find_paginate_by_changes_in_section_frontmatter() {
        let new = SectionFrontMatter { paginate_by: Some(10), ..SectionFrontMatter::default() };
        let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new);
        assert_eq!(changes, vec![SectionChangesNeeded::RenderWithPages]);
    }
}
<|endoftext|>


<commit_before>use std::error::Error as ErrorTrait;
use std::fmt::{self, Display};

pub type Result<I> = ::std::result::Result<I, Error>;

/// askama error type
///
/// # Feature Interaction
///
/// If the feature `serde_json` is enabled an
/// additional error variant `Json` is added.
///
/// # Why not `failure`/`error-chain`?
///
/// Error from `error-chain` are not `Sync` which
/// can lead to problems e.g. when this is used
/// by a crate which use `failure`. Implementing
/// `Fail` on the other hand prevents the implementation
/// of `std::error::Error` until specialization lands
/// on stable. While errors impl. `Fail` can be
/// converted to a type impl. `std::error::Error`
/// using a adapter the benefits `failure` would
/// bring to this crate are small, which is why
/// `std::error::Error` was used.
///
#[derive(Debug)]
pub enum Error {
    /// formatting error
    Fmt(fmt::Error),

    /// json conversion error
    #[cfg(feature = "serde_json")]
    Json(::serde_json::Error),

    /// This error needs to be non-exhaustive as
    /// the `Json` variants existence depends on
    /// a feature.
    #[doc(hidden)]
    __Nonexhaustive,
}

impl ErrorTrait for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Fmt(ref err) => err.description(),
            #[cfg(feature = "serde_json")]
            Error::Json(ref err) => err.description(),
            _ => "unknown error: __Nonexhaustive",
        }
    }

    fn cause(&self) -> Option<&dyn ErrorTrait> {
        match *self {
            Error::Fmt(ref err) => err.cause(),
            #[cfg(feature = "serde_json")]
            Error::Json(ref err) => err.cause(),
            _ => None,
        }
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Error::Fmt(ref err) => write!(formatter, "formatting error: {}", err),

            #[cfg(feature = "serde_json")]
            Error::Json(ref err) => write!(formatter, "json conversion error: {}", err),
            _ => write!(formatter, "unknown error: __Nonexhaustive"),
        }
    }
}

impl From<fmt::Error> for Error {
    fn from(err: fmt::Error) -> Self {
        Error::Fmt(err)
    }
}

#[cfg(feature = "serde_json")]
impl From<::serde_json::Error> for Error {
    fn from(err: ::serde_json::Error) -> Self {
        Error::Json(err)
    }
}

#[cfg(test)]
mod tests {
    use super::Error;

    trait AssertSendSyncStatic: Send + Sync + 'static {}
    impl AssertSendSyncStatic for Error {}
}
<commit_msg>Rename deprecate method Error::cause()<commit_after>use std::error::Error as ErrorTrait;
use std::fmt::{self, Display};

pub type Result<I> = ::std::result::Result<I, Error>;

/// askama error type
///
/// # Feature Interaction
///
/// If the feature `serde_json` is enabled an
/// additional error variant `Json` is added.
///
/// # Why not `failure`/`error-chain`?
///
/// Error from `error-chain` are not `Sync` which
/// can lead to problems e.g. when this is used
/// by a crate which use `failure`. Implementing
/// `Fail` on the other hand prevents the implementation
/// of `std::error::Error` until specialization lands
/// on stable. While errors impl. `Fail` can be
/// converted to a type impl. `std::error::Error`
/// using a adapter the benefits `failure` would
/// bring to this crate are small, which is why
/// `std::error::Error` was used.
///
#[derive(Debug)]
pub enum Error {
    /// formatting error
    Fmt(fmt::Error),

    /// json conversion error
    #[cfg(feature = "serde_json")]
    Json(::serde_json::Error),

    /// This error needs to be non-exhaustive as
    /// the `Json` variants existence depends on
    /// a feature.
    #[doc(hidden)]
    __Nonexhaustive,
}

impl ErrorTrait for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Fmt(ref err) => err.description(),
            #[cfg(feature = "serde_json")]
            Error::Json(ref err) => err.description(),
            _ => "unknown error: __Nonexhaustive",
        }
    }

    fn cause(&self) -> Option<&dyn ErrorTrait> {
        match *self {
            Error::Fmt(ref err) => err.source(),
            #[cfg(feature = "serde_json")]
            Error::Json(ref err) => err.source(),
            _ => None,
        }
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Error::Fmt(ref err) => write!(formatter, "formatting error: {}", err),

            #[cfg(feature = "serde_json")]
            Error::Json(ref err) => write!(formatter, "json conversion error: {}", err),
            _ => write!(formatter, "unknown error: __Nonexhaustive"),
        }
    }
}

impl From<fmt::Error> for Error {
    fn from(err: fmt::Error) -> Self {
        Error::Fmt(err)
    }
}

#[cfg(feature = "serde_json")]
impl From<::serde_json::Error> for Error {
    fn from(err: ::serde_json::Error) -> Self {
        Error::Json(err)
    }
}

#[cfg(test)]
mod tests {
    use super::Error;

    trait AssertSendSyncStatic: Send + Sync + 'static {}
    impl AssertSendSyncStatic for Error {}
}
<|endoftext|>


<commit_before>fn main () {
    println!("cargo:rustc-flags=-l sodium");
}
<commit_msg>Enable static linking support (similar to openssl-sys)<commit_after>use std::env;

fn main () {
    if let Some(lib_dir) = env::var("SODIUM_LIB_DIR").ok() {
    	println!("cargo:rustc-flags=-L native={}", lib_dir);
    }

    let mode = if env::var_os("SODIUM_STATIC").is_some() {
    	"static"
    } else {
    	"dylib"
    };

    println!("cargo:rustc-flags=-l {0}=sodium", mode);
}
<|endoftext|>


<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#![crate_name="mathx"]
#![crate_type="dylib"]
#![feature(libc)]

extern crate libc;
extern crate lua;

// import low level lua_State
use lua::ffi::lua_State;

// import high level lua_State wrapper and a type we'll need for library
// registration
use lua::{State, Function};

// import c_int since we need this to interface with Lua
use libc::c_int;

// simple binding to Rust's sin function
#[allow(non_snake_case)]
extern "C" fn sin(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  // convert the value on top of the stack to a number
  let num = state.to_number(-1);
  // push the sine of that number onto the stack
  state.push_number(num.sin());
  // return one value to Lua
  1
}

// simple binding to Rust's cos function
#[allow(non_snake_case)]
extern "C" fn cos(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  let num = state.to_number(-1);
  state.push_number(num.cos());
  1
}

// simple binding to Rust's tan function
#[allow(non_snake_case)]
extern "C" fn tan(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  let num = state.to_number(-1);
  state.push_number(num.tan());
  1
}

// mapping of function name to function pointer
const MATHX_LIB: [(&'static str, Function); 3] = [
  ("sin", Some(sin)),
  ("cos", Some(cos)),
  ("tan", Some(tan)),
];

// the format of this function name is defined by the Lua manual; the Lua
// interpreter will call into this when you require() this library
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn luaopen_mathx(L: *mut lua_State) -> c_int {
  // construct a state wrapper object from the pointer we were given
  let mut state = State::from_ptr(L);
  // create a new table and set fields for each function defined in MATHX_LIB
  state.new_lib(&MATHX_LIB);
  // return it on the stack
  1
}

<commit_msg>Update example<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#![crate_name="mathx"]
#![crate_type="dylib"]
#![feature(libc)]

extern crate libc;
extern crate lua;

// import low level lua_State
use lua::ffi::lua_State;

// import high level lua_State wrapper and a type we'll need for library
// registration
use lua::{State, Function};

// import c_int since we need this to interface with Lua
use libc::c_int;

// simple binding to Rust's sin function
#[allow(non_snake_case)]
unsafe extern "C" fn sin(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  // convert the value on top of the stack to a number
  let num = state.to_number(-1);
  // push the sine of that number onto the stack
  state.push_number(num.sin());
  // return one value to Lua
  1
}

// simple binding to Rust's cos function
#[allow(non_snake_case)]
unsafe extern "C" fn cos(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  let num = state.to_number(-1);
  state.push_number(num.cos());
  1
}

// simple binding to Rust's tan function
#[allow(non_snake_case)]
unsafe extern "C" fn tan(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  let num = state.to_number(-1);
  state.push_number(num.tan());
  1
}

// mapping of function name to function pointer
const MATHX_LIB: [(&'static str, Function); 3] = [
  ("sin", Some(sin)),
  ("cos", Some(cos)),
  ("tan", Some(tan)),
];

// the format of this function name is defined by the Lua manual; the Lua
// interpreter will call into this when you require() this library
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn luaopen_mathx(L: *mut lua_State) -> c_int {
  // construct a state wrapper object from the pointer we were given
  let mut state = State::from_ptr(L);
  // create a new table and set fields for each function defined in MATHX_LIB
  state.new_lib(&MATHX_LIB);
  // return it on the stack
  1
}

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::frame_builder::FrameBuildingContext;
use crate::internal_types::FastHashMap;
use crate::prim_store::PictureIndex;
use crate::picture::{PicturePrimitive, SurfaceIndex, ROOT_SURFACE_INDEX, SurfaceInfo};
use crate::picture::{TileCacheInstance, SliceId};
use crate::render_backend::DataStores;
use smallvec::SmallVec;

#[derive(Debug)]
pub struct PictureInfo {
    pub update_pass: Option<usize>,
    pub surface_index: SurfaceIndex,
    pub parent: Option<PictureIndex>,
}

/// A graph of picture dependencies, allowing updates to be processed without recursion
/// by building a list of passes.
pub struct PictureGraph {
    roots: Vec<PictureIndex>,
    pic_info: Vec<PictureInfo>,
    update_passes: Vec<Vec<PictureIndex>>,
}

impl PictureGraph {
    pub fn new() -> Self {
        PictureGraph {
            roots: Vec::new(),
            pic_info: Vec::new(),
            update_passes: Vec::new(),
        }
    }

    /// Add a root picture to the graph
    pub fn add_root(
        &mut self,
        pic_index: PictureIndex,
    ) {
        self.roots.push(pic_index);
    }

    /// Build a list of update passes based on the dependencies between pictures
    pub fn build_update_passes(
        &mut self,
        pictures: &mut [PicturePrimitive],
        frame_context: &FrameBuildingContext
    ) {
        self.pic_info.clear();
        self.pic_info.reserve(pictures.len());

        for _ in 0 .. pictures.len() {
            self.pic_info.push(PictureInfo {
                update_pass: None,
                parent: None,
                surface_index: ROOT_SURFACE_INDEX,
            })
        };

        let mut max_pass_index = 0;

        for pic_index in &self.roots {
            assign_update_pass(
                *pic_index,
                None,
                0,
                pictures,
                &mut self.pic_info,
                &mut max_pass_index,
                frame_context,
            );
        }

        let pass_count = max_pass_index + 1;

        self.update_passes.resize_with(pass_count, Vec::new);

        for (pic_index, info) in self.pic_info.iter().enumerate() {
            if let Some(update_pass) = info.update_pass {
                let pass = &mut self.update_passes[update_pass];
                pass.push(PictureIndex(pic_index));
            }
        }
    }

    /// Assign surfaces and scale factors to each picture (root -> leaf ordered pass)
    pub fn assign_surfaces(
        &mut self,
        pictures: &mut [PicturePrimitive],
        surfaces: &mut Vec<SurfaceInfo>,
        tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
        frame_context: &FrameBuildingContext,
    ) {
        for pass in &self.update_passes {
            for pic_index in pass {
                let parent = self.pic_info[pic_index.0].parent;

                let parent_surface_index = parent.map_or(ROOT_SURFACE_INDEX, |parent| {
                    self.pic_info[parent.0].surface_index
                });

                let info = &mut self.pic_info[pic_index.0];

                info.surface_index = pictures[pic_index.0].assign_surface(
                    frame_context,
                    tile_caches,
                    parent_surface_index,
                    surfaces,
                );
            }
        }
    }

    /// Propegate bounding rects from pictures to parents (leaf -> root ordered pass)
    pub fn propagate_bounding_rects(
        &mut self,
        pictures: &mut [PicturePrimitive],
        surfaces: &mut [SurfaceInfo],
        frame_context: &FrameBuildingContext,
        data_stores: &mut DataStores,
    ) {
        for pass in self.update_passes.iter().rev() {
            for pic_index in pass {
                let parent = self.pic_info[pic_index.0].parent;

                let surface_index = self.pic_info[pic_index.0].surface_index;

                let parent_surface_index = parent.map_or(ROOT_SURFACE_INDEX, |parent| {
                    self.pic_info[parent.0].surface_index
                });

                pictures[pic_index.0].propagate_bounding_rect(
                    surface_index,
                    parent_surface_index,
                    surfaces,
                    frame_context,
                    data_stores,
                );
            }
        }
    }
}

/// Recursive function that assigns pictures to the earliest pass possible that they
/// can be processed in, while maintaining dependency ordering.
fn assign_update_pass(
    pic_index: PictureIndex,
    parent_pic_index: Option<PictureIndex>,
    pass: usize,
    pictures: &mut [PicturePrimitive],
    pic_info: &mut [PictureInfo],
    max_pass_index: &mut usize,
    frame_context: &FrameBuildingContext
) {
    let pic = &mut pictures[pic_index.0];
    let info = &mut pic_info[pic_index.0];

    info.parent = parent_pic_index;

    let can_be_drawn = match info.update_pass {
        Some(update_pass) => {
            // No point in recursing into paths in the graph if this picture already
            // has been set to update after this pass.
            if update_pass > pass {
                return;
            }

            true
        }
        None => {
            // Check if this picture can be dropped from the graph we're building this frame
            pic.pre_update_visibility_check(frame_context)
        }
    };

    if can_be_drawn {
        info.update_pass = Some(pass);

        *max_pass_index = pass.max(*max_pass_index);

        let mut child_pictures: SmallVec<[PictureIndex; 8]> = SmallVec::new();
        child_pictures.extend_from_slice(&pic.prim_list.child_pictures);

        for child_pic_index in child_pictures {
            assign_update_pass(
                child_pic_index,
                Some(pic_index),
                pass + 1,
                pictures,
                pic_info,
                max_pass_index,
                frame_context,
            );
        }
    }
}
<commit_msg>Bug 1721943 - Fix clear of picture graph update passes r=kvark,gfx-reviewers<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::frame_builder::FrameBuildingContext;
use crate::internal_types::FastHashMap;
use crate::prim_store::PictureIndex;
use crate::picture::{PicturePrimitive, SurfaceIndex, ROOT_SURFACE_INDEX, SurfaceInfo};
use crate::picture::{TileCacheInstance, SliceId};
use crate::render_backend::DataStores;
use smallvec::SmallVec;

#[derive(Debug)]
pub struct PictureInfo {
    pub update_pass: Option<usize>,
    pub surface_index: SurfaceIndex,
    pub parent: Option<PictureIndex>,
}

/// A graph of picture dependencies, allowing updates to be processed without recursion
/// by building a list of passes.
pub struct PictureGraph {
    roots: Vec<PictureIndex>,
    pic_info: Vec<PictureInfo>,
    update_passes: Vec<Vec<PictureIndex>>,
}

impl PictureGraph {
    pub fn new() -> Self {
        PictureGraph {
            roots: Vec::new(),
            pic_info: Vec::new(),
            update_passes: Vec::new(),
        }
    }

    /// Add a root picture to the graph
    pub fn add_root(
        &mut self,
        pic_index: PictureIndex,
    ) {
        self.roots.push(pic_index);
    }

    /// Build a list of update passes based on the dependencies between pictures
    pub fn build_update_passes(
        &mut self,
        pictures: &mut [PicturePrimitive],
        frame_context: &FrameBuildingContext
    ) {
        self.pic_info.clear();
        self.pic_info.reserve(pictures.len());

        for _ in 0 .. pictures.len() {
            self.pic_info.push(PictureInfo {
                update_pass: None,
                parent: None,
                surface_index: ROOT_SURFACE_INDEX,
            })
        };

        let mut max_pass_index = 0;

        for pic_index in &self.roots {
            assign_update_pass(
                *pic_index,
                None,
                0,
                pictures,
                &mut self.pic_info,
                &mut max_pass_index,
                frame_context,
            );
        }

        let pass_count = max_pass_index + 1;

        self.update_passes.clear();
        self.update_passes.resize_with(pass_count, Vec::new);

        for (pic_index, info) in self.pic_info.iter().enumerate() {
            if let Some(update_pass) = info.update_pass {
                let pass = &mut self.update_passes[update_pass];
                pass.push(PictureIndex(pic_index));
            }
        }
    }

    /// Assign surfaces and scale factors to each picture (root -> leaf ordered pass)
    pub fn assign_surfaces(
        &mut self,
        pictures: &mut [PicturePrimitive],
        surfaces: &mut Vec<SurfaceInfo>,
        tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
        frame_context: &FrameBuildingContext,
    ) {
        for pass in &self.update_passes {
            for pic_index in pass {
                let parent = self.pic_info[pic_index.0].parent;

                let parent_surface_index = parent.map_or(ROOT_SURFACE_INDEX, |parent| {
                    self.pic_info[parent.0].surface_index
                });

                let info = &mut self.pic_info[pic_index.0];

                info.surface_index = pictures[pic_index.0].assign_surface(
                    frame_context,
                    tile_caches,
                    parent_surface_index,
                    surfaces,
                );
            }
        }
    }

    /// Propegate bounding rects from pictures to parents (leaf -> root ordered pass)
    pub fn propagate_bounding_rects(
        &mut self,
        pictures: &mut [PicturePrimitive],
        surfaces: &mut [SurfaceInfo],
        frame_context: &FrameBuildingContext,
        data_stores: &mut DataStores,
    ) {
        for pass in self.update_passes.iter().rev() {
            for pic_index in pass {
                let parent = self.pic_info[pic_index.0].parent;

                let surface_index = self.pic_info[pic_index.0].surface_index;

                let parent_surface_index = parent.map_or(ROOT_SURFACE_INDEX, |parent| {
                    self.pic_info[parent.0].surface_index
                });

                pictures[pic_index.0].propagate_bounding_rect(
                    surface_index,
                    parent_surface_index,
                    surfaces,
                    frame_context,
                    data_stores,
                );
            }
        }
    }
}

/// Recursive function that assigns pictures to the earliest pass possible that they
/// can be processed in, while maintaining dependency ordering.
fn assign_update_pass(
    pic_index: PictureIndex,
    parent_pic_index: Option<PictureIndex>,
    pass: usize,
    pictures: &mut [PicturePrimitive],
    pic_info: &mut [PictureInfo],
    max_pass_index: &mut usize,
    frame_context: &FrameBuildingContext
) {
    let pic = &mut pictures[pic_index.0];
    let info = &mut pic_info[pic_index.0];

    info.parent = parent_pic_index;

    let can_be_drawn = match info.update_pass {
        Some(update_pass) => {
            // No point in recursing into paths in the graph if this picture already
            // has been set to update after this pass.
            if update_pass > pass {
                return;
            }

            true
        }
        None => {
            // Check if this picture can be dropped from the graph we're building this frame
            pic.pre_update_visibility_check(frame_context)
        }
    };

    if can_be_drawn {
        info.update_pass = Some(pass);

        *max_pass_index = pass.max(*max_pass_index);

        let mut child_pictures: SmallVec<[PictureIndex; 8]> = SmallVec::new();
        child_pictures.extend_from_slice(&pic.prim_list.child_pictures);

        for child_pic_index in child_pictures {
            assign_update_pass(
                child_pic_index,
                Some(pic_index),
                pass + 1,
                pictures,
                pic_info,
                max_pass_index,
                frame_context,
            );
        }
    }
}
<|endoftext|>