<commit_before>extern crate gstreamer as gst;
use gstreamer::prelude::*;
use gstreamer::{ClockTime, QueryView, TocSetterExt};

extern crate glib;

use std::sync::mpsc::Sender;

use std::path::Path;

use super::ContextMessage;

pub struct SplitterContext {
    pipeline: gst::Pipeline,
    tag_toc_element: Option<gst::Element>,
    position_ref: Option<gst::Element>,
    position_query: gst::Query,
}

impl SplitterContext {
    pub fn new(
        input_path: &Path,
        output_path: &Path,
        start: u64,
        end: u64,
        ctx_tx: Sender<ContextMessage>,
    ) -> Result<SplitterContext, String> {
        println!("\n\n* Exporting {:?}...", input_path);

        let mut this = SplitterContext {
            pipeline: gst::Pipeline::new("pipeline"),
            tag_toc_element: None,
            position_ref: None,
            position_query: gst::Query::new_position(gst::Format::Time),
        };

        this.build_pipeline(input_path, output_path);
        this.register_bus_inspector(start, end, ctx_tx);

        match this.pipeline.set_state(gst::State::Paused) {
            gst::StateChangeReturn::Failure => Err("Could not set media in Paused state".into()),
            _ => Ok(this),
        }
    }

    pub fn get_position(&mut self) -> u64 {
        self.position_ref.as_ref().unwrap().query(
            self.position_query
                .get_mut()
                .unwrap(),
        );
        match self.position_query.view() {
            QueryView::Position(ref position) => position.get_result().get_value() as u64,
            _ => unreachable!(),
        }
    }

    // TODO: handle errors
    fn build_pipeline(&mut self, input_path: &Path, output_path: &Path) {
        // FIXME: multiple issues
        /* There are multiple showstoppers to implementing something ideal
         * to export splitted chapters with audio and video (and subtitles):
         * - matroska-mux drops seek events explicitely (a message states: "discard for now").
         * This means that it is currently not possible to build a pipeline that would allow
         * seeking in a matroska media (using demux to interprete timestamps) and mux back to
         * to matroska. One solution would be to export streams to files and mux back
         * crossing fingers to make sure everything remains in sync.
         * - nlesrc (from gstreamer-editing-services) allows extracting frames from a starting
         * positiong for a given duration. However, it is designed to work with single stream
         * medias and decodes to raw formats.
         * - filesink can't change the file location without setting the pipeline to Null
         * which also unlinks elements.
         * - wavenc doesn't send header after a second seek in segment mode.
         *
         * The two last issues lead to building a new pipeline for each chapter.
         *
         * Until I design a GUI for the user to select which stream to export to which codec,
         * current solution is to keep only the audio track and to save it as a wave file
         * which matches the initial purpose of this application */

        // Input
        let filesrc = gst::ElementFactory::make("filesrc", None).unwrap();
        filesrc
            .set_property("location", &gst::Value::from(input_path.to_str().unwrap()))
            .unwrap();
        let decodebin = gst::ElementFactory::make("decodebin", None).unwrap();

        // Output sink
        let audio_enc = gst::ElementFactory::make("wavenc", "audioenc").unwrap();
        let outsink = gst::ElementFactory::make("filesink", "filesink").unwrap();
        outsink
            .set_property(
                "location",
                &gst::Value::from(output_path.with_extension("wave").to_str().unwrap()),
            )
            .unwrap();

        {
            self.pipeline
                .add_many(&[&filesrc, &decodebin, &audio_enc, &outsink])
                .unwrap();
            filesrc.link(&decodebin).unwrap();
            decodebin.sync_state_with_parent().unwrap();
            audio_enc.link(&outsink).unwrap();
            outsink.sync_state_with_parent().unwrap();
        }

        self.position_ref = Some(decodebin.clone());
        self.tag_toc_element = Some(audio_enc.clone());

        let pipeline_cb = self.pipeline.clone();
        decodebin.connect_pad_added(move |_element, pad| {
            let caps = pad.get_current_caps().unwrap();
            let structure = caps.get_structure(0).unwrap();
            let name = structure.get_name();

            let queue = gst::ElementFactory::make("queue", None).unwrap();
            pipeline_cb.add(&queue).unwrap();
            let queue_sink_pad = queue.get_static_pad("sink").unwrap();
            assert_eq!(pad.link(&queue_sink_pad), gst::PadLinkReturn::Ok);
            queue.sync_state_with_parent().unwrap();
            let queue_src_pad = queue.get_static_pad("src").unwrap();

            if name.starts_with("audio/") && pipeline_cb.get_by_name("audioconvert").is_none() {
                let audio_conv = gst::ElementFactory::make("audioconvert", "audioconvert").unwrap();
                pipeline_cb.add(&audio_conv).unwrap();
                gst::Element::link_many(&[&queue, &audio_conv, &audio_enc]).unwrap();
                audio_conv.sync_state_with_parent().unwrap();
                audio_enc.sync_state_with_parent().unwrap();
            } else {
                let fakesink = gst::ElementFactory::make("fakesink", None).unwrap();
                pipeline_cb.add(&fakesink).unwrap();
                let fakesink_sink_pad = fakesink.get_static_pad("sink").unwrap();
                assert_eq!(
                    queue_src_pad.link(&fakesink_sink_pad),
                    gst::PadLinkReturn::Ok
                );
                fakesink.sync_state_with_parent().unwrap();
            }
        });
    }

