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

use WindowWrapper;
use blob;
use std::sync::mpsc::Receiver;
use webrender::api::*;
use wrench::Wrench;

pub struct RawtestHarness<'a> {
    wrench: &'a mut Wrench,
    rx: Receiver<()>,
    window: &'a mut WindowWrapper,
}

impl<'a> RawtestHarness<'a> {
    pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver<()>) -> Self {
        RawtestHarness {
            wrench,
            rx,
            window,
        }
    }

    pub fn run(mut self) {
        self.retained_blob_images_test();
        self.blob_update_test();
        self.tile_decomposition();
        self.save_restore();
    }

    fn render_and_get_pixels(&mut self, window_rect: DeviceUintRect) -> Vec<u8> {
        self.rx.recv().unwrap();
        self.wrench.render();
        self.wrench.renderer.read_pixels_rgba8(window_rect)
    }

    fn tile_decomposition(&mut self) {
        // This exposes a crash in tile decomposition
        let layout_size = LayoutSize::new(800., 800.);
        let mut resources = ResourceUpdates::new();

        let blob_img = self.wrench.api.generate_image_key();
        resources.add_image(
            blob_img,
            ImageDescriptor::new(151, 56, ImageFormat::BGRA8, true),
            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
            Some(128),
        );

        let root_background_color = Some(ColorF::new(1.0, 1.0, 1.0, 1.0));

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);

        let info = LayoutPrimitiveInfo::new(
            LayoutRect::new(LayoutPoint::new(448.899994, 74.0), LayoutSize::new(151.000031, 56.)));

        // setup some malicious image size parameters
        builder.push_image(
            &info,
            LayoutSize::new(151., 56.0),
            LayoutSize::new(151.0, 56.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            self.wrench.document_id,
            Epoch(0),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            resources,
        );
        self.wrench
            .api
            .generate_frame(self.wrench.document_id, None);

        self.rx.recv().unwrap();
        self.wrench.render();
    }

    fn retained_blob_images_test(&mut self) {
        let blob_img;
        let window_size = self.window.get_inner_size_pixels();
        let window_size = DeviceUintSize::new(window_size.0, window_size.1);

        let test_size = DeviceUintSize::new(400, 400);
        let document_id = self.wrench.document_id;

        let window_rect = DeviceUintRect::new(
            DeviceUintPoint::new(0, window_size.height - test_size.height),
            test_size,
        );
        let layout_size = LayoutSize::new(400., 400.);
        let mut resources = ResourceUpdates::new();
        {
            let api = &self.wrench.api;

            blob_img = api.generate_image_key();
            resources.add_image(
                blob_img,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
                ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                None,
            );
        }
        let root_background_color = Some(ColorF::new(1.0, 1.0, 1.0, 1.0));

        // draw the blob the first time
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
        let info = LayoutPrimitiveInfo::new(
            LayoutRect::new(LayoutPoint::new(0.0, 60.0), LayoutSize::new(200.0, 200.0)));

        builder.push_image(
            &info,
            LayoutSize::new(200.0, 200.0),
            LayoutSize::new(0.0, 0.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            document_id,
            Epoch(0),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            resources,
        );
        self.wrench.api.generate_frame(document_id, None);

        // draw the blob image a second time at a different location

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
        let info = LayoutPrimitiveInfo::new(
            LayoutRect::new(LayoutPoint::new(1.0, 60.0), LayoutSize::new(200.0, 200.0)));
        builder.push_image(
            &info,
            LayoutSize::new(200.0, 200.0),
            LayoutSize::new(0.0, 0.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            document_id,
            Epoch(1),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            ResourceUpdates::new(),
        );

        self.wrench.api.generate_frame(document_id, None);

        let pixels_first = self.render_and_get_pixels(window_rect);
        let pixels_second = self.render_and_get_pixels(window_rect);

        // use png;
        // png::save_flipped("out1.png", &pixels_first, window_rect.size);
        // png::save_flipped("out2.png", &pixels_second, window_rect.size);

        assert!(pixels_first != pixels_second);
    }

    fn blob_update_test(&mut self) {
        let blob_img;
        let window_size = self.window.get_inner_size_pixels();
        let window_size = DeviceUintSize::new(window_size.0, window_size.1);

        let test_size = DeviceUintSize::new(400, 400);
        let document_id = self.wrench.document_id;

        let window_rect = DeviceUintRect::new(
            DeviceUintPoint::new(0, window_size.height - test_size.height),
            test_size,
        );
        let layout_size = LayoutSize::new(400., 400.);
        let mut resources = ResourceUpdates::new();
        {
            let api = &self.wrench.api;

            blob_img = api.generate_image_key();
            resources.add_image(
                blob_img,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
                ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                None,
            );
        }
        let root_background_color = Some(ColorF::new(1.0, 1.0, 1.0, 1.0));

        // draw the blob the first time
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
        let info = LayoutPrimitiveInfo::new(
            LayoutRect::new(LayoutPoint::new(0.0, 60.0), LayoutSize::new(200.0, 200.0)));

        builder.push_image(
            &info,
            LayoutSize::new(200.0, 200.0),
            LayoutSize::new(0.0, 0.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            document_id,
            Epoch(0),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            resources,
        );
        self.wrench.api.generate_frame(document_id, None);
        let pixels_first = self.render_and_get_pixels(window_rect);

        // draw the blob image a second time after updating it with the same color
        let mut resources = ResourceUpdates::new();
        resources.update_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
            Some(DeviceUintRect::new(DeviceUintPoint::new(100, 100), DeviceUintSize::new(100, 100))),
        );

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
        let info = LayoutPrimitiveInfo::new(
            LayoutRect::new(LayoutPoint::new(0.0, 60.0), LayoutSize::new(200.0, 200.0)));
        builder.push_image(
            &info,
            LayoutSize::new(200.0, 200.0),
            LayoutSize::new(0.0, 0.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            document_id,
            Epoch(1),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            resources,
        );

        self.wrench.api.generate_frame(document_id, None);
        let pixels_second = self.render_and_get_pixels(window_rect);

        // draw the blob image a third time after updating it with a different color
        let mut resources = ResourceUpdates::new();
        resources.update_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 150, 150, 255))),
            Some(DeviceUintRect::new(DeviceUintPoint::new(200, 200), DeviceUintSize::new(100, 100))),
        );

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
        let info = LayoutPrimitiveInfo::new(
            LayoutRect::new(LayoutPoint::new(0.0, 60.0), LayoutSize::new(200.0, 200.0)));
        builder.push_image(
            &info,
            LayoutSize::new(200.0, 200.0),
            LayoutSize::new(0.0, 0.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            document_id,
            Epoch(2),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            resources,
        );

        self.wrench.api.generate_frame(document_id, None);

        let pixels_third = self.render_and_get_pixels(window_rect);

        assert!(pixels_first == pixels_second);
        assert!(pixels_first != pixels_third);
    }

    // Ensures that content doing a save-restore produces the same results as not
    fn save_restore(&mut self) {
        fn rect(x: f32, y: f32, w: f32, h: f32) -> LayoutRect {
            LayoutRect::new(
                    LayoutPoint::new(x, y),
                    LayoutSize::new(w, h))
        }

        let window_size = self.window.get_inner_size_pixels();
        let window_size = DeviceUintSize::new(window_size.0, window_size.1);

        let test_size = DeviceUintSize::new(400, 400);

        let window_rect = DeviceUintRect::new(
            DeviceUintPoint::new(0, window_size.height - test_size.height),
            test_size,
        );
        let layout_size = LayoutSize::new(400., 400.);
        let root_background_color = Some(ColorF::new(1.0, 1.0, 1.0, 1.0));

        let mut do_test = |should_try_and_fail| {
            let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);

            let clip = builder.define_clip(None, rect(110., 120., 200., 200.),
                                           None::<ComplexClipRegion>, None);
            builder.push_clip_id(clip);
            builder.push_rect(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
                              ColorF::new(0.0, 0.0, 1.0, 1.0));

            if should_try_and_fail {
                builder.save();
                let clip = builder.define_clip(None, rect(80., 80., 90., 90.),
                                           None::<ComplexClipRegion>, None);
                builder.push_clip_id(clip);
                builder.push_rect(&PrimitiveInfo::new(rect(110., 110., 50., 50.)),
                              ColorF::new(0.0, 1.0, 0.0, 1.0));
                builder.push_shadow(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
                    Shadow {
                        offset: LayoutVector2D::new(1.0, 1.0),
                        blur_radius: 1.0,
                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
                    });
                builder.push_line(&PrimitiveInfo::new(rect(110., 110., 50., 2.)),
                                  0.0, LineOrientation::Horizontal,
                                  &ColorF::new(0.0, 0.0, 0.0, 1.0), LineStyle::Solid);
                builder.restore();
            }

            {
                builder.save();
                let clip = builder.define_clip(None, rect(80., 80., 100., 100.),
                                               None::<ComplexClipRegion>, None);
                builder.push_clip_id(clip);
                builder.push_rect(&PrimitiveInfo::new(rect(150., 150., 100., 100.)),
                                  ColorF::new(0.0, 0.0, 1.0, 1.0));

                builder.pop_clip_id();
                builder.clear_save();
            }

            builder.pop_clip_id();

            self.wrench.api.set_display_list(
                self.wrench.document_id,
                Epoch(0),
                root_background_color,
                layout_size,
                builder.finalize(),
                false,
                ResourceUpdates::new(),
            );
            self.wrench
                .api
                .generate_frame(self.wrench.document_id, None);

            self.render_and_get_pixels(window_rect)
        };

        let first = do_test(false);
        let second = do_test(true);

        assert_eq!(first, second);
    }
}
<commit_msg>Make constructing the euclid types more ergonomic.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use WindowWrapper;
use blob;
use euclid::{TypedRect, TypedSize2D, TypedPoint2D};
use std::sync::mpsc::Receiver;
use webrender::api::*;
use wrench::Wrench;

