<commit_before>use kay::{World, Fate, ActorSystem};
use compact::CVec;
use descartes::{N, P2, V2, Area, WithUniqueOrthogonal, ClosedLinePath, LinePath,
AreaError, AreaEmbedding, AreaFilter};
use ordered_float::OrderedFloat;

use land_use::zone_planning::{Lot, BuildingIntent};
use land_use::buildings::BuildingStyle;
use land_use::buildings::architecture::ideal_lot_shape;
use economy::immigration_and_development::DevelopmentManagerID;
use itertools::{Itertools, MinMaxResult};

use cb_planning::construction::{Constructable, ConstructableID};
use cb_planning::{Prototype, PrototypeID};
use planning::{CBConstructionID, CBPrototypeKind};
use cb_util::log::{debug, error};
const LOG_T: &str = "Vacant Lots";

#[derive(Compact, Clone)]
pub struct VacantLot {
    pub id: VacantLotID,
    pub lot: Lot,
    based_on: PrototypeID,
}

impl Lot {
    pub fn width_depth_per_road_connection(&self) -> Vec<(P2, V2, f32, f32)> {
        let midpoints = self
            .area
            .primitives
            .iter()
            .flat_map(|primitive| {
                primitive
                    .boundary
                    .path()
                    .segments()
                    .map(|segment| segment.midpoint())
            })
            .collect::<Vec<_>>();

        self.all_road_connections()
            .into_iter()
            .map(|(point, direction)| {
                let depth_direction = direction;
                let width_direction = depth_direction.orthogonal_right();

                let depth = if let MinMaxResult::MinMax(front, back) = midpoints
                    .iter()
                    .map(|midpoint| OrderedFloat((*midpoint - point).dot(&depth_direction)))
                    .minmax()
                {
                    *back - *front
                } else {
                    0.0
                };

                let width = if let MinMaxResult::MinMax(left, right) = midpoints
                    .iter()
                    .map(|midpoint| OrderedFloat((*midpoint - point).dot(&width_direction)))
                    .minmax()
                {
                    *right - *left
                } else {
                    0.0
                };

                (point, direction, width, depth)
            })
            .collect()
    }