    // Uses ctx_tx to notify the UI controllers about the inspection process
    fn register_bus_inspector(&self, start: u64, end: u64, ctx_tx: Sender<ContextMessage>) {
        let mut init_done = false;
        let pipeline = self.pipeline.clone();
        let tag_toc_element = self.tag_toc_element
            .as_ref()
            .expect("SplitContext::register_bus_inspector no tag toc element")
            .clone();
        self.pipeline.get_bus().unwrap().add_watch(move |_, msg| {
            match msg.view() {
                gst::MessageView::Eos(..) => {
                    if pipeline.set_state(gst::State::Null) == gst::StateChangeReturn::Failure {
                        ctx_tx.send(ContextMessage::FailedToExport).expect(
                            "Error: Failed to notify UI",
                        );
                    }
                    init_done = false;
                    ctx_tx.send(ContextMessage::Eos).expect(
                        "Eos: Failed to notify UI",
                    );
                    return glib::Continue(false);
                }
                gst::MessageView::Error(err) => {
                    eprintln!(
                        "Error from {}: {} ({:?})",
                        msg.get_src().map(|s| s.get_path_string()).unwrap_or_else(
                            || {
                                String::from("None")
                            },
                        ),
                        err.get_error(),
                        err.get_debug()
                    );
                    ctx_tx.send(ContextMessage::FailedToExport).expect(
                        "Error: Failed to notify UI",
                    );
                    return glib::Continue(false);
                }
                gst::MessageView::AsyncDone(_) => {
                    if !init_done {
                        init_done = true;

                        // TODO: set tags
                        let tag_setter = tag_toc_element
                            .clone()
                            .dynamic_cast::<gst::TagSetter>()
                            .expect("SplitterContext(AsyncDone) not a TagSetter");
                        tag_setter.reset_tags();

                        // Don't export initial media's toc
                        // FIXME: actually remove the toc (see wavenc source code)
                        let toc_setter = tag_toc_element
                            .clone()
                            .dynamic_cast::<gst::TocSetter>()
                            .expect("SplitterContext(AsyncDone) not a TocSetter");
                        toc_setter.reset();

                        match pipeline.seek(
                            1f64,
                            gst::SeekFlags::FLUSH | gst::SeekFlags::ACCURATE,
                            gst::SeekType::Set,
                            ClockTime::from(start),
                            gst::SeekType::Set,
                            ClockTime::from(end),
                        ) {
                            Ok(_) => (),
                            Err(_) => {
                                ctx_tx.send(ContextMessage::FailedToExport).expect(
                                    "Error: Failed to notify UI",
                                )
                            }
                        };
                    } else if pipeline.set_state(gst::State::Playing) ==
                               gst::StateChangeReturn::Failure
                    {
                        ctx_tx.send(ContextMessage::FailedToExport).expect(
                            "Error: Failed to notify UI",
                        );
                    }
                }
                _ => (),
            }

            glib::Continue(true)
        });
    }
}
<commit_msg>Splitter: workaround to remove upstream TOC (#88)<commit_after>extern crate gstreamer as gst;
use gstreamer::prelude::*;
use gstreamer::{ClockTime, QueryView, TocSetterExt};

extern crate glib;

use std::sync::mpsc::Sender;

use std::path::Path;

use super::ContextMessage;

pub struct SplitterContext {
    pipeline: gst::Pipeline,
    tag_toc_element: Option<gst::Element>,
    position_ref: Option<gst::Element>,
    position_query: gst::Query,
}

impl SplitterContext {
    pub fn new(
        input_path: &Path,
        output_path: &Path,
        start: u64,
        end: u64,
        ctx_tx: Sender<ContextMessage>,
    ) -> Result<SplitterContext, String> {
        println!("\n\n* Exporting {:?}...", input_path);

        let mut this = SplitterContext {
            pipeline: gst::Pipeline::new("pipeline"),
            tag_toc_element: None,
            position_ref: None,
            position_query: gst::Query::new_position(gst::Format::Time),
        };

        this.build_pipeline(input_path, output_path);
        this.register_bus_inspector(start, end, ctx_tx);

        match this.pipeline.set_state(gst::State::Paused) {
            gst::StateChangeReturn::Failure => Err("Could not set media in Paused state".into()),
            _ => Ok(this),
        }
    }

    pub fn get_position(&mut self) -> u64 {
        self.position_ref.as_ref().unwrap().query(
            self.position_query
                .get_mut()
                .unwrap(),
        );
        match self.position_query.view() {
            QueryView::Position(ref position) => position.get_result().get_value() as u64,
            _ => unreachable!(),
        }
    }

    // TODO: handle errors
    fn build_pipeline(&mut self, input_path: &Path, output_path: &Path) {
        // FIXME: multiple issues
        /* There are multiple showstoppers to implementing something ideal
         * to export splitted chapters with audio and video (and subtitles):
         * - matroska-mux drops seek events explicitely (a message states: "discard for now").
         * This means that it is currently not possible to build a pipeline that would allow
         * seeking in a matroska media (using demux to interprete timestamps) and mux back to
         * to matroska. One solution would be to export streams to files and mux back
         * crossing fingers to make sure everything remains in sync.
         * - nlesrc (from gstreamer-editing-services) allows extracting frames from a starting
         * positiong for a given duration. However, it is designed to work with single stream
         * medias and decodes to raw formats.
         * - filesink can't change the file location without setting the pipeline to Null
         * which also unlinks elements.
         * - wavenc doesn't send header after a second seek in segment mode.
         *
         * The two last issues lead to building a new pipeline for each chapter.
         *
         * Until I design a GUI for the user to select which stream to export to which codec,
         * current solution is to keep only the audio track and to save it as a wave file
         * which matches the initial purpose of this application */

        // Input
        let filesrc = gst::ElementFactory::make("filesrc", None).unwrap();
        filesrc
            .set_property("location", &gst::Value::from(input_path.to_str().unwrap()))
            .unwrap();
        let decodebin = gst::ElementFactory::make("decodebin", None).unwrap();

        // Output sink
        let audio_enc = gst::ElementFactory::make("wavenc", "audioenc").unwrap();
        // Catch events and drop the upstream TOC
        let audio_enc_sink_pad = audio_enc.get_static_pad("sink").unwrap();
        audio_enc_sink_pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, |_pad, probe_info| {
            if let Some(ref data) = probe_info.data {
                match data {
                    &gst::PadProbeData::Event(ref event) => match event.view() {
                        gst::EventView::Toc(ref _toc) => return gst::PadProbeReturn::Drop,
                        _ => (),
                    },
                    _ => (),
                }
            }
            gst::PadProbeReturn::Ok
        });

        let outsink = gst::ElementFactory::make("filesink", "filesink").unwrap();
        outsink
            .set_property(
                "location",
                &gst::Value::from(output_path.with_extension("wave").to_str().unwrap()),
            )
            .unwrap();

        {
            self.pipeline
                .add_many(&[&filesrc, &decodebin, &audio_enc, &outsink])
                .unwrap();
            filesrc.link(&decodebin).unwrap();
            decodebin.sync_state_with_parent().unwrap();
            audio_enc.link(&outsink).unwrap();
            outsink.sync_state_with_parent().unwrap();
        }

        self.position_ref = Some(decodebin.clone());
        self.tag_toc_element = Some(audio_enc.clone());

        let pipeline_cb = self.pipeline.clone();
        decodebin.connect_pad_added(move |_element, pad| {
            let caps = pad.get_current_caps().unwrap();
            let structure = caps.get_structure(0).unwrap();
            let name = structure.get_name();

            let queue = gst::ElementFactory::make("queue", None).unwrap();
            pipeline_cb.add(&queue).unwrap();
            let queue_sink_pad = queue.get_static_pad("sink").unwrap();
            assert_eq!(pad.link(&queue_sink_pad), gst::PadLinkReturn::Ok);
            queue.sync_state_with_parent().unwrap();
            let queue_src_pad = queue.get_static_pad("src").unwrap();

            if name.starts_with("audio/") && pipeline_cb.get_by_name("audioconvert").is_none() {
                let audio_conv = gst::ElementFactory::make("audioconvert", "audioconvert").unwrap();
                pipeline_cb.add(&audio_conv).unwrap();
                gst::Element::link_many(&[&queue, &audio_conv, &audio_enc]).unwrap();
                audio_conv.sync_state_with_parent().unwrap();
                audio_enc.sync_state_with_parent().unwrap();
            } else {
                let fakesink = gst::ElementFactory::make("fakesink", None).unwrap();
                pipeline_cb.add(&fakesink).unwrap();
                let fakesink_sink_pad = fakesink.get_static_pad("sink").unwrap();
                assert_eq!(
                    queue_src_pad.link(&fakesink_sink_pad),
                    gst::PadLinkReturn::Ok
                );
                fakesink.sync_state_with_parent().unwrap();
            }
        });
    }

    // Uses ctx_tx to notify the UI controllers about the inspection process
    fn register_bus_inspector(&self, start: u64, end: u64, ctx_tx: Sender<ContextMessage>) {
        let mut init_done = false;
        let pipeline = self.pipeline.clone();
        let tag_toc_element = self.tag_toc_element
            .as_ref()
            .expect("SplitContext::register_bus_inspector no tag toc element")
            .clone();
        self.pipeline.get_bus().unwrap().add_watch(move |_, msg| {
            match msg.view() {
                gst::MessageView::Eos(..) => {
                    if pipeline.set_state(gst::State::Null) == gst::StateChangeReturn::Failure {
                        ctx_tx.send(ContextMessage::FailedToExport).expect(
                            "Error: Failed to notify UI",
                        );
                    }
                    init_done = false;
                    ctx_tx.send(ContextMessage::Eos).expect(
                        "Eos: Failed to notify UI",
                    );
                    return glib::Continue(false);
                }
                gst::MessageView::Error(err) => {
                    eprintln!(
                        "Error from {}: {} ({:?})",
                        msg.get_src().map(|s| s.get_path_string()).unwrap_or_else(
                            || {
                                String::from("None")
                            },
                        ),
                        err.get_error(),
                        err.get_debug()
                    );
                    ctx_tx.send(ContextMessage::FailedToExport).expect(
                        "Error: Failed to notify UI",
                    );
                    return glib::Continue(false);
                }
                gst::MessageView::AsyncDone(_) => {
                    if !init_done {
                        init_done = true;

                        // TODO: set tags
                        let tag_setter = tag_toc_element
                            .clone()
                            .dynamic_cast::<gst::TagSetter>()
                            .expect("SplitterContext(AsyncDone) not a TagSetter");
                        tag_setter.reset_tags();

                        // Don't export initial media's toc
                        // FIXME: actually remove the toc (see wavenc source code)
                        let toc_setter = tag_toc_element
                            .clone()
                            .dynamic_cast::<gst::TocSetter>()
                            .expect("SplitterContext(AsyncDone) not a TocSetter");
                        toc_setter.reset();

                        match pipeline.seek(
                            1f64,
                            gst::SeekFlags::FLUSH | gst::SeekFlags::ACCURATE,
                            gst::SeekType::Set,
                            ClockTime::from(start),
                            gst::SeekType::Set,
                            ClockTime::from(end),
                        ) {
                            Ok(_) => (),
                            Err(_) => {
                                ctx_tx.send(ContextMessage::FailedToExport).expect(
                                    "Error: Failed to notify UI",
                                )
                            }
                        };
                    } else if pipeline.set_state(gst::State::Playing) ==
                               gst::StateChangeReturn::Failure
                    {
                        ctx_tx.send(ContextMessage::FailedToExport).expect(
                            "Error: Failed to notify UI",
                        );
                    }
                }
                _ => (),
            }

            glib::Continue(true)
        });
    }
}
<|endoftext|>


<commit_before>/*!
 * Processing the markdown and resolving references.
 */

use std::{
    collections::HashSet,
    fs::File,
    io::{Read, Write},
    path::{Path, PathBuf},
};

use pulldown_cmark::{html, Event, LinkType, Parser, Tag};
use url::Url;

use crate::config::ResolvedConfig;

struct RenderAdapter<'a, 'b, 'c: 'a, I: Iterator<Item = Event<'b>>> {
    styles: &'a mut HashSet<&'c str>,
    render_stack: &'a mut Vec<PathBuf>,
    base_dir: &'a Path,
    out_dir: &'a Path,
    filename: &'a Path,
    iter: I,
}

impl<'a, 'b, 'c: 'a, I: Iterator<Item = Event<'b>>> Iterator for RenderAdapter<'a, 'b, 'c, I> {
    type Item = Event<'b>;

    fn next(&mut self) -> Option<Self::Item> {
        let item = self.iter.next()?;
        Some(match item {
            Event::Start(tag) => match tag {
                i @ Tag::Image(..) => {
                    self.styles.insert("image");
                    Event::Start(i)
                }
                p @ Tag::Paragraph => {
                    self.styles.insert("paragraph");
                    Event::Start(p)
                }
                Tag::Link(ty, url, title) => {
                    self.styles.insert("link");
                    match ty {
                        LinkType::Inline => {
                            if let Ok(parsed) = Url::parse(&url) {
                                // check if scheme is hyperref, if so add to stack and rewrite url
                                if parsed.scheme() == "hyperref" {
                                    let parsed_path: &Path = parsed.path().as_ref();
                                    let fname: PathBuf = if parsed_path.is_absolute() {
                                        self.out_dir.join(parsed_path.strip_prefix("/").unwrap())
                                    } else {
                                        self.filename
                                            .parent()
                                            .unwrap_or("/".as_ref())
                                            .join(parsed.path())
                                    }
                                    .with_extension("md");
                                    #[cfg(target_os = "windows")]
                                    // replace with backslashes so that \\?\ isn't broken
                                    let fname: PathBuf =
                                        fname.to_str().unwrap().replace("/", "\\").into();
                                    if let Ok(fname) = fname.canonicalize() {
                                        let fname_for_url =
                                            fname.strip_prefix(&self.base_dir).unwrap();
                                        #[cfg(target_os = "windows")]
                                    // windows is dumb again
                                    let fname_for_url: PathBuf =
                                        fname_for_url.to_str().unwrap().replace("\\", "/").into();
                                        // figure out new location
                                        let new_location = format!(
                                            "/{}",
                                            fname_for_url.with_extension("html").to_str().unwrap(),
                                        );
                                        if !self.render_stack.contains(&fname) {
                                            println!(
                                                "walk: {}",
                                                fname.to_str().unwrap_or("unknown")
                                            );
                                            self.render_stack.push(fname);
                                        }
                                        Event::Start(Tag::Link(ty, new_location.into(), title))
                                        // link.url = new_location.into_bytes();
                                    } else {
                                        println!("Couldn't resolve hyperref: {}", url);
                                        Event::Start(Tag::Link(ty, url, title))
                                    }
                                } else {
                                    Event::Start(Tag::Link(ty, url, title))
                                }
                            } else {
                                Event::Start(Tag::Link(ty, url, title))
                            }
                        }
                        _ => Event::Start(Tag::Link(ty, url, title)),
                    }
                }
                t => Event::Start(t),
            },
            ev => ev,
        })
    }
}