pub struct RawtestHarness<'a> {
    wrench: &'a mut Wrench,
    rx: Receiver<()>,
    window: &'a mut WindowWrapper,
}

fn point<T: Copy, U>(x: T, y: T) -> TypedPoint2D<T, U> {
    TypedPoint2D::new(x, y)
}

fn size<T: Copy, U>(x: T, y: T) -> TypedSize2D<T, U> {
    TypedSize2D::new(x, y)
}

fn rect<T: Copy, U>(x: T, y: T, width: T, height: T) -> TypedRect<T, U> {
    TypedRect::new(point(x, y), size(width, height))
}

impl<'a> RawtestHarness<'a> {
    pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver<()>) -> Self {
        RawtestHarness {
            wrench,
            rx,
            window,
        }
    }

    pub fn run(mut self) {
        self.retained_blob_images_test();
        self.blob_update_test();
        self.tile_decomposition();
        self.save_restore();
    }

    fn render_and_get_pixels(&mut self, window_rect: DeviceUintRect) -> Vec<u8> {
        self.rx.recv().unwrap();
        self.wrench.render();
        self.wrench.renderer.read_pixels_rgba8(window_rect)
    }

    fn tile_decomposition(&mut self) {
        // This exposes a crash in tile decomposition
        let layout_size = LayoutSize::new(800., 800.);
        let mut resources = ResourceUpdates::new();

        let blob_img = self.wrench.api.generate_image_key();
        resources.add_image(
            blob_img,
            ImageDescriptor::new(151, 56, ImageFormat::BGRA8, true),
            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
            Some(128),
        );

        let root_background_color = Some(ColorF::new(1.0, 1.0, 1.0, 1.0));

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);

        let info = LayoutPrimitiveInfo::new(rect(448.899994, 74.0, 151.000031, 56.));

        // setup some malicious image size parameters
        builder.push_image(
            &info,
            size(151., 56.0),
            size(151.0, 56.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            self.wrench.document_id,
            Epoch(0),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            resources,
        );
        self.wrench
            .api
            .generate_frame(self.wrench.document_id, None);

        self.rx.recv().unwrap();
        self.wrench.render();
    }

    fn retained_blob_images_test(&mut self) {
        let blob_img;
        let window_size = self.window.get_inner_size_pixels();
        let window_size = DeviceUintSize::new(window_size.0, window_size.1);

        let test_size = DeviceUintSize::new(400, 400);
        let document_id = self.wrench.document_id;

        let window_rect = DeviceUintRect::new(
            DeviceUintPoint::new(0, window_size.height - test_size.height),
            test_size,
        );
        let layout_size = LayoutSize::new(400., 400.);
        let mut resources = ResourceUpdates::new();
        {
            let api = &self.wrench.api;

            blob_img = api.generate_image_key();
            resources.add_image(
                blob_img,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
                ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                None,
            );
        }
        let root_background_color = Some(ColorF::new(1.0, 1.0, 1.0, 1.0));

        // draw the blob the first time
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
        let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));

        builder.push_image(
            &info,
            size(200.0, 200.0),
            size(0.0, 0.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            document_id,
            Epoch(0),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            resources,
        );
        self.wrench.api.generate_frame(document_id, None);

        // draw the blob image a second time at a different location

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
        let info = LayoutPrimitiveInfo::new(rect(1.0, 60.0, 200.0, 200.0));
        builder.push_image(
            &info,
            size(200.0, 200.0),
            size(0.0, 0.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            document_id,
            Epoch(1),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            ResourceUpdates::new(),
        );

        self.wrench.api.generate_frame(document_id, None);

        let pixels_first = self.render_and_get_pixels(window_rect);
        let pixels_second = self.render_and_get_pixels(window_rect);

        // use png;
        // png::save_flipped("out1.png", &pixels_first, window_rect.size);
        // png::save_flipped("out2.png", &pixels_second, window_rect.size);

        assert!(pixels_first != pixels_second);
    }

    fn blob_update_test(&mut self) {
        let blob_img;
        let window_size = self.window.get_inner_size_pixels();
        let window_size = DeviceUintSize::new(window_size.0, window_size.1);

        let test_size = DeviceUintSize::new(400, 400);
        let document_id = self.wrench.document_id;

        let window_rect = DeviceUintRect::new(
            point(0, window_size.height - test_size.height),
            test_size,
        );
        let layout_size = LayoutSize::new(400., 400.);
        let mut resources = ResourceUpdates::new();
        {
            let api = &self.wrench.api;

            blob_img = api.generate_image_key();
            resources.add_image(
                blob_img,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
                ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                None,
            );
        }
        let root_background_color = Some(ColorF::new(1.0, 1.0, 1.0, 1.0));

        // draw the blob the first time
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
        let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));

        builder.push_image(
            &info,
            size(200.0, 200.0),
            size(0.0, 0.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            document_id,
            Epoch(0),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            resources,
        );
        self.wrench.api.generate_frame(document_id, None);
        let pixels_first = self.render_and_get_pixels(window_rect);

        // draw the blob image a second time after updating it with the same color
        let mut resources = ResourceUpdates::new();
        resources.update_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
            Some(rect(100, 100, 100, 100)),
        );

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
        let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
        builder.push_image(
            &info,
            size(200.0, 200.0),
            size(0.0, 0.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            document_id,
            Epoch(1),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            resources,
        );

        self.wrench.api.generate_frame(document_id, None);
        let pixels_second = self.render_and_get_pixels(window_rect);

        // draw the blob image a third time after updating it with a different color
        let mut resources = ResourceUpdates::new();
        resources.update_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 150, 150, 255))),
            Some(rect(200, 200, 100, 100)),
        );

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
        let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
        builder.push_image(
            &info,
            size(200.0, 200.0),
            size(0.0, 0.0),
            ImageRendering::Auto,
            blob_img,
        );

        self.wrench.api.set_display_list(
            document_id,
            Epoch(2),
            root_background_color,
            layout_size,
            builder.finalize(),
            false,
            resources,
        );

        self.wrench.api.generate_frame(document_id, None);

        let pixels_third = self.render_and_get_pixels(window_rect);

        assert!(pixels_first == pixels_second);
        assert!(pixels_first != pixels_third);
    }

    // Ensures that content doing a save-restore produces the same results as not
    fn save_restore(&mut self) {
        let window_size = self.window.get_inner_size_pixels();
        let window_size = DeviceUintSize::new(window_size.0, window_size.1);

        let test_size = DeviceUintSize::new(400, 400);

        let window_rect = DeviceUintRect::new(
            DeviceUintPoint::new(0, window_size.height - test_size.height),
            test_size,
        );
        let layout_size = LayoutSize::new(400., 400.);
        let root_background_color = Some(ColorF::new(1.0, 1.0, 1.0, 1.0));

        let mut do_test = |should_try_and_fail| {
            let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);

            let clip = builder.define_clip(None, rect(110., 120., 200., 200.),
                                           None::<ComplexClipRegion>, None);
            builder.push_clip_id(clip);
            builder.push_rect(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
                              ColorF::new(0.0, 0.0, 1.0, 1.0));

            if should_try_and_fail {
                builder.save();
                let clip = builder.define_clip(None, rect(80., 80., 90., 90.),
                                           None::<ComplexClipRegion>, None);
                builder.push_clip_id(clip);
                builder.push_rect(&PrimitiveInfo::new(rect(110., 110., 50., 50.)),
                              ColorF::new(0.0, 1.0, 0.0, 1.0));
                builder.push_shadow(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
                    Shadow {
                        offset: LayoutVector2D::new(1.0, 1.0),
                        blur_radius: 1.0,
                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
                    });
                builder.push_line(&PrimitiveInfo::new(rect(110., 110., 50., 2.)),
                                  0.0, LineOrientation::Horizontal,
                                  &ColorF::new(0.0, 0.0, 0.0, 1.0), LineStyle::Solid);
                builder.restore();
            }

            {
                builder.save();
                let clip = builder.define_clip(None, rect(80., 80., 100., 100.),
                                               None::<ComplexClipRegion>, None);
                builder.push_clip_id(clip);
                builder.push_rect(&PrimitiveInfo::new(rect(150., 150., 100., 100.)),
                                  ColorF::new(0.0, 0.0, 1.0, 1.0));

                builder.pop_clip_id();
                builder.clear_save();
            }

            builder.pop_clip_id();

            self.wrench.api.set_display_list(
                self.wrench.document_id,
                Epoch(0),
                root_background_color,
                layout_size,
                builder.finalize(),
                false,
                ResourceUpdates::new(),
            );
            self.wrench
                .api
                .generate_frame(self.wrench.document_id, None);

            self.render_and_get_pixels(window_rect)
        };

        let first = do_test(false);
        let second = do_test(true);

        assert_eq!(first, second);
    }
}
<|endoftext|>


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