    #[allow(clippy::too_many_arguments)]
    pub fn split_for(
        &self,
        building_style: BuildingStyle,
        allow_left_split: bool,
        allow_right_split: bool,
        max_width_after_splitting: N,
        recursion_depth: usize,
        log_as: VacantLotID,
        log_in: &mut World,
    ) -> Result<Option<Lot>, AreaError> {
        let (needed_width, needed_depth, needed_compactness) = ideal_lot_shape(building_style);

        let debug_padding: String = ::std::iter::repeat(" ").take(recursion_depth).collect();
        debug(
            LOG_T,
            format!(
                "{}Trying to suggest lot for {:?}. Road Boundaries {:?}",
                debug_padding,
                building_style,
                self.road_boundaries
                    .iter()
                    .map(LinePath::length)
                    .collect::<Vec<_>>(),
            ),
            log_as,
            log_in,
        );

        // TODO: fix fucked up orientation of areas compared to descartes!!
        let compactness = -self.area.primitives[0].area() * 4.0 * ::std::f32::consts::PI
            / self.area.primitives[0].boundary.path().length().powi(2);

        for (point, direction, width, depth) in self.width_depth_per_road_connection() {
            debug(
                LOG_T,
                format!(
                    "{}Is: {:?} Needed: {:?}",
                    debug_padding,
                    (width, depth, compactness),
                    (needed_width, needed_depth, needed_compactness)
                ),
                log_as,
                log_in,
            );

            // make sure that we're making progress after recursing
            if width < max_width_after_splitting {
                let width_ratio = width / needed_width;
                let depth_ratio = depth / needed_depth;

                if width_ratio > 0.5 && width_ratio < 2.0 && depth_ratio > 0.5 && depth_ratio < 2.0
                {
                    if compactness >= needed_compactness {
                        return Ok(Some(self.clone()));
                    } else {
                        return Ok(None);
                    }
                } else if width_ratio > 2.0 && depth_ratio > 0.5 {
                    let orthogonal = direction.orthogonal_right();

                    let corners = vec![
                        point + needed_depth * direction,
                        point + needed_depth * direction + 1_000.0 * orthogonal,
                        point - 500.0 * direction + 1_000.0 * orthogonal,
                        point - 500.0 * direction,
                        point + needed_depth * direction,
                    ];

                    let splitter_area = Area::new_simple(
                        ClosedLinePath::new(LinePath::new(corners.into()).unwrap()).unwrap(),
                    );

                    debug(
                        LOG_T,
                        format!("{}Attempting width split", debug_padding),
                        log_as,
                        log_in,
                    );

                    #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
                    enum SplittingLabel {
                        Lot,
                        Splitter,
                    };

                    let mut embedding = AreaEmbedding::new(width / 10.0);

                    embedding.insert(self.area.clone(), SplittingLabel::Lot);
                    embedding.insert(splitter_area, SplittingLabel::Splitter);

                    if allow_right_split {
                        if let Ok(right_splits) = embedding
                            .view(
                                AreaFilter::has(SplittingLabel::Lot)
                                    .and(AreaFilter::has(SplittingLabel::Splitter)),
                            )
                            .get_areas_with_pieces()
                        {
                            for (right_split, _) in right_splits {
                                let road_boundaries = new_road_boundaries(
                                    &self.road_boundaries,
                                    right_split.primitives[0].boundary.path(),
                                );

                                if !road_boundaries.is_empty() {
                                    let split_lot = Lot {
                                        road_boundaries,
                                        area: right_split.clone(),
                                        original_area: right_split,
                                        ..self.clone()
                                    };

                                    // recurse!
                                    debug(
                                        LOG_T,
                                        format!(
                                            "{}Got right split lot, checking suitability",
                                            debug_padding,
                                        ),
                                        log_as,
                                        log_in,
                                    );
                                    if let Some(ok_split_lot) = split_lot.split_for(
                                        building_style,
                                        true, //false,
                                        true,
                                        width * 0.66,
                                        recursion_depth + 1,
                                        log_as,
                                        log_in,
                                    )? {
                                        return Ok(Some(ok_split_lot));
                                    }
                                }
                            }
                        }
                    }
                    if allow_left_split {
                        if let Ok(left_splits) = embedding
                            .view(
                                AreaFilter::has(SplittingLabel::Lot)
                                    .and(AreaFilter::has(SplittingLabel::Splitter).not()),
                            )
                            .get_areas_with_pieces()
                        {
                            for (left_split, _) in left_splits {
                                let road_boundaries = new_road_boundaries(
                                    &self.road_boundaries,
                                    left_split.primitives[0].boundary.path(),
                                );

                                if !road_boundaries.is_empty() {
                                    let split_lot = Lot {
                                        road_boundaries,
                                        original_area: left_split.clone(),
                                        area: left_split,
                                        ..self.clone()
                                    };

                                    // recurse!
                                    debug(
                                        LOG_T,
                                        format!(
                                            "{}Got right split lot, checking suitability",
                                            debug_padding,
                                        ),
                                        log_as,
                                        log_in,
                                    );
                                    if let Some(ok_split_lot) = split_lot.split_for(
                                        building_style,
                                        true,
                                        true, //false,
                                        width * 0.66,
                                        recursion_depth + 1,
                                        log_as,
                                        log_in,
                                    )? {
                                        return Ok(Some(ok_split_lot));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        Ok(None)
    }
}

fn new_road_boundaries(old_road_bundaries: &[LinePath], new_boundary: &LinePath) -> CVec<LinePath> {
    new_boundary
        .points
        .iter()
        .batching(|points_it| {
            let mut consecutive_points = CVec::new();

            loop {
                if let Some(point) = points_it.next() {
                    if old_road_bundaries
                        .iter()
                        .any(|old_boundary| old_boundary.includes(*point))
                    {
                        consecutive_points.push(*point);
                    } else {
                        break;
                    }
                } else if consecutive_points.is_empty() {
                    return None;
                } else {
                    break;
                }
            }

            Some(LinePath::new(consecutive_points))
        })
        .filter_map(|maybe_new_boundary| maybe_new_boundary)
        .collect()
}

impl VacantLot {
    pub fn spawn(
        id: VacantLotID,
        lot: &Lot,
        based_on: PrototypeID,
        _world: &mut World,
    ) -> VacantLot {
        VacantLot {
            id,
            based_on,
            lot: lot.clone(),
        }
    }

    pub fn suggest_lot(
        &mut self,
        building_style: BuildingStyle,
        requester: DevelopmentManagerID,
        world: &mut World,
    ) {
        if self
            .lot
            .land_uses
            .iter()
            .any(|land_use| building_style.can_appear_in(*land_use))
        {
            debug(LOG_T, "Trying suggest", self.id, world);
            match self.lot.split_for(
                building_style,
                true,
                true,
                ::std::f32::INFINITY,
                0,
                self.id,
                world,
            ) {
                Ok(Some(suitable_lot)) => requester.on_suggested_lot(
                    BuildingIntent {
                        lot: suitable_lot,
                        building_style,
                    },
                    self.based_on,
                    world,
                ),
                Ok(None) => {}
                Err(_err) => error(LOG_T, "Geometry", self.id, world),
            }
        }
    }
}

impl Constructable<CBPrototypeKind> for VacantLot {
    fn morph(
        &mut self,
        _: &Prototype<CBPrototypeKind>,
        _report_to: CBConstructionID,
        _world: &mut World,
    ) {
        unreachable!()
    }

    fn destruct(&mut self, report_to: CBConstructionID, world: &mut World) -> Fate {
        report_to.action_done(self.id.into(), world);
        Fate::Die
    }
}

pub fn setup(system: &mut ActorSystem) {
    system.register::<VacantLot>();
    auto_setup(system);
}

mod kay_auto;
pub use self::kay_auto::*;
<commit_msg>Manually implement #298<commit_after>use kay::{World, Fate, ActorSystem};
use compact::CVec;
use descartes::{N, P2, V2, Area, WithUniqueOrthogonal, ClosedLinePath, LinePath,
AreaError, AreaEmbedding, AreaFilter};
use ordered_float::OrderedFloat;

use land_use::zone_planning::{Lot, BuildingIntent};
use land_use::buildings::BuildingStyle;
use land_use::buildings::architecture::ideal_lot_shape;
use economy::immigration_and_development::DevelopmentManagerID;
use itertools::Itertools;

use cb_planning::construction::{Constructable, ConstructableID};
use cb_planning::{Prototype, PrototypeID};
use planning::{CBConstructionID, CBPrototypeKind};
use cb_util::log::{debug, error};
const LOG_T: &str = "Vacant Lots";

#[derive(Compact, Clone)]
pub struct VacantLot {
    pub id: VacantLotID,
    pub lot: Lot,
    based_on: PrototypeID,
}

impl Lot {
    pub fn width_depth_per_road_connection(&self) -> Vec<(P2, V2, f32, f32)> {
        let midpoints = self
            .area
            .primitives
            .iter()
            .flat_map(|primitive| {
                primitive
                    .boundary
                    .path()
                    .segments()
                    .map(|segment| segment.midpoint())
            })
            .collect::<Vec<_>>();

        self.all_road_connections()
            .into_iter()
            .map(|(point, direction)| {
                let depth_direction = direction;
                let width_direction = depth_direction.orthogonal_right();

                let depth = midpoints
                    .iter()
                    .map(|midpoint| OrderedFloat((*midpoint - point).dot(&depth_direction)))
                    .minmax()
                    .into_option()
	                .map_or(0.0, |(front, back)| *back - *front);

                let width = midpoints
                    .iter()
                    .map(|midpoint| OrderedFloat((*midpoint - point).dot(&width_direction)))
                    .minmax()
                    .into_option()
                    .map_or(0.0, |(left, right)| *right - *left);

                (point, direction, width, depth)
            })
            .collect()
    }

    #[allow(clippy::too_many_arguments)]
    pub fn split_for(
        &self,
        building_style: BuildingStyle,
        allow_left_split: bool,
        allow_right_split: bool,
        max_width_after_splitting: N,
        recursion_depth: usize,
        log_as: VacantLotID,
        log_in: &mut World,
    ) -> Result<Option<Lot>, AreaError> {
        let (needed_width, needed_depth, needed_compactness) = ideal_lot_shape(building_style);

        let debug_padding: String = ::std::iter::repeat(" ").take(recursion_depth).collect();
        debug(
            LOG_T,
            format!(
                "{}Trying to suggest lot for {:?}. Road Boundaries {:?}",
                debug_padding,
                building_style,
                self.road_boundaries
                    .iter()
                    .map(LinePath::length)
                    .collect::<Vec<_>>(),
            ),
            log_as,
            log_in,
        );

        // TODO: fix fucked up orientation of areas compared to descartes!!
        let compactness = -self.area.primitives[0].area() * 4.0 * ::std::f32::consts::PI
            / self.area.primitives[0].boundary.path().length().powi(2);

        for (point, direction, width, depth) in self.width_depth_per_road_connection() {
            debug(
                LOG_T,
                format!(
                    "{}Is: {:?} Needed: {:?}",
                    debug_padding,
                    (width, depth, compactness),
                    (needed_width, needed_depth, needed_compactness)
                ),
                log_as,
                log_in,
            );

            // make sure that we're making progress after recursing
            if width < max_width_after_splitting {
                let width_ratio = width / needed_width;
                let depth_ratio = depth / needed_depth;

                if width_ratio > 0.5 && width_ratio < 2.0 && depth_ratio > 0.5 && depth_ratio < 2.0
                {
                    if compactness >= needed_compactness {
                        return Ok(Some(self.clone()));
                    } else {
                        return Ok(None);
                    }
                } else if width_ratio > 2.0 && depth_ratio > 0.5 {
                    let orthogonal = direction.orthogonal_right();

                    let corners = vec![
                        point + needed_depth * direction,
                        point + needed_depth * direction + 1_000.0 * orthogonal,
                        point - 500.0 * direction + 1_000.0 * orthogonal,
                        point - 500.0 * direction,
                        point + needed_depth * direction,
                    ];

                    let splitter_area = Area::new_simple(
                        ClosedLinePath::new(LinePath::new(corners.into()).unwrap()).unwrap(),
                    );

                    debug(
                        LOG_T,
                        format!("{}Attempting width split", debug_padding),
                        log_as,
                        log_in,
                    );

                    #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
                    enum SplittingLabel {
                        Lot,
                        Splitter,
                    };

                    let mut embedding = AreaEmbedding::new(width / 10.0);

                    embedding.insert(self.area.clone(), SplittingLabel::Lot);
                    embedding.insert(splitter_area, SplittingLabel::Splitter);

                    if allow_right_split {
                        if let Ok(right_splits) = embedding
                            .view(
                                AreaFilter::has(SplittingLabel::Lot)
                                    .and(AreaFilter::has(SplittingLabel::Splitter)),
                            )
                            .get_areas_with_pieces()
                        {
                            for (right_split, _) in right_splits {
                                let road_boundaries = new_road_boundaries(
                                    &self.road_boundaries,
                                    right_split.primitives[0].boundary.path(),
                                );

                                if !road_boundaries.is_empty() {
                                    let split_lot = Lot {
                                        road_boundaries,
                                        area: right_split.clone(),
                                        original_area: right_split,
                                        ..self.clone()
                                    };

                                    // recurse!
                                    debug(
                                        LOG_T,
                                        format!(
                                            "{}Got right split lot, checking suitability",
                                            debug_padding,
                                        ),
                                        log_as,
                                        log_in,
                                    );
                                    if let Some(ok_split_lot) = split_lot.split_for(
                                        building_style,
                                        true, //false,
                                        true,
                                        width * 0.66,
                                        recursion_depth + 1,
                                        log_as,
                                        log_in,
                                    )? {
                                        return Ok(Some(ok_split_lot));
                                    }
                                }
                            }
                        }
                    }
                    if allow_left_split {
                        if let Ok(left_splits) = embedding
                            .view(
                                AreaFilter::has(SplittingLabel::Lot)
                                    .and(AreaFilter::has(SplittingLabel::Splitter).not()),
                            )
                            .get_areas_with_pieces()
                        {
                            for (left_split, _) in left_splits {
                                let road_boundaries = new_road_boundaries(
                                    &self.road_boundaries,
                                    left_split.primitives[0].boundary.path(),
                                );

                                if !road_boundaries.is_empty() {
                                    let split_lot = Lot {
                                        road_boundaries,
                                        original_area: left_split.clone(),
                                        area: left_split,
                                        ..self.clone()
                                    };

                                    // recurse!
                                    debug(
                                        LOG_T,
                                        format!(
                                            "{}Got right split lot, checking suitability",
                                            debug_padding,
                                        ),
                                        log_as,
                                        log_in,
                                    );
                                    if let Some(ok_split_lot) = split_lot.split_for(
                                        building_style,
                                        true,
                                        true, //false,
                                        width * 0.66,
                                        recursion_depth + 1,
                                        log_as,
                                        log_in,
                                    )? {
                                        return Ok(Some(ok_split_lot));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        Ok(None)
    }
}

fn new_road_boundaries(old_road_bundaries: &[LinePath], new_boundary: &LinePath) -> CVec<LinePath> {
    new_boundary
        .points
        .iter()
        .batching(|points_it| {
            let mut consecutive_points = CVec::new();

            loop {
                if let Some(point) = points_it.next() {
                    if old_road_bundaries
                        .iter()
                        .any(|old_boundary| old_boundary.includes(*point))
                    {
                        consecutive_points.push(*point);
                    } else {
                        break;
                    }
                } else if consecutive_points.is_empty() {
                    return None;
                } else {
                    break;
                }
            }

            Some(LinePath::new(consecutive_points))
        })
        .filter_map(|maybe_new_boundary| maybe_new_boundary)
        .collect()
}

impl VacantLot {
    pub fn spawn(
        id: VacantLotID,
        lot: &Lot,
        based_on: PrototypeID,
        _world: &mut World,
    ) -> VacantLot {
        VacantLot {
            id,
            based_on,
            lot: lot.clone(),
        }
    }

    pub fn suggest_lot(
        &mut self,
        building_style: BuildingStyle,
        requester: DevelopmentManagerID,
        world: &mut World,
    ) {
        if self
            .lot
            .land_uses
            .iter()
            .any(|land_use| building_style.can_appear_in(*land_use))
        {
            debug(LOG_T, "Trying suggest", self.id, world);
            match self.lot.split_for(
                building_style,
                true,
                true,
                ::std::f32::INFINITY,
                0,
                self.id,
                world,
            ) {
                Ok(Some(suitable_lot)) => requester.on_suggested_lot(
                    BuildingIntent {
                        lot: suitable_lot,
                        building_style,
                    },
                    self.based_on,
                    world,
                ),
                Ok(None) => {}
                Err(_err) => error(LOG_T, "Geometry", self.id, world),
            }
        }
    }
}

impl Constructable<CBPrototypeKind> for VacantLot {
    fn morph(
        &mut self,
        _: &Prototype<CBPrototypeKind>,
        _report_to: CBConstructionID,
        _world: &mut World,
    ) {
        unreachable!()
    }

    fn destruct(&mut self, report_to: CBConstructionID, world: &mut World) -> Fate {
        report_to.action_done(self.id.into(), world);
        Fate::Die
    }
}

pub fn setup(system: &mut ActorSystem) {
    system.register::<VacantLot>();
    auto_setup(system);
}

mod kay_auto;
pub use self::kay_auto::*;
<|endoftext|>


<commit_before>//! Metrics sent to GA.
//!
//! This module defines some events that can be used to capture usage metrics and send them
//! to a Google Analytics account.

use std::cell::RefCell;
use std::env::consts::{ARCH, OS};
use std::env::var;
use std::process::Command;
use std::str;
use std::sync::Mutex;

use anyhow::anyhow;
use lazy_static::lazy_static;
use maplit::hashmap;
use reqwest::Client;
use tracing::{debug, warn};
use uuid::Uuid;

static CIS: &'static [&str] = &[
  "CI",
  "CONTINUOUS_INTEGRATION",
  "BSTRUSE_BUILD_DIR",
  "APPVEYOR",
  "BUDDY_WORKSPACE_URL",
  "BUILDKITE",
  "CF_BUILD_URL",
  "CIRCLECI",
  "CODEBUILD_BUILD_ARN",
  "CONCOURSE_URL",
  "DRONE",
  "GITLAB_CI",
  "GO_SERVER_URL",
  "JENKINS_URL",
  "PROBO_ENVIRONMENT",
  "SEMAPHORE",
  "SHIPPABLE",
  "TDDIUM",
  "TEAMCITY_VERSION",
  "TF_BUILD",
  "TRAVIS",
  "WERCKER_ROOT"
];

/// Metric events to send
pub enum MetricEvent {
  /// Consumer test was run (number of interactions)
  ConsumerTestRun {
    /// Number of interactions in the test
    interactions: usize,
    /// Test framework used
    test_framework: String,
    /// Application name that executed the test
    app_name: String,
    /// Application version that executed the test
    app_version: String
  },

  /// Provider verification test ran (mode build tool or unit test etc.)
  ProviderVerificationRan {
    /// Number of verification tests run
    tests_run: usize,
    /// Test framework used
    test_framework: String,
    /// Application name that executed the test
    app_name: String,
    /// Application version that executed the test
    app_version: String
  }
}

impl MetricEvent {
  /// Application name for the event. For FFI calls, this will be the app doing the calling.
  pub(crate) fn app_name(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { app_name, .. } => app_name.as_str(),
      MetricEvent::ProviderVerificationRan { app_name, .. } => app_name.as_str()
    }
  }

  /// Application version for the event. For FFI calls, this will be the app doing the calling.
  pub(crate) fn app_version(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { app_version, .. } => app_version.as_str(),
      MetricEvent::ProviderVerificationRan { app_version, .. } => app_version.as_str()
    }
  }

  /// Test framework used (unit test framework or build tool)
  pub(crate) fn test_framework(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { test_framework, .. } => test_framework.as_str(),
      MetricEvent::ProviderVerificationRan { test_framework, .. } => test_framework.as_str()
    }
  }

  /// Event name
  pub(crate) fn name(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { .. } => "Pact consumer tests ran",
      MetricEvent::ProviderVerificationRan { .. } => "Pacts verified"
    }
  }

  /// Event category
  pub(crate) fn category(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { .. } => "ConsumerTest",
      MetricEvent::ProviderVerificationRan { .. } => "ProviderTest"
    }
  }

  /// Event action that occurred
  pub(crate) fn action(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { .. } => "Completed",
      MetricEvent::ProviderVerificationRan { .. } => "Completed"
    }
  }

  /// Value for the event
  pub(crate) fn value(&self) -> String {
    match self {
      MetricEvent::ConsumerTestRun { interactions, .. } => interactions.to_string(),
      MetricEvent::ProviderVerificationRan { tests_run, .. } => tests_run.to_string()
    }
  }
}

const GA_ACCOUNT: &str = "UA-117778936-1";
const GA_URL: &str = "https://www.google-analytics.com/collect";

lazy_static! {
  static ref WARNING_LOGGED: Mutex<RefCell<bool>> = Mutex::new(RefCell::new(false));
}

/// This sends anonymous metrics to a Google Analytics account. It is used to track usage of
/// Pact library and operating system versions. This can be disabled by setting the
/// `pact_do_not_track` environment variable to `true`.
///
/// This function needs to run in the context of a Tokio runtime.
pub fn send_metrics(event: MetricEvent) {
  let do_not_track = var("PACT_DO_NOT_TRACK")
    .or_else(|_| var("pact_do_not_track"))
    .map(|v| v == "true")
    .unwrap_or(false);

  if do_not_track {
    debug!("'PACT_DO_NOT_TRACK' environment variable is set, will not send metrics");
  } else {
    match tokio::runtime::Handle::try_current() {
      Ok(handle) => {
        let mut guard = WARNING_LOGGED.lock().unwrap();
        let warning_logged = (*guard).get_mut();
        if *warning_logged == false {
          warn!(
            "\n\nPlease note:\n\
            We are tracking events anonymously to gather important usage statistics like Pact version \
            and operating system. To disable tracking, set the 'PACT_DO_NOT_TRACK' environment \
            variable to 'true'.\n\n"
          );
          *warning_logged = true;
        }

        handle.spawn(async move {
          let ci_context = if CIS.iter()
            .any(|n| var(n).map(|val| !val.is_empty()).unwrap_or(false)) {
            "CI"
          } else {
            "unknown"
          };
          let osarch = format!("{}-{}", OS, ARCH);
          let uid = hostname_hash();
          let value = event.value();

          let event_payload = hashmap!{
            "v" => "1",                                       // Version of the API
            "t" => "event",                                   // Hit type, Specifies the metric is for an event
            "tid" => GA_ACCOUNT,                              // Property ID
            "cid" => uid.as_str(),                            // Anonymous Client ID.
            "an" => event.app_name(),                         // App name.
            "aid" => event.app_name(),                        // App Id
            "av" => event.app_version(),                      // App version.
            "aip" => "true",                                  // Anonymise IP address
            "ds" => "client",                                 // Data source
            "cd2" => ci_context,                              // Custom Dimension 2: context
            "cd3" => osarch.as_str(),                         // Custom Dimension 3: osarch
            "cd6" => event.test_framework(),                  // Custom Dimension 6: test_framework
            "cd7" => env!("CARGO_PKG_VERSION"),               // Custom Dimension 7: platform_version
            "el" => event.name(),                             // Event
            "ec" => event.category(),                         // Category
            "ea" => event.action(),                           // Action
            "ev" => value.as_str()                            // Value
          };
          debug!("Sending event to GA - {:?}", event_payload);
          let result = Client::new().post(GA_URL)
            .form(&event_payload)
            .send()
            .await;
          if let Err(err) = result {
            debug!("Failed to post event - {}", err);
          }
        });
      },
      Err(err) => {
        debug!("Could not get the tokio runtime, will not send metrics - {}", err)
      }
    }
  }
}

/// Calculates a one-way hash of the hostname where the event occurred
fn hostname_hash() -> String {
  let host_name = if OS == "windows" {
    var("COMPUTERNAME")
  } else {
    var("HOSTNAME")
  }.or_else(|_| {
    exec_hostname_command()
  }).unwrap_or_else(|_| {
    Uuid::new_v4().to_string()
  });

  let digest = md5::compute(host_name.as_bytes());
  format!("{:x}", digest)
}

/// Execute the hostname command to get the hostname
fn exec_hostname_command() -> anyhow::Result<String> {
  match Command::new("hostname").output() {
    Ok(output) => if output.status.success() {
      Ok(str::from_utf8(&*output.stdout)?.to_string())
    } else {
      Err(anyhow!("Failed to invoke hostname command: status {}", output.status))
    }
    Err(err) => Err(anyhow!("Failed to invoke hostname command: {}", err))
  }
}
<commit_msg>refactor: split metrics into sync and async functions<commit_after>//! Metrics sent to GA.
//!
//! This module defines some events that can be used to capture usage metrics and send them
//! to a Google Analytics account.

use std::cell::RefCell;
use std::env::consts::{ARCH, OS};
use std::env::var;
use std::process::Command;
use std::str;
use std::sync::Mutex;

use anyhow::anyhow;
use lazy_static::lazy_static;
use maplit::hashmap;
use reqwest::Client;
use tracing::{debug, warn};
use uuid::Uuid;

static CIS: &'static [&str] = &[
  "CI",
  "CONTINUOUS_INTEGRATION",
  "BSTRUSE_BUILD_DIR",
  "APPVEYOR",
  "BUDDY_WORKSPACE_URL",
  "BUILDKITE",
  "CF_BUILD_URL",
  "CIRCLECI",
  "CODEBUILD_BUILD_ARN",
  "CONCOURSE_URL",
  "DRONE",
  "GITLAB_CI",
  "GO_SERVER_URL",
  "JENKINS_URL",
  "PROBO_ENVIRONMENT",
  "SEMAPHORE",
  "SHIPPABLE",
  "TDDIUM",
  "TEAMCITY_VERSION",
  "TF_BUILD",
  "TRAVIS",
  "WERCKER_ROOT"
];

/// Metric events to send
pub enum MetricEvent {
  /// Consumer test was run (number of interactions)
  ConsumerTestRun {
    /// Number of interactions in the test
    interactions: usize,
    /// Test framework used
    test_framework: String,
    /// Application name that executed the test
    app_name: String,
    /// Application version that executed the test
    app_version: String
  },

  /// Provider verification test ran (mode build tool or unit test etc.)
  ProviderVerificationRan {
    /// Number of verification tests run
    tests_run: usize,
    /// Test framework used
    test_framework: String,
    /// Application name that executed the test
    app_name: String,
    /// Application version that executed the test
    app_version: String
  }
}

impl MetricEvent {
  /// Application name for the event. For FFI calls, this will be the app doing the calling.
  pub(crate) fn app_name(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { app_name, .. } => app_name.as_str(),
      MetricEvent::ProviderVerificationRan { app_name, .. } => app_name.as_str()
    }
  }

  /// Application version for the event. For FFI calls, this will be the app doing the calling.
  pub(crate) fn app_version(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { app_version, .. } => app_version.as_str(),
      MetricEvent::ProviderVerificationRan { app_version, .. } => app_version.as_str()
    }
  }

  /// Test framework used (unit test framework or build tool)
  pub(crate) fn test_framework(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { test_framework, .. } => test_framework.as_str(),
      MetricEvent::ProviderVerificationRan { test_framework, .. } => test_framework.as_str()
    }
  }

  /// Event name
  pub(crate) fn name(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { .. } => "Pact consumer tests ran",
      MetricEvent::ProviderVerificationRan { .. } => "Pacts verified"
    }
  }

  /// Event category
  pub(crate) fn category(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { .. } => "ConsumerTest",
      MetricEvent::ProviderVerificationRan { .. } => "ProviderTest"
    }
  }

  /// Event action that occurred
  pub(crate) fn action(&self) -> &str {
    match self {
      MetricEvent::ConsumerTestRun { .. } => "Completed",
      MetricEvent::ProviderVerificationRan { .. } => "Completed"
    }
  }

  /// Value for the event
  pub(crate) fn value(&self) -> String {
    match self {
      MetricEvent::ConsumerTestRun { interactions, .. } => interactions.to_string(),
      MetricEvent::ProviderVerificationRan { tests_run, .. } => tests_run.to_string()
    }
  }
}

const GA_ACCOUNT: &str = "UA-117778936-1";
const GA_URL: &str = "https://www.google-analytics.com/collect";

lazy_static! {
  static ref WARNING_LOGGED: Mutex<RefCell<bool>> = Mutex::new(RefCell::new(false));
}

/// This sends anonymous metrics to a Google Analytics account. It is used to track usage of
/// Pact library and operating system versions. This can be disabled by setting the
/// `pact_do_not_track` environment variable to `true`.
///
/// This function needs to run in the context of a Tokio runtime.
pub fn send_metrics(event: MetricEvent) {
  match tokio::runtime::Handle::try_current() {
    Ok(handle) => {
      handle.spawn(async move {
        send_metrics_async(event).await
      });
    },
    Err(err) => {
      debug!("Could not get the tokio runtime, will not send metrics - {}", err)
    }
  }
}

/// This sends anonymous metrics to a Google Analytics account. It is used to track usage of
/// Pact library and operating system versions. This can be disabled by setting the
/// `pact_do_not_track` environment variable to `true`.
pub async fn send_metrics_async(event: MetricEvent) {
  if do_not_track() {
    debug!("'PACT_DO_NOT_TRACK' environment variable is set, will not send metrics");
  } else {
    log_warning();
    let ci_context = if CIS.iter()
      .any(|n| var(n).map(|val| !val.is_empty()).unwrap_or(false)) {
      "CI"
    } else {
      "unknown"
    };
    let osarch = format!("{}-{}", OS, ARCH);
    let uid = hostname_hash();
    let value = event.value();

    let event_payload = hashmap! {
          "v" => "1",                                       // Version of the API
          "t" => "event",                                   // Hit type, Specifies the metric is for an event
          "tid" => GA_ACCOUNT,                              // Property ID
          "cid" => uid.as_str(),                            // Anonymous Client ID.
          "an" => event.app_name(),                         // App name.
          "aid" => event.app_name(),                        // App Id
          "av" => event.app_version(),                      // App version.
          "aip" => "true",                                  // Anonymise IP address
          "ds" => "client",                                 // Data source
          "cd2" => ci_context,                              // Custom Dimension 2: context
          "cd3" => osarch.as_str(),                         // Custom Dimension 3: osarch
          "cd6" => event.test_framework(),                  // Custom Dimension 6: test_framework
          "cd7" => env!("CARGO_PKG_VERSION"),               // Custom Dimension 7: platform_version
          "el" => event.name(),                             // Event
          "ec" => event.category(),                         // Category
          "ea" => event.action(),                           // Action
          "ev" => value.as_str()                            // Value
          };
    debug!("Sending event to GA - {:?}", event_payload);
    let result = Client::new().post(GA_URL)
      .form(&event_payload)
      .send()
      .await;
    if let Err(err) = result {
      debug!("Failed to post event - {}", err);
    }
  }
}

fn log_warning() {
  let mut guard = WARNING_LOGGED.lock().unwrap();
  let warning_logged = (*guard).get_mut();
  if *warning_logged == false {
    warn!(
      "\n\nPlease note:\n\
      We are tracking events anonymously to gather important usage statistics like Pact version \
      and operating system. To disable tracking, set the 'PACT_DO_NOT_TRACK' environment \
      variable to 'true'.\n\n"
    );
    *warning_logged = true;
  }
}

fn do_not_track() -> bool {
  var("PACT_DO_NOT_TRACK")
    .or_else(|_| var("pact_do_not_track"))
    .map(|v| v == "true")
    .unwrap_or(false)
}

/// Calculates a one-way hash of the hostname where the event occurred
fn hostname_hash() -> String {
  let host_name = if OS == "windows" {
    var("COMPUTERNAME")
  } else {
    var("HOSTNAME")
  }.or_else(|_| {
    exec_hostname_command()
  }).unwrap_or_else(|_| {
    Uuid::new_v4().to_string()
  });

  let digest = md5::compute(host_name.as_bytes());
  format!("{:x}", digest)
}

/// Execute the hostname command to get the hostname
fn exec_hostname_command() -> anyhow::Result<String> {
  match Command::new("hostname").output() {
    Ok(output) => if output.status.success() {
      Ok(str::from_utf8(&*output.stdout)?.to_string())
    } else {
      Err(anyhow!("Failed to invoke hostname command: status {}", output.status))
    }
    Err(err) => Err(anyhow!("Failed to invoke hostname command: {}", err))
  }
}
<|endoftext|>


<commit_before>fn main() {
    // Iterators can be collected into vectors
    let mut collected_iterator: Vec<int> = range(0i, 10).collect();
    println!("Collected range(0, 10) into: {}", collected_iterator);

    // The `vec!` macro can be used to initialize a vector
    let mut xs = vec![1i, 2, 3];
    println!("Initial vector: {}", xs);

    // Insert new element at the end of the vector
    println!("Push 4 into the vector")
    xs.push(4);
    println!("Vector: {}", xs);

    // Error! Immutable vectors can't grow
    collected_iterator.push(0);
    // FIXME ^ Comment out this line

    // The `len` method yields the current size of the vector
    println!("Vector size: {}", xs.len());

    // Indexing is done using the square brackets (indexing starts at 0)
    println!("Second element: {}", xs[1]);

    // `pop` removes the last element from the vector and returns it
    println!("Pop last element: {}", xs.pop());

    // Out of bounds indexing yields a task failure
    println!("Fourth element: {}", xs[3]);
}
<commit_msg>Fix vec.rs<commit_after>fn main() {
    // Iterators can be collected into vectors
    let collected_iterator: Vec<int> = range(0i, 10).collect();
    println!("Collected range(0, 10) into: {}", collected_iterator);

    // The `vec!` macro can be used to initialize a vector
    let mut xs = vec![1i, 2, 3];
    println!("Initial vector: {}", xs);

    // Insert new element at the end of the vector
    println!("Push 4 into the vector");
    xs.push(4);
    println!("Vector: {}", xs);

    // Error! Immutable vectors can't grow
    collected_iterator.push(0);
    // FIXME ^ Comment out this line

    // The `len` method yields the current size of the vector
    println!("Vector size: {}", xs.len());

    // Indexing is done using the square brackets (indexing starts at 0)
    println!("Second element: {}", xs[1]);

    // `pop` removes the last element from the vector and returns it
    println!("Pop last element: {}", xs.pop());

    // Out of bounds indexing yields a task failure
    println!("Fourth element: {}", xs[3]);
}
<|endoftext|>


<commit_before>use std::collections::hashmap::HashMap;
use time;
use std::mem;
use sync::comm::Receiver;
use image::{GenericImage, SubImage, Pixel};
use image::{ImageBuf, Rgba};
use image;
use glfw;
use glfw::Context as _Context;
use gfx;
use gfx::tex;
use gfx::{Device, DeviceHelper, ToSlice, CommandBuffer};
use gfx::{GlDevice};
use key;
use atlas::{AtlasBuilder, Atlas};
use util;
use color;
use color::{Rgb};
use geom::{V2};

static FONT_DATA: &'static [u8] = include_bin!("../assets/font.png");

pub struct Canvas {
    title: String,
    dim: V2<u32>,
    frame_interval: Option<f64>,
    builder: AtlasBuilder,
    font_glyphs: HashMap<char, Image>,
}

/// Toplevel graphics drawing and input reading context.
impl Canvas {
    pub fn new() -> Canvas {
        let mut ret = Canvas {
            title: "window".to_string(),
            dim: V2(640, 360),
            frame_interval: None,
            builder: AtlasBuilder::new(),
            font_glyphs: HashMap::new(),
        };
        ret.init_font();
        ret
    }

    pub fn set_title(mut self, title: &str) -> Canvas {
        self.title = title.to_string();
        self
    }

    /// Set the frame rate.
    pub fn set_frame_interval(mut self, interval_s: f64) -> Canvas {
        assert!(interval_s > 0.00001);
        self.frame_interval = Some(interval_s);
        self
    }

    /// Set the resolution.
    pub fn set_dim(mut self, dim: V2<u32>) -> Canvas {
        self.dim = dim;
        self
    }

    pub fn add_image<P: Pixel<u8>, I: GenericImage<P>>(
        &mut self, image: I) -> Image {
        Image(self.builder.push(image))
    }

    /// Start running the engine, return an event iteration.
    pub fn run(&mut self) -> Context {
        Context::new(
            self.dim,
            self.title.as_slice(),
            Some(1.0 / 30.0),
            Atlas::new(&self.builder))
    }

    fn init_font(&mut self) {
        let mut font_sheet = util::color_key(
            &image::load_from_memory(FONT_DATA, image::PNG).unwrap(),
            0x80u8, 0x80u8, 0x80u8);
        for i in range(0u32, 96u32) {
            let x = 8u32 * (i % 16u32);
            let y = 8u32 * (i / 16u32);
            let glyph = self.add_image(SubImage::new(&mut font_sheet, x, y, 8, 8));
            self.font_glyphs.insert((i + 32) as u8 as char, glyph);
        }
    }
}

/// Interface to render to a live display.
pub struct Context {
    glfw: glfw::Glfw,
    window: glfw::Window,
    events: Receiver<(f64, glfw::WindowEvent)>,
    graphics: gfx::Graphics<gfx::GlDevice, gfx::GlCommandBuffer>,
    frame: gfx::Frame,

    state: State,
    frame_interval: Option<f64>,
    last_render_time: f64,
    atlas: Atlas,
    atlas_tex: Texture,
    resolution: V2<u32>,
}

#[deriving(PartialEq)]
enum State {
    Normal,
    EndFrame,
}

impl Context {
    fn new(
        dim: V2<u32>,
        title: &str,
        frame_interval: Option<f64>,
        atlas: Atlas) -> Context {

        let glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();
        let (window, events) = glfw
            .create_window(dim.0, dim.1,
            title.as_slice(), glfw::Windowed)
            .expect("Failed to open window");
        window.make_current();
        glfw.set_error_callback(glfw::FAIL_ON_ERRORS);
        window.set_key_polling(true);
        window.set_char_polling(true);

        let device = gfx::GlDevice::new(|s| window.get_proc_address(s));
        let mut graphics = gfx::Graphics::new(device);
        //let frame = gfx::Frame::new(dim.0 as u16, dim.1 as u16);
        let (w, h) = window.get_framebuffer_size();
        let frame = gfx::Frame::new(w as u16, h as u16);
        let atlas_tex = Texture::from_rgba8(&atlas.image, &mut graphics.device);

        Context {
            glfw: glfw,
            window: window,
            events: events,
            graphics: graphics,
            frame: frame,
            state: Normal,
            frame_interval: frame_interval,
            last_render_time: time::precise_time_s(),
            atlas: atlas,
            atlas_tex: atlas_tex,
            resolution: dim,
        }
    }

    /// Clear the screen
    pub fn clear(&mut self, color: &Rgb) {
        self.graphics.clear(
            gfx::ClearData {
                color: color.to_array(),
                depth: 1.0,
                stencil: 0,
            }, gfx::Color, &self.frame);
    }

    /// Mess with drawy stuff
    pub fn draw_test(&mut self) {
        let img = self.font_image('X').unwrap();
        self.draw_image(V2(1, 1), img, &color::ALICEBLUE);
    }

    pub fn draw_image(&mut self, offset: V2<int>, Image(idx): Image, color: &Rgb) {
        let scale = self.resolution.map(|x| 2.0 / (x as f32));

        let texcoords = self.atlas.texcoords[idx];
        let V2(u1, v1) = texcoords.min();
        let V2(u2, v2) = texcoords.max();

        let vertices = self.atlas.vertices[idx];
        let V2(x1, y1) = (vertices.min() + offset.map(|x| x as f32)).mul(scale) - V2(1f32, 1f32);
        let V2(x2, y2) = (vertices.max() + offset.map(|x| x as f32)).mul(scale) - V2(1f32, 1f32);

        let mesh = self.graphics.device.create_mesh([
            Vertex { pos: [x1, -y2], tex_coord: [u1, v2] },
            Vertex { pos: [x2, -y2], tex_coord: [u2, v2] },
            Vertex { pos: [x1, -y1], tex_coord: [u1, v1] },

            Vertex { pos: [x2, -y2], tex_coord: [u2, v2] },
            Vertex { pos: [x1, -y1], tex_coord: [u1, v1] },
            Vertex { pos: [x2, -y1], tex_coord: [u2, v1] },
        ]);

        let sampler_info = Some(self.graphics.device.create_sampler(
            tex::SamplerInfo::new(tex::Scale, tex::Clamp)));
        let params = ShaderParam {
            color: color.to_array(),
            s_texture: (self.atlas_tex.tex, sampler_info),
        };

        let slice = mesh.to_slice(gfx::TriangleList);
        let program = self.graphics.device.link_program(
            VERTEX_SRC.clone(), FRAGMENT_SRC.clone()).unwrap();
        let batch: gfx::batch::RefBatch<_ShaderParamLink, ShaderParam> = self.graphics.make_batch(
            &program, &mesh, slice, &gfx::DrawState::new()).unwrap();
        self.graphics.draw(&batch, &params, &self.frame);
    }

    pub fn font_image(&self, c: char) -> Option<Image> {
        let idx = c as int;
        if idx >= 32 && idx < 128 {
            Some(Image((idx - 32) as uint))
        } else {
            None
        }
    }

    pub fn image_dim(&self, Image(idx): Image) -> V2<uint> {
        self.atlas.vertices[idx].1.map(|x| x as uint)
    }
}

pub enum Event<'a> {
    /// Time to render the screen. Call your own render code on the Context
    /// value when you get this.
    Render(&'a mut Context),
    /// Some printable text entered. Data is whole strings to account for
    /// exotic input devices.
    Text(String),
    KeyPressed(key::Key),
    KeyReleased(key::Key),
}

impl<'a> Iterator<Event<'a>> for Context {
    fn next(&mut self) -> Option<Event<'a>> {
        // After a render event, control will return here on a new
        // iter call. Do post-render work here.
        if self.state == EndFrame {
            self.state = Normal;
            self.graphics.end_frame();
            self.window.swap_buffers();
        }

        loop {
            if self.window.should_close() {
                return None;
            }

            self.glfw.poll_events();

            match self.events.try_recv() {
                Ok((_, event)) => {
                    match event {
                        glfw::CharEvent(ch) => {
                            return Some(Text(String::from_char(1, ch)));
                        }
                        glfw::KeyEvent(k, _scan, action, _mods) => {
                            match key::translate_glfw_key(k).map(|k| {
                                if action == glfw::Press || action == glfw::Repeat {
                                    KeyPressed(k)
                                }
                                else {
                                    KeyReleased(k)
                                }
                            }) {
                                Some(e) => { return Some(e); }
                                _ => ()
                            }
                        }
                        // TODO Mouse events.
                        _ => ()
                    }
                }
                _ => ()
            }

            let t = time::precise_time_s();
            if self.frame_interval.map_or(true,
                |x| t - self.last_render_time >= x) {
                self.last_render_time = t;

                // Time to render, must return a handle to self.
                // XXX: Need unsafe hackery to get around lifetimes check.
                self.state = EndFrame;
                let (w, h) = self.window.get_framebuffer_size();
                self.frame = gfx::Frame::new(w as u16, h as u16);
                unsafe {
                    return Some(Render(mem::transmute(self)))
                }
            }
        }
    }
}

/// Drawable images stored in the Canvas.
#[deriving(Clone, PartialEq)]
pub struct Image(uint);

static VERTEX_SRC: gfx::ShaderSource = shaders! {
GLSL_120: b"
    #version 120

    uniform vec4 color;

    attribute vec2 a_pos;
    attribute vec2 a_tex_coord;

    // TODO: Make color a uniform argument.
    varying vec2 v_tex_coord;
    varying vec4 v_color;

    void main() {
        v_tex_coord = a_tex_coord;
        v_color = color;
        gl_Position = vec4(a_pos, 0.0, 1.0);
    }
"
};

static FRAGMENT_SRC: gfx::ShaderSource = shaders! {
GLSL_120: b"
    #version 120

    uniform sampler2D s_texture;

    varying vec2 v_tex_coord;
    varying vec4 v_color;

    void main() {
        vec4 tex_color = texture2D(s_texture, v_tex_coord);
        if (tex_color.a == 0.0) discard;
        gl_FragColor = v_color * tex_color;
    }
"
};

#[shader_param(Program)]
pub struct ShaderParam {
    pub color: [f32, ..4],
    pub s_texture: gfx::shade::TextureParam,
}

#[vertex_format]
struct Vertex {
    #[name = "a_pos"]
    pos: [f32, ..2],

    #[name = "a_tex_coord"]
    tex_coord: [f32, ..2],
}

impl Clone for Vertex {
    fn clone(&self) -> Vertex { *self }
}

struct Texture {
    tex: gfx::TextureHandle,
    pub width: u32,
    pub height: u32,
}

impl Texture {
    fn from_rgba8<D: Device<C>, C: CommandBuffer>(
        img: &ImageBuf<Rgba<u8>>,
        d: &mut D) -> Texture {
        let (w, h) = img.dimensions();
        let mut info = gfx::tex::TextureInfo::new();
        info.width = w as u16;
        info.height = h as u16;
        info.kind = gfx::tex::Texture2D;
        info.format = gfx::tex::RGBA8;

        let tex = d.create_texture(info).unwrap();
        d.update_texture(&tex, &info.to_image_info(), img.pixelbuf()).unwrap();

        Texture {
            tex: tex,
            width: w,
            height: h,
        }
    }
}
<commit_msg>Line primitive<commit_after>use std::collections::hashmap::HashMap;
use time;
use std::mem;
use sync::comm::Receiver;
use image::{GenericImage, SubImage, Pixel};
use image::{ImageBuf, Rgba};
use image;
use glfw;
use glfw::Context as _Context;
use gfx;
use gfx::tex;
use gfx::{Device, DeviceHelper, ToSlice, CommandBuffer};
use gfx::{GlDevice};
use key;
use atlas::{AtlasBuilder, Atlas};
use util;
use color;
use color::{Rgb};
use geom::{V2};

static FONT_DATA: &'static [u8] = include_bin!("../assets/font.png");

pub struct Canvas {
    title: String,
    dim: V2<u32>,
    frame_interval: Option<f64>,
    builder: AtlasBuilder,
    font_glyphs: HashMap<char, Image>,
}

/// Toplevel graphics drawing and input reading context.
impl Canvas {
    pub fn new() -> Canvas {
        let mut ret = Canvas {
            title: "window".to_string(),
            dim: V2(640, 360),
            frame_interval: None,
            builder: AtlasBuilder::new(),
            font_glyphs: HashMap::new(),
        };
        ret.init_font();
        ret
    }

    pub fn set_title(mut self, title: &str) -> Canvas {
        self.title = title.to_string();
        self
    }

    /// Set the frame rate.
    pub fn set_frame_interval(mut self, interval_s: f64) -> Canvas {
        assert!(interval_s > 0.00001);
        self.frame_interval = Some(interval_s);
        self
    }

    /// Set the resolution.
    pub fn set_dim(mut self, dim: V2<u32>) -> Canvas {
        self.dim = dim;
        self
    }

    pub fn add_image<P: Pixel<u8>, I: GenericImage<P>>(
        &mut self, image: I) -> Image {
        Image(self.builder.push(image))
    }

    /// Start running the engine, return an event iteration.
    pub fn run(&mut self) -> Context {
        Context::new(
            self.dim,
            self.title.as_slice(),
            Some(1.0 / 30.0),
            Atlas::new(&self.builder))
    }

    fn init_font(&mut self) {
        let mut font_sheet = util::color_key(
            &image::load_from_memory(FONT_DATA, image::PNG).unwrap(),
            0x80u8, 0x80u8, 0x80u8);
        for i in range(0u32, 96u32) {
            let x = 8u32 * (i % 16u32);
            let y = 8u32 * (i / 16u32);
            let glyph = self.add_image(SubImage::new(&mut font_sheet, x, y, 8, 8));
            self.font_glyphs.insert((i + 32) as u8 as char, glyph);
        }
    }
}

/// Interface to render to a live display.
pub struct Context {
    glfw: glfw::Glfw,
    window: glfw::Window,
    events: Receiver<(f64, glfw::WindowEvent)>,
    graphics: gfx::Graphics<gfx::GlDevice, gfx::GlCommandBuffer>,
    frame: gfx::Frame,

    state: State,
    frame_interval: Option<f64>,
    last_render_time: f64,
    atlas: Atlas,
    atlas_tex: Texture,
    flatshade: gfx::TextureHandle,
    resolution: V2<u32>,
}

#[deriving(PartialEq)]
enum State {
    Normal,
    EndFrame,
}

impl Context {
    fn new(
        dim: V2<u32>,
        title: &str,
        frame_interval: Option<f64>,
        atlas: Atlas) -> Context {

        let glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();
        let (window, events) = glfw
            .create_window(dim.0, dim.1,
            title.as_slice(), glfw::Windowed)
            .expect("Failed to open window");
        window.make_current();
        glfw.set_error_callback(glfw::FAIL_ON_ERRORS);
        window.set_key_polling(true);
        window.set_char_polling(true);

        let device = gfx::GlDevice::new(|s| window.get_proc_address(s));
        let mut graphics = gfx::Graphics::new(device);
        //let frame = gfx::Frame::new(dim.0 as u16, dim.1 as u16);
        let (w, h) = window.get_framebuffer_size();
        let frame = gfx::Frame::new(w as u16, h as u16);
        let atlas_tex = Texture::from_rgba8(&atlas.image, &mut graphics.device);

        // Blank white texture so we can draw flatshaded stuff without
        // switching to non-texturing shader.
        let mut inf = tex::TextureInfo::new();
        inf.width = 1;
        inf.height = 1;
        inf.kind = tex::Texture2D;
        inf.format = tex::RGBA8;
        let flatshade = graphics.device.create_texture(inf).unwrap();
        graphics.device.update_texture(&flatshade, &inf.to_image_info(), &[0xffu8, 0xff, 0xff, 0xff]).unwrap();

        Context {
            glfw: glfw,
            window: window,
            events: events,
            graphics: graphics,
            frame: frame,
            state: Normal,
            frame_interval: frame_interval,
            last_render_time: time::precise_time_s(),
            atlas: atlas,
            atlas_tex: atlas_tex,
            flatshade: flatshade,
            resolution: dim,
        }
    }

    /// Clear the screen
    pub fn clear(&mut self, color: &Rgb) {
        self.graphics.clear(
            gfx::ClearData {
                color: color.to_array(),
                depth: 1.0,
                stencil: 0,
            }, gfx::Color, &self.frame);
    }

    /// Mess with drawy stuff
    pub fn draw_test(&mut self) {
        let img = self.font_image('X').unwrap();
        self.draw_image(V2(1, 1), img, &color::ALICEBLUE);
        self.draw_line(V2(10, 10), V2(100, 50), 3f32, &color::YELLOW);
    }

    pub fn draw_image(&mut self, offset: V2<int>, Image(idx): Image, color: &Rgb) {
        let scale = self.resolution.map(|x| 2.0 / (x as f32));

        let texcoords = self.atlas.texcoords[idx];
        let V2(u1, v1) = texcoords.min();
        let V2(u2, v2) = texcoords.max();

        let vertices = self.atlas.vertices[idx];
        let V2(x1, y1) = (vertices.min() + offset.map(|x| x as f32)).mul(scale) - V2(1f32, 1f32);
        let V2(x2, y2) = (vertices.max() + offset.map(|x| x as f32)).mul(scale) - V2(1f32, 1f32);

        let mesh = self.graphics.device.create_mesh([
            Vertex { pos: [x1, -y2], tex_coord: [u1, v2] },
            Vertex { pos: [x2, -y2], tex_coord: [u2, v2] },
            Vertex { pos: [x1, -y1], tex_coord: [u1, v1] },

            Vertex { pos: [x2, -y2], tex_coord: [u2, v2] },
            Vertex { pos: [x1, -y1], tex_coord: [u1, v1] },
            Vertex { pos: [x2, -y1], tex_coord: [u2, v1] },
        ]);

        let sampler_info = Some(self.graphics.device.create_sampler(
            tex::SamplerInfo::new(tex::Scale, tex::Clamp)));
        let params = ShaderParam {
            color: color.to_array(),
            s_texture: (self.atlas_tex.tex, sampler_info),
        };

        let slice = mesh.to_slice(gfx::TriangleList);
        let program = self.graphics.device.link_program(
            VERTEX_SRC.clone(), FRAGMENT_SRC.clone()).unwrap();
        let batch: gfx::batch::RefBatch<_ShaderParamLink, ShaderParam> = self.graphics.make_batch(
            &program, &mesh, slice, &gfx::DrawState::new()).unwrap();
        self.graphics.draw(&batch, &params, &self.frame);
    }

    pub fn draw_line(&mut self, p1: V2<int>, p2: V2<int>, thickness: f32, color: &Rgb) {
        let scale = self.resolution.map(|x| 2.0 / (x as f32));
        let p1 = p1.map(|x| x as f32).mul(scale) - V2(1f32, 1f32);
        let p2 = p2.map(|x| x as f32).mul(scale) - V2(1f32, 1f32);

        let mesh = self.graphics.device.create_mesh([
            Vertex { pos: [p1.0, -p1.1], tex_coord: [0.0, 0.0] },
            Vertex { pos: [p2.0, -p2.1], tex_coord: [0.0, 0.0] },
        ]);

        let sampler_info = Some(self.graphics.device.create_sampler(
            tex::SamplerInfo::new(tex::Scale, tex::Clamp)));
        let params = ShaderParam {
            color: color.to_array(),
            s_texture: (self.flatshade, sampler_info),
        };
        let slice = mesh.to_slice(gfx::Line);
        let program = self.graphics.device.link_program(
            VERTEX_SRC.clone(), FRAGMENT_SRC.clone()).unwrap();
        let mut draw_state = gfx::DrawState::new();
        draw_state.primitive.method = gfx::state::Line(thickness);
        let batch: gfx::batch::RefBatch<_ShaderParamLink, ShaderParam> = self.graphics.make_batch(
            &program, &mesh, slice, &draw_state).unwrap();
        self.graphics.draw(&batch, &params, &self.frame);
    }

    pub fn font_image(&self, c: char) -> Option<Image> {
        let idx = c as int;
        if idx >= 32 && idx < 128 {
            Some(Image((idx - 32) as uint))
        } else {
            None
        }
    }

    pub fn image_dim(&self, Image(idx): Image) -> V2<uint> {
        self.atlas.vertices[idx].1.map(|x| x as uint)
    }
}

pub enum Event<'a> {
    /// Time to render the screen. Call your own render code on the Context
    /// value when you get this.
    Render(&'a mut Context),
    /// Some printable text entered. Data is whole strings to account for
    /// exotic input devices.
    Text(String),
    KeyPressed(key::Key),
    KeyReleased(key::Key),
}

impl<'a> Iterator<Event<'a>> for Context {
    fn next(&mut self) -> Option<Event<'a>> {
        // After a render event, control will return here on a new
        // iter call. Do post-render work here.
        if self.state == EndFrame {
            self.state = Normal;
            self.graphics.end_frame();
            self.window.swap_buffers();
        }

        loop {
            if self.window.should_close() {
                return None;
            }

            self.glfw.poll_events();

            match self.events.try_recv() {
                Ok((_, event)) => {
                    match event {
                        glfw::CharEvent(ch) => {
                            return Some(Text(String::from_char(1, ch)));
                        }
                        glfw::KeyEvent(k, _scan, action, _mods) => {
                            match key::translate_glfw_key(k).map(|k| {
                                if action == glfw::Press || action == glfw::Repeat {
                                    KeyPressed(k)
                                }
                                else {
                                    KeyReleased(k)
                                }
                            }) {
                                Some(e) => { return Some(e); }
                                _ => ()
                            }
                        }
                        // TODO Mouse events.
                        _ => ()
                    }
                }
                _ => ()
            }

            let t = time::precise_time_s();
            if self.frame_interval.map_or(true,
                |x| t - self.last_render_time >= x) {
                self.last_render_time = t;

                // Time to render, must return a handle to self.
                // XXX: Need unsafe hackery to get around lifetimes check.
                self.state = EndFrame;
                let (w, h) = self.window.get_framebuffer_size();
                self.frame = gfx::Frame::new(w as u16, h as u16);
                unsafe {
                    return Some(Render(mem::transmute(self)))
                }
            }
        }
    }
}

/// Drawable images stored in the Canvas.
#[deriving(Clone, PartialEq)]
pub struct Image(uint);

static VERTEX_SRC: gfx::ShaderSource = shaders! {
GLSL_120: b"
    #version 120

    uniform vec4 color;

    attribute vec2 a_pos;
    attribute vec2 a_tex_coord;

    // TODO: Make color a uniform argument.
    varying vec2 v_tex_coord;
    varying vec4 v_color;

    void main() {
        v_tex_coord = a_tex_coord;
        v_color = color;
        gl_Position = vec4(a_pos, 0.0, 1.0);
    }
"
};

static FRAGMENT_SRC: gfx::ShaderSource = shaders! {
GLSL_120: b"
    #version 120

    uniform sampler2D s_texture;

    varying vec2 v_tex_coord;
    varying vec4 v_color;

    void main() {
        vec4 tex_color = texture2D(s_texture, v_tex_coord);
        if (tex_color.a == 0.0) discard;
        gl_FragColor = v_color * tex_color;
    }
"
};

#[shader_param(Program)]
pub struct ShaderParam {
    pub color: [f32, ..4],
    pub s_texture: gfx::shade::TextureParam,
}

#[vertex_format]
struct Vertex {
    #[name = "a_pos"]
    pos: [f32, ..2],

