<commit_before>extern crate cairo;

extern crate gtk;
use gtk::{Inhibit, ToolButtonExt, WidgetExt};

#[cfg(feature = "profiling-audio-draw")]
use chrono::Utc;

use std::boxed::Box;

use std::rc::Rc;
use std::cell::RefCell;

use std::sync::{Arc, Mutex};

use media::{Context, DoubleAudioBuffer, SampleExtractor, Timestamp};

use super::{BACKGROUND_COLOR, ControllerState, DoubleWaveformBuffer, MainController,
            WaveformConditions, WaveformBuffer};

const BUFFER_DURATION:   u64 = 60_000_000_000;    // 60 s
const MIN_REQ_DURATION:  f64 =      1_953_125f64; //  2 ms / 1000 px
const MAX_REQ_DURATION:  f64 = 32_000_000_000f64; // 32 s  / 1000 px
const INIT_REQ_DURATION: f64 =  4_000_000_000f64; //  4 s  / 1000 px
const STEP_REQ_DURATION: f64 =  2f64;

pub struct AudioController {
    container: gtk::Container,
    drawingarea: gtk::DrawingArea,
    zoom_in_btn: gtk::ToolButton,
    zoom_out_btn: gtk::ToolButton,

    is_active: bool,

    requested_duration: Rc<RefCell<f64>>,

    waveform_mtx: Arc<Mutex<Box<SampleExtractor>>>,
    dbl_buffer_mtx: Arc<Mutex<DoubleAudioBuffer>>,
}

impl AudioController {
    pub fn new(builder: &gtk::Builder) -> Self {
        let dbl_buffer_mtx = DoubleWaveformBuffer::new(BUFFER_DURATION);
        let waveform_mtx = dbl_buffer_mtx.lock()
            .expect("AudioController::new: couldn't lock dbl_buffer_mtx")
            .get_exposed_buffer_mtx();

        AudioController {
            container: builder.get_object("audio-container").unwrap(),
            drawingarea: builder.get_object("audio-drawingarea").unwrap(),
            zoom_in_btn: builder.get_object("audio_zoom_in-toolbutton").unwrap(),
            zoom_out_btn: builder.get_object("audio_zoom_out-toolbutton").unwrap(),

            is_active: false,
            requested_duration: Rc::new(RefCell::new(INIT_REQ_DURATION)),
            waveform_mtx: waveform_mtx,
            dbl_buffer_mtx: dbl_buffer_mtx,
        }
    }

    pub fn register_callbacks(&self, main_ctrl: &Rc<RefCell<MainController>>) {
        // draw
        let waveform_mtx = Arc::clone(&self.waveform_mtx);
        let requested_duration = Rc::clone(&self.requested_duration);
        self.drawingarea.connect_draw(move |drawing_area, cairo_ctx| {
            AudioController::draw(
                drawing_area,
                cairo_ctx,
                &waveform_mtx,
                *requested_duration.borrow(),
            ).into()
        });

        // widget size changed
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.drawingarea.connect_size_allocate(move |drawingarea, _| {
            AudioController::refresh(
                drawingarea,
                *requested_duration.borrow(),
                &dbl_buffer_mtx,
            );
        });

        // click in drawing_area
        let main_ctrl_rc = Rc::clone(&main_ctrl);
        let waveform_mtx = Arc::clone(&self.waveform_mtx);
        self.drawingarea.connect_button_press_event(move |_, event_button| {
            let button = event_button.get_button();
            if button == 1 {
                if let Some(position) = {
                    let waveform_buffer_grd = &mut *waveform_mtx.lock()
                        .expect("Couldn't lock waveform buffer in audio controller draw");
                    waveform_buffer_grd
                        .as_mut_any().downcast_mut::<WaveformBuffer>()
                        .expect("SamplesExtratctor is not a waveform buffer in audio controller draw")
                        .get_position(event_button.get_position().0)
                }
                {
                    main_ctrl_rc.borrow_mut().seek(position, true); // accurate (slow)
                }
            }
            Inhibit(true)
        });

        // click zoom in
        let drawingarea = self.drawingarea.clone();
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.zoom_in_btn.connect_clicked(move |_| {
            let (can_update, duration) = {
                let mut duration_grd = requested_duration.borrow_mut();
                *duration_grd /= STEP_REQ_DURATION;
                if *duration_grd >= MIN_REQ_DURATION {
                    (true, *duration_grd)
                } else {
                    *duration_grd = MIN_REQ_DURATION;
                    (false, *duration_grd)
                }
            };

            if can_update {
                AudioController::refresh(
                    &drawingarea,
                    duration,
                    &dbl_buffer_mtx,
                );
            }
        });

        // click zoom out
        let drawingarea = self.drawingarea.clone();
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.zoom_out_btn.connect_clicked(move |_| {
            let (can_update, duration) = {
                let mut duration_grd = requested_duration.borrow_mut();
                *duration_grd *= STEP_REQ_DURATION;
                if *duration_grd <= MAX_REQ_DURATION {
                    (true, *duration_grd)
                } else {
                    *duration_grd = MAX_REQ_DURATION;
                    (false, *duration_grd)
                }
            };

            if can_update {
                AudioController::refresh(
                    &drawingarea,
                    duration,
                    &dbl_buffer_mtx,
                );
            }
        });
    }

    pub fn cleanup(&mut self) {
        self.is_active = false;
        {
            self.dbl_buffer_mtx.lock()
                .expect("AudioController::cleanup: Couldn't lock dbl_buffer_mtx")
                .cleanup();
        }
        *self.requested_duration.borrow_mut() = INIT_REQ_DURATION;
        self.drawingarea.queue_draw();
    }

    pub fn get_dbl_buffer_mtx(&self) -> Arc<Mutex<DoubleAudioBuffer>> {
        Arc::clone(&self.dbl_buffer_mtx)
    }

    pub fn new_media(&mut self, context: &Context) {
        let has_audio = context.info.lock()
                .expect("Failed to lock media info while initializing audio controller")
                .audio_best
                .is_some();

        if has_audio {
            self.is_active = true;
            self.container.show();
        } else {
            self.container.hide();
        }
    }

    pub fn seek(&mut self, position: u64, state: &ControllerState) {
        {
            self.waveform_mtx.lock()
                .expect("AudioController::seek: Couldn't lock waveform_mtx")
                .as_mut_any().downcast_mut::<WaveformBuffer>()
                .expect("AudioController::seek: SamplesExtratctor is not a WaveformBuffer")
                .seek(position, *state == ControllerState::Playing);
        }

        if *state == ControllerState::Paused {
            // refresh the buffer in order to render the waveform
            // with samples that might not be rendered in current WaveformImage yet
            self.dbl_buffer_mtx.lock()
                .expect("AudioController::seek: couldn't lock dbl_buffer_mtx")
                .refresh();
        }
        self.tick();
    }

    pub fn tick(&self) {
        if self.is_active {
            self.drawingarea.queue_draw();
        }
    }

    fn clean_cairo_context(cr: &cairo::Context) {
        cr.set_source_rgb(
            BACKGROUND_COLOR.0,
            BACKGROUND_COLOR.1,
            BACKGROUND_COLOR.2
        );
        cr.paint();
    }

    fn draw(
        drawingarea: &gtk::DrawingArea,
        cr: &cairo::Context,
        waveform_mtx: &Arc<Mutex<Box<SampleExtractor>>>,
        requested_duration: f64,
    ) -> Inhibit {
        #[cfg(feature = "profiling-audio-draw")]
        let before_init = Utc::now();

        let allocation = drawingarea.get_allocation();
        if allocation.width.is_negative() {
            println!("negative allocation.width: {}", allocation.width);
            return Inhibit(true);
        }

        #[cfg(feature = "profiling-audio-draw")]
        let before_lock = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_cndt = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_image = Utc::now();

        let (first_pos, last_opt, cursor_opt) = {
            let waveform_grd = &mut *waveform_mtx.lock()
                .expect("AudioController::draw: couldn't lock waveform_mtx");
            let waveform_buffer = waveform_grd
                .as_mut_any().downcast_mut::<WaveformBuffer>()
                .expect("AudioController::draw: SamplesExtratctor is not a WaveformBuffer");

            #[cfg(feature = "profiling-audio-draw")]
            let _before_cndt = Utc::now();

            waveform_buffer.update_conditions(
                requested_duration,
                allocation.width,
                allocation.height
            );

            match waveform_buffer.get_image() {
                Some((image, x_offset, first_pos, last_opt, cursor_opt)) => {
                    #[cfg(feature = "profiling-audio-draw")]
                    let _before_image = Utc::now();

                    cr.set_source_surface(image, -x_offset, 0f64);
                    cr.paint();

                    (first_pos, last_opt, cursor_opt)
                },
                None => {
                    AudioController::clean_cairo_context(cr);
                    return Inhibit(true)
                },
            }
        };

        #[cfg(feature = "profiling-audio-draw")]
        let before_pos = Utc::now();

        let height = f64::from(allocation.height);
        let width = f64::from(allocation.width);
        cr.scale(1f64, 1f64);
        cr.set_source_rgb(1f64, 1f64, 0f64);
        cr.set_font_size(14f64);

        // first position
        let first_text = format!("{}", Timestamp::format(first_pos, false));
        let first_text_width = 2f64 + cr.text_extents(&first_text).width;
        cr.move_to(2f64, 30f64);
        cr.show_text(&first_text);

        // last position
        if let Some((last_x, last_pos)) = last_opt {
            let last_text = format!("{}", Timestamp::format(last_pos, false));
            // align to a 5px multiple box in order to avoid flickering
            let last_text_width =
                (cr.text_extents(&last_text).width / 5f64).ceil() * 5f64;
            if last_x + last_text_width > first_text_width + 10f64 {
                // last text won't overlap with first text
                cr.move_to(last_x - last_text_width, 30f64);
                cr.show_text(&last_text);
            }
        }

        if let Some((current_x, current_pos)) = cursor_opt {
            // draw current pos
            let cursor_text = format!("{}", Timestamp::format(current_pos, true));
            let cursor_text_width = 5f64 + cr.text_extents(&cursor_text).width;
            let cursor_text_x =
                if current_x + cursor_text_width < width {
                    current_x + 5f64
                } else {
                    current_x - cursor_text_width
                };
            cr.move_to(cursor_text_x, 15f64);
            cr.show_text(&cursor_text);

            cr.set_line_width(1f64);
            cr.move_to(current_x, 0f64);
            cr.line_to(current_x, height);
            cr.stroke();
        }

        #[cfg(feature = "profiling-audio-draw")]
        let end = Utc::now();

        #[cfg(feature = "profiling-audio-draw")]
        println!("audio-draw,{},{},{},{},{},{}",
            before_init.time().format("%H:%M:%S%.6f"),
            before_lock.time().format("%H:%M:%S%.6f"),
            _before_cndt.time().format("%H:%M:%S%.6f"),
            _before_image.time().format("%H:%M:%S%.6f"),
            before_pos.time().format("%H:%M:%S%.6f"),
            end.time().format("%H:%M:%S%.6f"),
        );

       Inhibit(true)
    }