use WindowWrapper;
use base64;
use image::load as load_piston_image;
use image::png::PNGEncoder;
use image::{ColorType, ImageFormat};
use parse_function::parse_function;
use png::save_flipped;
use std::cmp;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Receiver, Sender};
use webrender_traits::*;
use wrench::{Wrench, WrenchThing};
use yaml_frame_reader::YamlFrameReader;

pub struct ReftestOptions {
    // These override values that are lower.
    pub allow_max_difference: usize,
    pub allow_num_differences: usize,
}

impl ReftestOptions {
    pub fn default() -> Self {
        ReftestOptions {
            allow_max_difference: 0,
            allow_num_differences: 0,
        }
    }
}

pub enum ReftestOp {
    Equal,
    NotEqual,
}
pub struct Reftest {
    op: ReftestOp,
    test: PathBuf,
    reference: PathBuf,
    max_difference: usize,
    num_differences: usize,
}

struct ReftestImage {
    data: Vec<u8>,
    size: DeviceUintSize,
}
enum ReftestImageComparison {
    Equal,
    NotEqual { max_difference: usize, count_different: usize },
}

impl ReftestImage {
    fn compare(&self, other: &ReftestImage) -> ReftestImageComparison {
        assert_eq!(self.size, other.size);
        assert_eq!(self.data.len(), other.data.len());
        assert_eq!(self.data.len() % 4, 0);

        let mut count = 0;
        let mut max = 0;

        for (a, b) in self.data.chunks(4).zip(other.data.chunks(4)) {
            if a != b {
                let pixel_max = a.iter()
                                 .zip(b.iter())
                                 .map(|(x, y)| (*x as isize - *y as isize).abs() as usize)
                                 .max().unwrap();

                count += 1;
                max = cmp::max(max, pixel_max);
            }
        }

        if count != 0 {
            ReftestImageComparison::NotEqual {
                max_difference: max,
                count_different: count,
            }
        } else {
            ReftestImageComparison::Equal
        }
    }

    fn create_data_uri(mut self) -> String {
        let width = self.size.width;
        let height = self.size.height;

        // flip image vertically (texture is upside down)
        let orig_pixels = self.data.clone();
        let stride = width as usize * 4;
        for y in 0..height as usize {
            let dst_start = y * stride;
            let src_start = (height as usize - y - 1) * stride;
            let src_slice = &orig_pixels[src_start .. src_start + stride];
            (&mut self.data[dst_start .. dst_start + stride]).clone_from_slice(&src_slice[..stride]);
        }

        let mut png: Vec<u8> = vec![];
        {
            let encoder = PNGEncoder::new(&mut png);
            encoder.encode(&self.data[..],
                            width,
                            height,
                            ColorType::RGBA(8)).expect("Unable to encode PNG!");
        }
        let png_base64 = base64::encode(&png);
        format!("data:image/png;base64,{}", png_base64)
    }
}

struct ReftestManifest {
    reftests: Vec<Reftest>,
}
impl ReftestManifest {
    fn new(manifest: &Path, options: &ReftestOptions) -> ReftestManifest {
        let dir = manifest.parent().unwrap();
        let f = File::open(manifest).expect(&format!("couldn't open manifest: {}", manifest.display()));
        let file = BufReader::new(&f);

        let mut reftests = Vec::new();

        for line in file.lines() {
            let l = line.unwrap();

            // strip the comments
            let s = &l[0..l.find('#').unwrap_or(l.len())];
            let s = s.trim();
            if s.is_empty() {
                continue;
            }

            let items: Vec<&str> = s.split_whitespace().collect();

            match items[0] {
                "include" => {
                    let include = dir.join(items[1]);

                    reftests.append(&mut ReftestManifest::new(include.as_path(), options).reftests);
                }
                item_str => {
                    // If the first item is "fuzzy(<val>,<count>)" the positions of the operator
                    // and paths in the array are offset.
                    // TODO: This is simple but not great because it does not support having spaces
                    // in the fuzzy syntax, like between the arguments.
                    let (max, count, offset) =  if item_str.starts_with("fuzzy(") {
                        let (_, args, _) = parse_function(item_str);
                        (args[0].parse().unwrap(),  args[1].parse().unwrap(), 1)
                    } else {
                        (0, 0, 0)
                    };

                    reftests.push(Reftest {
                        op: parse_operator(items[offset]).expect("unexpected match operator"),
                        test: dir.join(items[offset + 1]),
                        reference: dir.join(items[offset + 2]),
                        max_difference: cmp::max(max, options.allow_max_difference),
                        num_differences: cmp::max(count, options.allow_num_differences),
                    });
                }
            };
        }

        ReftestManifest {
            reftests: reftests
        }
    }

    fn find(&self, prefix: &Path) -> Vec<&Reftest> {
        self.reftests.iter().filter(|x| {
            x.test.starts_with(prefix) || x.reference.starts_with(prefix)
        }).collect()
    }
}

fn parse_operator(op_str: &str) -> Option<ReftestOp> {
    match op_str {
        "==" => Some(ReftestOp::Equal),
        "!=" => Some(ReftestOp::NotEqual),
        _ => None,
    }
}

pub struct ReftestHarness<'a> {
    wrench: &'a mut Wrench,
    window: &'a mut WindowWrapper,
    rx: Receiver<()>,
}
impl<'a> ReftestHarness<'a> {
    pub fn new(wrench: &'a mut Wrench,
               window: &'a mut WindowWrapper) -> ReftestHarness<'a>
    {
        // setup a notifier so we can wait for frames to be finished
        struct Notifier {
            tx: Sender<()>,
        };
        impl RenderNotifier for Notifier {
            fn new_frame_ready(&mut self) {
                self.tx.send(()).unwrap();
            }
            fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {}
        }
        let (tx, rx) = channel();
        wrench.renderer.set_render_notifier(Box::new(Notifier { tx: tx }));

        ReftestHarness {
            wrench: wrench,
            window: window,
            rx: rx,
        }
    }

    pub fn run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) {
        let manifest = ReftestManifest::new(base_manifest, options);
        let reftests = manifest.find(reftests.unwrap_or(&PathBuf::new()));

        let mut total_passing = 0;
        let mut total_failing = 0;

        for t in reftests {
            if self.run_reftest(t) {
                total_passing += 1;
            } else {
                total_failing += 1;
            }
        }

        println!("REFTEST INFO | {} passing, {} failing", total_passing, total_failing);

        // panic here so that we fail CI
        assert!(total_failing <= 0);
    }

    fn run_reftest(&mut self, t: &Reftest) -> bool {
        let name = match t.op {
            ReftestOp::Equal => format!("{} == {}", t.test.display(), t.reference.display()),
            ReftestOp::NotEqual => format!("{} != {}", t.test.display(), t.reference.display()),
        };

        println!("REFTEST {}", name);

        let window_size = DeviceUintSize::new(self.window.get_inner_size_pixels().0,
                                              self.window.get_inner_size_pixels().1);
        let reference = match t.reference.extension().unwrap().to_str().unwrap() {
            "yaml" => self.render_yaml(t.reference.as_path(), window_size),
            "png" => self.load_image(t.reference.as_path(), ImageFormat::PNG),
            other => panic!("Unknown reftest extension: {}", other),
        };
        // the reference can be smaller than the window size,
        // in which case we only compare the intersection
        let test = self.render_yaml(t.test.as_path(), reference.size);
        let comparison = test.compare(&reference);

        match (&t.op, comparison) {
            (&ReftestOp::Equal, ReftestImageComparison::Equal) => true,
            (&ReftestOp::Equal,
              ReftestImageComparison::NotEqual { max_difference, count_different }) => {
                if max_difference > t.max_difference || count_different > t.num_differences {
                    println!("{} | {} | {}: {}, {}: {}",
                             "REFTEST TEST-UNEXPECTED-FAIL", name,
                             "image comparison, max difference", max_difference,
                             "number of differing pixels", count_different);
                    println!("REFTEST   IMAGE 1 (TEST): {}", test.create_data_uri());
                    println!("REFTEST   IMAGE 2 (REFERENCE): {}", reference.create_data_uri());
                    println!("REFTEST TEST-END | {}", name);

                    false
                } else {
                    true
                }
            },
            (&ReftestOp::NotEqual, ReftestImageComparison::Equal) => {
                println!("REFTEST TEST-UNEXPECTED-FAIL | {} | image comparison", name);
                println!("REFTEST TEST-END | {}", name);

                false
            },
            (&ReftestOp::NotEqual, ReftestImageComparison::NotEqual { .. }) => true,
        }
    }