/// Processes files
pub struct Processor {
    /// Stuff is derived from this
    config: ResolvedConfig,
}

impl Processor {
    pub fn new(config: ResolvedConfig) -> Self {
        Self { config }
    }

    pub fn render_index(&self, force: bool) -> anyhow::Result<()> {
        self.render(&self.config.inputs.index, "", force)
    }

    pub fn render_keep(&self, force: bool) -> anyhow::Result<()> {
        self.render(&self.config.inputs.keep, "", force)
    }

    pub fn render_toplevel(&self, force: bool) -> anyhow::Result<()> {
        self.render_index(force)?;
        self.render_keep(force)?;
        Ok(())
    }

    pub fn render(&self, filename: &Path, base: &str, force: bool) -> anyhow::Result<()> {
        let out_dir = &self.config.roots.output;
        let base_dir = &self.config.roots.source;
        let style_chunks_root = &self.config.lib.styles.chunks_root;
        let prelude_html = &self.config.lib.prelude_location;

        if !filename.exists() {
            println!(
                "nonexistent: {}, nothing to do",
                filename.to_str().unwrap_or("unknown")
            );
            return Ok(());
        }

        // create out dir if doesn't exist
        if !out_dir.exists() {
            std::fs::create_dir_all(out_dir)?;
        }
        // // canonicalize paths
        // let (filename, base_dir, out_dir) = (
        //     filename.as_ref().canonicalize()?,
        //     base_dir.as_ref().canonicalize()?,
        //     out_dir.as_ref().canonicalize()?,
        // );
        // as-ref paths
        // let (filename, base_dir, out_dir) = (filename.as_ref(), base_dir.as_ref(), out_dir.as_ref());

        // NOTE: can't canonicalize here since the output path may not exist
        let out_path = out_dir
            .join(filename.strip_prefix(&base_dir)?)
            .with_extension("html");
        println!("out: {}", out_path.to_str().unwrap_or("unknown"));

        let buf = {
            let mut s = String::new();
            let mut f = File::open(&filename)?;
            f.read_to_string(&mut s)?;
            Ok::<_, std::io::Error>(s)
        }?;

        let mut styles = {
            let mut h = HashSet::new();
            h.insert("_global");
            h
        };
        let mut stack = Vec::new();

        let html = {
            let parser = Parser::new(&buf);
            let adapter = RenderAdapter {
                base_dir,
                filename,
                out_dir,
                render_stack: &mut stack,
                styles: &mut styles,
                iter: parser,
            };

            let mut s = String::new();
            html::push_html(&mut s, adapter);
            s
        };

        let styles = {
            let mut new_styles = Vec::new();
            for sname in styles.into_iter() {
                let path = style_chunks_root.join(sname).with_extension("css");
                // skip missing files
                if let Ok(path) = AsRef::<Path>::as_ref(&path).canonicalize() {
                    let css_out_path = out_dir.join("css").join(sname).with_extension("css");
                    if let Some(p) = css_out_path.parent() {
                        std::fs::create_dir_all(p)?;
                    }
                    // Minify style first
                    let minified_css = {
                        let mut s = String::new();
                        let mut f = File::open(&path)?;
                        f.read_to_string(&mut s)?;
                        let minified = html_minifier::css::minify(&s)
                            .map_err(|_| anyhow::anyhow!("minify failed"))?;
                        println!(
                            "Minified {} bytes -> {} bytes ({:.3}%)",
                            s.len(),
                            minified.len(),
                            (((minified.len() as f64) - (s.len() as f64)) / s.len() as f64) * 100.
                        );
                        Ok::<_, anyhow::Error>(minified)
                    }?;
                    {
                        let mut f = File::create(&css_out_path)?;
                        f.write_all(minified_css.as_bytes())?;
                    }
                    new_styles.push(format!(
                        r#"
    <link rel="preload" href="/{0}" as="style" />
    <link rel="stylesheet" type="text/css" href="/{0}" />
    "#,
                        css_out_path
                            .strip_prefix(out_dir)
                            .unwrap_or(&css_out_path)
                            .to_str()
                            .unwrap_or("unknown")
                            .replace("\\", "/")
                    ));
                }
            }
            Ok::<_, std::io::Error>(new_styles)
        }?;
        let html = {
            println!("path = {}", prelude_html.to_str().unwrap_or("unknown"));
            let mut f = File::open(prelude_html)?;
            let mut s = String::new();
            f.read_to_string(&mut s)?;
            Ok::<_, std::io::Error>(s)
        }?
        .replace("@@@SLOT_STYLES@@@", &format!("\n{}\n", styles.join("\n")))
        .replace("@@@SLOT_CONTENT@@@", &html);

        // Minify HTML
        let minified = html_minifier::minify(&html)?;

        println!(
            "Minified {} bytes -> {} bytes ({:.3}%)",
            html.len(),
            minified.len(),
            (((minified.len() as f64) - (html.len() as f64)) / html.len() as f64) * 100.
        );

        // write only if file doesn't exist
        let needs_update = if let (Ok(out_metadata), Ok(in_metadata)) =
            (out_path.metadata(), filename.metadata())
        {
            in_metadata.modified()? >= out_metadata.modified()?
        } else {
            // failed to get metadata, or either path doesn't exist
            true
        };
        if !needs_update && !force {
            // nothing to do
            println!("fresh: {}", out_path.to_str().unwrap_or("unknown"));
        } else {
            // first, recursively create parents
            if let Some(p) = out_path.parent() {
                std::fs::create_dir_all(p)?;
            }

            if out_path.file_stem().unwrap().to_str().unwrap() == "_keep" {
                println!("special: _keep, no output");
            } else {
                let mut f = File::create(&out_path)?;
                f.write_all(minified.as_bytes())?;
                // println!("{}", html);
                println!("new: {}", out_path.to_str().unwrap_or("unknown"));
            }
        }

        // write stack
        for fname in stack {
            let base_add = fname.strip_prefix(&base_dir).unwrap();
            let new_base = format!("{}/{}", base, base_add.to_str().unwrap());
            self.render(&fname, &new_base, force)?;
        }

        Ok(())
    }
}
<commit_msg>renderadapter: use a context<commit_after>/*!
 * Processing the markdown and resolving references.
 */

use std::{
    collections::HashSet,
    fs::File,
    io::{Read, Write},
    path::{Path, PathBuf},
};

use pulldown_cmark::{html, Event, LinkType, Parser, Tag};
use url::Url;

use crate::config::ResolvedConfig;

struct RenderAdapter<'a, 'b, 'c: 'a, I: Iterator<Item = Event<'b>>> {
    ctx: &'a mut ProcessorContext<'a, 'c>,
    iter: I,
}

impl<'a, 'b, 'c: 'a, I: Iterator<Item = Event<'b>>> Iterator for RenderAdapter<'a, 'b, 'c, I> {
    type Item = Event<'b>;

    fn next(&mut self) -> Option<Self::Item> {
        let item = self.iter.next()?;
        let styles = &mut self.ctx.styles;
        let render_stack = &mut *self.ctx.render_stack;
        let out_dir = &self.ctx.config.roots.output;
        let base_dir = &self.ctx.config.roots.source;
        let filename = self.ctx.filename;
        Some(match item {
            Event::Start(tag) => match tag {
                i @ Tag::Image(..) => {
                    styles.insert("image");
                    Event::Start(i)
                }
                p @ Tag::Paragraph => {
                    styles.insert("paragraph");
                    Event::Start(p)
                }
                Tag::Link(ty, url, title) => {
                    styles.insert("link");
                    match ty {
                        LinkType::Inline => {
                            if let Ok(parsed) = Url::parse(&url) {
                                // check if scheme is hyperref, if so add to stack and rewrite url
                                if parsed.scheme() == "hyperref" {
                                    let parsed_path: &Path = parsed.path().as_ref();
                                    let fname: PathBuf = if parsed_path.is_absolute() {
                                        out_dir.join(parsed_path.strip_prefix("/").unwrap())
                                    } else {
                                        filename
                                            .parent()
                                            .unwrap_or("/".as_ref())
                                            .join(parsed.path())
                                    }
                                    .with_extension("md");
                                    #[cfg(target_os = "windows")]
                                    // replace with backslashes so that \\?\ isn't broken
                                    let fname: PathBuf =
                                        fname.to_str().unwrap().replace("/", "\\").into();
                                    if let Ok(fname) = fname.canonicalize() {
                                        let fname_for_url = fname.strip_prefix(&base_dir).unwrap();
                                        #[cfg(target_os = "windows")]
                                    // windows is dumb again
                                    let fname_for_url: PathBuf =
                                        fname_for_url.to_str().unwrap().replace("\\", "/").into();
                                        // figure out new location
                                        let new_location = format!(
                                            "/{}",
                                            fname_for_url.with_extension("html").to_str().unwrap(),
                                        );
                                        if !render_stack.contains(&fname) {
                                            println!(
                                                "walk: {}",
                                                fname.to_str().unwrap_or("unknown")
                                            );
                                            render_stack.push(fname);
                                        }
                                        Event::Start(Tag::Link(ty, new_location.into(), title))
                                        // link.url = new_location.into_bytes();
                                    } else {
                                        println!("Couldn't resolve hyperref: {}", url);
                                        Event::Start(Tag::Link(ty, url, title))
                                    }
                                } else {
                                    Event::Start(Tag::Link(ty, url, title))
                                }
                            } else {
                                Event::Start(Tag::Link(ty, url, title))
                            }
                        }
                        _ => Event::Start(Tag::Link(ty, url, title)),
                    }
                }
                t => Event::Start(t),
            },
            ev => ev,
        })
    }
}