    fn refresh(
        drawingarea: &gtk::DrawingArea,
        requested_duration: f64,
        dbl_buffer_mtx: &Arc<Mutex<DoubleAudioBuffer>>,
    ) {
        let allocation = drawingarea.get_allocation();
        {
            // refresh the buffer in order to render the waveform
            // in latest conditions
            dbl_buffer_mtx.lock()
                .expect("AudioController::size-allocate: couldn't lock dbl_buffer_mtx")
                .refresh_with_conditions(
                    Box::new(WaveformConditions::new(
                        requested_duration,
                        allocation.width,
                        allocation.height
                    ))
                );
        }

        drawingarea.queue_draw();
    }
}
<commit_msg>Last timestamp: get a bigger box to handle larger digits variations (#52)<commit_after>extern crate cairo;

extern crate gtk;
use gtk::{Inhibit, ToolButtonExt, WidgetExt};

#[cfg(feature = "profiling-audio-draw")]
use chrono::Utc;

use std::boxed::Box;

use std::rc::Rc;
use std::cell::RefCell;

use std::sync::{Arc, Mutex};

use media::{Context, DoubleAudioBuffer, SampleExtractor, Timestamp};

use super::{BACKGROUND_COLOR, ControllerState, DoubleWaveformBuffer, MainController,
            WaveformConditions, WaveformBuffer};

const BUFFER_DURATION:   u64 = 60_000_000_000;    // 60 s
const MIN_REQ_DURATION:  f64 =      1_953_125f64; //  2 ms / 1000 px
const MAX_REQ_DURATION:  f64 = 32_000_000_000f64; // 32 s  / 1000 px
const INIT_REQ_DURATION: f64 =  4_000_000_000f64; //  4 s  / 1000 px
const STEP_REQ_DURATION: f64 =  2f64;

pub struct AudioController {
    container: gtk::Container,
    drawingarea: gtk::DrawingArea,
    zoom_in_btn: gtk::ToolButton,
    zoom_out_btn: gtk::ToolButton,

    is_active: bool,

    requested_duration: Rc<RefCell<f64>>,

    waveform_mtx: Arc<Mutex<Box<SampleExtractor>>>,
    dbl_buffer_mtx: Arc<Mutex<DoubleAudioBuffer>>,
}

impl AudioController {
    pub fn new(builder: &gtk::Builder) -> Self {
        let dbl_buffer_mtx = DoubleWaveformBuffer::new(BUFFER_DURATION);
        let waveform_mtx = dbl_buffer_mtx.lock()
            .expect("AudioController::new: couldn't lock dbl_buffer_mtx")
            .get_exposed_buffer_mtx();

        AudioController {
            container: builder.get_object("audio-container").unwrap(),
            drawingarea: builder.get_object("audio-drawingarea").unwrap(),
            zoom_in_btn: builder.get_object("audio_zoom_in-toolbutton").unwrap(),
            zoom_out_btn: builder.get_object("audio_zoom_out-toolbutton").unwrap(),

            is_active: false,
            requested_duration: Rc::new(RefCell::new(INIT_REQ_DURATION)),
            waveform_mtx: waveform_mtx,
            dbl_buffer_mtx: dbl_buffer_mtx,
        }
    }

    pub fn register_callbacks(&self, main_ctrl: &Rc<RefCell<MainController>>) {
        // draw
        let waveform_mtx = Arc::clone(&self.waveform_mtx);
        let requested_duration = Rc::clone(&self.requested_duration);
        self.drawingarea.connect_draw(move |drawing_area, cairo_ctx| {
            AudioController::draw(
                drawing_area,
                cairo_ctx,
                &waveform_mtx,
                *requested_duration.borrow(),
            ).into()
        });

        // widget size changed
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.drawingarea.connect_size_allocate(move |drawingarea, _| {
            AudioController::refresh(
                drawingarea,
                *requested_duration.borrow(),
                &dbl_buffer_mtx,
            );
        });

        // click in drawing_area
        let main_ctrl_rc = Rc::clone(&main_ctrl);
        let waveform_mtx = Arc::clone(&self.waveform_mtx);
        self.drawingarea.connect_button_press_event(move |_, event_button| {
            let button = event_button.get_button();
            if button == 1 {
                if let Some(position) = {
                    let waveform_buffer_grd = &mut *waveform_mtx.lock()
                        .expect("Couldn't lock waveform buffer in audio controller draw");
                    waveform_buffer_grd
                        .as_mut_any().downcast_mut::<WaveformBuffer>()
                        .expect("SamplesExtratctor is not a waveform buffer in audio controller draw")
                        .get_position(event_button.get_position().0)
                }
                {
                    main_ctrl_rc.borrow_mut().seek(position, true); // accurate (slow)
                }
            }
            Inhibit(true)
        });

        // click zoom in
        let drawingarea = self.drawingarea.clone();
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.zoom_in_btn.connect_clicked(move |_| {
            let (can_update, duration) = {
                let mut duration_grd = requested_duration.borrow_mut();
                *duration_grd /= STEP_REQ_DURATION;
                if *duration_grd >= MIN_REQ_DURATION {
                    (true, *duration_grd)
                } else {
                    *duration_grd = MIN_REQ_DURATION;
                    (false, *duration_grd)
                }
            };

            if can_update {
                AudioController::refresh(
                    &drawingarea,
                    duration,
                    &dbl_buffer_mtx,
                );
            }
        });

        // click zoom out
        let drawingarea = self.drawingarea.clone();
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.zoom_out_btn.connect_clicked(move |_| {
            let (can_update, duration) = {
                let mut duration_grd = requested_duration.borrow_mut();
                *duration_grd *= STEP_REQ_DURATION;
                if *duration_grd <= MAX_REQ_DURATION {
                    (true, *duration_grd)
                } else {
                    *duration_grd = MAX_REQ_DURATION;
                    (false, *duration_grd)
                }
            };

            if can_update {
                AudioController::refresh(
                    &drawingarea,
                    duration,
                    &dbl_buffer_mtx,
                );
            }
        });
    }

    pub fn cleanup(&mut self) {
        self.is_active = false;
        {
            self.dbl_buffer_mtx.lock()
                .expect("AudioController::cleanup: Couldn't lock dbl_buffer_mtx")
                .cleanup();
        }
        *self.requested_duration.borrow_mut() = INIT_REQ_DURATION;
        self.drawingarea.queue_draw();
    }

    pub fn get_dbl_buffer_mtx(&self) -> Arc<Mutex<DoubleAudioBuffer>> {
        Arc::clone(&self.dbl_buffer_mtx)
    }

    pub fn new_media(&mut self, context: &Context) {
        let has_audio = context.info.lock()
                .expect("Failed to lock media info while initializing audio controller")
                .audio_best
                .is_some();

        if has_audio {
            self.is_active = true;
            self.container.show();
        } else {
            self.container.hide();
        }
    }

    pub fn seek(&mut self, position: u64, state: &ControllerState) {
        {
            self.waveform_mtx.lock()
                .expect("AudioController::seek: Couldn't lock waveform_mtx")
                .as_mut_any().downcast_mut::<WaveformBuffer>()
                .expect("AudioController::seek: SamplesExtratctor is not a WaveformBuffer")
                .seek(position, *state == ControllerState::Playing);
        }

        if *state == ControllerState::Paused {
            // refresh the buffer in order to render the waveform
            // with samples that might not be rendered in current WaveformImage yet
            self.dbl_buffer_mtx.lock()
                .expect("AudioController::seek: couldn't lock dbl_buffer_mtx")
                .refresh();
        }
        self.tick();
    }

    pub fn tick(&self) {
        if self.is_active {
            self.drawingarea.queue_draw();
        }
    }

    fn clean_cairo_context(cr: &cairo::Context) {
        cr.set_source_rgb(
            BACKGROUND_COLOR.0,
            BACKGROUND_COLOR.1,
            BACKGROUND_COLOR.2
        );
        cr.paint();
    }

    fn draw(
        drawingarea: &gtk::DrawingArea,
        cr: &cairo::Context,
        waveform_mtx: &Arc<Mutex<Box<SampleExtractor>>>,
        requested_duration: f64,
    ) -> Inhibit {
        #[cfg(feature = "profiling-audio-draw")]
        let before_init = Utc::now();

        let allocation = drawingarea.get_allocation();
        if allocation.width.is_negative() {
            println!("negative allocation.width: {}", allocation.width);
            return Inhibit(true);
        }

        #[cfg(feature = "profiling-audio-draw")]
        let before_lock = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_cndt = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_image = Utc::now();

        let (first_pos, last_opt, cursor_opt) = {
            let waveform_grd = &mut *waveform_mtx.lock()
                .expect("AudioController::draw: couldn't lock waveform_mtx");
            let waveform_buffer = waveform_grd
                .as_mut_any().downcast_mut::<WaveformBuffer>()
                .expect("AudioController::draw: SamplesExtratctor is not a WaveformBuffer");

            #[cfg(feature = "profiling-audio-draw")]
            let _before_cndt = Utc::now();

            waveform_buffer.update_conditions(
                requested_duration,
                allocation.width,
                allocation.height
            );

            match waveform_buffer.get_image() {
                Some((image, x_offset, first_pos, last_opt, cursor_opt)) => {
                    #[cfg(feature = "profiling-audio-draw")]
                    let _before_image = Utc::now();

                    cr.set_source_surface(image, -x_offset, 0f64);
                    cr.paint();

                    (first_pos, last_opt, cursor_opt)
                },
                None => {
                    AudioController::clean_cairo_context(cr);
                    return Inhibit(true)
                },
            }
        };

        #[cfg(feature = "profiling-audio-draw")]
        let before_pos = Utc::now();

        let height = f64::from(allocation.height);
        let width = f64::from(allocation.width);
        cr.scale(1f64, 1f64);
        cr.set_source_rgb(1f64, 1f64, 0f64);
        cr.set_font_size(14f64);

        // first position
        let first_text = format!("{}", Timestamp::format(first_pos, false));
        let first_text_width = 2f64 + cr.text_extents(&first_text).width;
        cr.move_to(2f64, 30f64);
        cr.show_text(&first_text);

        // last position
        if let Some((last_x, last_pos)) = last_opt {
            let last_text = format!("{}", Timestamp::format(last_pos, false));
            let last_text_width = cr.text_extents(&last_text).width;
            if last_x + last_text_width > first_text_width + 10f64 {
                // last text won't overlap with first text
                // align actual width to a 15px multiple box in order to avoid
                // variations due to actual size of individual digits
                let last_text_width = (last_text_width / 15f64).ceil() * 15f64;
                cr.move_to(last_x - last_text_width, 30f64);
                cr.show_text(&last_text);
            }
        }

        if let Some((current_x, current_pos)) = cursor_opt {
            // draw current pos
            let cursor_text = format!("{}", Timestamp::format(current_pos, true));
            let cursor_text_width = 5f64 + cr.text_extents(&cursor_text).width;
            let cursor_text_x =
                if current_x + cursor_text_width < width {
                    current_x + 5f64
                } else {
                    current_x - cursor_text_width
                };
            cr.move_to(cursor_text_x, 15f64);
            cr.show_text(&cursor_text);

            cr.set_line_width(1f64);
            cr.move_to(current_x, 0f64);
            cr.line_to(current_x, height);
            cr.stroke();
        }

        #[cfg(feature = "profiling-audio-draw")]
        let end = Utc::now();

        #[cfg(feature = "profiling-audio-draw")]
        println!("audio-draw,{},{},{},{},{},{}",
            before_init.time().format("%H:%M:%S%.6f"),
            before_lock.time().format("%H:%M:%S%.6f"),
            _before_cndt.time().format("%H:%M:%S%.6f"),
            _before_image.time().format("%H:%M:%S%.6f"),
            before_pos.time().format("%H:%M:%S%.6f"),
            end.time().format("%H:%M:%S%.6f"),
        );

       Inhibit(true)
    }

    fn refresh(
        drawingarea: &gtk::DrawingArea,
        requested_duration: f64,
        dbl_buffer_mtx: &Arc<Mutex<DoubleAudioBuffer>>,
    ) {
        let allocation = drawingarea.get_allocation();
        {
            // refresh the buffer in order to render the waveform
            // in latest conditions
            dbl_buffer_mtx.lock()
                .expect("AudioController::size-allocate: couldn't lock dbl_buffer_mtx")
                .refresh_with_conditions(
                    Box::new(WaveformConditions::new(
                        requested_duration,
                        allocation.width,
                        allocation.height
                    ))
                );
        }

        drawingarea.queue_draw();
    }
}
<|endoftext|>


<commit_before>//! Provides a queue of block indices that the sampler positions can be initialized
//! from for the worker threads. The queue itself is not changed after creation
//! we simply work through it with an atomic counter to track the index of the next
//! block to work on

use std::vec::Vec;
use std::sync::atomic::{AtomicUint, Ordering};
use sampler::morton;

/// The queue of blocks to be worked on shared immutably between worker threads.
pub struct BlockQueue {
    /// The block indices of blocks to work on for the image
    blocks: Vec<(u32, u32)>,
    /// Get the dimensions of an individual block
    dimensions: (u32, u32),
    /// Index of the next block to be worked on
    next: AtomicUint,
}

/// Iterator to work through the queue safely
pub struct BlockQueueIterator<'a> {
    queue: &'a BlockQueue,
}