    fn load_image(&mut self, filename: &Path, format: ImageFormat) -> ReftestImage {
        let file = BufReader::new(File::open(filename).unwrap());
        let img_raw = load_piston_image(file, format).unwrap();
        let img = img_raw.flipv().to_rgba();
        let size = img.dimensions();
        ReftestImage {
            data: img.into_raw(),
            size: DeviceUintSize::new(size.0, size.1)
        }
    }

    fn render_yaml(&mut self, filename: &Path, size: DeviceUintSize) -> ReftestImage {
        let mut reader = YamlFrameReader::new(filename);
        reader.do_frame(self.wrench);

        // wait for the frame
        self.rx.recv().unwrap();
        self.wrench.render();

        let window_size = self.window.get_inner_size_pixels();
        assert!(size.width <= window_size.0 && size.height <= window_size.1);

        // taking the bottom left sub-rectangle
        let rect = DeviceUintRect::new(DeviceUintPoint::new(0, window_size.1 - size.height),
                                       size);
        let pixels = self.wrench.renderer.read_pixels_rgba8(rect);
        self.window.swap_buffers();

        let write_debug_images = false;
        if write_debug_images {
            let debug_path = filename.with_extension("yaml.png");
            save_flipped(debug_path, &pixels, size);
        }

        ReftestImage {
            data: pixels,
            size: size,
        }
    }
}
<commit_msg>Print list of unexpected test results after refest run<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use WindowWrapper;
use base64;
use image::load as load_piston_image;
use image::png::PNGEncoder;
use image::{ColorType, ImageFormat};
use parse_function::parse_function;
use png::save_flipped;
use std::cmp;
use std::fmt::{Display, Error, Formatter};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Receiver, Sender};
use webrender_traits::*;
use wrench::{Wrench, WrenchThing};
use yaml_frame_reader::YamlFrameReader;

pub struct ReftestOptions {
    // These override values that are lower.
    pub allow_max_difference: usize,
    pub allow_num_differences: usize,
}

impl ReftestOptions {
    pub fn default() -> Self {
        ReftestOptions {
            allow_max_difference: 0,
            allow_num_differences: 0,
        }
    }
}

pub enum ReftestOp {
    Equal,
    NotEqual,
}

impl Display for ReftestOp {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        write!(f, "{}",
               match *self {
                   ReftestOp::Equal => "==".to_owned(),
                   ReftestOp::NotEqual => "!=".to_owned(),
                })
    }
}

pub struct Reftest {
    op: ReftestOp,
    test: PathBuf,
    reference: PathBuf,
    max_difference: usize,
    num_differences: usize,
}

impl Display for Reftest {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        write!(f, "{} {} {}", self.test.display(), self.op, self.reference.display())
    }
}

struct ReftestImage {
    data: Vec<u8>,
    size: DeviceUintSize,
}
enum ReftestImageComparison {
    Equal,
    NotEqual { max_difference: usize, count_different: usize },
}

impl ReftestImage {
    fn compare(&self, other: &ReftestImage) -> ReftestImageComparison {
        assert_eq!(self.size, other.size);
        assert_eq!(self.data.len(), other.data.len());
        assert_eq!(self.data.len() % 4, 0);

        let mut count = 0;
        let mut max = 0;

        for (a, b) in self.data.chunks(4).zip(other.data.chunks(4)) {
            if a != b {
                let pixel_max = a.iter()
                                 .zip(b.iter())
                                 .map(|(x, y)| (*x as isize - *y as isize).abs() as usize)
                                 .max().unwrap();

                count += 1;
                max = cmp::max(max, pixel_max);
            }
        }

        if count != 0 {
            ReftestImageComparison::NotEqual {
                max_difference: max,
                count_different: count,
            }
        } else {
            ReftestImageComparison::Equal
        }
    }

    fn create_data_uri(mut self) -> String {
        let width = self.size.width;
        let height = self.size.height;

        // flip image vertically (texture is upside down)
        let orig_pixels = self.data.clone();
        let stride = width as usize * 4;
        for y in 0..height as usize {
            let dst_start = y * stride;
            let src_start = (height as usize - y - 1) * stride;
            let src_slice = &orig_pixels[src_start .. src_start + stride];
            (&mut self.data[dst_start .. dst_start + stride]).clone_from_slice(&src_slice[..stride]);
        }

        let mut png: Vec<u8> = vec![];
        {
            let encoder = PNGEncoder::new(&mut png);
            encoder.encode(&self.data[..],
                            width,
                            height,
                            ColorType::RGBA(8)).expect("Unable to encode PNG!");
        }
        let png_base64 = base64::encode(&png);
        format!("data:image/png;base64,{}", png_base64)
    }
}

struct ReftestManifest {
    reftests: Vec<Reftest>,
}
impl ReftestManifest {
    fn new(manifest: &Path, options: &ReftestOptions) -> ReftestManifest {
        let dir = manifest.parent().unwrap();
        let f = File::open(manifest).expect(&format!("couldn't open manifest: {}", manifest.display()));
        let file = BufReader::new(&f);

        let mut reftests = Vec::new();

        for line in file.lines() {
            let l = line.unwrap();

            // strip the comments
            let s = &l[0..l.find('#').unwrap_or(l.len())];
            let s = s.trim();
            if s.is_empty() {
                continue;
            }

            let items: Vec<&str> = s.split_whitespace().collect();

            match items[0] {
                "include" => {
                    let include = dir.join(items[1]);

                    reftests.append(&mut ReftestManifest::new(include.as_path(), options).reftests);
                }
                item_str => {
                    // If the first item is "fuzzy(<val>,<count>)" the positions of the operator
                    // and paths in the array are offset.
                    // TODO: This is simple but not great because it does not support having spaces
                    // in the fuzzy syntax, like between the arguments.
                    let (max, count, offset) =  if item_str.starts_with("fuzzy(") {
                        let (_, args, _) = parse_function(item_str);
                        (args[0].parse().unwrap(),  args[1].parse().unwrap(), 1)
                    } else {
                        (0, 0, 0)
                    };

                    reftests.push(Reftest {
                        op: parse_operator(items[offset]).expect("unexpected match operator"),
                        test: dir.join(items[offset + 1]),
                        reference: dir.join(items[offset + 2]),
                        max_difference: cmp::max(max, options.allow_max_difference),
                        num_differences: cmp::max(count, options.allow_num_differences),
                    });
                }
            };
        }

        ReftestManifest {
            reftests: reftests
        }
    }

    fn find(&self, prefix: &Path) -> Vec<&Reftest> {
        self.reftests.iter().filter(|x| {
            x.test.starts_with(prefix) || x.reference.starts_with(prefix)
        }).collect()
    }
}

fn parse_operator(op_str: &str) -> Option<ReftestOp> {
    match op_str {
        "==" => Some(ReftestOp::Equal),
        "!=" => Some(ReftestOp::NotEqual),
        _ => None,
    }
}

pub struct ReftestHarness<'a> {
    wrench: &'a mut Wrench,
    window: &'a mut WindowWrapper,
    rx: Receiver<()>,
}
impl<'a> ReftestHarness<'a> {
    pub fn new(wrench: &'a mut Wrench,
               window: &'a mut WindowWrapper) -> ReftestHarness<'a>
    {
        // setup a notifier so we can wait for frames to be finished
        struct Notifier {
            tx: Sender<()>,
        };
        impl RenderNotifier for Notifier {
            fn new_frame_ready(&mut self) {
                self.tx.send(()).unwrap();
            }
            fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {}
        }
        let (tx, rx) = channel();
        wrench.renderer.set_render_notifier(Box::new(Notifier { tx: tx }));

        ReftestHarness {
            wrench: wrench,
            window: window,
            rx: rx,
        }
    }

    pub fn run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) {
        let manifest = ReftestManifest::new(base_manifest, options);
        let reftests = manifest.find(reftests.unwrap_or(&PathBuf::new()));

        let mut total_passing = 0;
        let mut failing = Vec::new();

        for t in reftests {
            if self.run_reftest(t) {
                total_passing += 1;
            } else {
                failing.push(t);
            }
        }

        println!("REFTEST INFO | {} passing, {} failing", total_passing, failing.len());

        if !failing.is_empty() {
            println!("\nReftests with unexpected results:");

            for reftest in &failing {
                println!("\t{}", reftest);
            }
        }

        // panic here so that we fail CI
        assert!(failing.is_empty());
    }