/// Processing context for a single file
pub struct ProcessorContext<'a, 'b: 'a> {
    styles: &'a mut HashSet<&'b str>,
    filename: &'a Path,
    render_stack: &'a mut Vec<PathBuf>,
    config: &'a ResolvedConfig,
}

/// Processes files
pub struct Processor {
    /// Stuff is derived from this
    config: ResolvedConfig,
}

impl Processor {
    pub fn new(config: ResolvedConfig) -> Self {
        Self { config }
    }

    pub fn render_index(&self, force: bool) -> anyhow::Result<()> {
        self.render(&self.config.inputs.index, "", force)
    }

    pub fn render_keep(&self, force: bool) -> anyhow::Result<()> {
        self.render(&self.config.inputs.keep, "", force)
    }

    pub fn render_toplevel(&self, force: bool) -> anyhow::Result<()> {
        self.render_index(force)?;
        self.render_keep(force)?;
        Ok(())
    }

    pub fn render(&self, filename: &Path, base: &str, force: bool) -> anyhow::Result<()> {
        let out_dir = &self.config.roots.output;
        let base_dir = &self.config.roots.source;
        let style_chunks_root = &self.config.lib.styles.chunks_root;
        let prelude_html = &self.config.lib.prelude_location;

        if !filename.exists() {
            println!(
                "nonexistent: {}, nothing to do",
                filename.to_str().unwrap_or("unknown")
            );
            return Ok(());
        }

        // create out dir if doesn't exist
        if !out_dir.exists() {
            std::fs::create_dir_all(out_dir)?;
        }
        // // canonicalize paths
        // let (filename, base_dir, out_dir) = (
        //     filename.as_ref().canonicalize()?,
        //     base_dir.as_ref().canonicalize()?,
        //     out_dir.as_ref().canonicalize()?,
        // );
        // as-ref paths
        // let (filename, base_dir, out_dir) = (filename.as_ref(), base_dir.as_ref(), out_dir.as_ref());

        // NOTE: can't canonicalize here since the output path may not exist
        let out_path = out_dir
            .join(filename.strip_prefix(&base_dir)?)
            .with_extension("html");
        println!("out: {}", out_path.to_str().unwrap_or("unknown"));

        let buf = {
            let mut s = String::new();
            let mut f = File::open(&filename)?;
            f.read_to_string(&mut s)?;
            Ok::<_, std::io::Error>(s)
        }?;

        let mut styles = {
            let mut h = HashSet::new();
            h.insert("_global");
            h
        };
        let mut stack = Vec::new();

        let html = {
            let parser = Parser::new(&buf);
            let mut ctx = ProcessorContext {
                filename,
                styles: &mut styles,
                config: &self.config,
                render_stack: &mut stack,
            };
            let adapter = RenderAdapter {
                ctx: &mut ctx,
                iter: parser,
            };

            let mut s = String::new();
            html::push_html(&mut s, adapter);
            s
        };

        let styles = {
            let mut new_styles = Vec::new();
            for sname in styles.into_iter() {
                let path = style_chunks_root.join(sname).with_extension("css");
                // skip missing files
                if let Ok(path) = AsRef::<Path>::as_ref(&path).canonicalize() {
                    let css_out_path = out_dir.join("css").join(sname).with_extension("css");
                    if let Some(p) = css_out_path.parent() {
                        std::fs::create_dir_all(p)?;
                    }
                    // Minify style first
                    let minified_css = {
                        let mut s = String::new();
                        let mut f = File::open(&path)?;
                        f.read_to_string(&mut s)?;
                        let minified = html_minifier::css::minify(&s)
                            .map_err(|_| anyhow::anyhow!("minify failed"))?;
                        println!(
                            "Minified {} bytes -> {} bytes ({:.3}%)",
                            s.len(),
                            minified.len(),
                            (((minified.len() as f64) - (s.len() as f64)) / s.len() as f64) * 100.
                        );
                        Ok::<_, anyhow::Error>(minified)
                    }?;
                    {
                        let mut f = File::create(&css_out_path)?;
                        f.write_all(minified_css.as_bytes())?;
                    }
                    new_styles.push(format!(
                        r#"
    <link rel="preload" href="/{0}" as="style" />
    <link rel="stylesheet" type="text/css" href="/{0}" />
    "#,
                        css_out_path
                            .strip_prefix(out_dir)
                            .unwrap_or(&css_out_path)
                            .to_str()
                            .unwrap_or("unknown")
                            .replace("\\", "/")
                    ));
                }
            }
            Ok::<_, std::io::Error>(new_styles)
        }?;
        let html = {
            println!("path = {}", prelude_html.to_str().unwrap_or("unknown"));
            let mut f = File::open(prelude_html)?;
            let mut s = String::new();
            f.read_to_string(&mut s)?;
            Ok::<_, std::io::Error>(s)
        }?
        .replace("@@@SLOT_STYLES@@@", &format!("\n{}\n", styles.join("\n")))
        .replace("@@@SLOT_CONTENT@@@", &html);

        // Minify HTML
        let minified = html_minifier::minify(&html)?;

        println!(
            "Minified {} bytes -> {} bytes ({:.3}%)",
            html.len(),
            minified.len(),
            (((minified.len() as f64) - (html.len() as f64)) / html.len() as f64) * 100.
        );

        // write only if file doesn't exist
        let needs_update = if let (Ok(out_metadata), Ok(in_metadata)) =
            (out_path.metadata(), filename.metadata())
        {
            in_metadata.modified()? >= out_metadata.modified()?
        } else {
            // failed to get metadata, or either path doesn't exist
            true
        };
        if !needs_update && !force {
            // nothing to do
            println!("fresh: {}", out_path.to_str().unwrap_or("unknown"));
        } else {
            // first, recursively create parents
            if let Some(p) = out_path.parent() {
                std::fs::create_dir_all(p)?;
            }

            if out_path.file_stem().unwrap().to_str().unwrap() == "_keep" {
                println!("special: _keep, no output");
            } else {
                let mut f = File::create(&out_path)?;
                f.write_all(minified.as_bytes())?;
                // println!("{}", html);
                println!("new: {}", out_path.to_str().unwrap_or("unknown"));
            }
        }

        // write stack
        for fname in stack {
            let base_add = fname.strip_prefix(&base_dir).unwrap();
            let new_base = format!("{}/{}", base, base_add.to_str().unwrap());
            self.render(&fname, &new_base, force)?;
        }

        Ok(())
    }
}
<|endoftext|>


<commit_before>use std::path::PathBuf;
use std::io::Write;
use temp;
use toml;
use rustup_utils;
use manifest::Component;
use dist::TargetTriple;
use rustup_error::ForeignError;

pub type Result<T> = ::std::result::Result<T, ErrorChain>;

easy_error! {
    #[derive(Debug)]
    pub chain_error ChainError;

    #[derive(Debug)]
    pub error_chain ErrorChain;

    #[derive(Debug)]
    pub error Error {
        Utils(e: rustup_utils::Error) {
            description(e.description())
            display("{}", e)
            from()
        }
        Temp(e: ForeignError) {
            description(&e.description)
            display("{}", e.display)
        }
        InvalidFileExtension {
            description("invalid file extension")
        }
        InvalidInstaller {
            description("invalid installer")
        }
        InvalidTargetTriple(t: String) {
            description("invalid target triple")
            display("invalid target triple: '{}'", t)
        }
        InvalidToolchainName(t: String) {
            description("invalid toolchain name")
            display("invalid toolchain name: '{}'", t)
        }
        InvalidCustomToolchainName(t: String) {
            description("invalid custom toolchain name")
            display("invalid toolchain name: '{}'", t)
        }
        NotInstalledHere {
            description("not installed here")
        }
        UnsupportedHost(h: String) {
            description("binary package not provided for fost")
            display("a binary package was not provided for: '{}'", h)
        }
        ChecksumFailed {
            url: String,
            expected: String,
            calculated: String,
        } {
            description("checksum failed")
            display("checksum failed, expected: '{}', calculated: '{}'",
                    expected,
                    calculated)
        }
        ComponentConflict {
            name: String,
            path: PathBuf,
        } {
            description("conflicting component")
            display("failed to install component: '{}', detected conflict: '{:?}'",
                    name,
                    path)
        }
        ComponentMissingFile {
            name: String,
            path: PathBuf,
        } {
            description("missing file in component")
            display("failure removing component '{}', directory does not exist: '{:?}'",
                    name,
                    path)
        }
        ComponentMissingDir {
            name: String,
            path: PathBuf,
        } {
            description("missing directory in component")
            display("failure removing component '{}', directory does not exist: '{:?}'",
                    name,
                    path)
        }
        CorruptComponent(name: String) {
            description("corrupt component manifest")
            display("component manifest for '{}' is corrupt", name)
        }
        ExtractingPackage {
            description("failed to extract package")
        }
        ExtensionNotFound(c: Component) {
            description("could not find extension")
            display("could not find extension: '{}-{}'", c.pkg, c.target)
        }
        InvalidChangeSet {
            description("invalid change-set")
        }
        NoGPG {
            description("could not find 'gpg' on PATH")
        }
        BadInstallerVersion(v: String) {
            description("unsupported installer version")
            display("unsupported installer version: {}", v)
        }
        BadInstalledMetadataVersion(v: String) {
            description("unsupported metadata version in existing installation")
            display("unsupported metadata version in existing installation: {}", v)
        }
        ComponentDirPermissionsFailed {
            description("I/O error walking directory during install")
        }
        ComponentFilePermissionsFailed {
            description("error setting file permissions during install")
        }
        ComponentDownloadFailed(c: Component) {
            description("component download failed")
            display("component download failed for {}-{}", c.pkg, c.target)
        }
        ObsoleteDistManifest {
            description("the server unexpectedly provided an obsolete version of the distribution manifest")
        }
        Parsing(e: Vec<toml::ParserError>) {
            description("error parsing manifest")
        }
        MissingKey(k: String) {
            description("missing key")
            display("missing key: '{}'", k)
        }
        ExpectedType(t: &'static str, n: String) {
            description("expected type")
            display("expected type: '{}' for '{}'", t, n)
        }
        PackageNotFound(p: String) {
            description("package not found")
            display("package not found: '{}'", p)
        }
        TargetNotFound(t: TargetTriple) {
            description("target not found")
            display("target not found: '{}'", t)
        }
        MissingRoot {
            description("mamnifest has no root package")
        }
        UnsupportedVersion(v: String) {
            description("unsupported manifest version")
            display("manifest version '{}' is not supported", v)
        }
        MissingPackageForComponent(c: Component) {
            description("missing package for component")
            display("server sent a broken manifest: missing package for component {}", c.name())
        }
        RequestedComponentsUnavailable(c: Vec<Component>) {
            description("some requested components are unavailable to download")
            display("{}", component_unavailable_msg(&c))
        }
        NoManifestFound(ch: String, e: Box<ErrorChain>) {
            description("no release found")
            display("{}", no_manifest_found_msg(&ch, &e))
        }
        CreatingFile(p: PathBuf) {
            description("error creating file")
            display("error creating file '{}'", p.display())
        }
    }
}