impl BlockQueue {
    /// Create a block queue for the image with dimensions `img`.
    /// Panics if the image is not evenly broken into blocks of dimension `dim`
    pub fn new(img: (u32, u32), dim: (u32, u32)) -> BlockQueue {
        if img.0 % dim.0 != 0 || img.1 % dim.1 != 0 {
            panic!("Image with dimension {:?} not evenly divided by blocks of {:?}", img, dim);
        }
        let num_blocks = (img.0 / dim.0, img.1 / dim.1);
        // TODO: the .. operator precedence is very low so we need this paren here at the moment
        // once (hopefully) it's raised we can remove the parens
        let mut blocks: Vec<(u32, u32)> = (0..num_blocks.0 * num_blocks.1)
            .map(|i| (i % num_blocks.0, i / num_blocks.0)).collect();
        blocks.sort_by(|a, b| morton::morton2(a).cmp(&morton::morton2(b)));
        BlockQueue { blocks: blocks, dimensions: dim, next: AtomicUint::new(0) }
    }
    /// Get the dimensions of an individual block in the queue
    pub fn block_dim(&self) -> (u32, u32) { self.dimensions }
    /// Get an iterator to work through the queue
    pub fn iter(&self) -> BlockQueueIterator { BlockQueueIterator { queue: self } }
    /// Get the next block in the queue or None if the queue is finished
    fn next(&self) -> Option<(u32, u32)> {
        let i = self.next.fetch_add(1, Ordering::AcqRel);
        if i >= self.blocks.len() {
            None
        } else {
            Some(self.blocks[i])
        }
    }
    /// Get the length of the queue
    pub fn len(&self) -> usize { self.blocks.len() }
}

impl<'a> Iterator for BlockQueueIterator<'a> {
    type Item = (u32, u32);
    fn next(&mut self) -> Option<(u32, u32)> {
        self.queue.next()
    }
}

<commit_msg>Migrate to AtomicUsize which is replacing AtomicUint<commit_after>//! Provides a queue of block indices that the sampler positions can be initialized
//! from for the worker threads. The queue itself is not changed after creation
//! we simply work through it with an atomic counter to track the index of the next
//! block to work on

use std::vec::Vec;
use std::sync::atomic::{AtomicUsize, Ordering};
use sampler::morton;

/// The queue of blocks to be worked on shared immutably between worker threads.
pub struct BlockQueue {
    /// The block indices of blocks to work on for the image
    blocks: Vec<(u32, u32)>,
    /// Get the dimensions of an individual block
    dimensions: (u32, u32),
    /// Index of the next block to be worked on
    next: AtomicUsize,
}

/// Iterator to work through the queue safely
pub struct BlockQueueIterator<'a> {
    queue: &'a BlockQueue,
}

impl BlockQueue {
    /// Create a block queue for the image with dimensions `img`.
    /// Panics if the image is not evenly broken into blocks of dimension `dim`
    pub fn new(img: (u32, u32), dim: (u32, u32)) -> BlockQueue {
        if img.0 % dim.0 != 0 || img.1 % dim.1 != 0 {
            panic!("Image with dimension {:?} not evenly divided by blocks of {:?}", img, dim);
        }
        let num_blocks = (img.0 / dim.0, img.1 / dim.1);
        // TODO: the .. operator precedence is very low so we need this paren here at the moment
        // once (hopefully) it's raised we can remove the parens
        let mut blocks: Vec<(u32, u32)> = (0..num_blocks.0 * num_blocks.1)
            .map(|i| (i % num_blocks.0, i / num_blocks.0)).collect();
        blocks.sort_by(|a, b| morton::morton2(a).cmp(&morton::morton2(b)));
        BlockQueue { blocks: blocks, dimensions: dim, next: AtomicUsize::new(0) }
    }
    /// Get the dimensions of an individual block in the queue
    pub fn block_dim(&self) -> (u32, u32) { self.dimensions }
    /// Get an iterator to work through the queue
    pub fn iter(&self) -> BlockQueueIterator { BlockQueueIterator { queue: self } }
    /// Get the next block in the queue or None if the queue is finished
    fn next(&self) -> Option<(u32, u32)> {
        let i = self.next.fetch_add(1, Ordering::AcqRel);
        if i >= self.blocks.len() {
            None
        } else {
            Some(self.blocks[i])
        }
    }
    /// Get the length of the queue
    pub fn len(&self) -> usize { self.blocks.len() }
}

impl<'a> Iterator for BlockQueueIterator<'a> {
    type Item = (u32, u32);
    fn next(&mut self) -> Option<(u32, u32)> {
        self.queue.next()
    }
}

<|endoftext|>


<commit_before>use germanwhist::{self, Action, ActionError, GameEvent,
                  Round, PlayerState, ScoringRules};

use ndarray::prelude::*;
use rand::{Rng, thread_rng};

pub enum LearningModelError {
    MismatchedSize
}

/// Reinforcement Learning traits and implementations
pub trait LearningModel {
    //
    fn evaluate_q(&self, ArrayView<f32, Ix1>) -> f32;

    // Compute q, and the gradient.
    fn evaluate_q_grad(&self, p: &ArrayView<f32, Ix1>, grad: ArrayViewMut<f32, Ix1>) -> f32;

    // input size
    fn input_size(&self) -> usize;

    // num parameters
    fn num_parameters(&self) -> usize;

}

pub struct SarsaLambdaParameters {
    lambda: f32,
    gamma: f32,
    eps: f32
}

pub struct SarsaPlayer {
    state: PlayerState,
    e_trace: Array<f32, Ix1>,
    last_q: f32
}

impl SarsaPlayer {
    fn new(state: PlayerState, model_size: usize) -> SarsaPlayer {
        let e_trace = Array::zeros(model_size);

        SarsaPlayer { state,  e_trace, last_q: 0.0 }
    }

    /// Choose an epsilon-greedy action.
    fn epsilon_greedy_action<M: LearningModel>(&self, model: &M, eps: f32,
                                               actions: &[Action], mut sa: ArrayViewMut<f32, Ix1>) -> Action {
        /// epsilon-greedy state-choosing
        let mut rng = thread_rng();
        let r = rng.next_f32();

        // choose a random action with probability epsilon
        if r < eps {
            let random_action = rng.choose(actions).expect("must have positive number of actions");
            self.state.state_action_vector(sa.view_mut(), true, Some(random_action));
            *random_action
        } else {
            self.state.state_action_vector(sa.view_mut(), true, None);
            self.greedy_action(model, actions, sa)
        }
    }

    /// Fill in the state action input `sa` most beneficial action of those provided in the
    /// current state, returning the Q-value at that state.
    fn greedy_action<M: LearningModel>(&self, model: &M, actions: &[Action], mut sa: ArrayViewMut<f32, Ix1>) -> Action {
        let (max_action, _) = actions.iter().zip(actions.iter().map(|a| {
            self.state.state_action_vector(sa.view_mut(), false, Some(a));
            model.evaluate_q(sa.view())
        })).max_by(|a, b| a.1.partial_cmp(&b.1).unwrap()).expect("action list should not be empty");

        self.state.state_action_vector(sa, false, Some(max_action));
        *max_action
    }

}

pub struct SarsaLambda<M: LearningModel> {
    players: [SarsaPlayer; 2],
    model: M,
    engine: germanwhist::Round,
    param: SarsaLambdaParameters
}