    fn run_reftest(&mut self, t: &Reftest) -> bool {
        println!("REFTEST {}", t);

        let window_size = DeviceUintSize::new(self.window.get_inner_size_pixels().0,
                                              self.window.get_inner_size_pixels().1);
        let reference = match t.reference.extension().unwrap().to_str().unwrap() {
            "yaml" => self.render_yaml(t.reference.as_path(), window_size),
            "png" => self.load_image(t.reference.as_path(), ImageFormat::PNG),
            other => panic!("Unknown reftest extension: {}", other),
        };
        // the reference can be smaller than the window size,
        // in which case we only compare the intersection
        let test = self.render_yaml(t.test.as_path(), reference.size);
        let comparison = test.compare(&reference);

        match (&t.op, comparison) {
            (&ReftestOp::Equal, ReftestImageComparison::Equal) => true,
            (&ReftestOp::Equal,
              ReftestImageComparison::NotEqual { max_difference, count_different }) => {
                if max_difference > t.max_difference || count_different > t.num_differences {
                    println!("{} | {} | {}: {}, {}: {}",
                             "REFTEST TEST-UNEXPECTED-FAIL", t,
                             "image comparison, max difference", max_difference,
                             "number of differing pixels", count_different);
                    println!("REFTEST   IMAGE 1 (TEST): {}", test.create_data_uri());
                    println!("REFTEST   IMAGE 2 (REFERENCE): {}", reference.create_data_uri());
                    println!("REFTEST TEST-END | {}", t);

                    false
                } else {
                    true
                }
            },
            (&ReftestOp::NotEqual, ReftestImageComparison::Equal) => {
                println!("REFTEST TEST-UNEXPECTED-FAIL | {} | image comparison", t);
                println!("REFTEST TEST-END | {}", t);

                false
            },
            (&ReftestOp::NotEqual, ReftestImageComparison::NotEqual { .. }) => true,
        }
    }

    fn load_image(&mut self, filename: &Path, format: ImageFormat) -> ReftestImage {
        let file = BufReader::new(File::open(filename).unwrap());
        let img_raw = load_piston_image(file, format).unwrap();
        let img = img_raw.flipv().to_rgba();
        let size = img.dimensions();
        ReftestImage {
            data: img.into_raw(),
            size: DeviceUintSize::new(size.0, size.1)
        }
    }

    fn render_yaml(&mut self, filename: &Path, size: DeviceUintSize) -> ReftestImage {
        let mut reader = YamlFrameReader::new(filename);
        reader.do_frame(self.wrench);

        // wait for the frame
        self.rx.recv().unwrap();
        self.wrench.render();

        let window_size = self.window.get_inner_size_pixels();
        assert!(size.width <= window_size.0 && size.height <= window_size.1);

        // taking the bottom left sub-rectangle
        let rect = DeviceUintRect::new(DeviceUintPoint::new(0, window_size.1 - size.height),
                                       size);
        let pixels = self.wrench.renderer.read_pixels_rgba8(rect);
        self.window.swap_buffers();

        let write_debug_images = false;
        if write_debug_images {
            let debug_path = filename.with_extension("yaml.png");
            save_flipped(debug_path, &pixels, size);
        }

        ReftestImage {
            data: pixels,
            size: size,
        }
    }
}
<|endoftext|>


<commit_before>use node;
use time;
use std::collections::VecDeque;

pub const POLL_FREQUENCY_MS: u64 = 50;
pub const TRIES: u8 = 5;

#[test]
fn node_ping() {
   let alpha = node::Node::new();
   let beta  = node::Node::new();
   let beta_seed = beta.local_info();

   // Bootstrapping alpha:
   alpha.bootstrap(beta_seed);

   // Before sending the ping, beta does not know about alpha.
   assert!(beta.resources.table.specific_node(&alpha.resources.id).is_none());

   // Alpha pings beta.
   assert_eq!(alpha.ping(beta.resources.id.clone()), node::PingResult::Alive);

   // And beta now knows of alpha
   assert!(beta.resources.table.specific_node(&alpha.resources.id).is_some());
}

#[test]
fn reception_iterator_times_out_correctly() {
   let alpha = node::Node::new();
   let span = time::Duration::seconds(1);
   let maximum = time::Duration::seconds(3);
   let receptions = alpha.receptions().during(span);

   let before = time::SteadyTime::now();
   for rpc in receptions {
      // nothing is happening, so this should time out in around a second (not necessarily precise)
      panic!();
   }
   let after = time::SteadyTime::now();

   assert!(after - before < maximum);
}

#[test]
fn chained_node_find() {
   let mut nodes: VecDeque<node::Node> = (0..10).map(|_| { node::Node::new() }).collect();

   // We inform each node of the existance of the next three.
   for i in 1..4 {
      for (alpha, beta) in nodes.iter().zip(nodes.iter().skip(i)) {
         alpha.resources.table.insert_node(beta.local_info());
      }
   }

   // Head finds tail
   let head = nodes.pop_front().unwrap();
   let tail = nodes.pop_back().unwrap();
   assert_eq!(head.find_node(tail.id()).unwrap(), tail.local_info());
}
<commit_msg>Bootstrap test<commit_after>use node;
use time;
use std::collections::VecDeque;
use node::receptions;

pub const POLL_FREQUENCY_MS: u64 = 50;
pub const TRIES: u8 = 5;

#[test]
fn node_ping() {
   let alpha = node::Node::new();
   let beta  = node::Node::new();
   let beta_seed = beta.local_info();

   // Bootstrapping alpha:
   alpha.bootstrap(beta_seed);

   // Before sending the ping, beta does not know about alpha.
   assert!(beta.resources.table.specific_node(&alpha.resources.id).is_none());

   // Alpha pings beta.
   assert_eq!(alpha.ping(beta.resources.id.clone()), node::PingResult::Alive);

   // And beta now knows of alpha
   assert!(beta.resources.table.specific_node(&alpha.resources.id).is_some());
}

#[test]
fn reception_iterator_times_out_correctly() {
   let alpha = node::Node::new();
   let span = time::Duration::seconds(1);
   let maximum = time::Duration::seconds(3);
   let receptions = alpha.receptions().during(span);

   let before = time::SteadyTime::now();
   for rpc in receptions {
      // nothing is happening, so this should time out in around a second (not necessarily precise)
      panic!();
   }
   let after = time::SteadyTime::now();

   assert!(after - before < maximum);
}

#[test]
fn chained_node_find() {
   let mut nodes: VecDeque<node::Node> = (0..10).map(|_| { node::Node::new() }).collect();

   // We inform each node of the existance of the next three.
   for i in 1..4 {
      for (alpha, beta) in nodes.iter().zip(nodes.iter().skip(i)) {
         alpha.resources.table.insert_node(beta.local_info());
      }
   }

   // Head finds tail
   let head = nodes.pop_front().unwrap();
   let tail = nodes.pop_back().unwrap();
   assert_eq!(head.find_node(tail.id()).unwrap(), tail.local_info());
}

#[test]
fn bootstrapping_and_finding_on_simulated_network() {
   let origin = node::Node::new();

   let mut nodes: VecDeque<node::Node> = (0..100).map(|_| { node::Node::new() }).collect();

   for node in nodes.iter() {
      node.bootstrap(origin.local_info());
   }

   // Head finds tail in a few steps.
   let head = nodes.pop_front().unwrap();
   let tail = nodes.pop_back().unwrap();

   let receptions =
      head.receptions()
          .during(time::Duration::seconds(3))
          .rpc(receptions::RpcFilter::FindNodeResponse);

   assert_eq!(head.find_node(tail.id()).unwrap(), tail.local_info());
   let maximum_steps = 10;
   assert!(receptions.count() < maximum_steps);
}
<|endoftext|>


<commit_before>use std::{
    env::current_exe,
    path::PathBuf,
};
use piston_window::*;
use crate::{
    direction::Direction,
    game::*,
    settings::Settings,
};

pub struct App<'a> {
    window: PistonWindow,
    window_size: [u32; 2],
    game: Game<'a>,
    turn_active: bool,
    turn_end_timer: f64,
    cpu_turn_timer: f64,
    mouse_cursor: [f64; 2],
    grid_area: [u32; 4],
}