fn component_unavailable_msg(cs: &[Component]) -> String {
    assert!(!cs.is_empty());

    let mut buf = vec![];

    if cs.len() == 1 {
        let _ = write!(buf, "component '{}' for '{}' is unavailable for download",
                       cs[0].pkg, cs[0].target);
    } else {
        use itertools::Itertools;
        let same_target = cs.iter().all(|c| c.target == cs[0].target);
        if same_target {
            let mut cs_strs = cs.iter().map(|c| format!("'{}'", c.pkg));
            let cs_str = cs_strs.join(", ");
            let _ = write!(buf, "some components unavailable for download: {}",
                           cs_str);
        } else {
            let mut cs_strs = cs.iter().map(|c| format!("'{}' for '{}'", c.pkg, c.target));
            let cs_str = cs_strs.join(", ");
            let _ = write!(buf, "some components unavailable for download: {}",
                           cs_str);
        }
    }

    String::from_utf8(buf).expect("")
}

// FIXME This should be two different errors
fn no_manifest_found_msg(ch: &str, e: &ErrorChain) -> String {

    let mut buf = vec![];

    match *e {
        ErrorChain(Error::Utils(rustup_utils::Error::Download404 { .. }), _ ) => {
            let _ = write!(buf, "no release found for '{}'", ch);
        }
        _ => {
            // FIXME: Need handle other common cases nicely,
            // like dns lookup, network unavailable.
            let _ = write!(buf, "failed to download manifest for '{}': {}", ch, e);
        }
    }

    String::from_utf8(buf).expect("")
}

impl From<rustup_utils::ErrorChain> for ErrorChain {
    fn from(e: rustup_utils::ErrorChain) -> Self {
        ErrorChain(Error::Utils(e.0), e.1)
    }
}

impl From<temp::Error> for ErrorChain {
    fn from(e: temp::Error) -> Self {
        ErrorChain(Error::Temp(ForeignError::new(&e)), Some(Box::new(e)))
    }
}
<commit_msg>Remove unused from<commit_after>use std::path::PathBuf;
use std::io::Write;
use temp;
use toml;
use rustup_utils;
use manifest::Component;
use dist::TargetTriple;
use rustup_error::ForeignError;

pub type Result<T> = ::std::result::Result<T, ErrorChain>;

easy_error! {
    #[derive(Debug)]
    pub chain_error ChainError;

    #[derive(Debug)]
    pub error_chain ErrorChain;

    #[derive(Debug)]
    pub error Error {
        Utils(e: rustup_utils::Error) {
            description(e.description())
            display("{}", e)
        }
        Temp(e: ForeignError) {
            description(&e.description)
            display("{}", e.display)
        }
        InvalidFileExtension {
            description("invalid file extension")
        }
        InvalidInstaller {
            description("invalid installer")
        }
        InvalidTargetTriple(t: String) {
            description("invalid target triple")
            display("invalid target triple: '{}'", t)
        }
        InvalidToolchainName(t: String) {
            description("invalid toolchain name")
            display("invalid toolchain name: '{}'", t)
        }
        InvalidCustomToolchainName(t: String) {
            description("invalid custom toolchain name")
            display("invalid toolchain name: '{}'", t)
        }
        NotInstalledHere {
            description("not installed here")
        }
        UnsupportedHost(h: String) {
            description("binary package not provided for fost")
            display("a binary package was not provided for: '{}'", h)
        }
        ChecksumFailed {
            url: String,
            expected: String,
            calculated: String,
        } {
            description("checksum failed")
            display("checksum failed, expected: '{}', calculated: '{}'",
                    expected,
                    calculated)
        }
        ComponentConflict {
            name: String,
            path: PathBuf,
        } {
            description("conflicting component")
            display("failed to install component: '{}', detected conflict: '{:?}'",
                    name,
                    path)
        }
        ComponentMissingFile {
            name: String,
            path: PathBuf,
        } {
            description("missing file in component")
            display("failure removing component '{}', directory does not exist: '{:?}'",
                    name,
                    path)
        }
        ComponentMissingDir {
            name: String,
            path: PathBuf,
        } {
            description("missing directory in component")
            display("failure removing component '{}', directory does not exist: '{:?}'",
                    name,
                    path)
        }
        CorruptComponent(name: String) {
            description("corrupt component manifest")
            display("component manifest for '{}' is corrupt", name)
        }
        ExtractingPackage {
            description("failed to extract package")
        }
        ExtensionNotFound(c: Component) {
            description("could not find extension")
            display("could not find extension: '{}-{}'", c.pkg, c.target)
        }
        InvalidChangeSet {
            description("invalid change-set")
        }
        NoGPG {
            description("could not find 'gpg' on PATH")
        }
        BadInstallerVersion(v: String) {
            description("unsupported installer version")
            display("unsupported installer version: {}", v)
        }
        BadInstalledMetadataVersion(v: String) {
            description("unsupported metadata version in existing installation")
            display("unsupported metadata version in existing installation: {}", v)
        }
        ComponentDirPermissionsFailed {
            description("I/O error walking directory during install")
        }
        ComponentFilePermissionsFailed {
            description("error setting file permissions during install")
        }
        ComponentDownloadFailed(c: Component) {
            description("component download failed")
            display("component download failed for {}-{}", c.pkg, c.target)
        }
        ObsoleteDistManifest {
            description("the server unexpectedly provided an obsolete version of the distribution manifest")
        }
        Parsing(e: Vec<toml::ParserError>) {
            description("error parsing manifest")
        }
        MissingKey(k: String) {
            description("missing key")
            display("missing key: '{}'", k)
        }
        ExpectedType(t: &'static str, n: String) {
            description("expected type")
            display("expected type: '{}' for '{}'", t, n)
        }
        PackageNotFound(p: String) {
            description("package not found")
            display("package not found: '{}'", p)
        }
        TargetNotFound(t: TargetTriple) {
            description("target not found")
            display("target not found: '{}'", t)
        }
        MissingRoot {
            description("mamnifest has no root package")
        }
        UnsupportedVersion(v: String) {
            description("unsupported manifest version")
            display("manifest version '{}' is not supported", v)
        }
        MissingPackageForComponent(c: Component) {
            description("missing package for component")
            display("server sent a broken manifest: missing package for component {}", c.name())
        }
        RequestedComponentsUnavailable(c: Vec<Component>) {
            description("some requested components are unavailable to download")
            display("{}", component_unavailable_msg(&c))
        }
        NoManifestFound(ch: String, e: Box<ErrorChain>) {
            description("no release found")
            display("{}", no_manifest_found_msg(&ch, &e))
        }
        CreatingFile(p: PathBuf) {
            description("error creating file")
            display("error creating file '{}'", p.display())
        }
    }
}

fn component_unavailable_msg(cs: &[Component]) -> String {
    assert!(!cs.is_empty());

    let mut buf = vec![];

    if cs.len() == 1 {
        let _ = write!(buf, "component '{}' for '{}' is unavailable for download",
                       cs[0].pkg, cs[0].target);
    } else {
        use itertools::Itertools;
        let same_target = cs.iter().all(|c| c.target == cs[0].target);
        if same_target {
            let mut cs_strs = cs.iter().map(|c| format!("'{}'", c.pkg));
            let cs_str = cs_strs.join(", ");
            let _ = write!(buf, "some components unavailable for download: {}",
                           cs_str);
        } else {
            let mut cs_strs = cs.iter().map(|c| format!("'{}' for '{}'", c.pkg, c.target));
            let cs_str = cs_strs.join(", ");
            let _ = write!(buf, "some components unavailable for download: {}",
                           cs_str);
        }
    }

    String::from_utf8(buf).expect("")
}

// FIXME This should be two different errors
fn no_manifest_found_msg(ch: &str, e: &ErrorChain) -> String {

    let mut buf = vec![];

    match *e {
        ErrorChain(Error::Utils(rustup_utils::Error::Download404 { .. }), _ ) => {
            let _ = write!(buf, "no release found for '{}'", ch);
        }
        _ => {
            // FIXME: Need handle other common cases nicely,
            // like dns lookup, network unavailable.
            let _ = write!(buf, "failed to download manifest for '{}': {}", ch, e);
        }
    }

    String::from_utf8(buf).expect("")
}

impl From<rustup_utils::ErrorChain> for ErrorChain {
    fn from(e: rustup_utils::ErrorChain) -> Self {
        ErrorChain(Error::Utils(e.0), e.1)
    }
}

impl From<temp::Error> for ErrorChain {
    fn from(e: temp::Error) -> Self {
        ErrorChain(Error::Temp(ForeignError::new(&e)), Some(Box::new(e)))
    }
}
<|endoftext|>


<commit_before>// Copyright (c) <2015> <lummax>
// Licensed under MIT (http://opensource.org/licenses/MIT)

mod block_info;
mod block_allocator;
mod allocator;

use self::block_allocator::BlockAllocator;
use self::allocator::Allocator;
use self::allocator::NormalAllocator;
use self::allocator::OverflowAllocator;
use self::allocator::EvacAllocator;

pub use self::block_info::BlockInfo;