impl<M: LearningModel> SarsaLambda<M> {
    pub fn new(rules: ScoringRules, model: M, param: SarsaLambdaParameters) -> Result<SarsaLambda<M>, LearningModelError> {
        let players = [SarsaPlayer::new(PlayerState::new(0), model.num_parameters()),
                       SarsaPlayer::new(PlayerState::new(1), model.num_parameters())];

        if players[0].state.state_vector_size() != model.input_size() {
            return Err(LearningModelError::MismatchedSize);
        }

        Ok(SarsaLambda { players,
                         engine: Round::new(rules),
                         model,
                         param })
    }

    pub fn train_on_episode(&mut self) -> Result<(), ActionError> {
        // start a new round
        let ev = self.engine.start_round(None);
        for i in 0..2 {
            self.players[i].e_trace.fill(0.0);
            for e in &ev[i] {
                self.players[i].state.on_event(e)
            }
        }

        let mut player_action = Array::zeros(self.players[0].state.state_vector_size());
        let mut grad = Array::zeros(self.players[0].state.state_vector_size());

        // Evaluate the episode, tracking and updating the trace for each player
        while !self.engine.is_game_over() {
            // Get the next action based on whose turn it is.
            let active = self.engine.active_player();

            // choose the epsilon-greedy action for that player.
            let possible_actions = self.engine.possible_actions();
            let chosen_action = self.players[active].epsilon_greedy_action(
                &self.model, self.param.eps, &possible_actions, player_action.view_mut());

            // evaluate the gradient for the state-action pair
            let q_predict = self.model.evaluate_q_grad(&player_action.view(), grad.view_mut());

            // update the model from the previous turn
            {
                let player = &self.players[active];
                self.model.update_weights(self.param.gamma * q_predict - player.last_q, player.e_trace);
            }

            // update the eligibility trace
            {
                let player = &mut self.players[active];
                player.e_trace *= self.param.lambda * self.param.gamma;
                player.e_trace += &grad;
                player.last_q = q_predict;
            }

            // play the chosen action
            let evs = self.engine.play_action(chosen_action)?;

            for i in 0..2 {
                for ev in &evs[i] {
                    self.players[i].state.on_event(ev);
                }
            }
        }

        // Once the game is over, perform the final update based on the game result.
        let winner = self.engine.winner().expect("must be a winner at game-over phase.");
        let loser = 1 - winner;
        self.model.update_weights(1.0 - self.players[winner].last_q, self.players[winner].e_trace);
        self.model.update_weights(0.0 - self.players[loser].last_q, self.players[loser].e_trace);
        Ok(())
    }
}
<commit_msg>LearningModel: Add update_weights<commit_after>use germanwhist::{self, Action, ActionError, GameEvent,
                  Round, PlayerState, ScoringRules};

use ndarray::prelude::*;
use ndarray::Data;
use rand::{Rng, thread_rng};

pub enum LearningModelError {
    MismatchedSize
}

/// Reinforcement Learning traits and implementations
pub trait LearningModel {
    // Evaluate the gradient
    fn evaluate_q(&self, ArrayView<f32, Ix1>) -> f32;

    // Compute q, and the gradient.
    fn evaluate_q_grad(&self, p: &ArrayView<f32, Ix1>, grad: ArrayViewMut<f32, Ix1>) -> f32;

    // input size
    fn input_size(&self) -> usize;

    // num parameters
    fn num_parameters(&self) -> usize;

    // Update the weights of the model
    fn update_weights<T: Data<Elem=f32>>(&mut self, error: f32, dir: &ArrayBase<T, Ix1>);

}

pub struct SarsaLambdaParameters {
    lambda: f32,
    gamma: f32,
    eps: f32
}

pub struct SarsaPlayer {
    state: PlayerState,
    e_trace: Array<f32, Ix1>,
    last_q: f32
}

impl SarsaPlayer {
    fn new(state: PlayerState, model_size: usize) -> SarsaPlayer {
        let e_trace = Array::zeros(model_size);

        SarsaPlayer { state,  e_trace, last_q: 0.0 }
    }

    /// Choose an epsilon-greedy action.
    fn epsilon_greedy_action<M: LearningModel>(&self, model: &M, eps: f32,
                                               actions: &[Action], mut sa: ArrayViewMut<f32, Ix1>) -> Action {
        /// epsilon-greedy state-choosing
        let mut rng = thread_rng();
        let r = rng.next_f32();

        // choose a random action with probability epsilon
        if r < eps {
            let random_action = rng.choose(actions).expect("must have positive number of actions");
            self.state.state_action_vector(sa.view_mut(), true, Some(random_action));
            *random_action
        } else {
            self.state.state_action_vector(sa.view_mut(), true, None);
            self.greedy_action(model, actions, sa)
        }
    }

    /// Fill in the state action input `sa` most beneficial action of those provided in the
    /// current state, returning the Q-value at that state.
    fn greedy_action<M: LearningModel>(&self, model: &M, actions: &[Action], mut sa: ArrayViewMut<f32, Ix1>) -> Action {
        let (max_action, _) = actions.iter().zip(actions.iter().map(|a| {
            self.state.state_action_vector(sa.view_mut(), false, Some(a));
            model.evaluate_q(sa.view())
        })).max_by(|a, b| a.1.partial_cmp(&b.1).unwrap()).expect("action list should not be empty");

        self.state.state_action_vector(sa, false, Some(max_action));
        *max_action
    }

}

pub struct SarsaLambda<M: LearningModel> {
    players: [SarsaPlayer; 2],
    model: M,
    engine: germanwhist::Round,
    param: SarsaLambdaParameters
}

impl<M: LearningModel> SarsaLambda<M> {
    pub fn new(rules: ScoringRules, model: M, param: SarsaLambdaParameters) -> Result<SarsaLambda<M>, LearningModelError> {
        let players = [SarsaPlayer::new(PlayerState::new(0), model.num_parameters()),
                       SarsaPlayer::new(PlayerState::new(1), model.num_parameters())];

        if players[0].state.state_vector_size() != model.input_size() {
            return Err(LearningModelError::MismatchedSize);
        }

        Ok(SarsaLambda { players,
                         engine: Round::new(rules),
                         model,
                         param })
    }

    pub fn train_on_episode(&mut self) -> Result<(), ActionError> {
        // start a new round
        let ev = self.engine.start_round(None);
        for i in 0..2 {
            self.players[i].e_trace.fill(0.0);
            for e in &ev[i] {
                self.players[i].state.on_event(e)
            }
        }

        let mut player_action = Array::zeros(self.players[0].state.state_vector_size());
        let mut grad = Array::zeros(self.players[0].state.state_vector_size());

        // Evaluate the episode, tracking and updating the trace for each player
        while !self.engine.is_game_over() {
            // Get the next action based on whose turn it is.
            let active = self.engine.active_player();

            // choose the epsilon-greedy action for that player.
            let possible_actions = self.engine.possible_actions();
            let chosen_action = self.players[active].epsilon_greedy_action(
                &self.model, self.param.eps, &possible_actions, player_action.view_mut());

            // evaluate the gradient for the state-action pair
            let q_predict = self.model.evaluate_q_grad(&player_action.view(), grad.view_mut());

            // update the model from the previous turn
            {
                let player = &self.players[active];
                self.model.update_weights(self.param.gamma * q_predict - player.last_q, &player.e_trace);
            }

            // update the eligibility trace
            {
                let player = &mut self.players[active];
                player.e_trace *= self.param.lambda * self.param.gamma;
                player.e_trace += &grad;
                player.last_q = q_predict;
            }

            // play the chosen action
            let evs = self.engine.play_action(chosen_action)?;

            for i in 0..2 {
                for ev in &evs[i] {
                    self.players[i].state.on_event(ev);
                }
            }
        }

        // Once the game is over, perform the final update based on the game result.
        let winner = self.engine.winner().expect("must be a winner at game-over phase.");
        let loser = 1 - winner;
        self.model.update_weights(1.0 - self.players[winner].last_q, &self.players[winner].e_trace);
        self.model.update_weights(0.0 - self.players[loser].last_q, &self.players[loser].e_trace);
        Ok(())
    }
}
<|endoftext|>


<commit_before>use font::{CSSFontWeight, SpecifiedFontStyle, UsedFontStyle};
use native::FontHandle;

use dvec::DVec;
use send_map::{linear, SendMap};

#[cfg(target_os = "macos")]
type FontListHandle/& = quartz::font_list::QuartzFontListHandle;

#[cfg(target_os = "linux")]
type FontListHandle/& = fontconfig::font_list::FontconfigFontListHandle;

pub impl FontListHandle {
    #[cfg(target_os = "macos")]
    static pub fn new(fctx: &native::FontContextHandle) -> Result<FontListHandle, ()> {
        Ok(quartz::font_list::QuartzFontListHandle::new(fctx))
    }

    #[cfg(target_os = "linux")]
    static pub fn new(fctx: &native::FontContextHandle) -> Result<FontListHandle, ()> {
        Ok(fontconfig::font_list::FontconfigFontListHandle::new(fctx))
    }
}

type FontFamilyMap = linear::LinearMap<~str, @FontFamily>;

pub struct FontList {
    mut family_map: FontFamilyMap,
    mut handle: FontListHandle,
}

pub impl FontList {
    static fn new(fctx: &native::FontContextHandle) -> FontList {
        let handle = result::unwrap(FontListHandle::new(fctx));
        let list = FontList {
            handle: move handle,
            family_map: linear::LinearMap(),
        };
        list.refresh(fctx);
        return move list;
    }

    priv fn refresh(fctx: &native::FontContextHandle) {
        // TODO(Issue #186): don't refresh unless something actually
        // changed.  Does OSX have a notification for this event?
        do util::time::time("gfx::font_list: regenerating available font families and faces") {
            self.family_map = self.handle.get_available_families(fctx);
        }
    }

    fn find_font_in_family(family_name: &str,
                           style: &SpecifiedFontStyle) -> Option<@FontEntry> {
        let family = self.find_family(family_name);
        let mut result : Option<@FontEntry> = None;

        // TODO(Issue #192: handle generic font families, like 'serif' and 'sans-serif'.

        // if such family exists, try to match style to a font
        do family.iter |fam| {
            result = fam.find_font_for_style(style);
        }

        let decision = if result.is_some() { "Found" } else { "Couldn't find" };
        debug!("FontList: %s font face in family[%?] matching style: %?", decision, style, family_name);

        return result;
    }

    priv fn find_family(family_name: &str) -> Option<@FontFamily> {
        // look up canonical name
        let family = self.family_map.find(&str::from_slice(family_name));

        let decision = if family.is_some() { "Found" } else { "Couldn't find" };
        debug!("FontList: %s font family with name=%s", decision, family_name);

        // TODO(Issue #188): look up localized font family names if canonical name not found
        return family;
    }
}

// Holds a specific font family, and the various
pub struct FontFamily {
    family_name: @str,
    entries: DVec<@FontEntry>,
}