    #[name = "a_tex_coord"]
    tex_coord: [f32, ..2],
}

impl Clone for Vertex {
    fn clone(&self) -> Vertex { *self }
}

struct Texture {
    tex: gfx::TextureHandle,
    pub width: u32,
    pub height: u32,
}

impl Texture {
    fn from_rgba8<D: Device<C>, C: CommandBuffer>(
        img: &ImageBuf<Rgba<u8>>,
        d: &mut D) -> Texture {
        let (w, h) = img.dimensions();
        let mut info = tex::TextureInfo::new();
        info.width = w as u16;
        info.height = h as u16;
        info.kind = tex::Texture2D;
        info.format = tex::RGBA8;

        let tex = d.create_texture(info).unwrap();
        d.update_texture(&tex, &info.to_image_info(), img.pixelbuf()).unwrap();

        Texture {
            tex: tex,
            width: w,
            height: h,
        }
    }
}
<|endoftext|>


<commit_before>use std::sync::{
    atomic::{AtomicBool, Ordering},
    mpsc, Arc,
};

#[cfg(not(target_os = "redox"))]
use std::thread;

use super::MouseState;
use crate::{
    event::{ButtonState, Key, KeyEvent, MouseButton, MouseEvent},
    render::RenderContext2D,
    window_adapter::WindowAdapter,
    WindowRequest,
};

#[cfg(not(target_os = "redox"))]
use sdl2::event;

use orbclient::Renderer;

#[cfg(not(target_os = "redox"))]
use raw_window_handle::HasRawWindowHandle;

use orbtk_utils::Point;

/// Represents a wrapper for a orbclient window. It handles events, propagate them to
/// the window adapter and handles the update and render pipeline.
pub struct Window
where
    A: WindowAdapter,
{
    window: orbclient::Window,
    adapter: A,
    render_context: RenderContext2D,
    request_receiver: Option<mpsc::Receiver<WindowRequest>>,
    //window_state: WindowState,
    mouse: MouseState,
    update: bool,
    redraw: Arc<AtomicBool>,
    close: bool,
    has_clipboard_update: bool,
    #[cfg(not(target_os = "redox"))]
    _sdl2_sync_thread: Option<thread::JoinHandle<()>>,
}

// Internal sync method for OrbClient sdl2 backend
#[cfg(not(target_os = "redox"))]
fn init_sync(
    window: &orbclient::Window,
    receiver: mpsc::Receiver<WindowRequest>,
) -> (mpsc::Receiver<WindowRequest>, thread::JoinHandle<()>) {
    let (internal_sender, internal_receiver) = mpsc::channel();

    let event_sender = window.event_sender();
    let id = window.id();

    let _sdl2_sync_thread = thread::spawn(move || loop {
        for request in receiver.iter() {
            let _ = internal_sender.send(request.clone());
            if request == WindowRequest::Redraw {
                let _ = event_sender.push_event(event::Event::Window {
                    window_id: id,
                    timestamp: 0,
                    win_event: event::WindowEvent::None,
                });
            }
        }
    });

    (internal_receiver, _sdl2_sync_thread)
}

impl Window
where
    A: WindowAdapter,
{
    #[cfg(target_os = "redox")]
    pub fn new(
        window: orbclient::Window,
        adapter: A,
        render_context: RenderContext2D,
        request_receiver: Option<mpsc::Receiver<WindowRequest>>,
    ) -> Self {
        Window {
            window,
            adapter,
            render_context,
            request_receiver,
            // window_state: WindowState::default(),
            mouse: MouseState::default(),
            update: true,
            redraw: Arc::new(AtomicBool::new(true)),
            close: false,
            has_clipboard_update: true,
        }
    }

    #[cfg(not(target_os = "redox"))]
    pub fn new(
        window: orbclient::Window,
        adapter: A,
        render_context: RenderContext2D,
        request_receiver: Option<mpsc::Receiver<WindowRequest>>,
    ) -> Self {
        let mut adapter = adapter;
        let redraw: Arc<AtomicBool> = Arc::new(AtomicBool::new(true));

        adapter.set_raw_window_handle(window.raw_window_handle());

        let (request_receiver, _sdl2_sync_thread) = {
            if let Some(receiver) = request_receiver {
                let (rec, sync) = init_sync(&window, receiver);
                (Some(rec), Some(sync))
            } else {
                (None, None)
            }
        };

        Window {
            window,
            adapter,
            render_context,
            _sdl2_sync_thread,
            request_receiver,
            // window_state: WindowState::default(),
            mouse: MouseState::default(),
            update: true,
            redraw,
            close: false,
            has_clipboard_update: true,
        }
    }

    #[cfg(not(target_os = "redox"))]
    fn has_clipboard_update(&self) -> bool {
        self.has_clipboard_update
    }

    // todo: workaround until clipboard update events available on orbital
    #[cfg(target_os = "redox")]
    fn has_clipboard_update(&self) -> bool {
        true
    }
}

#[cfg(not(target_os = "redox"))]
unsafe impl raw_window_handle::HasRawWindowHandle for Window
where
    A: WindowAdapter,
{
    fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
        self.window.raw_window_handle()
    }
}