use std::{mem, ptr};
use std::collections::RingBuf;
use std::rc::Rc;
use std::cell::RefCell;

use constants::{BLOCK_SIZE, MEDIUM_OBJECT};
use gc_object::{GCRTTI, GCObject, GCObjectRef};

/// The `ImmixSpace` is the default space to allocate objects into.
///
/// Objects larger than `LARGE_OBJECT` are allocated using the
/// `LargeObjectSpace`.
///
/// The `ImmixSpace` partitions the heap into `BLOCK_SIZE` blocks of
/// `LINE_SIZE` lines. Objects are allocated into free lines on free or
/// partially used blocks.
///
/// Lines are marked with the number of live objects. This counter is
/// maintained by the `RCCollector` and the `ImmixCollector`. If it drops to
/// zero the line can be reclaimed. If a block has only free lines it can be
/// returned to the global block allocator.
pub struct ImmixSpace {
    /// The global `BlockAllocator` to get new blocks from.
    block_allocator: Rc<RefCell<BlockAllocator>>,

    /// The nomal allocator for objects smaller than `MEDIUM_OBJECT` bytes.
    allocator: NormalAllocator,

    /// The overflow allocator for objects larger than `MEDIUM_OBJECT` bytes.
    overflow_allocator: OverflowAllocator,

    /// The evacuation allocator used during an evacuating collection.
    evac_allocator: EvacAllocator,

    /// The current live mark for new objects. See `Spaces.current_live_mark`.
    current_live_mark: bool,
}

impl ImmixSpace {
    /// Create a new `ImmixSpace`.
    ///
    /// This also initializes the `BlockAllocator` which will allocate a
    /// memory map of `HEAP_SIZE` bytes. The allocation will fail if there is
    /// not enough memory available.
    pub fn new() -> ImmixSpace {
        let block_allocator = Rc::new(RefCell::new(BlockAllocator::new()));
        let normal_block_allocator = block_allocator.clone();
        let overflow_block_allocator = block_allocator.clone();
        return ImmixSpace {
            block_allocator: block_allocator,
            allocator: NormalAllocator::new(normal_block_allocator),
            overflow_allocator: OverflowAllocator::new(overflow_block_allocator),
            evac_allocator: EvacAllocator::new(),
            current_live_mark: false,
        };
    }

    /// Decrement the lines on which the object is allocated.
    pub fn decrement_lines(&self, object: GCObjectRef) {
        debug_assert!(self.is_gc_object(object),
                     "decrement_lines() on invalid object {:p}", object);
        unsafe{ (*ImmixSpace::get_block_ptr(object)).decrement_lines(object); }
    }

    /// Increment the lines on which the object is allocated.
    pub fn increment_lines(&self, object: GCObjectRef) {
        debug_assert!(self.is_gc_object(object),
                      "increment_lines() on invalid object {:p}", object);
        unsafe{ (*ImmixSpace::get_block_ptr(object)).increment_lines(object); }
    }

    /// Set an address in this space as a valid object.
    pub fn set_gc_object(&self, object: GCObjectRef) {
        debug_assert!(self.block_allocator.borrow().is_in_space(object),
                      "set_gc_object() on invalid object {:p}", object);
        unsafe{ (*ImmixSpace::get_block_ptr(object)).set_gc_object(object); }
    }

    /// Unset an address as a valid object within the immix space.
    pub fn unset_gc_object(&self, object: GCObjectRef) {
        debug_assert!(self.block_allocator.borrow().is_in_space(object),
                      "unset_gc_object() on invalid object {:p}", object);
        unsafe{ (*ImmixSpace::get_block_ptr(object)).unset_gc_object(object); }
    }

    /// Return if the object an the address is a valid object within the immix
    /// space.
    pub fn is_gc_object(&self, object: GCObjectRef) -> bool {
        if self.block_allocator.borrow().is_in_space(object) {
            return unsafe{ (*ImmixSpace::get_block_ptr(object)).is_gc_object(object) };
        }
        return false;
    }

    /// Return the total number of possible blocks.
    pub fn total_blocks(&self) -> usize {
        return self.block_allocator.borrow().total_blocks();
    }

    /// Return the number of unallocated blocks.
    pub fn available_blocks(&self) -> usize {
        return self.block_allocator.borrow().available_blocks();
    }

    /// Get the number of currently free blocks in the evacuation allocator.
    pub fn evac_headroom(&self) -> usize {
        return self.evac_allocator.evac_headroom();
    }

    /// Return a collection of blocks to the global block allocator.
    pub fn return_blocks(&mut self, blocks: RingBuf<*mut BlockInfo>) {
        self.block_allocator.borrow_mut().return_blocks(blocks);
    }

    /// Set the current live mark to `current_live_mark`.
    pub fn set_current_live_mark(&mut self, current_live_mark: bool) {
        self.current_live_mark = current_live_mark;
    }

    /// Set the recyclable blocks for the `NormalAllocator`.
    pub fn set_recyclable_blocks(&mut self, blocks: RingBuf<*mut BlockInfo>) {
        self.allocator.set_recyclable_blocks(blocks);
    }

    /// Extend the list of free blocks in the `EvacAllocator` for evacuation.
    pub fn extend_evac_headroom(&mut self, blocks: RingBuf<*mut BlockInfo>) {
        self.evac_allocator.extend_evac_headroom(blocks);
    }

    /// Get all block managed by all allocators, draining any local
    /// collections.
    pub fn get_all_blocks(&mut self) -> RingBuf<*mut BlockInfo> {
        return self.allocator.get_all_blocks().drain()
                   .chain(self.overflow_allocator.get_all_blocks().drain())
                   .chain(self.evac_allocator.get_all_blocks().drain())
                   .collect();
    }

    /// Allocate an object of `size` bytes or return `None` if the allocation
    /// failed.
    ///
    /// This object is initialized and ready to use.
    pub fn allocate(&mut self, rtti: *const GCRTTI) -> Option<GCObjectRef> {
        let size = unsafe{ (*rtti).object_size() };
        debug!("Request to allocate an object of size {}", size);
        if let Some(object) = if size < MEDIUM_OBJECT { self.allocator.allocate(size) }
                              else { self.overflow_allocator.allocate(size) } {
            unsafe { ptr::write(object, GCObject::new(rtti, self.current_live_mark)); }
            unsafe{ (*ImmixSpace::get_block_ptr(object)).set_new_object(object); }
            self.set_gc_object(object);
            return Some(object);
        }
        return None;
    }

    /// Evacuate the object to another block using the `EvacAllocator`
    /// returning the new address or `None` if no evacuation was performed.
    ///
    /// An object is evacuated if it is not pinned, it resides on an
    /// evacuation candidate block and the evacuation allocator hat enough
    /// space left.
    ///
    /// On successful evacuation the old object is marked as forewarded an an
    /// forewarding pointer is installed.
    pub fn maybe_evacuate(&mut self, object: GCObjectRef) -> Option<GCObjectRef> {
        let block_info = unsafe{ ImmixSpace::get_block_ptr(object) };
        let is_pinned = unsafe{ (*object).is_pinned() };
        let is_candidate = unsafe{ (*block_info).is_evacuation_candidate() };
        if is_pinned || !is_candidate {
            return None;
        }
        let size = unsafe{ (*object).object_size() };
        if let Some(new_object) = self.evac_allocator.allocate(size) {
            unsafe{
                ptr::copy_nonoverlapping_memory(new_object as *mut u8,
                                                object as *const u8, size);
                debug_assert!(*object == *new_object,
                              "Evacuated object was not copied correcty");
                (*object).set_forwarded(new_object);
                self.set_gc_object(object);
            }
            debug!("Evacuated object {:p} from block {:p} to {:p}", object,
                   block_info, new_object);
            valgrind_freelike!(object);
            return Some(new_object);
        }
        debug!("Can't evacuation object {:p} from block {:p}", object, block_info);
        return None;
    }
}

impl ImmixSpace {
    /// Get the block for the given object.
    unsafe fn get_block_ptr(object: GCObjectRef) -> *mut BlockInfo {
        let block_offset = object as usize % BLOCK_SIZE;
        let block = mem::transmute((object as *mut u8).offset(-(block_offset as isize)));
        debug!("Block for object {:p}: {:p} with offset: {}", object, block, block_offset);
        return block;
    }
}
<commit_msg>Fix new rust borrowchecker destruction rule issue<commit_after>// Copyright (c) <2015> <lummax>
// Licensed under MIT (http://opensource.org/licenses/MIT)

mod block_info;
mod block_allocator;
mod allocator;

use self::block_allocator::BlockAllocator;
use self::allocator::Allocator;
use self::allocator::NormalAllocator;
use self::allocator::OverflowAllocator;
use self::allocator::EvacAllocator;

pub use self::block_info::BlockInfo;

use std::{mem, ptr};
use std::collections::RingBuf;
use std::rc::Rc;
use std::cell::RefCell;

use constants::{BLOCK_SIZE, MEDIUM_OBJECT};
use gc_object::{GCRTTI, GCObject, GCObjectRef};

/// The `ImmixSpace` is the default space to allocate objects into.
///
/// Objects larger than `LARGE_OBJECT` are allocated using the
/// `LargeObjectSpace`.
///
/// The `ImmixSpace` partitions the heap into `BLOCK_SIZE` blocks of
/// `LINE_SIZE` lines. Objects are allocated into free lines on free or
/// partially used blocks.
///
/// Lines are marked with the number of live objects. This counter is
/// maintained by the `RCCollector` and the `ImmixCollector`. If it drops to
/// zero the line can be reclaimed. If a block has only free lines it can be
/// returned to the global block allocator.
pub struct ImmixSpace {
    /// The global `BlockAllocator` to get new blocks from.
    block_allocator: Rc<RefCell<BlockAllocator>>,

    /// The nomal allocator for objects smaller than `MEDIUM_OBJECT` bytes.
    allocator: NormalAllocator,

    /// The overflow allocator for objects larger than `MEDIUM_OBJECT` bytes.
    overflow_allocator: OverflowAllocator,

    /// The evacuation allocator used during an evacuating collection.
    evac_allocator: EvacAllocator,