pub impl FontFamily {
    static fn new(family_name: &str) -> FontFamily {
        FontFamily {
            family_name: str::from_slice(family_name).to_managed(),
            entries: DVec(),
        }
    }

    pure fn find_font_for_style(style: &SpecifiedFontStyle) -> Option<@FontEntry> {
        assert self.entries.len() > 0;

        // TODO(Issue #189): optimize lookup for
        // regular/bold/italic/bolditalic with fixed offsets and a
        // static decision table for fallback between these values.

        // TODO(Issue #190): if not in the fast path above, do
        // expensive matching of weights, etc.
        for self.entries.each |entry| {
            if (style.weight.is_bold() == entry.is_bold()) &&
               (style.italic == entry.is_italic()) {

                return Some(*entry);
            }
        }

        return None;
    }
}

// This struct summarizes an available font's features. In the future,
// this will include fiddly settings such as special font table handling.

// In the common case, each FontFamily will have a singleton FontEntry, or
// it will have the standard four faces: Normal, Bold, Italic, BoldItalic.
struct FontEntry {
    family: @FontFamily,
    face_name: ~str,
    priv weight: CSSFontWeight,
    priv italic: bool,
    handle: FontHandle,
    // TODO: array of OpenType features, etc.
}

impl FontEntry {
    static fn new(family: @FontFamily, handle: FontHandle) -> FontEntry {
        FontEntry {
            family: family,
            face_name: handle.face_name(),
            weight: handle.boldness(),
            italic: handle.is_italic(),
            handle: move handle
        }
    }

    pure fn is_bold() -> bool {
        self.weight.is_bold()
    }

    pure fn is_italic() -> bool { self.italic }
}
<commit_msg>Fix crash in debug logging of FontList. Closes #204.<commit_after>use font::{CSSFontWeight, SpecifiedFontStyle, UsedFontStyle};
use native::FontHandle;

use dvec::DVec;
use send_map::{linear, SendMap};

#[cfg(target_os = "macos")]
type FontListHandle/& = quartz::font_list::QuartzFontListHandle;

#[cfg(target_os = "linux")]
type FontListHandle/& = fontconfig::font_list::FontconfigFontListHandle;

pub impl FontListHandle {
    #[cfg(target_os = "macos")]
    static pub fn new(fctx: &native::FontContextHandle) -> Result<FontListHandle, ()> {
        Ok(quartz::font_list::QuartzFontListHandle::new(fctx))
    }

    #[cfg(target_os = "linux")]
    static pub fn new(fctx: &native::FontContextHandle) -> Result<FontListHandle, ()> {
        Ok(fontconfig::font_list::FontconfigFontListHandle::new(fctx))
    }
}

type FontFamilyMap = linear::LinearMap<~str, @FontFamily>;

pub struct FontList {
    mut family_map: FontFamilyMap,
    mut handle: FontListHandle,
}

pub impl FontList {
    static fn new(fctx: &native::FontContextHandle) -> FontList {
        let handle = result::unwrap(FontListHandle::new(fctx));
        let list = FontList {
            handle: move handle,
            family_map: linear::LinearMap(),
        };
        list.refresh(fctx);
        return move list;
    }

    priv fn refresh(fctx: &native::FontContextHandle) {
        // TODO(Issue #186): don't refresh unless something actually
        // changed.  Does OSX have a notification for this event?
        do util::time::time("gfx::font_list: regenerating available font families and faces") {
            self.family_map = self.handle.get_available_families(fctx);
        }
    }

    fn find_font_in_family(family_name: &str,
                           style: &SpecifiedFontStyle) -> Option<@FontEntry> {
        let family = self.find_family(family_name);
        let mut result : Option<@FontEntry> = None;

        // TODO(Issue #192: handle generic font families, like 'serif' and 'sans-serif'.

        // if such family exists, try to match style to a font
        do family.iter |fam| {
            result = fam.find_font_for_style(style);
        }

        let decision = if result.is_some() { "Found" } else { "Couldn't find" };
        debug!("FontList: %s font face in family[%s] matching style", decision, family_name);

        return result;
    }

    priv fn find_family(family_name: &str) -> Option<@FontFamily> {
        // look up canonical name
        let family = self.family_map.find(&str::from_slice(family_name));

        let decision = if family.is_some() { "Found" } else { "Couldn't find" };
        debug!("FontList: %s font family with name=%s", decision, family_name);

        // TODO(Issue #188): look up localized font family names if canonical name not found
        return family;
    }
}

// Holds a specific font family, and the various
pub struct FontFamily {
    family_name: @str,
    entries: DVec<@FontEntry>,
}

pub impl FontFamily {
    static fn new(family_name: &str) -> FontFamily {
        FontFamily {
            family_name: str::from_slice(family_name).to_managed(),
            entries: DVec(),
        }
    }

    pure fn find_font_for_style(style: &SpecifiedFontStyle) -> Option<@FontEntry> {
        assert self.entries.len() > 0;

        // TODO(Issue #189): optimize lookup for
        // regular/bold/italic/bolditalic with fixed offsets and a
        // static decision table for fallback between these values.

        // TODO(Issue #190): if not in the fast path above, do
        // expensive matching of weights, etc.
        for self.entries.each |entry| {
            if (style.weight.is_bold() == entry.is_bold()) &&
               (style.italic == entry.is_italic()) {

                return Some(*entry);
            }
        }

        return None;
    }
}

// This struct summarizes an available font's features. In the future,
// this will include fiddly settings such as special font table handling.

// In the common case, each FontFamily will have a singleton FontEntry, or
// it will have the standard four faces: Normal, Bold, Italic, BoldItalic.
struct FontEntry {
    family: @FontFamily,
    face_name: ~str,
    priv weight: CSSFontWeight,
    priv italic: bool,
    handle: FontHandle,
    // TODO: array of OpenType features, etc.
}

impl FontEntry {
    static fn new(family: @FontFamily, handle: FontHandle) -> FontEntry {
        FontEntry {
            family: family,
            face_name: handle.face_name(),
            weight: handle.boldness(),
            italic: handle.is_italic(),
            handle: move handle
        }
    }

    pure fn is_bold() -> bool {
        self.weight.is_bold()
    }

    pure fn is_italic() -> bool { self.italic }
}
<|endoftext|>


<commit_before>use au = gfx::geometry;
use core::dlist::DList;
use core::dvec::DVec;
use css::values::{BoxAuto, BoxLength, Px};
use dl = gfx::display_list;
use dom::node::Node;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use gfx::geometry::au;
use layout::box::{RenderBox, ImageBox, TextBox, GenericBox, UnscannedTextBox};
use layout::context::LayoutContext;
use layout::flow::{FlowContext, InlineFlow};
use layout::text::TextBoxData;
use num::Num;
use servo_text::text_run::TextRun;
use servo_text::util::*;
use std::arc;
use util::tree;

/*
Lineboxes are represented as offsets into the child list, rather than
as an object that "owns" boxes. Choosing a different set of line
breaks requires a new list of offsets, and possibly some splitting and
merging of TextBoxes.

A similar list will keep track of the mapping between CSS boxes and
the corresponding render boxes in the inline flow.

After line breaks are determined, render boxes in the inline flow may
overlap visually. For example, in the case of nested inline CSS boxes,
outer inlines must be at least as large as the inner inlines, for
purposes of drawing noninherited things like backgrounds, borders,
outlines.

N.B. roc has an alternative design where the list instead consists of
things like "start outer box, text, start inner box, text, end inner
box, text, end outer box, text". This seems a little complicated to
serve as the starting point, but the current design doesn't make it
hard to try out that alternative.
*/

type BoxRange = {start: u8, len: u8};
type NodeRange = {node: Node, span: BoxRange};

// stack-allocated object for scanning an inline flow into
// TextRun-containing TextBoxes.
struct TextRunScanner {
    mut in_clump: bool,
    mut clump_start: uint,
    mut clump_end: uint,
    flow: @FlowContext,
}

fn TextRunScanner(flow: @FlowContext) -> TextRunScanner {
    TextRunScanner {
        in_clump: false,
        clump_start: 0,
        clump_end: 0,
        flow: flow,
    }
}

impl TextRunScanner {
    fn scan_for_runs(ctx: &LayoutContext) {
        // if reused, must be reset.
        assert !self.in_clump;
        assert self.flow.inline().boxes.len() > 0;

        do self.flow.inline().boxes.swap |in_boxes| {
            debug!("TextRunScanner: scanning %u boxes for text runs...", in_boxes.len());

            let temp_boxes = DVec();
            let mut prev_box: @RenderBox = in_boxes[0];

            for uint::range(0, in_boxes.len()) |i| {
                debug!("TextRunScanner: considering box: %?", in_boxes[i].debug_str());

                let can_coalesce_with_prev = i > 0 && boxes_can_be_coalesced(prev_box, in_boxes[i]);

                match (self.in_clump, can_coalesce_with_prev) {
                    // start a new clump
                    (false, _)    => { self.reset_clump_to_index(i); },
                    // extend clump
                    (true, true)  => { self.clump_end = i; },
                    // boundary detected; flush and start new clump
                    (true, false) => {
                        self.flush_clump_to_list(ctx, in_boxes, &temp_boxes);
                        self.reset_clump_to_index(i);
                    }
                };

                prev_box = in_boxes[i];
            }
            // handle remaining clumps
            if self.in_clump {
                self.flush_clump_to_list(ctx, in_boxes, &temp_boxes);
            }

            debug!("TextRunScanner: swapping out boxes.");
            // swap out old and new box list of flow, by supplying
            // temp boxes as return value to boxes.swap |...|
            dvec::unwrap(temp_boxes)
        }

        // helper functions
        pure fn boxes_can_be_coalesced(a: @RenderBox, b: @RenderBox) -> bool {
            assert !core::box::ptr_eq(a, b);

            match (a, b) {
                // TODO: check whether text styles, fonts are the same.
                (@UnscannedTextBox(*), @UnscannedTextBox(*)) => a.can_merge_with_box(b),
                (_, _) => false
            }
        }
    }

    fn reset_clump_to_index(i: uint) {
        debug!("TextRunScanner: resetting clump to %u", i);

        self.clump_start = i;
        self.clump_end = i;
        self.in_clump = true;
    }