impl Window
where
    A: WindowAdapter,
{
    /// Check if the window is open.
    pub fn is_open(&self) -> bool {
        !self.close
    }

    fn push_mouse_event(&mut self, pressed: bool, button: MouseButton) {
        let state = if pressed {
            ButtonState::Down
        } else {
            ButtonState::Up
        };

        self.adapter.mouse_event(MouseEvent {
            position: Point::new(self.mouse.mouse_pos.0 as f64, self.mouse.mouse_pos.1 as f64),
            button,
            state,
        });
    }

    fn push_key_event(&mut self, key_event: orbclient::KeyEvent) {
        let mut key = Key::from(key_event.character);
        let state = {
            if key_event.pressed {
                ButtonState::Down
            } else {
                ButtonState::Up
            }
        };

        let text = {
            if key_event.character != '\0'
                && key_event.character != '\n'
                && key_event.character != '\u{1b}'
            {
                key_event.character.to_string()
            } else {
                match key_event.scancode {
                    orbclient::K_ALT => key = Key::Alt,
                    orbclient::K_BKSP => key = Key::Backspace,
                    orbclient::K_CAPS => key = Key::CapsLock,
                    orbclient::K_CTRL => key = Key::Control,
                    orbclient::K_DEL => key = Key::Delete,
                    orbclient::K_DOWN => key = Key::Down,
                    orbclient::K_ENTER => key = Key::Enter,
                    orbclient::K_ESC => key = Key::Escape,
                    orbclient::K_HOME => {
                        key = Key::Home;
                    }
                    orbclient::K_LEFT => key = Key::Left,
                    orbclient::K_LEFT_SHIFT => key = Key::ShiftL,
                    orbclient::K_RIGHT => key = Key::Right,
                    orbclient::K_RIGHT_SHIFT => key = Key::ShiftR,
                    orbclient::K_TAB => key = Key::Tab,
                    orbclient::K_UP => key = Key::Up,
                    _ => key = Key::Unknown,
                };
                String::default()
            }
        };

        self.adapter.key_event(KeyEvent { state, key, text });
    }

    /// Updates the clipboard.
    pub fn update_clipboard(&mut self) {
        let mut clipboard_value = if self.has_clipboard_update() {
            self.has_clipboard_update = false;
            Some(self.window.clipboard())
        } else {
            None
        };

        self.adapter.clipboard_update(&mut clipboard_value);

        if let Some(value) = clipboard_value {
            self.window.set_clipboard(value.as_str());
        }
    }

    /// Drain events and propagate the events to the adapter.
    pub fn drain_events(&mut self) {
        for event in self.window.events() {
            match event.to_option() {
                orbclient::EventOption::Key(event) => {
                    self.push_key_event(event);
                    self.update = true;
                }
                orbclient::EventOption::TextInput(event) => {
                    self.adapter.text_input(event.character.to_string());
                    self.update = true;
                }
                orbclient::EventOption::Mouse(event) => {
                    self.mouse.mouse_pos = (event.x as f32, event.y as f32);
                    self.adapter.mouse(event.x as f64, event.y as f64);
                    self.update = true;
                }
                orbclient::EventOption::MouseRelative(_) => {}
                orbclient::EventOption::ClipboardUpdate(_) => {
                    self.has_clipboard_update = true;
                }
                orbclient::EventOption::Button(event) => {
                    if event.left != self.mouse.button_left {
                        if event.left {
                            self.push_mouse_event(true, MouseButton::Left);
                        } else {
                            self.push_mouse_event(false, MouseButton::Left);
                        }
                        self.mouse.button_left = event.left;
                        self.update = true;
                    }

                    if event.middle != self.mouse.button_middle {
                        if event.middle {
                            self.push_mouse_event(true, MouseButton::Middle);
                        } else {
                            self.push_mouse_event(false, MouseButton::Middle);
                        }
                        self.mouse.button_middle = event.middle;
                        self.update = true;
                    }

                    if event.right != self.mouse.button_right {
                        if event.right {
                            self.push_mouse_event(true, MouseButton::Right);
                        } else {
                            self.push_mouse_event(false, MouseButton::Right);
                        }
                        self.mouse.button_right = event.right;
                        self.update = true;
                    }
                }
                orbclient::EventOption::Scroll(event) => {
                    self.adapter.scroll(event.x as f64, event.y as f64);
                    self.update = true;
                }
                orbclient::EventOption::Quit(_) => {
                    self.close = true;
                    self.update = true
                }
                orbclient::EventOption::Focus(_) => {}
                orbclient::EventOption::Move(_) => {}
                orbclient::EventOption::Resize(event) => {
                    self.adapter.resize(event.width as f64, event.height as f64);
                    self.render_context
                        .resize(event.width as f64, event.height as f64);
                    self.update = true;
                    self.redraw.store(true, Ordering::Relaxed);
                }
                orbclient::EventOption::Screen(_) => {}
                orbclient::EventOption::Clipboard(_) => {}
                orbclient::EventOption::Drop(event) => {
                    self.window.sync_path();
                    if let Some(text) = self.window.pop_drop_content() {
                        if event.kind == orbclient::DROP_FILE {
                            self.adapter.file_drop_event(text);
                        } else {
                            self.adapter.text_drop_event(text);
                        }
                    }
                }
                orbclient::EventOption::Unknown(_) => {}
                orbclient::EventOption::None => {}
                orbclient::EventOption::Hover(_) => {}
            }
        }
    }

    /// Receives window request from the application and handles them.
    pub fn receive_requests(&mut self) {
        if let Some(request_receiver) = &self.request_receiver {
            for request in request_receiver.try_iter() {
                match request {
                    WindowRequest::Redraw => {
                        if !self.update && !self.redraw.load(Ordering::Relaxed) {
                            self.update = true;
                            self.redraw.store(true, Ordering::Relaxed)
                        }
                    }
                    WindowRequest::ChangeTitle(title) => {
                        self.window.set_title(title.as_str());
                        self.update = true;
                        self.redraw.store(true, Ordering::Relaxed)
                    }
                    WindowRequest::Close => {
                        self.close = true;
                    }
                }
            }
        }
    }

    /// Runs update on the adapter.
    pub fn update(&mut self) {
        //super::CONSOLE.time("complete");
        if !self.update {
            return;
        }

        self.adapter.run(&mut self.render_context);
        self.update = false;
        self.redraw.store(true, Ordering::Relaxed)
    }

    /// Swaps the current frame buffer.
    pub fn render(&mut self) {
        if self.redraw.load(Ordering::Relaxed) {
            let bytes = self.render_context.data_u8_mut();
            let len = bytes.len() / std::mem::size_of::<orbclient::Color>();
            let color_data = unsafe {
                std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut orbclient::Color, len)
            };

            if color_data.len() == self.window.data().len() {
                self.window.data_mut().clone_from_slice(color_data);

                // CONSOLE.time_end("render");
                self.redraw.store(false, Ordering::Relaxed)
                //super::CONSOLE.time_end("complete");
            }
        }

        self.window.sync();
    }
}
<commit_msg>orbtk_orbclient: update window documentation<commit_after>use std::sync::{
    atomic::{AtomicBool, Ordering},
    mpsc, Arc,
};