impl<'a> App<'a> {
    pub fn new(settings: &Settings) -> App {
        let grid_area = [
            settings.space_size,
            settings.space_size * 3,
            settings.spaces[0] as u32 * settings.space_size,
            settings.spaces[1] as u32 * settings.space_size
        ];

        let window_size = [
            grid_area[2] + settings.space_size * 2,
            grid_area[3] + settings.space_size * 4
        ];

        let window_title = "Battleship";
        let window: PistonWindow = WindowSettings::new(
            window_title,
            window_size,
            )
            .exit_on_esc(true)
            .resizable(false)
            .build()
            .unwrap();

        App {
            window: window,
            window_size: window_size,
            game: Game::new(&settings),
            turn_active: true,
            turn_end_timer: 0.0,
            cpu_turn_timer: 0.0,
            mouse_cursor: [0.0; 2],
            grid_area: grid_area,
        }
    }

    pub fn init(&mut self) {
        self.window.set_ups(60);
        self.window.set_max_fps(60);

        // TODO set textures in a not terrible way
        let assets_dir = Self::get_assets_dir(current_exe().unwrap()).unwrap();
        let images_dir: PathBuf = assets_dir.join("images");
        let mut space_textures = vec![];

        for state in 0..3 {
            let image_file = format!("gridspace-{}.png", state);
            space_textures.push(self.get_texture(images_dir.join(&image_file)));
        }

        space_textures.push(self.get_texture(images_dir.join("shipspace.png")));

        let grid_cursor_texture = self.get_texture(images_dir.join("grid-cursor.png"));

        let mut ship_textures = vec![];
        for ship_size in 2..6 {
            let image_file = format!("ship-{}.png", ship_size);
            ship_textures.push(self.get_texture(images_dir.join(&image_file)));
        }

        let player_text = [
            self.get_texture(images_dir.join("player-1.png")),
            self.get_texture(images_dir.join("player-2.png")),
        ];

        let game_over_text = [
            self.get_texture(images_dir.join("game-over.png")),
            self.get_texture(images_dir.join("wins.png")),
        ];

        while let Some(e) = self.window.next() {
            if let Some(p) = e.press_args() {
                match p {
                    Button::Mouse(mouse::MouseButton::Left) => self.mouse_left_click(),
                    Button::Mouse(mouse::MouseButton::Right) => self.button_secondary(),
                    Button::Keyboard(keyboard::Key::Left) => self.button_left(),
                    Button::Keyboard(keyboard::Key::Right) => self.button_right(),
                    Button::Keyboard(keyboard::Key::Up) => self.button_up(),
                    Button::Keyboard(keyboard::Key::Down) => self.button_down(),
                    Button::Keyboard(keyboard::Key::Return) => self.button_primary(),
                    Button::Keyboard(keyboard::Key::Space) => self.button_secondary(),
                    _ => {}
                }
            }

            if let Some(c) = e.mouse_cursor_args() {
                self.mouse_cursor_movement(&c);
            }

            if let Some(u) = e.update_args() {
                self.update(&u);
            }

            if e.render_args().is_some() {
                let current_player = self.game.active_player();
                let game_state = self.game.state();
                let shown_player = match game_state {
                    GameState::ShipPlacement => current_player,
                    _ => self.game.inactive_player()
                };

                let space_size_u32 = self.game.settings.space_size as u32;
                let grid_area = self.grid_area;
                let window_size = self.window_size;
                let turn_end_timer = self.turn_end_timer;
                let game_winner = self.game.get_winner();
                let game_turn = self.game.turn();
                let turn_active = self.turn_active;

                self.window.draw_2d(&e, |c, g| {
                    clear([0.6, 0.6, 1.0, 1.0], g);

                    // Ship icons above grid
                    for (i, ship) in shown_player.ships().iter().enumerate() {
                        if ship.is_active() {
                            let transform = c.transform.trans(
                                (space_size_u32 * 2 * i as u32 + grid_area[0] * 2) as f64,
                                30.0 as f64,
                            );
                            image(&ship_textures[i], transform, g);
                        }
                    }

                    // Grid spaces
                    for space in shown_player.spaces() {
                        let space_pos = space.pos();
                        let transform = c.transform.trans(
                            (space_size_u32 * space_pos[0] as u32 + grid_area[0]) as f64,
                            (space_size_u32 * space_pos[1] as u32 + grid_area[1]) as f64,
                        );

                        // Only show ship locations during ship placement or if the
                        // current player is computer-controlled.
                        if shown_player.ship_is_in_space(space_pos) && (game_state == GameState::ShipPlacement || (space.is_unchecked() && current_player.is_cpu)) {
                            image(&space_textures[3], transform, g);
                        } else {
                            let space_state = if space.is_unchecked() {
                                0
                            } else if space.is_empty() {
                                1
                            } else {
                                2
                            };
                            image(&space_textures[space_state], transform, g);
                        }
                    }

                    // During ship placement, show the temporary position of the
                    // next ship to be placed.
                    if game_state == GameState::ShipPlacement {
                        for pos in &shown_player.temp_ship_pos {
                            let transform = c.transform.trans(
                                (space_size_u32 * pos[0] as u32 + grid_area[0]) as f64,
                                (space_size_u32 * pos[1] as u32 + grid_area[1]) as f64,
                            );
                            image(&space_textures[3], transform, g);
                        }
                    }

                    // During the game, show the player's grid cursor.
                    if game_state == GameState::Active && turn_end_timer == 0.0 && !current_player.is_cpu {
                        let grid_cursor = current_player.get_grid_cursor();
                        let transform = c.transform.trans(
                            (space_size_u32 * grid_cursor[0] as u32 + grid_area[0]) as f64,
                            (space_size_u32 * grid_cursor[1] as u32 + grid_area[1]) as f64,
                        );
                        image(&grid_cursor_texture, transform, g);
                    }

                    // Current player text image
                    if game_winner.is_none() {
                        let turn = game_turn;
                        let player_text_size = player_text[turn].get_size();
                        let transform = c.transform.trans(
                            ((window_size[0] - player_text_size.0) / 2) as f64,
                            2.0
                        );
                        image(&player_text[turn], transform, g);
                    }

                    // During turn transitions / game over, cover the window with
                    // a black rectangle of increasing opacity.
                    if !turn_active && turn_end_timer >= 0.75 {
                        let alpha = if game_state != GameState::Over {
                            (turn_end_timer as f32 - 0.75) / 0.75
                        } else {
                            (turn_end_timer as f32 - 0.75) / 1.125
                        };
                        rectangle(
                            [0.0, 0.0, 0.0, alpha],
                            [0.0, 0.0, window_size[0] as f64, window_size[1] as f64],
                            c.transform,
                            g
                        );
                    }

                    // Game over content, to appear over the black rectangle.
                    if turn_end_timer >= 1.5 && game_winner.is_some() {
                        let winner = game_winner.unwrap();
                        let game_over_text_size = game_over_text[0].get_size();
                        let wins_text_size = game_over_text[1].get_size();
                        let player_text_size = player_text[winner].get_size();
                        image(&game_over_text[0], c.transform.trans(
                            ((window_size[0] - game_over_text_size.0) / 2) as f64,
                            2.0
                        ), g);
                        image(&player_text[winner], c.transform.trans(
                            ((window_size[0] - player_text_size.0 - wins_text_size.0 - 2) / 2) as f64,
                            22.0
                        ), g);
                        image(&game_over_text[1], c.transform.trans(
                            ((window_size[0] + player_text_size.0 - wins_text_size.0 + 2) / 2) as f64,
                            22.0
                        ), g);
                    }
                });
            }
        }
    }

    fn update(&mut self, u: &UpdateArgs) {
        let state = self.game.state();
        let active_player_is_cpu = self.game.is_active_player_cpu();

        if state == GameState::ShipPlacement {
            if active_player_is_cpu {
                self.game.active_player_mut().cpu_place_ships();
            }

            if self.game.active_player().ships().len() == 4 {
                self.game.switch_turn();
            }

            // All ships have been placed; start the game.
            if self.game.active_player().ships().len() == 4 && self.game.inactive_player().ships().len() == 4 {
                self.game.start();
            }
        } else if state == GameState::Active || state == GameState::Over {
            if !self.turn_active {
                // Continue/end the end-of-turn delay.
                if self.turn_end_timer < 1.5 {
                    self.turn_end_timer += u.dt;
                } else if state == GameState::Active {
                    self.game.switch_turn();
                    self.turn_end_timer = 0.0;
                    self.turn_active = true;
                }
            }

            // Continue/end the delay when CPU players take their turn.
            if self.turn_active && active_player_is_cpu {
                self.cpu_turn_timer += u.dt;

                if self.cpu_turn_timer >= 1.0 {
                    let cpu_space = self.game.inactive_player().cpu_select_space();
                    self.game.select_opponent_space(&cpu_space);
                    self.cpu_turn_timer = 0.0;
                    self.turn_active = false;
                }
            }
        }
    }