    // a 'clump' is a range of inline flow leaves that can be merged
    // together into a single RenderBox. Adjacent text with the same
    // style can be merged, and nothing else can.
    //
    // the flow keeps track of the RenderBoxes contained by all
    // non-leaf DOM nodes. This is necessary for correct painting
    // order. Since we compress several leaf RenderBoxes here, the
    // mapping must be adjusted.
    fn flush_clump_to_list(ctx: &LayoutContext,
                           in_boxes: &[@RenderBox], out_boxes: &DVec<@RenderBox>) {
        assert self.in_clump;

        debug!("TextRunScanner: flushing boxes when start=%?,end=%?", self.clump_start, self.clump_end);

        let is_singleton = (self.clump_start == self.clump_end);
        let is_text_clump = match in_boxes[self.clump_start] {
            @UnscannedTextBox(*) => true,
            _ => false
        };

        do self.flow.inline().elems.borrow |_node_map: &[NodeRange]| {
        // TODO: repair the mapping of DOM elements to boxes if it changed.
        // (the mapping does not yet exist; see Issue #103)
        match (is_singleton, is_text_clump) {
            (false, false) => fail ~"WAT: can't coalesce non-text boxes in flush_clump_to_list()!",
            (true, false) => {
                debug!("TextRunScanner: pushing single non-text box when start=%u,end=%u",
                       self.clump_start, self.clump_end);
                out_boxes.push(in_boxes[self.clump_start]);
            },
            (true, true)  => {
                let text = in_boxes[self.clump_start].raw_text();
                // TODO: use actual CSS 'white-space' property of relevant style.
                let compression = CompressWhitespaceNewline;
                let transformed_text = transform_text(text, compression);
                // TODO: use actual font for corresponding DOM node to create text run.
                let run = @TextRun(&*ctx.font_cache.get_test_font(), move transformed_text);
                let box_guts = TextBoxData(run, 0, run.text.len());
                debug!("TextRunScanner: pushing single text box when start=%u,end=%u", self.clump_start, self.clump_end);
                out_boxes.push(@TextBox(copy *in_boxes[self.clump_start].d(), box_guts));
            },
            (false, true) => {
                let mut run_str : ~str = ~"";
                // TODO: is using ropes to construct the merged text any faster?
                do uint::range(self.clump_start, self.clump_end+1) |i| {
                    run_str = str::append(copy run_str, in_boxes[i].raw_text()); true
                }
                // TODO: use actual font for corresponding DOM node to create text run.
                let run = @TextRun(&*ctx.font_cache.get_test_font(), move run_str);
                let box_guts = TextBoxData(run, 0, run.text.len());
                debug!("TextRunScanner: pushing box(es) when start=%?,end=%?", self.clump_start, self.clump_end);
                out_boxes.push(@TextBox(copy *in_boxes[self.clump_start].d(), box_guts));
            }
        }
        self.in_clump = false;
        }
    }
}

struct InlineFlowData {
    // A vec of all inline render boxes. Several boxes may
    // correspond to one Node/Element.
    boxes: DVec<@RenderBox>,
    // vec of ranges into boxes that represents line positions.
    // these ranges are disjoint, and are the result of inline layout.
    lines: DVec<BoxRange>,
    // vec of ranges into boxes that represent elements. These
    // ranges must be disjoint or well-nested, and are only related to
    // the content of boxes (not lines)
    elems: DVec<NodeRange>
}

fn InlineFlowData() -> InlineFlowData {
    InlineFlowData {
        boxes: DVec(),
        lines: DVec(),
        elems: DVec(),
    }
}

trait InlineLayout {
    pure fn starts_inline_flow() -> bool;

    fn bubble_widths_inline(@self, ctx: &LayoutContext);
    fn assign_widths_inline(@self, ctx: &LayoutContext);
    fn assign_height_inline(@self, ctx: &LayoutContext);
    fn build_display_list_inline(@self, a: &dl::DisplayListBuilder, b: &Rect<au>, c: &Point2D<au>, d: &dl::DisplayList);
}

impl FlowContext : InlineLayout {
    pure fn starts_inline_flow() -> bool { match self { InlineFlow(*) => true, _ => false } }

    fn bubble_widths_inline(@self, ctx: &LayoutContext) {
        assert self.starts_inline_flow();

        debug!("box count: %u", self.inline().boxes.len());

        let scanner = TextRunScanner(self);
        scanner.scan_for_runs(ctx);

        let mut min_width = au(0);
        let mut pref_width = au(0);

        debug!("/box count: %u", self.inline().boxes.len());

        for self.inline().boxes.each |box| {
            debug!("measuring box: %s", box.debug_str());
            min_width = au::max(min_width, box.get_min_width(ctx));
            pref_width = au::max(pref_width, box.get_pref_width(ctx));
        }

        self.d().min_width = min_width;
        self.d().pref_width = pref_width;
    }

    /* Recursively (top-down) determines the actual width of child
    contexts and boxes. When called on this context, the context has
    had its width set by the parent context. */
    fn assign_widths_inline(@self, ctx: &LayoutContext) {
        assert self.starts_inline_flow();

        /* Perform inline flow with the available width. */
        //let avail_width = self.d().position.size.width;

        let line_height = au::from_px(20);
        //let mut cur_x = au(0);
        let mut cur_y = au(0);

        // TODO: remove test font uses
        let test_font = ctx.font_cache.get_test_font();

        for self.inline().boxes.each |box| {
            /* TODO: actually do inline flow.
            - Create a working linebox, and successively put boxes
            into it, splitting if necessary.

            - Set width and height for each positioned element based on
            where its chunks ended up.

            - Save the dvec of this context's lineboxes. */

            box.d().position.size.width = match *box {
                @ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).width),
                @TextBox(_,d) => {
                    // TODO: measure twice, cut once doesn't apply to text. Shouldn't need
                    // to measure text again here (should be inside TextBox.split)
                    let metrics = test_font.measure_text(d.run, d.offset, d.length);
                    metrics.advance
                },
                // TODO: this should be set to the extents of its children
                @GenericBox(*) => au(0),
                _ => fail fmt!("Tried to assign width to unknown Box variant: %?", box)
            };

            box.d().position.size.height = match *box {
                @ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).height),
                // TODO: we should use the bounding box of the actual text, i think?
                @TextBox(*) => test_font.metrics.em_size,
                // TODO: this should be set to the extents of its children
                @GenericBox(*) => au(0),
                _ => fail fmt!("Tried to assign width to unknown Box variant: %?", box)
            };

            box.d().position.origin = Point2D(au(0), cur_y);
            cur_y = cur_y.add(&au::max(line_height, box.d().position.size.height));
        } // for boxes.each |box|

    self.d().position.size.height = cur_y;

    /* There are no child contexts, so stop here. */

    // TODO: once there are 'inline-block' elements, this won't be
    // true.  In that case, perform inline flow, and then set the
    // block flow context's width as the width of the
    // 'inline-block' box that created this flow.
    }

    fn assign_height_inline(@self, _ctx: &LayoutContext) {
        // Don't need to set box or ctx heights, since that is done
        // during inline flowing.
    }

    fn build_display_list_inline(@self, builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
                                 offset: &Point2D<au>, list: &dl::DisplayList) {

        assert self.starts_inline_flow();

        // TODO: if the CSS box introducing this inline context is *not* anonymous,
        // we need to draw it too, in a way similar to BlowFlowContext

        // TODO: once we form line boxes and have their cached bounds, we can be
        // smarter and not recurse on a line if nothing in it can intersect dirty
        debug!("building display list for %u inline boxes", self.inline().boxes.len());
        for self.inline().boxes.each |box| {
            box.build_display_list(builder, dirty, offset, list)
        }

        // TODO: should inline-block elements have flows as children
        // of the inline flow, or should the flow be nested inside the
        // box somehow? Maybe it's best to unify flows and boxes into
        // the same enum, so inline-block flows are normal
        // (indivisible) children in the inline flow child list.
    }

} // @FlowContext : InlineLayout
<commit_msg>Clarify the purpose of dvec params to TextRunScanner::flush_clump_to_list().<commit_after>use au = gfx::geometry;
use core::dlist::DList;
use core::dvec::DVec;
use css::values::{BoxAuto, BoxLength, Px};
use dl = gfx::display_list;
use dom::node::Node;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use gfx::geometry::au;
use layout::box::{RenderBox, ImageBox, TextBox, GenericBox, UnscannedTextBox};
use layout::context::LayoutContext;
use layout::flow::{FlowContext, InlineFlow};
use layout::text::TextBoxData;
use num::Num;
use servo_text::text_run::TextRun;
use servo_text::util::*;
use std::arc;
use util::tree;

/*
Lineboxes are represented as offsets into the child list, rather than
as an object that "owns" boxes. Choosing a different set of line
breaks requires a new list of offsets, and possibly some splitting and
merging of TextBoxes.

A similar list will keep track of the mapping between CSS boxes and
the corresponding render boxes in the inline flow.

After line breaks are determined, render boxes in the inline flow may
overlap visually. For example, in the case of nested inline CSS boxes,
outer inlines must be at least as large as the inner inlines, for
purposes of drawing noninherited things like backgrounds, borders,
outlines.

N.B. roc has an alternative design where the list instead consists of
things like "start outer box, text, start inner box, text, end inner
box, text, end outer box, text". This seems a little complicated to
serve as the starting point, but the current design doesn't make it
hard to try out that alternative.
*/

type BoxRange = {start: u8, len: u8};
type NodeRange = {node: Node, span: BoxRange};

// stack-allocated object for scanning an inline flow into
// TextRun-containing TextBoxes.
struct TextRunScanner {
    mut in_clump: bool,
    mut clump_start: uint,
    mut clump_end: uint,
    flow: @FlowContext,
}

fn TextRunScanner(flow: @FlowContext) -> TextRunScanner {
    TextRunScanner {
        in_clump: false,
        clump_start: 0,
        clump_end: 0,
        flow: flow,
    }
}

impl TextRunScanner {
    fn scan_for_runs(ctx: &LayoutContext) {
        // if reused, must be reset.
        assert !self.in_clump;
        assert self.flow.inline().boxes.len() > 0;

        do self.flow.inline().boxes.swap |in_boxes| {
            debug!("TextRunScanner: scanning %u boxes for text runs...", in_boxes.len());

            let out_boxes = DVec();
            let mut prev_box: @RenderBox = in_boxes[0];

            for uint::range(0, in_boxes.len()) |i| {
                debug!("TextRunScanner: considering box: %?", in_boxes[i].debug_str());

                let can_coalesce_with_prev = i > 0 && boxes_can_be_coalesced(prev_box, in_boxes[i]);

                match (self.in_clump, can_coalesce_with_prev) {
                    // start a new clump
                    (false, _)    => { self.reset_clump_to_index(i); },
                    // extend clump
                    (true, true)  => { self.clump_end = i; },
                    // boundary detected; flush and start new clump
                    (true, false) => {
                        self.flush_clump_to_list(ctx, in_boxes, &out_boxes);
                        self.reset_clump_to_index(i);
                    }
                };

                prev_box = in_boxes[i];
            }
            // handle remaining clumps
            if self.in_clump {
                self.flush_clump_to_list(ctx, in_boxes, &out_boxes);
            }

            debug!("TextRunScanner: swapping out boxes.");
            // swap out old and new box list of flow, by supplying
            // temp boxes as return value to boxes.swap |...|
            dvec::unwrap(out_boxes)
        }

        // helper functions
        pure fn boxes_can_be_coalesced(a: @RenderBox, b: @RenderBox) -> bool {
            assert !core::box::ptr_eq(a, b);

            match (a, b) {
                // TODO: check whether text styles, fonts are the same.
                (@UnscannedTextBox(*), @UnscannedTextBox(*)) => a.can_merge_with_box(b),
                (_, _) => false
            }
        }
    }