#[cfg(not(target_os = "redox"))]
use std::thread;

use super::MouseState;
use crate::{
    event::{ButtonState, Key, KeyEvent, MouseButton, MouseEvent},
    render::RenderContext2D,
    window_adapter::WindowAdapter,
    WindowRequest,
};

#[cfg(not(target_os = "redox"))]
use sdl2::event;

use orbclient::Renderer;

#[cfg(not(target_os = "redox"))]
use raw_window_handle::HasRawWindowHandle;

use orbtk_utils::Point;

/// Represents a wrapper for an orbclient window.

/// Events are handled and propagated to the window adapter. The
/// window adapter updates involved entities and activates the render
/// pipeline.
pub struct Window
where
    A: WindowAdapter,
{
    window: orbclient::Window,
    adapter: A,
    render_context: RenderContext2D,
    request_receiver: Option<mpsc::Receiver<WindowRequest>>,
    //window_state: WindowState,
    mouse: MouseState,
    update: bool,
    redraw: Arc<AtomicBool>,
    close: bool,
    has_clipboard_update: bool,
    #[cfg(not(target_os = "redox"))]
    _sdl2_sync_thread: Option<thread::JoinHandle<()>>,
}

// Internal sync method for OrbClient sdl2 backend
#[cfg(not(target_os = "redox"))]
fn init_sync(
    window: &orbclient::Window,
    receiver: mpsc::Receiver<WindowRequest>,
) -> (mpsc::Receiver<WindowRequest>, thread::JoinHandle<()>) {
    let (internal_sender, internal_receiver) = mpsc::channel();

    let event_sender = window.event_sender();
    let id = window.id();

    let _sdl2_sync_thread = thread::spawn(move || loop {
        for request in receiver.iter() {
            let _ = internal_sender.send(request.clone());
            if request == WindowRequest::Redraw {
                let _ = event_sender.push_event(event::Event::Window {
                    window_id: id,
                    timestamp: 0,
                    win_event: event::WindowEvent::None,
                });
            }
        }
    });

    (internal_receiver, _sdl2_sync_thread)
}