    /// The current live mark for new objects. See `Spaces.current_live_mark`.
    current_live_mark: bool,
}

impl ImmixSpace {
    /// Create a new `ImmixSpace`.
    ///
    /// This also initializes the `BlockAllocator` which will allocate a
    /// memory map of `HEAP_SIZE` bytes. The allocation will fail if there is
    /// not enough memory available.
    pub fn new() -> ImmixSpace {
        let block_allocator = Rc::new(RefCell::new(BlockAllocator::new()));
        let normal_block_allocator = block_allocator.clone();
        let overflow_block_allocator = block_allocator.clone();
        return ImmixSpace {
            block_allocator: block_allocator,
            allocator: NormalAllocator::new(normal_block_allocator),
            overflow_allocator: OverflowAllocator::new(overflow_block_allocator),
            evac_allocator: EvacAllocator::new(),
            current_live_mark: false,
        };
    }

    /// Decrement the lines on which the object is allocated.
    pub fn decrement_lines(&self, object: GCObjectRef) {
        debug_assert!(self.is_gc_object(object),
                     "decrement_lines() on invalid object {:p}", object);
        unsafe{ (*ImmixSpace::get_block_ptr(object)).decrement_lines(object); }
    }

    /// Increment the lines on which the object is allocated.
    pub fn increment_lines(&self, object: GCObjectRef) {
        debug_assert!(self.is_gc_object(object),
                      "increment_lines() on invalid object {:p}", object);
        unsafe{ (*ImmixSpace::get_block_ptr(object)).increment_lines(object); }
    }

    /// Set an address in this space as a valid object.
    pub fn set_gc_object(&self, object: GCObjectRef) {
        debug_assert!(self.block_allocator.borrow().is_in_space(object),
                      "set_gc_object() on invalid object {:p}", object);
        unsafe{ (*ImmixSpace::get_block_ptr(object)).set_gc_object(object); }
    }

    /// Unset an address as a valid object within the immix space.
    pub fn unset_gc_object(&self, object: GCObjectRef) {
        debug_assert!(self.block_allocator.borrow().is_in_space(object),
                      "unset_gc_object() on invalid object {:p}", object);
        unsafe{ (*ImmixSpace::get_block_ptr(object)).unset_gc_object(object); }
    }

    /// Return if the object an the address is a valid object within the immix
    /// space.
    pub fn is_gc_object(&self, object: GCObjectRef) -> bool {
        if self.block_allocator.borrow().is_in_space(object) {
            return unsafe{ (*ImmixSpace::get_block_ptr(object)).is_gc_object(object) };
        }
        return false;
    }

    /// Return the total number of possible blocks.
    pub fn total_blocks(&self) -> usize {
        return self.block_allocator.borrow().total_blocks();
    }

    /// Return the number of unallocated blocks.
    pub fn available_blocks(&self) -> usize {
        return self.block_allocator.borrow().available_blocks();
    }

    /// Get the number of currently free blocks in the evacuation allocator.
    pub fn evac_headroom(&self) -> usize {
        return self.evac_allocator.evac_headroom();
    }

    /// Return a collection of blocks to the global block allocator.
    pub fn return_blocks(&mut self, blocks: RingBuf<*mut BlockInfo>) {
        self.block_allocator.borrow_mut().return_blocks(blocks);
    }

    /// Set the current live mark to `current_live_mark`.
    pub fn set_current_live_mark(&mut self, current_live_mark: bool) {
        self.current_live_mark = current_live_mark;
    }

    /// Set the recyclable blocks for the `NormalAllocator`.
    pub fn set_recyclable_blocks(&mut self, blocks: RingBuf<*mut BlockInfo>) {
        self.allocator.set_recyclable_blocks(blocks);
    }

    /// Extend the list of free blocks in the `EvacAllocator` for evacuation.
    pub fn extend_evac_headroom(&mut self, blocks: RingBuf<*mut BlockInfo>) {
        self.evac_allocator.extend_evac_headroom(blocks);
    }

    /// Get all block managed by all allocators, draining any local
    /// collections.
    pub fn get_all_blocks(&mut self) -> RingBuf<*mut BlockInfo> {
        let mut normal_blocks = self.allocator.get_all_blocks();
        let mut overflow_blocks = self.overflow_allocator.get_all_blocks();
        let mut evac_blocks = self.evac_allocator.get_all_blocks();
        return normal_blocks.drain()
                            .chain(overflow_blocks.drain())
                            .chain(evac_blocks.drain())
                            .collect();
    }

    /// Allocate an object of `size` bytes or return `None` if the allocation
    /// failed.
    ///
    /// This object is initialized and ready to use.
    pub fn allocate(&mut self, rtti: *const GCRTTI) -> Option<GCObjectRef> {
        let size = unsafe{ (*rtti).object_size() };
        debug!("Request to allocate an object of size {}", size);
        if let Some(object) = if size < MEDIUM_OBJECT { self.allocator.allocate(size) }
                              else { self.overflow_allocator.allocate(size) } {
            unsafe { ptr::write(object, GCObject::new(rtti, self.current_live_mark)); }
            unsafe{ (*ImmixSpace::get_block_ptr(object)).set_new_object(object); }
            self.set_gc_object(object);
            return Some(object);
        }
        return None;
    }

    /// Evacuate the object to another block using the `EvacAllocator`
    /// returning the new address or `None` if no evacuation was performed.
    ///
    /// An object is evacuated if it is not pinned, it resides on an
    /// evacuation candidate block and the evacuation allocator hat enough
    /// space left.
    ///
    /// On successful evacuation the old object is marked as forewarded an an
    /// forewarding pointer is installed.
    pub fn maybe_evacuate(&mut self, object: GCObjectRef) -> Option<GCObjectRef> {
        let block_info = unsafe{ ImmixSpace::get_block_ptr(object) };
        let is_pinned = unsafe{ (*object).is_pinned() };
        let is_candidate = unsafe{ (*block_info).is_evacuation_candidate() };
        if is_pinned || !is_candidate {
            return None;
        }
        let size = unsafe{ (*object).object_size() };
        if let Some(new_object) = self.evac_allocator.allocate(size) {
            unsafe{
                ptr::copy_nonoverlapping_memory(new_object as *mut u8,
                                                object as *const u8, size);
                debug_assert!(*object == *new_object,
                              "Evacuated object was not copied correcty");
                (*object).set_forwarded(new_object);
                self.set_gc_object(object);
            }
            debug!("Evacuated object {:p} from block {:p} to {:p}", object,
                   block_info, new_object);
            valgrind_freelike!(object);
            return Some(new_object);
        }
        debug!("Can't evacuation object {:p} from block {:p}", object, block_info);
        return None;
    }
}

impl ImmixSpace {
    /// Get the block for the given object.
    unsafe fn get_block_ptr(object: GCObjectRef) -> *mut BlockInfo {
        let block_offset = object as usize % BLOCK_SIZE;
        let block = mem::transmute((object as *mut u8).offset(-(block_offset as isize)));
        debug!("Block for object {:p}: {:p} with offset: {}", object, block, block_offset);
        return block;
    }
}
<|endoftext|>


<commit_before>// Chapter 1
// ------------------ print macros start ------------------
// Macros: Macros look like functions, except that their name ends with a bang !,
// but instead of generating a function call, macros are expanded into source code
// that gets compiled with the rest of the program. eg. println!
// println! -> prints statement to console in a new line

// !!!--- start uncommenting from line below ---!!!
// fn main() {
//     println!("April has {} days.", 31);
//     println!("{month} has {numberOfDays} days.",
//         month = "January",
//         numberOfDays = "31");
//     // Special formating is used here
//     // {:b} -> converts 2 to 10 (binary form)
//     println!("{} of {:b} people know binary, the other half doesn't", 1, 2);
//     let width = 6;
//     println!("{number:>width$} {spaces} spaces and {number}", number = 1, width = width, spaces = width - 1);
//     println!("{}", format!("{:05}", 12));

//     // rounds off the number
//     let pi = 3.141592;
//     println!("Pi is roughly {:.3}", pi);
// }
// !!!--- end uncommenting from line above ---!!!
// ------------------ print macros end ------------------

// Chapter 2
// ------------------ Debugging start ------------------
// In JS we use console.log() for logging and debugging purposes.
// console.log() can be used to print pretty any type of object.
// However, in Rust this is not possible as all types which want to use
// std::fmt formatting traits require an implementation to be printable.
// The fmt::Debug trait makes this very straightforward.
// All types can derive (automatically create) the fmt::Debug implementation.
// This is not true for fmt::Display which must be manually implemented.
// Below given example shows automatic derive(Debug) implementation
// and manual Display implementation.

// !!!--- start uncommenting from line below ---!!!
// Import (via `use`) the `fmt` module to make it available.
use std::fmt; // Import `fmt`
#[derive(Debug)]
struct Person<'a> {
    name: &'a str,
    age: u8
}

// Implement `Display` for `Person`.
impl<'a> fmt::Display for Person<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "(Name is {} and Age is {})", self.name, self.age)
    }
}
fn main() {
    let name = "Peter";
    let age = 27;
    let peter = Person { name, age };

    println!("Compare Person");
    println!("Debug: Normal {:?}", peter);
    println!("Debug: Pretty {:#?}", peter);
    println!("Display: {}", peter);
}
// !!!--- end uncommenting from line above ---!!!
// ------------------ Debugging end ------------------<commit_msg>use '?' operator<commit_after>// Chapter 1
// ------------------ print macros start ------------------
// Macros: Macros look like functions, except that their name ends with a bang !,
// but instead of generating a function call, macros are expanded into source code
// that gets compiled with the rest of the program. eg. println!
// println! -> prints statement to console in a new line

// !!!--- start uncommenting from line below ---!!!
// fn main() {
//     println!("April has {} days.", 31);
//     println!("{month} has {numberOfDays} days.",
//         month = "January",
//         numberOfDays = "31");
//     // Special formating is used here
//     // {:b} -> converts 2 to 10 (binary form)
//     println!("{} of {:b} people know binary, the other half doesn't", 1, 2);
//     let width = 6;
//     println!("{number:>width$} {spaces} spaces and {number}", number = 1, width = width, spaces = width - 1);
//     println!("{}", format!("{:05}", 12));