    fn reset_clump_to_index(i: uint) {
        debug!("TextRunScanner: resetting clump to %u", i);

        self.clump_start = i;
        self.clump_end = i;
        self.in_clump = true;
    }

    // a 'clump' is a range of inline flow leaves that can be merged
    // together into a single RenderBox. Adjacent text with the same
    // style can be merged, and nothing else can.
    //
    // the flow keeps track of the RenderBoxes contained by all
    // non-leaf DOM nodes. This is necessary for correct painting
    // order. Since we compress several leaf RenderBoxes here, the
    // mapping must be adjusted.
    //
    // N.B. in_boxes is passed by reference, since we cannot
    // recursively borrow or swap the flow's dvec of boxes. When all
    // boxes are appended, the caller swaps the flow's box list.
    fn flush_clump_to_list(ctx: &LayoutContext,
                           in_boxes: &[@RenderBox], out_boxes: &DVec<@RenderBox>) {
        assert self.in_clump;

        debug!("TextRunScanner: flushing boxes when start=%?,end=%?", self.clump_start, self.clump_end);

        let is_singleton = (self.clump_start == self.clump_end);
        let is_text_clump = match in_boxes[self.clump_start] {
            @UnscannedTextBox(*) => true,
            _ => false
        };

        do self.flow.inline().elems.borrow |_node_map: &[NodeRange]| {
        // TODO: repair the mapping of DOM elements to boxes if it changed.
        // (the mapping does not yet exist; see Issue #103)
        match (is_singleton, is_text_clump) {
            (false, false) => fail ~"WAT: can't coalesce non-text boxes in flush_clump_to_list()!",
            (true, false) => {
                debug!("TextRunScanner: pushing single non-text box when start=%u,end=%u",
                       self.clump_start, self.clump_end);
                out_boxes.push(in_boxes[self.clump_start]);
            },
            (true, true)  => {
                let text = in_boxes[self.clump_start].raw_text();
                // TODO: use actual CSS 'white-space' property of relevant style.
                let compression = CompressWhitespaceNewline;
                let transformed_text = transform_text(text, compression);
                // TODO: use actual font for corresponding DOM node to create text run.
                let run = @TextRun(&*ctx.font_cache.get_test_font(), move transformed_text);
                let box_guts = TextBoxData(run, 0, run.text.len());
                debug!("TextRunScanner: pushing single text box when start=%u,end=%u", self.clump_start, self.clump_end);
                out_boxes.push(@TextBox(copy *in_boxes[self.clump_start].d(), box_guts));
            },
            (false, true) => {
                let mut run_str : ~str = ~"";
                // TODO: is using ropes to construct the merged text any faster?
                do uint::range(self.clump_start, self.clump_end+1) |i| {
                    run_str = str::append(copy run_str, in_boxes[i].raw_text()); true
                }
                // TODO: use actual font for corresponding DOM node to create text run.
                let run = @TextRun(&*ctx.font_cache.get_test_font(), move run_str);
                let box_guts = TextBoxData(run, 0, run.text.len());
                debug!("TextRunScanner: pushing box(es) when start=%?,end=%?", self.clump_start, self.clump_end);
                out_boxes.push(@TextBox(copy *in_boxes[self.clump_start].d(), box_guts));
            }
        }
        self.in_clump = false;
        }
    }
}

struct InlineFlowData {
    // A vec of all inline render boxes. Several boxes may
    // correspond to one Node/Element.
    boxes: DVec<@RenderBox>,
    // vec of ranges into boxes that represents line positions.
    // these ranges are disjoint, and are the result of inline layout.
    lines: DVec<BoxRange>,
    // vec of ranges into boxes that represent elements. These
    // ranges must be disjoint or well-nested, and are only related to
    // the content of boxes (not lines)
    elems: DVec<NodeRange>
}

fn InlineFlowData() -> InlineFlowData {
    InlineFlowData {
        boxes: DVec(),
        lines: DVec(),
        elems: DVec(),
    }
}

trait InlineLayout {
    pure fn starts_inline_flow() -> bool;

    fn bubble_widths_inline(@self, ctx: &LayoutContext);
    fn assign_widths_inline(@self, ctx: &LayoutContext);
    fn assign_height_inline(@self, ctx: &LayoutContext);
    fn build_display_list_inline(@self, a: &dl::DisplayListBuilder, b: &Rect<au>, c: &Point2D<au>, d: &dl::DisplayList);
}

impl FlowContext : InlineLayout {
    pure fn starts_inline_flow() -> bool { match self { InlineFlow(*) => true, _ => false } }

    fn bubble_widths_inline(@self, ctx: &LayoutContext) {
        assert self.starts_inline_flow();

        debug!("box count: %u", self.inline().boxes.len());

        let scanner = TextRunScanner(self);
        scanner.scan_for_runs(ctx);

        let mut min_width = au(0);
        let mut pref_width = au(0);

        debug!("/box count: %u", self.inline().boxes.len());

        for self.inline().boxes.each |box| {
            debug!("measuring box: %s", box.debug_str());
            min_width = au::max(min_width, box.get_min_width(ctx));
            pref_width = au::max(pref_width, box.get_pref_width(ctx));
        }

        self.d().min_width = min_width;
        self.d().pref_width = pref_width;
    }

    /* Recursively (top-down) determines the actual width of child
    contexts and boxes. When called on this context, the context has
    had its width set by the parent context. */
    fn assign_widths_inline(@self, ctx: &LayoutContext) {
        assert self.starts_inline_flow();

        /* Perform inline flow with the available width. */
        //let avail_width = self.d().position.size.width;

        let line_height = au::from_px(20);
        //let mut cur_x = au(0);
        let mut cur_y = au(0);

        // TODO: remove test font uses
        let test_font = ctx.font_cache.get_test_font();

        for self.inline().boxes.each |box| {
            /* TODO: actually do inline flow.
            - Create a working linebox, and successively put boxes
            into it, splitting if necessary.

            - Set width and height for each positioned element based on
            where its chunks ended up.

            - Save the dvec of this context's lineboxes. */

            box.d().position.size.width = match *box {
                @ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).width),
                @TextBox(_,d) => {
                    // TODO: measure twice, cut once doesn't apply to text. Shouldn't need
                    // to measure text again here (should be inside TextBox.split)
                    let metrics = test_font.measure_text(d.run, d.offset, d.length);
                    metrics.advance
                },
                // TODO: this should be set to the extents of its children
                @GenericBox(*) => au(0),
                _ => fail fmt!("Tried to assign width to unknown Box variant: %?", box)
            };

            box.d().position.size.height = match *box {
                @ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).height),
                // TODO: we should use the bounding box of the actual text, i think?
                @TextBox(*) => test_font.metrics.em_size,
                // TODO: this should be set to the extents of its children
                @GenericBox(*) => au(0),
                _ => fail fmt!("Tried to assign width to unknown Box variant: %?", box)
            };

            box.d().position.origin = Point2D(au(0), cur_y);
            cur_y = cur_y.add(&au::max(line_height, box.d().position.size.height));
        } // for boxes.each |box|

    self.d().position.size.height = cur_y;

    /* There are no child contexts, so stop here. */

    // TODO: once there are 'inline-block' elements, this won't be
    // true.  In that case, perform inline flow, and then set the
    // block flow context's width as the width of the
    // 'inline-block' box that created this flow.
    }

    fn assign_height_inline(@self, _ctx: &LayoutContext) {
        // Don't need to set box or ctx heights, since that is done
        // during inline flowing.
    }

    fn build_display_list_inline(@self, builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
                                 offset: &Point2D<au>, list: &dl::DisplayList) {

        assert self.starts_inline_flow();

        // TODO: if the CSS box introducing this inline context is *not* anonymous,
        // we need to draw it too, in a way similar to BlowFlowContext

        // TODO: once we form line boxes and have their cached bounds, we can be
        // smarter and not recurse on a line if nothing in it can intersect dirty
        debug!("building display list for %u inline boxes", self.inline().boxes.len());
        for self.inline().boxes.each |box| {
            box.build_display_list(builder, dirty, offset, list)
        }

        // TODO: should inline-block elements have flows as children
        // of the inline flow, or should the flow be nested inside the
        // box somehow? Maybe it's best to unify flows and boxes into
        // the same enum, so inline-block flows are normal
        // (indivisible) children in the inline flow child list.
    }

} // @FlowContext : InlineLayout
<|endoftext|>


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

#![feature(rustc_attrs)]
#![allow(dead_code)]
#![allow(unused_imports)]

mod foo {
    extern crate core;
}

// Check that private crates can be used from outside their modules, albeit with warnings
use foo::core; //~ WARN extern crate `core` is private
//~^ WARN this was previously accepted by the compiler but is being phased out
use foo::core::cell; //~ WARN extern crate `core` is private
//~^ WARN this was previously accepted by the compiler but is being phased out

fn f() {
    foo::core::cell::Cell::new(0); //~ WARN extern crate `core` is private
    //~^ WARN this was previously accepted by the compiler but is being phased out

    use foo::*;
    mod core {} // Check that private crates are not glob imported
}

#[rustc_error]
fn main() {} //~ ERROR compilation successful
<commit_msg>Add regression test for #22146<commit_after>// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![feature(rustc_attrs)]
#![allow(dead_code)]
#![allow(unused_imports)]

mod foo {
    extern crate core;
}

// Check that private crates can be used from outside their modules, albeit with warnings
use foo::core; //~ WARN extern crate `core` is private
//~^ WARN this was previously accepted by the compiler but is being phased out
use foo::core::cell; //~ WARN extern crate `core` is private
//~^ WARN this was previously accepted by the compiler but is being phased out

fn f() {
    foo::core::cell::Cell::new(0); //~ WARN extern crate `core` is private
    //~^ WARN this was previously accepted by the compiler but is being phased out

    use foo::*;
    mod core {} // Check that private crates are not glob imported
}

mod bar {
    pub extern crate core;
}

mod baz {
    pub use bar::*;
    use self::core::cell; // Check that public extern crates are glob imported
}