impl Window
where
    A: WindowAdapter,
{
    #[cfg(target_os = "redox")]
    pub fn new(
        window: orbclient::Window,
        adapter: A,
        render_context: RenderContext2D,
        request_receiver: Option<mpsc::Receiver<WindowRequest>>,
    ) -> Self {
        Window {
            window,
            adapter,
            render_context,
            request_receiver,
            // window_state: WindowState::default(),
            mouse: MouseState::default(),
            update: true,
            redraw: Arc::new(AtomicBool::new(true)),
            close: false,
            has_clipboard_update: true,
        }
    }

    #[cfg(not(target_os = "redox"))]
    pub fn new(
        window: orbclient::Window,
        adapter: A,
        render_context: RenderContext2D,
        request_receiver: Option<mpsc::Receiver<WindowRequest>>,
    ) -> Self {
        let mut adapter = adapter;
        let redraw: Arc<AtomicBool> = Arc::new(AtomicBool::new(true));

        adapter.set_raw_window_handle(window.raw_window_handle());

        let (request_receiver, _sdl2_sync_thread) = {
            if let Some(receiver) = request_receiver {
                let (rec, sync) = init_sync(&window, receiver);
                (Some(rec), Some(sync))
            } else {
                (None, None)
            }
        };

        Window {
            window,
            adapter,
            render_context,
            _sdl2_sync_thread,
            request_receiver,
            // window_state: WindowState::default(),
            mouse: MouseState::default(),
            update: true,
            redraw,
            close: false,
            has_clipboard_update: true,
        }
    }

    #[cfg(not(target_os = "redox"))]
    fn has_clipboard_update(&self) -> bool {
        self.has_clipboard_update
    }

    // todo: workaround until clipboard update events available on orbital
    #[cfg(target_os = "redox")]
    fn has_clipboard_update(&self) -> bool {
        true
    }
}