    fn primary_action(&mut self, grid_pos: &[u8; 2]) {
        if self.game.is_player_placing_ship() {
            self.game.active_player_mut().place_temp_ship();
        }

        if self.game.is_player_selecting_space() {
            self.game.select_opponent_space(grid_pos);
            self.turn_active = false;
        }
    }

    /// Processes left button presses according to the current program state.
    fn button_left(&mut self) {
        self.movement(Direction::West);
    }

    /// Processes right button presses according to the current program state.
    fn button_right(&mut self) {
        self.movement(Direction::East);
    }

    /// Processes up button presses according to the current program state.
    fn button_up(&mut self) {
        self.movement(Direction::North);
    }

    /// Processes down button presses according to the current program state.
    fn button_down(&mut self) {
        self.movement(Direction::South);
    }

    /// Processes primary button presses according to the current program state.
    fn button_primary(&mut self) {
        let grid_pos = self.game.active_player().get_grid_cursor();
        self.primary_action(&grid_pos);
    }

    /// Processes secondary button presses according to the current program state.
    fn button_secondary(&mut self) {
        if self.game.is_player_placing_ship() {
            self.game.active_player_mut().rotate_temp_ship();
        }
    }

    /// Processes left mouse clicks according to the current program state.
    fn mouse_left_click(&mut self) {
        if let Some(grid_pos) = self.mouse_cursor_grid_position() {
            self.primary_action(&grid_pos);
        }
    }

    /// Performs grid movement according to the current program state.
    fn movement(&mut self, direction: Direction) {
        if self.game.is_player_placing_ship() {
            self.game.active_player_mut().move_temp_ship(direction);
        }

        if self.game.is_player_selecting_space() {
            self.game.active_player_mut().move_grid_cursor(direction);
        }
    }

    /// Records the last known mouse cursor position.
    pub fn mouse_cursor_movement(&mut self, c: &[f64; 2]) {
        self.mouse_cursor = *c;

        if let Some(grid_pos) = self.mouse_cursor_grid_position() {
            let is_player_turn = self.game.is_player_turn();
            let game_state = self.game.state();
            let ref mut player = self.game.active_player_mut();

            if game_state == GameState::ShipPlacement {
                if let Some(ship) = player.get_ship_position(
                    grid_pos,
                    player.temp_ship_dir,
                    player.ships().len() as u8 + 2
                ) {
                    player.temp_ship_pos = ship;
                }
            }

            if game_state == GameState::Active && is_player_turn {
                player.set_grid_cursor(&grid_pos);
            }
        }
    }

    /// Returns the grid coordinates of the mouse cursor position.
    fn mouse_cursor_grid_position(&self) -> Option<[u8; 2]> {
        let position: Option<[u8; 2]>;
        if self.mouse_cursor[0] >= self.grid_area[0] as f64
            && self.mouse_cursor[1] >= self.grid_area[1] as f64
            && self.mouse_cursor[0] < (self.grid_area[0] + self.grid_area[2]) as f64
            && self.mouse_cursor[1] < (self.grid_area[1] + self.grid_area[3]) as f64
        {
            position = Some([
                ((self.mouse_cursor[0] - self.grid_area[0] as f64) / self.grid_area[0] as f64) as u8,
                ((self.mouse_cursor[1] - self.grid_area[1] as f64) / self.grid_area[0] as f64) as u8,
            ]);
        } else {
            position = None;
        }

        position
    }

    /// Returns the texture from the file at the given path.
    fn get_texture(&mut self, path: PathBuf) -> G2dTexture {
        Texture::from_path(
            &mut self.window.factory,
            path,
            Flip::None,
            &TextureSettings::new(),
        ).unwrap()
    }

    /// Returns the assets directory, if it could be found.
    fn get_assets_dir(mut dir: PathBuf) -> Result<PathBuf, &'static str> {
        let mut result = None;

        while dir.pop() {
            if dir.join("assets").exists() {
                result = Some(dir.join("assets"));
                break;
            }
        }

        result.ok_or("could not find assets directory")
    }
}

<commit_msg>Slight cleanup of mouse_cursor_grid_position()<commit_after>use std::{
    env::current_exe,
    path::PathBuf,
};
use piston_window::*;
use crate::{
    direction::Direction,
    game::*,
    settings::Settings,
};

pub struct App<'a> {
    window: PistonWindow,
    window_size: [u32; 2],
    game: Game<'a>,
    turn_active: bool,
    turn_end_timer: f64,
    cpu_turn_timer: f64,
    mouse_cursor: [f64; 2],
    grid_area: [u32; 4],
}

impl<'a> App<'a> {
    pub fn new(settings: &Settings) -> App {
        let grid_area = [
            settings.space_size,
            settings.space_size * 3,
            settings.spaces[0] as u32 * settings.space_size,
            settings.spaces[1] as u32 * settings.space_size
        ];

        let window_size = [
            grid_area[2] + settings.space_size * 2,
            grid_area[3] + settings.space_size * 4
        ];

        let window_title = "Battleship";
        let window: PistonWindow = WindowSettings::new(
            window_title,
            window_size,
            )
            .exit_on_esc(true)
            .resizable(false)
            .build()
            .unwrap();

        App {
            window: window,
            window_size: window_size,
            game: Game::new(&settings),
            turn_active: true,
            turn_end_timer: 0.0,
            cpu_turn_timer: 0.0,
            mouse_cursor: [0.0; 2],
            grid_area: grid_area,
        }
    }