#[rustc_error]
fn main() {} //~ ERROR compilation successful
<|endoftext|>


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

//! Lints in the Rust compiler.
//!
//! This currently only contains the definitions and implementations
//! of most of the lints that `rustc` supports directly, it does not
//! contain the infrastructure for defining/registering lints. That is
//! available in `rustc::lint` and `rustc_plugin` respectively.
//!
//! # Note
//!
//! This API is completely unstable and subject to change.

#![crate_name = "rustc_lint"]
#![unstable(feature = "rustc_private", issue = "27812")]
#![crate_type = "dylib"]
#![crate_type = "rlib"]
#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
      html_favicon_url = "https://doc.rust-lang.org/favicon.ico",
      html_root_url = "https://doc.rust-lang.org/nightly/")]

#![cfg_attr(test, feature(test))]
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(num_bits_bytes)]
#![feature(quote)]
#![feature(rustc_diagnostic_macros)]
#![feature(rustc_private)]
#![feature(slice_patterns)]
#![feature(staged_api)]
#![feature(str_char)]

#[macro_use]
extern crate syntax;
#[macro_use]
extern crate rustc;
#[macro_use]
extern crate log;
extern crate rustc_front;
extern crate rustc_back;

pub use rustc::lint as lint;
pub use rustc::middle as middle;
pub use rustc::session as session;
pub use rustc::util as util;

use session::Session;
use lint::LintId;
use lint::FutureIncompatibleInfo;

mod bad_style;
mod builtin;
mod types;
mod unused;

use bad_style::*;
use builtin::*;
use types::*;
use unused::*;

/// Tell the `LintStore` about all the built-in lints (the ones
/// defined in this crate and the ones defined in
/// `rustc::lint::builtin`).
pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
    macro_rules! add_builtin {
        ($sess:ident, $($name:ident),*,) => (
            {$(
                store.register_late_pass($sess, false, box $name);
                )*}
            )
    }

    macro_rules! add_early_builtin {
        ($sess:ident, $($name:ident),*,) => (
            {$(
                store.register_early_pass($sess, false, box $name);
                )*}
            )
    }

    macro_rules! add_builtin_with_new {
        ($sess:ident, $($name:ident),*,) => (
            {$(
                store.register_late_pass($sess, false, box $name::new());
                )*}
            )
    }

    macro_rules! add_lint_group {
        ($sess:ident, $name:expr, $($lint:ident),*) => (
            store.register_group($sess, false, $name, vec![$(LintId::of($lint)),*]);
            )
    }

    add_early_builtin!(sess,
                       UnusedParens,
                       );

    add_builtin!(sess,
                 HardwiredLints,
                 WhileTrue,
                 ImproperCTypes,
                 BoxPointers,
                 UnusedAttributes,
                 PathStatements,
                 UnusedResults,
                 NonCamelCaseTypes,
                 NonSnakeCase,
                 NonUpperCaseGlobals,
                 UnusedImportBraces,
                 NonShorthandFieldPatterns,
                 UnusedUnsafe,
                 UnsafeCode,
                 UnusedMut,
                 UnusedAllocation,
                 MissingCopyImplementations,
                 UnstableFeatures,
                 Deprecated,
                 UnconditionalRecursion,
                 InvalidNoMangleItems,
                 PluginAsLibrary,
                 DropWithReprExtern,
                 MutableTransmutes,
                 );

    add_builtin_with_new!(sess,
                          TypeLimits,
                          MissingDoc,
                          MissingDebugImplementations,
                          );

    add_lint_group!(sess, "bad_style",
                    NON_CAMEL_CASE_TYPES, NON_SNAKE_CASE, NON_UPPER_CASE_GLOBALS);

    add_lint_group!(sess, "unused",
                    UNUSED_IMPORTS, UNUSED_VARIABLES, UNUSED_ASSIGNMENTS, DEAD_CODE,
                    UNUSED_MUT, UNREACHABLE_CODE, UNUSED_MUST_USE,
                    UNUSED_UNSAFE, PATH_STATEMENTS, UNUSED_ATTRIBUTES);

    // Guidelines for creating a future incompatibility lint:
    //
    // - Create a lint defaulting to warn as normal, with ideally the same error
    //   message you would normally give
    // - Add a suitable reference, typically an RFC or tracking issue. Go ahead
    //   and include the full URL.
    // - Later, change lint to error
    // - Eventually, remove lint
    store.register_future_incompatible(sess, vec![
        FutureIncompatibleInfo {
            id: LintId::of(PRIVATE_IN_PUBLIC),
            reference: "the explanation for E0446 (`--explain E0446`)",
        },
        FutureIncompatibleInfo {
            id: LintId::of(INVALID_TYPE_PARAM_DEFAULT),
            reference: "PR 30742 <https://github.com/rust-lang/rust/pull/30724>",
        },
        FutureIncompatibleInfo {
            id: LintId::of(MATCH_OF_UNIT_VARIANT_VIA_PAREN_DOTDOT),
            reference: "RFC 218 <https://github.com/rust-lang/rfcs/blob/master/text/0218-empty-struct-with-braces.md>",
        },
        ]);

    // We have one lint pass defined specially
    store.register_late_pass(sess, false, box lint::GatherNodeLevels);

    // Insert temporary renamings for a one-time deprecation
    store.register_renamed("unknown_features", "unused_features");

    store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate");
    store.register_removed("negate_unsigned", "cast a signed value instead");
}
<commit_msg>Fix long line in URL<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Lints in the Rust compiler.
//!
//! This currently only contains the definitions and implementations
//! of most of the lints that `rustc` supports directly, it does not
//! contain the infrastructure for defining/registering lints. That is
//! available in `rustc::lint` and `rustc_plugin` respectively.
//!
//! # Note
//!
//! This API is completely unstable and subject to change.

#![crate_name = "rustc_lint"]
#![unstable(feature = "rustc_private", issue = "27812")]
#![crate_type = "dylib"]
#![crate_type = "rlib"]
#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
      html_favicon_url = "https://doc.rust-lang.org/favicon.ico",
      html_root_url = "https://doc.rust-lang.org/nightly/")]

#![cfg_attr(test, feature(test))]
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(num_bits_bytes)]
#![feature(quote)]
#![feature(rustc_diagnostic_macros)]
#![feature(rustc_private)]
#![feature(slice_patterns)]
#![feature(staged_api)]
#![feature(str_char)]

#[macro_use]
extern crate syntax;
#[macro_use]
extern crate rustc;
#[macro_use]
extern crate log;
extern crate rustc_front;
extern crate rustc_back;

pub use rustc::lint as lint;
pub use rustc::middle as middle;
pub use rustc::session as session;
pub use rustc::util as util;

use session::Session;
use lint::LintId;
use lint::FutureIncompatibleInfo;

mod bad_style;
mod builtin;
mod types;
mod unused;

use bad_style::*;
use builtin::*;
use types::*;
use unused::*;

/// Tell the `LintStore` about all the built-in lints (the ones
/// defined in this crate and the ones defined in
/// `rustc::lint::builtin`).
pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
    macro_rules! add_builtin {
        ($sess:ident, $($name:ident),*,) => (
            {$(
                store.register_late_pass($sess, false, box $name);
                )*}
            )
    }

    macro_rules! add_early_builtin {
        ($sess:ident, $($name:ident),*,) => (
            {$(
                store.register_early_pass($sess, false, box $name);
                )*}
            )
    }

    macro_rules! add_builtin_with_new {
        ($sess:ident, $($name:ident),*,) => (
            {$(
                store.register_late_pass($sess, false, box $name::new());
                )*}
            )
    }

    macro_rules! add_lint_group {
        ($sess:ident, $name:expr, $($lint:ident),*) => (
            store.register_group($sess, false, $name, vec![$(LintId::of($lint)),*]);
            )
    }

    add_early_builtin!(sess,
                       UnusedParens,
                       );

    add_builtin!(sess,
                 HardwiredLints,
                 WhileTrue,
                 ImproperCTypes,
                 BoxPointers,
                 UnusedAttributes,
                 PathStatements,
                 UnusedResults,
                 NonCamelCaseTypes,
                 NonSnakeCase,
                 NonUpperCaseGlobals,
                 UnusedImportBraces,
                 NonShorthandFieldPatterns,
                 UnusedUnsafe,
                 UnsafeCode,
                 UnusedMut,
                 UnusedAllocation,
                 MissingCopyImplementations,
                 UnstableFeatures,
                 Deprecated,
                 UnconditionalRecursion,
                 InvalidNoMangleItems,
                 PluginAsLibrary,
                 DropWithReprExtern,
                 MutableTransmutes,
                 );

    add_builtin_with_new!(sess,
                          TypeLimits,
                          MissingDoc,
                          MissingDebugImplementations,
                          );

    add_lint_group!(sess, "bad_style",
                    NON_CAMEL_CASE_TYPES, NON_SNAKE_CASE, NON_UPPER_CASE_GLOBALS);

    add_lint_group!(sess, "unused",
                    UNUSED_IMPORTS, UNUSED_VARIABLES, UNUSED_ASSIGNMENTS, DEAD_CODE,
                    UNUSED_MUT, UNREACHABLE_CODE, UNUSED_MUST_USE,
                    UNUSED_UNSAFE, PATH_STATEMENTS, UNUSED_ATTRIBUTES);

    // Guidelines for creating a future incompatibility lint:
    //
    // - Create a lint defaulting to warn as normal, with ideally the same error
    //   message you would normally give
    // - Add a suitable reference, typically an RFC or tracking issue. Go ahead
    //   and include the full URL.
    // - Later, change lint to error
    // - Eventually, remove lint
    store.register_future_incompatible(sess, vec![
        FutureIncompatibleInfo {
            id: LintId::of(PRIVATE_IN_PUBLIC),
            reference: "the explanation for E0446 (`--explain E0446`)",
        },
        FutureIncompatibleInfo {
            id: LintId::of(INVALID_TYPE_PARAM_DEFAULT),
            reference: "PR 30742 <https://github.com/rust-lang/rust/pull/30724>",
        },
        FutureIncompatibleInfo {
            id: LintId::of(MATCH_OF_UNIT_VARIANT_VIA_PAREN_DOTDOT),
            reference: "RFC 218 <https://github.com/rust-lang/rfcs/blob/\
                        master/text/0218-empty-struct-with-braces.md>",
        },
        ]);

    // We have one lint pass defined specially
    store.register_late_pass(sess, false, box lint::GatherNodeLevels);

    // Insert temporary renamings for a one-time deprecation
    store.register_renamed("unknown_features", "unused_features");

    store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate");
    store.register_removed("negate_unsigned", "cast a signed value instead");
}
<|endoftext|>