#[cfg(not(target_os = "redox"))]
unsafe impl raw_window_handle::HasRawWindowHandle for Window
where
    A: WindowAdapter,
{
    fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
        self.window.raw_window_handle()
    }
}

impl Window
where
    A: WindowAdapter,
{
    /// Check if the window is open.
    pub fn is_open(&self) -> bool {
        !self.close
    }

    fn push_mouse_event(&mut self, pressed: bool, button: MouseButton) {
        let state = if pressed {
            ButtonState::Down
        } else {
            ButtonState::Up
        };

        self.adapter.mouse_event(MouseEvent {
            position: Point::new(self.mouse.mouse_pos.0 as f64, self.mouse.mouse_pos.1 as f64),
            button,
            state,
        });
    }

    fn push_key_event(&mut self, key_event: orbclient::KeyEvent) {
        let mut key = Key::from(key_event.character);
        let state = {
            if key_event.pressed {
                ButtonState::Down
            } else {
                ButtonState::Up
            }
        };

        let text = {
            if key_event.character != '\0'
                && key_event.character != '\n'
                && key_event.character != '\u{1b}'
            {
                key_event.character.to_string()
            } else {
                match key_event.scancode {
                    orbclient::K_ALT => key = Key::Alt,
                    orbclient::K_BKSP => key = Key::Backspace,
                    orbclient::K_CAPS => key = Key::CapsLock,
                    orbclient::K_CTRL => key = Key::Control,
                    orbclient::K_DEL => key = Key::Delete,
                    orbclient::K_DOWN => key = Key::Down,
                    orbclient::K_ENTER => key = Key::Enter,
                    orbclient::K_ESC => key = Key::Escape,
                    orbclient::K_HOME => {
                        key = Key::Home;
                    }
                    orbclient::K_LEFT => key = Key::Left,
                    orbclient::K_LEFT_SHIFT => key = Key::ShiftL,
                    orbclient::K_RIGHT => key = Key::Right,
                    orbclient::K_RIGHT_SHIFT => key = Key::ShiftR,
                    orbclient::K_TAB => key = Key::Tab,
                    orbclient::K_UP => key = Key::Up,
                    _ => key = Key::Unknown,
                };
                String::default()
            }
        };

        self.adapter.key_event(KeyEvent { state, key, text });
    }

    /// Updates the clipboard.
    pub fn update_clipboard(&mut self) {
        let mut clipboard_value = if self.has_clipboard_update() {
            self.has_clipboard_update = false;
            Some(self.window.clipboard())
        } else {
            None
        };

        self.adapter.clipboard_update(&mut clipboard_value);

        if let Some(value) = clipboard_value {
            self.window.set_clipboard(value.as_str());
        }
    }

    /// Drain events and propagate the events to the adapter.
    pub fn drain_events(&mut self) {
        for event in self.window.events() {
            match event.to_option() {
                orbclient::EventOption::Key(event) => {
                    self.push_key_event(event);
                    self.update = true;
                }
                orbclient::EventOption::TextInput(event) => {
                    self.adapter.text_input(event.character.to_string());
                    self.update = true;
                }
                orbclient::EventOption::Mouse(event) => {
                    self.mouse.mouse_pos = (event.x as f32, event.y as f32);
                    self.adapter.mouse(event.x as f64, event.y as f64);
                    self.update = true;
                }
                orbclient::EventOption::MouseRelative(_) => {}
                orbclient::EventOption::ClipboardUpdate(_) => {
                    self.has_clipboard_update = true;
                }
                orbclient::EventOption::Button(event) => {
                    if event.left != self.mouse.button_left {
                        if event.left {
                            self.push_mouse_event(true, MouseButton::Left);
                        } else {
                            self.push_mouse_event(false, MouseButton::Left);
                        }
                        self.mouse.button_left = event.left;
                        self.update = true;
                    }

                    if event.middle != self.mouse.button_middle {
                        if event.middle {
                            self.push_mouse_event(true, MouseButton::Middle);
                        } else {
                            self.push_mouse_event(false, MouseButton::Middle);
                        }
                        self.mouse.button_middle = event.middle;
                        self.update = true;
                    }

                    if event.right != self.mouse.button_right {
                        if event.right {
                            self.push_mouse_event(true, MouseButton::Right);
                        } else {
                            self.push_mouse_event(false, MouseButton::Right);
                        }
                        self.mouse.button_right = event.right;
                        self.update = true;
                    }
                }
                orbclient::EventOption::Scroll(event) => {
                    self.adapter.scroll(event.x as f64, event.y as f64);
                    self.update = true;
                }
                orbclient::EventOption::Quit(_) => {
                    self.close = true;
                    self.update = true
                }
                orbclient::EventOption::Focus(_) => {}
                orbclient::EventOption::Move(_) => {}
                orbclient::EventOption::Resize(event) => {
                    self.adapter.resize(event.width as f64, event.height as f64);
                    self.render_context
                        .resize(event.width as f64, event.height as f64);
                    self.update = true;
                    self.redraw.store(true, Ordering::Relaxed);
                }
                orbclient::EventOption::Screen(_) => {}
                orbclient::EventOption::Clipboard(_) => {}
                orbclient::EventOption::Drop(event) => {
                    self.window.sync_path();
                    if let Some(text) = self.window.pop_drop_content() {
                        if event.kind == orbclient::DROP_FILE {
                            self.adapter.file_drop_event(text);
                        } else {
                            self.adapter.text_drop_event(text);
                        }
                    }
                }
                orbclient::EventOption::Unknown(_) => {}
                orbclient::EventOption::None => {}
                orbclient::EventOption::Hover(_) => {}
            }
        }
    }

    /// Receives window request from the application and handles them.
    pub fn receive_requests(&mut self) {
        if let Some(request_receiver) = &self.request_receiver {
            for request in request_receiver.try_iter() {
                match request {
                    WindowRequest::Redraw => {
                        if !self.update && !self.redraw.load(Ordering::Relaxed) {
                            self.update = true;
                            self.redraw.store(true, Ordering::Relaxed)
                        }
                    }
                    WindowRequest::ChangeTitle(title) => {
                        self.window.set_title(title.as_str());
                        self.update = true;
                        self.redraw.store(true, Ordering::Relaxed)
                    }
                    WindowRequest::Close => {
                        self.close = true;
                    }
                }
            }
        }
    }

    /// Runs update on the adapter.
    pub fn update(&mut self) {
        //super::CONSOLE.time("complete");
        if !self.update {
            return;
        }

        self.adapter.run(&mut self.render_context);
        self.update = false;
        self.redraw.store(true, Ordering::Relaxed)
    }

    /// Swaps the current frame buffer.
    pub fn render(&mut self) {
        if self.redraw.load(Ordering::Relaxed) {
            let bytes = self.render_context.data_u8_mut();
            let len = bytes.len() / std::mem::size_of::<orbclient::Color>();
            let color_data = unsafe {
                std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut orbclient::Color, len)
            };

            if color_data.len() == self.window.data().len() {
                self.window.data_mut().clone_from_slice(color_data);

                // CONSOLE.time_end("render");
                self.redraw.store(false, Ordering::Relaxed)
                //super::CONSOLE.time_end("complete");
            }
        }

        self.window.sync();
    }
}
<|endoftext|>