    pub fn init(&mut self) {
        self.window.set_ups(60);
        self.window.set_max_fps(60);

        // TODO set textures in a not terrible way
        let assets_dir = Self::get_assets_dir(current_exe().unwrap()).unwrap();
        let images_dir: PathBuf = assets_dir.join("images");
        let mut space_textures = vec![];

        for state in 0..3 {
            let image_file = format!("gridspace-{}.png", state);
            space_textures.push(self.get_texture(images_dir.join(&image_file)));
        }

        space_textures.push(self.get_texture(images_dir.join("shipspace.png")));

        let grid_cursor_texture = self.get_texture(images_dir.join("grid-cursor.png"));

        let mut ship_textures = vec![];
        for ship_size in 2..6 {
            let image_file = format!("ship-{}.png", ship_size);
            ship_textures.push(self.get_texture(images_dir.join(&image_file)));
        }

        let player_text = [
            self.get_texture(images_dir.join("player-1.png")),
            self.get_texture(images_dir.join("player-2.png")),
        ];

        let game_over_text = [
            self.get_texture(images_dir.join("game-over.png")),
            self.get_texture(images_dir.join("wins.png")),
        ];

        while let Some(e) = self.window.next() {
            if let Some(p) = e.press_args() {
                match p {
                    Button::Mouse(mouse::MouseButton::Left) => self.mouse_left_click(),
                    Button::Mouse(mouse::MouseButton::Right) => self.button_secondary(),
                    Button::Keyboard(keyboard::Key::Left) => self.button_left(),
                    Button::Keyboard(keyboard::Key::Right) => self.button_right(),
                    Button::Keyboard(keyboard::Key::Up) => self.button_up(),
                    Button::Keyboard(keyboard::Key::Down) => self.button_down(),
                    Button::Keyboard(keyboard::Key::Return) => self.button_primary(),
                    Button::Keyboard(keyboard::Key::Space) => self.button_secondary(),
                    _ => {}
                }
            }

            if let Some(c) = e.mouse_cursor_args() {
                self.mouse_cursor_movement(&c);
            }

            if let Some(u) = e.update_args() {
                self.update(&u);
            }

            if e.render_args().is_some() {
                let current_player = self.game.active_player();
                let game_state = self.game.state();
                let shown_player = match game_state {
                    GameState::ShipPlacement => current_player,
                    _ => self.game.inactive_player()
                };

                let space_size_u32 = self.game.settings.space_size as u32;
                let grid_area = self.grid_area;
                let window_size = self.window_size;
                let turn_end_timer = self.turn_end_timer;
                let game_winner = self.game.get_winner();
                let game_turn = self.game.turn();
                let turn_active = self.turn_active;

                self.window.draw_2d(&e, |c, g| {
                    clear([0.6, 0.6, 1.0, 1.0], g);

                    // Ship icons above grid
                    for (i, ship) in shown_player.ships().iter().enumerate() {
                        if ship.is_active() {
                            let transform = c.transform.trans(
                                (space_size_u32 * 2 * i as u32 + grid_area[0] * 2) as f64,
                                30.0 as f64,
                            );
                            image(&ship_textures[i], transform, g);
                        }
                    }

                    // Grid spaces
                    for space in shown_player.spaces() {
                        let space_pos = space.pos();
                        let transform = c.transform.trans(
                            (space_size_u32 * space_pos[0] as u32 + grid_area[0]) as f64,
                            (space_size_u32 * space_pos[1] as u32 + grid_area[1]) as f64,
                        );

                        // Only show ship locations during ship placement or if the
                        // current player is computer-controlled.
                        if shown_player.ship_is_in_space(space_pos) && (game_state == GameState::ShipPlacement || (space.is_unchecked() && current_player.is_cpu)) {
                            image(&space_textures[3], transform, g);
                        } else {
                            let space_state = if space.is_unchecked() {
                                0
                            } else if space.is_empty() {
                                1
                            } else {
                                2
                            };
                            image(&space_textures[space_state], transform, g);
                        }
                    }

                    // During ship placement, show the temporary position of the
                    // next ship to be placed.
                    if game_state == GameState::ShipPlacement {
                        for pos in &shown_player.temp_ship_pos {
                            let transform = c.transform.trans(
                                (space_size_u32 * pos[0] as u32 + grid_area[0]) as f64,
                                (space_size_u32 * pos[1] as u32 + grid_area[1]) as f64,
                            );
                            image(&space_textures[3], transform, g);
                        }
                    }

                    // During the game, show the player's grid cursor.
                    if game_state == GameState::Active && turn_end_timer == 0.0 && !current_player.is_cpu {
                        let grid_cursor = current_player.get_grid_cursor();
                        let transform = c.transform.trans(
                            (space_size_u32 * grid_cursor[0] as u32 + grid_area[0]) as f64,
                            (space_size_u32 * grid_cursor[1] as u32 + grid_area[1]) as f64,
                        );
                        image(&grid_cursor_texture, transform, g);
                    }

                    // Current player text image
                    if game_winner.is_none() {
                        let turn = game_turn;
                        let player_text_size = player_text[turn].get_size();
                        let transform = c.transform.trans(
                            ((window_size[0] - player_text_size.0) / 2) as f64,
                            2.0
                        );
                        image(&player_text[turn], transform, g);
                    }

                    // During turn transitions / game over, cover the window with
                    // a black rectangle of increasing opacity.
                    if !turn_active && turn_end_timer >= 0.75 {
                        let alpha = if game_state != GameState::Over {
                            (turn_end_timer as f32 - 0.75) / 0.75
                        } else {
                            (turn_end_timer as f32 - 0.75) / 1.125
                        };
                        rectangle(
                            [0.0, 0.0, 0.0, alpha],
                            [0.0, 0.0, window_size[0] as f64, window_size[1] as f64],
                            c.transform,
                            g
                        );
                    }

                    // Game over content, to appear over the black rectangle.
                    if turn_end_timer >= 1.5 && game_winner.is_some() {
                        let winner = game_winner.unwrap();
                        let game_over_text_size = game_over_text[0].get_size();
                        let wins_text_size = game_over_text[1].get_size();
                        let player_text_size = player_text[winner].get_size();
                        image(&game_over_text[0], c.transform.trans(
                            ((window_size[0] - game_over_text_size.0) / 2) as f64,
                            2.0
                        ), g);
                        image(&player_text[winner], c.transform.trans(
                            ((window_size[0] - player_text_size.0 - wins_text_size.0 - 2) / 2) as f64,
                            22.0
                        ), g);
                        image(&game_over_text[1], c.transform.trans(
                            ((window_size[0] + player_text_size.0 - wins_text_size.0 + 2) / 2) as f64,
                            22.0
                        ), g);
                    }
                });
            }
        }
    }

    fn update(&mut self, u: &UpdateArgs) {
        let state = self.game.state();
        let active_player_is_cpu = self.game.is_active_player_cpu();

        if state == GameState::ShipPlacement {
            if active_player_is_cpu {
                self.game.active_player_mut().cpu_place_ships();
            }

            if self.game.active_player().ships().len() == 4 {
                self.game.switch_turn();
            }

            // All ships have been placed; start the game.
            if self.game.active_player().ships().len() == 4 && self.game.inactive_player().ships().len() == 4 {
                self.game.start();
            }
        } else if state == GameState::Active || state == GameState::Over {
            if !self.turn_active {
                // Continue/end the end-of-turn delay.
                if self.turn_end_timer < 1.5 {
                    self.turn_end_timer += u.dt;
                } else if state == GameState::Active {
                    self.game.switch_turn();
                    self.turn_end_timer = 0.0;
                    self.turn_active = true;
                }
            }

            // Continue/end the delay when CPU players take their turn.
            if self.turn_active && active_player_is_cpu {
                self.cpu_turn_timer += u.dt;

                if self.cpu_turn_timer >= 1.0 {
                    let cpu_space = self.game.inactive_player().cpu_select_space();
                    self.game.select_opponent_space(&cpu_space);
                    self.cpu_turn_timer = 0.0;
                    self.turn_active = false;
                }
            }
        }
    }

    fn primary_action(&mut self, grid_pos: &[u8; 2]) {
        if self.game.is_player_placing_ship() {
            self.game.active_player_mut().place_temp_ship();
        }

        if self.game.is_player_selecting_space() {
            self.game.select_opponent_space(grid_pos);
            self.turn_active = false;
        }
    }

    /// Processes left button presses according to the current program state.
    fn button_left(&mut self) {
        self.movement(Direction::West);
    }

    /// Processes right button presses according to the current program state.
    fn button_right(&mut self) {
        self.movement(Direction::East);
    }

    /// Processes up button presses according to the current program state.
    fn button_up(&mut self) {
        self.movement(Direction::North);
    }

    /// Processes down button presses according to the current program state.
    fn button_down(&mut self) {
        self.movement(Direction::South);
    }

    /// Processes primary button presses according to the current program state.
    fn button_primary(&mut self) {
        let grid_pos = self.game.active_player().get_grid_cursor();
        self.primary_action(&grid_pos);
    }

    /// Processes secondary button presses according to the current program state.
    fn button_secondary(&mut self) {
        if self.game.is_player_placing_ship() {
            self.game.active_player_mut().rotate_temp_ship();
        }
    }

    /// Processes left mouse clicks according to the current program state.
    fn mouse_left_click(&mut self) {
        if let Some(grid_pos) = self.mouse_cursor_grid_position() {
            self.primary_action(&grid_pos);
        }
    }

    /// Performs grid movement according to the current program state.
    fn movement(&mut self, direction: Direction) {
        if self.game.is_player_placing_ship() {
            self.game.active_player_mut().move_temp_ship(direction);
        }

        if self.game.is_player_selecting_space() {
            self.game.active_player_mut().move_grid_cursor(direction);
        }
    }

    /// Records the last known mouse cursor position.
    pub fn mouse_cursor_movement(&mut self, c: &[f64; 2]) {
        self.mouse_cursor = *c;

        if let Some(grid_pos) = self.mouse_cursor_grid_position() {
            let is_player_turn = self.game.is_player_turn();
            let game_state = self.game.state();
            let ref mut player = self.game.active_player_mut();

            if game_state == GameState::ShipPlacement {
                if let Some(ship) = player.get_ship_position(
                    grid_pos,
                    player.temp_ship_dir,
                    player.ships().len() as u8 + 2
                ) {
                    player.temp_ship_pos = ship;
                }
            }

            if game_state == GameState::Active && is_player_turn {
                player.set_grid_cursor(&grid_pos);
            }
        }
    }

    /// Returns the grid coordinates of the mouse cursor position.
    fn mouse_cursor_grid_position(&self) -> Option<[u8; 2]> {
        match self.mouse_over_grid() {
            true => Some([
                ((self.mouse_cursor[0] - self.grid_area[0] as f64) / self.grid_area[0] as f64) as u8,
                ((self.mouse_cursor[1] - self.grid_area[1] as f64) / self.grid_area[0] as f64) as u8,
            ]),
            false => None,
        }
    }

    fn mouse_over_grid(&self) -> bool {
        self.mouse_cursor[0] >= self.grid_area[0] as f64
        && self.mouse_cursor[1] >= self.grid_area[1] as f64
        && self.mouse_cursor[0] < (self.grid_area[0] + self.grid_area[2]) as f64
        && self.mouse_cursor[1] < (self.grid_area[1] + self.grid_area[3]) as f64
    }

    /// Returns the texture from the file at the given path.
    fn get_texture(&mut self, path: PathBuf) -> G2dTexture {
        Texture::from_path(
            &mut self.window.factory,
            path,
            Flip::None,
            &TextureSettings::new(),
        ).unwrap()
    }

    /// Returns the assets directory, if it could be found.
    fn get_assets_dir(mut dir: PathBuf) -> Result<PathBuf, &'static str> {
        let mut result = None;

        while dir.pop() {
            if dir.join("assets").exists() {
                result = Some(dir.join("assets"));
                break;
            }
        }

        result.ok_or("could not find assets directory")
    }
}

<|endoftext|>