//     // rounds off the number
//     let pi = 3.141592;
//     println!("Pi is roughly {:.3}", pi);
// }
// !!!--- end uncommenting from line above ---!!!
// ------------------ print macros end ------------------

// Chapter 2
// ------------------ Debugging start ------------------
// In JS we use console.log() for logging and debugging purposes.
// console.log() can be used to print pretty any type of object.
// However, in Rust this is not possible as all types which want to use
// std::fmt formatting traits require an implementation to be printable.
// The fmt::Debug trait makes this very straightforward.
// All types can derive (automatically create) the fmt::Debug implementation.
// This is not true for fmt::Display which must be manually implemented.
// Below given example shows automatic derive(Debug) implementation
// and manual Display implementation.

// !!!--- start uncommenting from line below ---!!!
// Import (via `use`) the `fmt` module to make it available.
use std::fmt; // Import `fmt`
#[derive(Debug)]
struct Person<'a> {
    name: &'a str,
    age: u8
}

// Implement `Display` for `Person`.
impl<'a> fmt::Display for Person<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Try `write!` to see if it errors. If it errors, return
        // the error. Otherwise continue.
        write!(f, "(Name is {} and Age is {})", self.name, self.age)?;
        write!(f, "")
    }
}
fn main() {
    let name = "Peter";
    let age = 27;
    let peter = Person { name, age };

    println!("Compare Person");
    println!("Debug: Normal {:?}", peter);
    println!("Debug: Pretty {:#?}", peter);
    println!("Display: {}", peter);
}
// !!!--- end uncommenting from line above ---!!!
// ------------------ Debugging end ------------------<|endoftext|>


<commit_before>mod async_helpers;

use std::error::Error;
use std::io::Write;
use std::time::Instant;

use zeromq::{Socket, SocketRecv, SocketSend};

#[async_helpers::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // Socket to receive messages on
    let mut receiver = zeromq::PullSocket::new();
    receiver.bind("tcp://127.0.0.1:5558").await?;

    // Socket for worker control
    let mut controller = zeromq::PubSocket::new();
    controller.bind("tcp://127.0.0.1:5559").await?;

    receiver.recv().await?;

    let tstart = Instant::now();

    for task_nbr in 0..100u8 {
        receiver.recv().await?;
        if task_nbr % 10 == 0 {
            print!(":");
        } else {
            print!(".");
        }
        std::io::stdout().flush()?;
    }
    println!(
        "\nTotal elapsed time: {} msec",
        tstart.elapsed().as_micros()
    );

    // Send kill signal to workers
    controller.send("KILL".into()).await?;

    receiver.close().await;
    controller.close().await;
    Ok(())
}
<commit_msg>change display time to milliseconds<commit_after>mod async_helpers;

use std::error::Error;
use std::io::Write;
use std::time::Instant;

use zeromq::{Socket, SocketRecv, SocketSend};

#[async_helpers::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // Socket to receive messages on
    let mut receiver = zeromq::PullSocket::new();
    receiver.bind("tcp://127.0.0.1:5558").await?;

    // Socket for worker control
    let mut controller = zeromq::PubSocket::new();
    controller.bind("tcp://127.0.0.1:5559").await?;

    receiver.recv().await?;

    let tstart = Instant::now();

    for task_nbr in 0..100u8 {
        receiver.recv().await?;
        if task_nbr % 10 == 0 {
            print!(":");
        } else {
            print!(".");
        }
        std::io::stdout().flush()?;
    }
    println!(
        "\nTotal elapsed time: {} msec",
        tstart.elapsed().as_millis()
    );

    // Send kill signal to workers
    controller.send("KILL".into()).await?;

    receiver.close().await;
    controller.close().await;
    Ok(())
}
<|endoftext|>


<commit_before>extern crate nalgebra as na;

use rand::distributions::{Standard, Distribution};
use rand::{SeedableRng, rngs::StdRng};

use na::{Point2, Vector2};
use ncollide2d::shape::{ConvexPolygon, Cuboid, ShapeHandle};
use nphysics2d::object::{ColliderDesc, RigidBodyDesc, DefaultBodySet, DefaultColliderSet, Ground, BodyPartHandle};
use nphysics2d::force_generator::DefaultForceGeneratorSet;
use nphysics2d::joint::DefaultJointConstraintSet;
use nphysics2d::world::{DefaultMechanicalWorld, DefaultGeometricalWorld};
use nphysics_testbed2d::Testbed;

pub fn init_world(testbed: &mut Testbed) {
    /*
     * World
     */
    let mechanical_world = DefaultMechanicalWorld::new(Vector2::new(0.0, -9.81));
    let geometrical_world = DefaultGeometricalWorld::new();
    let mut bodies = DefaultBodySet::new();
    let mut colliders = DefaultColliderSet::new();
    let joint_constraints = DefaultJointConstraintSet::new();
    let force_generators = DefaultForceGeneratorSet::new();

    /*
     * Ground
     */
    let ground_size = 50.0;
    let ground_shape =
        ShapeHandle::new(Cuboid::new(Vector2::new(ground_size, 1.0)));

    let ground_handle = bodies.insert(Ground::new());
    let co = ColliderDesc::new(ground_shape)
        .translation(-Vector2::y())
        .build(BodyPartHandle(ground_handle, 0));
    colliders.insert(co);

    /*
     * Create the convex geometries.
     */
    let npts = 10usize;
    let num = 25;
    let shift = 0.4;
    let centerx = shift * (num as f32) / 2.0;
    let centery = shift;
    let mut rng = StdRng::seed_from_u64(0);
    let distribution = Standard;

    for i in 0usize..num {
        for j in 0usize..num {
            let x = i as f32 * shift - centerx;
            let y = j as f32 * shift + centery;

            let mut pts = Vec::with_capacity(npts);

            for _ in 0..npts {
                let pt: Point2<f32> = distribution.sample(&mut rng);
                pts.push(pt * 0.4);
            }

            // Build the rigid body.
            let rb = RigidBodyDesc::new()
                .translation(Vector2::new(x, y))
                .build();
            let rb_handle = bodies.insert(rb);

            // Build the collider.
            let geom = ShapeHandle::new(ConvexPolygon::try_from_points(&pts).unwrap());
            let co = ColliderDesc::new(geom)
                .ccd_enabled(true)
                .density(1.0)
                .build(BodyPartHandle(rb_handle, 0));
            colliders.insert(co);
        }
    }

    /*
     * Set up the testbed.
     */
    testbed.set_ground_handle(Some(ground_handle));
    testbed.set_world(mechanical_world, geometrical_world, bodies, colliders, joint_constraints, force_generators);
    testbed.look_at(Point2::new(0.0, -2.5), 95.0);
}

fn main() {
    let testbed = Testbed::from_builders(0, vec![
        ("Convex", init_world),
    ]);
    testbed.run()
}<commit_msg>Disable CCD on the convex2 demo.<commit_after>extern crate nalgebra as na;

use rand::distributions::{Standard, Distribution};
use rand::{SeedableRng, rngs::StdRng};

use na::{Point2, Vector2};
use ncollide2d::shape::{ConvexPolygon, Cuboid, ShapeHandle};
use nphysics2d::object::{ColliderDesc, RigidBodyDesc, DefaultBodySet, DefaultColliderSet, Ground, BodyPartHandle};
use nphysics2d::force_generator::DefaultForceGeneratorSet;
use nphysics2d::joint::DefaultJointConstraintSet;
use nphysics2d::world::{DefaultMechanicalWorld, DefaultGeometricalWorld};
use nphysics_testbed2d::Testbed;

pub fn init_world(testbed: &mut Testbed) {
    /*
     * World
     */
    let mechanical_world = DefaultMechanicalWorld::new(Vector2::new(0.0, -9.81));
    let geometrical_world = DefaultGeometricalWorld::new();
    let mut bodies = DefaultBodySet::new();
    let mut colliders = DefaultColliderSet::new();
    let joint_constraints = DefaultJointConstraintSet::new();
    let force_generators = DefaultForceGeneratorSet::new();

    /*
     * Ground
     */
    let ground_size = 50.0;
    let ground_shape =
        ShapeHandle::new(Cuboid::new(Vector2::new(ground_size, 1.0)));

    let ground_handle = bodies.insert(Ground::new());
    let co = ColliderDesc::new(ground_shape)
        .translation(-Vector2::y())
        .build(BodyPartHandle(ground_handle, 0));
    colliders.insert(co);

    /*
     * Create the convex geometries.
     */
    let npts = 10usize;
    let num = 25;
    let shift = 0.4;
    let centerx = shift * (num as f32) / 2.0;
    let centery = shift;
    let mut rng = StdRng::seed_from_u64(0);
    let distribution = Standard;

    for i in 0usize..num {
        for j in 0usize..num {
            let x = i as f32 * shift - centerx;
            let y = j as f32 * shift + centery;

            let mut pts = Vec::with_capacity(npts);

            for _ in 0..npts {
                let pt: Point2<f32> = distribution.sample(&mut rng);
                pts.push(pt * 0.4);
            }

            // Build the rigid body.
            let rb = RigidBodyDesc::new()
                .translation(Vector2::new(x, y))
                .build();
            let rb_handle = bodies.insert(rb);

            // Build the collider.
            let geom = ShapeHandle::new(ConvexPolygon::try_from_points(&pts).unwrap());
            let co = ColliderDesc::new(geom)
                .density(1.0)
                .build(BodyPartHandle(rb_handle, 0));
            colliders.insert(co);
        }
    }

    /*
     * Set up the testbed.
     */
    testbed.set_ground_handle(Some(ground_handle));
    testbed.set_world(mechanical_world, geometrical_world, bodies, colliders, joint_constraints, force_generators);
    testbed.look_at(Point2::new(0.0, -2.5), 95.0);
}

fn main() {
    let testbed = Testbed::from_builders(0, vec![
        ("Convex", init_world),
    ]);
    testbed.run()
}<|endoftext|>