<commit_before>use std::borrow::Cow;
use std::env;
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::process::Command;

fn main() {
	// TODO: Replace with is_some_with once stabilized
	// https://github.com/rust-lang/rust/issues/93050
	let targets_hermit =
		matches!(env::var_os("CARGO_CFG_TARGET_OS"), Some(os) if os == OsStr::new("hermit"));
	if !targets_hermit {
		return;
	}

	let kernel_src = if has_feature("with_submodule") {
		KernelSrc::from_submodule()
	} else {
		KernelSrc::download()
	};

	kernel_src.build();
}

struct KernelSrc {
	src_dir: PathBuf,
}

impl KernelSrc {
	fn from_submodule() -> Self {
		let mut src_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
		src_dir.set_file_name("libhermit-rs");
		Self { src_dir }
	}

	fn download() -> Self {
		let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
		let src_dir = out_dir.join("rusty-hermit");

		if !src_dir.exists() {
			let status = Command::new("cargo")
				.current_dir(out_dir)
				.arg("download")
				.arg("--output")
				.arg(&src_dir)
				.arg("--extract")
				.arg("rusty-hermit")
				.status()
				.expect("failed to start kernel download");
			assert!(
				status.success(),
				"Unable to download rusty-hermit. Is cargo-download installed?"
			);
		}

		Self { src_dir }
	}

	fn build(self) {
		let target_dir = {
			let mut target_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
			target_dir.push("target");
			target_dir
		};
		let manifest_path = self.src_dir.join("Cargo.toml");
		assert!(
			manifest_path.exists(),
			"kernel manifest path `{}` does not exist",
			manifest_path.display()
		);
		let user_target = env::var("TARGET").unwrap();
		let profile = env::var("PROFILE").expect("PROFILE was not set");
		let mut cmd = Command::new("cargo");

		let kernel_target = match user_target.as_str() {
			"x86_64-unknown-hermit" => "x86_64-unknown-none-hermitkernel",
			"aarch64-unknonw-hermit" => "aarch64-unknown-none-hermitkernel",
			_ => panic!("Unsupported target: {}", user_target),
		};

		cmd.arg("build")
			.arg("-Z")
			.arg("build-std=core,alloc")
			.arg("--target")
			.arg(kernel_target)
			.arg("--manifest-path")
			.arg("Cargo.toml");

		cmd.current_dir(&self.src_dir);

		cmd.env_remove("RUSTUP_TOOLCHAIN");
		if option_env!("RUSTC_WORKSPACE_WRAPPER")
			.unwrap_or_default()
			.ends_with("clippy-driver")
		{
			cmd.env("RUSTC_WORKSPACE_WRAPPER", "clippy-driver");
		}

		cmd.env("CARGO_TERM_COLOR", "always");

		cmd.arg("--target-dir").arg(&target_dir);

		if profile == "release" {
			cmd.arg("--release");
		}

		// Control enabled features via this crate's features
		cmd.arg("--no-default-features");
		forward_features(
			&mut cmd,
			["acpi", "fsgsbase", "pci", "smp", "vga"].into_iter(),
		);

		let mut rustflags = vec!["-Zmutable-noalias=no".to_string()];
		let outer_rustflags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap();

		if has_feature("instrument") {
			rustflags.push("-Zinstrument-mcount".to_string());
			// Add outer rustflags to command
			rustflags.push(outer_rustflags);
		} else {
			// If the `instrument` feature feature is not enabled,
			// filter it from outer rustflags before adding them to the command.
			if !outer_rustflags.is_empty() {
				let flags = outer_rustflags
					.split('\x1f')
					.filter(|&flag| !flag.contains("instrument-mcount"))
					.map(String::from);
				rustflags.extend(flags);
			}
		}

		cmd.env("CARGO_ENCODED_RUSTFLAGS", rustflags.join("\x1f"));

		let status = cmd.status().expect("failed to start kernel build");
		assert!(status.success());

		let lib_location = target_dir
			.join(kernel_target)
			.join(&profile)
			.canonicalize()
			.unwrap();

		println!("Lib location: {}", lib_location.display());

		let lib = lib_location.join("libhermit.a");

		let mut symbols = vec!["rust_begin_unwind", "rust_oom"];

		match kernel_target {
			"x86_64-unknown-none-hermitkernel" => {
				symbols.extend(include_str!("x86_64-duplicate-symbols").lines())
			}
			"aarch64-unknown-none-hermitkernel" => {
				symbols.extend(include_str!("aarch64-duplicate-symbols").lines())
			}
			_ => (),
		}

		// Kernel and user space has its own versions of panic handler, oom handler, memcpy, memset, etc,
		// Consequently, we rename the functions in the libos to avoid collisions.
		rename_symbols(symbols.iter(), &lib);

		println!("cargo:rustc-link-search=native={}", lib_location.display());
		println!("cargo:rustc-link-lib=static=hermit");

		println!("cargo:rerun-if-changed={}", self.src_dir.display());
		// HERMIT_LOG_LEVEL_FILTER sets the log level filter at compile time
		println!("cargo:rerun-if-env-changed=HERMIT_LOG_LEVEL_FILTER");
	}
}

fn has_feature(feature: &str) -> bool {
	let mut var = "CARGO_FEATURE_".to_string();

	var.extend(feature.chars().map(|c| match c {
		'-' => '_',
		c => c.to_ascii_uppercase(),
	}));

	env::var_os(&var).is_some()
}

fn forward_features<'a>(cmd: &mut Command, features: impl Iterator<Item = &'a str>) {
	let features = features.filter(|f| has_feature(f)).collect::<Vec<_>>();
	if !features.is_empty() {
		cmd.arg("--features");
		cmd.arg(features.join(" "));
	}
}

fn rename_symbols(symbols: impl Iterator<Item = impl AsRef<OsStr>>, lib: &Path) {
	let args = symbols.into_iter().flat_map(|symbol| {
		let option = OsStr::new("--redefine-sym");
		let arg = [symbol.as_ref(), "=kernel-".as_ref(), symbol.as_ref()]
			.into_iter()
			.collect::<OsString>();
		[Cow::Borrowed(option), Cow::Owned(arg)]
	});

	let status = Command::new("rust-objcopy")
		.args(args)
		.arg(lib)
		.status()
		.expect("Failed to execute rust-objcopy. Is cargo-binutils installed?");
	assert!(status.success(), "rust-objcopy was not successful");
}
<commit_msg>build.rs: Fix typo<commit_after>use std::borrow::Cow;
use std::env;
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::process::Command;

fn main() {
	// TODO: Replace with is_some_with once stabilized
	// https://github.com/rust-lang/rust/issues/93050
	let targets_hermit =
		matches!(env::var_os("CARGO_CFG_TARGET_OS"), Some(os) if os == OsStr::new("hermit"));
	if !targets_hermit {
		return;
	}

	let kernel_src = if has_feature("with_submodule") {
		KernelSrc::from_submodule()
	} else {
		KernelSrc::download()
	};

	kernel_src.build();
}

struct KernelSrc {
	src_dir: PathBuf,
}

impl KernelSrc {
	fn from_submodule() -> Self {
		let mut src_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
		src_dir.set_file_name("libhermit-rs");
		Self { src_dir }
	}

	fn download() -> Self {
		let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
		let src_dir = out_dir.join("rusty-hermit");

		if !src_dir.exists() {
			let status = Command::new("cargo")
				.current_dir(out_dir)
				.arg("download")
				.arg("--output")
				.arg(&src_dir)
				.arg("--extract")
				.arg("rusty-hermit")
				.status()
				.expect("failed to start kernel download");
			assert!(
				status.success(),
				"Unable to download rusty-hermit. Is cargo-download installed?"
			);
		}

		Self { src_dir }
	}

	fn build(self) {
		let target_dir = {
			let mut target_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
			target_dir.push("target");
			target_dir
		};
		let manifest_path = self.src_dir.join("Cargo.toml");
		assert!(
			manifest_path.exists(),
			"kernel manifest path `{}` does not exist",
			manifest_path.display()
		);
		let user_target = env::var("TARGET").unwrap();
		let profile = env::var("PROFILE").expect("PROFILE was not set");
		let mut cmd = Command::new("cargo");

		let kernel_target = match user_target.as_str() {
			"x86_64-unknown-hermit" => "x86_64-unknown-none-hermitkernel",
			"aarch64-unknown-hermit" => "aarch64-unknown-none-hermitkernel",
			_ => panic!("Unsupported target: {}", user_target),
		};

		cmd.arg("build")
			.arg("-Z")
			.arg("build-std=core,alloc")
			.arg("--target")
			.arg(kernel_target)
			.arg("--manifest-path")
			.arg("Cargo.toml");

		cmd.current_dir(&self.src_dir);

		cmd.env_remove("RUSTUP_TOOLCHAIN");
		if option_env!("RUSTC_WORKSPACE_WRAPPER")
			.unwrap_or_default()
			.ends_with("clippy-driver")
		{
			cmd.env("RUSTC_WORKSPACE_WRAPPER", "clippy-driver");
		}

		cmd.env("CARGO_TERM_COLOR", "always");

		cmd.arg("--target-dir").arg(&target_dir);

		if profile == "release" {
			cmd.arg("--release");
		}

		// Control enabled features via this crate's features
		cmd.arg("--no-default-features");
		forward_features(
			&mut cmd,
			["acpi", "fsgsbase", "pci", "smp", "vga"].into_iter(),
		);

		let mut rustflags = vec!["-Zmutable-noalias=no".to_string()];
		let outer_rustflags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap();

		if has_feature("instrument") {
			rustflags.push("-Zinstrument-mcount".to_string());
			// Add outer rustflags to command
			rustflags.push(outer_rustflags);
		} else {
			// If the `instrument` feature feature is not enabled,
			// filter it from outer rustflags before adding them to the command.
			if !outer_rustflags.is_empty() {
				let flags = outer_rustflags
					.split('\x1f')
					.filter(|&flag| !flag.contains("instrument-mcount"))
					.map(String::from);
				rustflags.extend(flags);
			}
		}

		cmd.env("CARGO_ENCODED_RUSTFLAGS", rustflags.join("\x1f"));

		let status = cmd.status().expect("failed to start kernel build");
		assert!(status.success());

		let lib_location = target_dir
			.join(kernel_target)
			.join(&profile)
			.canonicalize()
			.unwrap();

		println!("Lib location: {}", lib_location.display());

		let lib = lib_location.join("libhermit.a");

		let mut symbols = vec!["rust_begin_unwind", "rust_oom"];

		match kernel_target {
			"x86_64-unknown-none-hermitkernel" => {
				symbols.extend(include_str!("x86_64-duplicate-symbols").lines())
			}
			"aarch64-unknown-none-hermitkernel" => {
				symbols.extend(include_str!("aarch64-duplicate-symbols").lines())
			}
			_ => (),
		}

		// Kernel and user space has its own versions of panic handler, oom handler, memcpy, memset, etc,
		// Consequently, we rename the functions in the libos to avoid collisions.
		rename_symbols(symbols.iter(), &lib);

		println!("cargo:rustc-link-search=native={}", lib_location.display());
		println!("cargo:rustc-link-lib=static=hermit");

		println!("cargo:rerun-if-changed={}", self.src_dir.display());
		// HERMIT_LOG_LEVEL_FILTER sets the log level filter at compile time
		println!("cargo:rerun-if-env-changed=HERMIT_LOG_LEVEL_FILTER");
	}
}

fn has_feature(feature: &str) -> bool {
	let mut var = "CARGO_FEATURE_".to_string();

	var.extend(feature.chars().map(|c| match c {
		'-' => '_',
		c => c.to_ascii_uppercase(),
	}));

	env::var_os(&var).is_some()
}

fn forward_features<'a>(cmd: &mut Command, features: impl Iterator<Item = &'a str>) {
	let features = features.filter(|f| has_feature(f)).collect::<Vec<_>>();
	if !features.is_empty() {
		cmd.arg("--features");
		cmd.arg(features.join(" "));
	}
}

fn rename_symbols(symbols: impl Iterator<Item = impl AsRef<OsStr>>, lib: &Path) {
	let args = symbols.into_iter().flat_map(|symbol| {
		let option = OsStr::new("--redefine-sym");
		let arg = [symbol.as_ref(), "=kernel-".as_ref(), symbol.as_ref()]
			.into_iter()
			.collect::<OsString>();
		[Cow::Borrowed(option), Cow::Owned(arg)]
	});

	let status = Command::new("rust-objcopy")
		.args(args)
		.arg(lib)
		.status()
		.expect("Failed to execute rust-objcopy. Is cargo-binutils installed?");
	assert!(status.success(), "rust-objcopy was not successful");
}
<|endoftext|>


<commit_before>/*! Hex map display demo */

extern crate num;
extern crate rustc_serialize;
extern crate image;
extern crate tiled;

#[macro_use] extern crate calx_ecs;
extern crate calx;

mod cmd;
mod render;
mod spr;
mod world;

use std::convert::{Into};
use std::collections::{HashSet};
use calx_ecs::{Entity};
use calx::backend::{CanvasBuilder, Canvas, CanvasUtil, Event, MouseButton, Key};
use calx::{V2, Rect, Rgba, color, Dir6};
use calx::{Projection, Kernel, KernelTerrain};

use spr::Spr;
use render::RenderTerrain;
use world::World;

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum Terrain {
    Floor,
    Grass,
    Water,
    Tree,
    Wall,
    Door,
    Window,
    Magma,
    Rock,
    Void,
}

impl Terrain {
    pub fn new(id: u8) -> Terrain {
        // Tiled indexes start from 1.
        let id = id - 1;
        assert!(id <= Terrain::Rock as u8);
        unsafe {
            std::mem::transmute(id)
        }
    }

    pub fn can_walk(self) -> bool {
        use Terrain::*;
        match self {
            Floor | Grass | Door => true,
            _ => false,
        }
    }
}

impl KernelTerrain for Terrain {
    fn is_wall(&self) -> bool {
        use Terrain::*;
        match *self {
            Wall | Door | Window => true,
            _ => false
        }
    }

    fn is_block(&self) -> bool { *self == Terrain::Rock }
}

pub trait Drawable {
    fn draw(&self, ctx: &mut Canvas, offset: V2<f32>, z: f32);
}

pub struct SprDrawable {
    pub spr: Spr,
    pub fore: Rgba,
    pub back: Rgba,
}

impl SprDrawable {
    pub fn new<A: Into<Rgba>, B: Into<Rgba>>(spr: Spr, fore: A, back: B) -> SprDrawable {
        SprDrawable {
            spr: spr,
            fore: fore.into(),
            back: back.into(),
        }
    }
}

impl Drawable for SprDrawable {
    fn draw(&self, ctx: &mut Canvas, offset: V2<f32>, z: f32) {
        ctx.draw_image(self.spr.get(), offset, z, self.fore, self.back);
    }
}

pub struct Sprite {
    pub drawable: Box<Drawable>,
    pub pos: V2<f32>,

    sort_key: (i8, i32),
}

impl Sprite {
    pub fn new(drawable: Box<Drawable>, pos: V2<f32>, layer: i8) -> Sprite {
        // Fix some numerical inaccuracy noise.
        let pos = pos.map(|x| x.round());
        Sprite {
            drawable: drawable,
            pos: pos,
            sort_key: (layer, -pos.1 as i32)
        }
    }

    pub fn new_spr<A: Into<Rgba>, B: Into<Rgba>>(
        spr: Spr, fore: A, back: B, pos: V2<f32>, layer: i8) -> Sprite {
        Sprite::new(Box::new(SprDrawable::new(spr, fore, back)), pos, layer)
    }

    #[inline]
    pub fn cmp(&self, other: &Sprite) -> std::cmp::Ordering {
        // Cmp backwards, draw order is from large (far away) to small (close by) values.
        other.sort_key.cmp(&self.sort_key)
    }
}

fn unit_focus_sprites(screen_pos: V2<f32>) -> Vec<Sprite> {
    let mut ret = Vec::new();
    let color = color::LIME;
    for idx in 0..6 {
        ret.push(Sprite::new_spr(Spr::EdgeNW + idx, color, color::BLACK, screen_pos, 0));
    }
    ret
}

#[derive(Copy, Clone)]
pub struct Effect {
    pub kind: Fx,
    pub life: u32,
    pub pos: V2<i32>,
}

impl Effect {
    pub fn new(kind: Fx, map_pos: V2<i32>) -> Effect {
        // TODO: Lifetime parametrization.
        Effect {
            kind: kind,
            life: 10,
            pos: map_pos,
        }
    }

    pub fn update(&mut self) { if self.life > 0 { self.life -= 1; } }
    pub fn is_alive(&self) -> bool { self.life > 0 }

    pub fn sprite(&self, proj: &Projection) -> Sprite {
        // XXX: Cloning self, inefficient.
        Sprite::new(Box::new(*self), proj.project(self.pos.map(|x| (x as f32))), 0)
    }
}

impl Drawable for Effect {
    fn draw(&self, ctx: &mut Canvas, offset: V2<f32>, z: f32) {
        match self.kind {
            Fx::PathOk => {
                ctx.draw_rect(&Rect(offset, V2(16.0, 16.0)), z, color::LIME);
            }
            Fx::PathBlocked => {
                ctx.draw_line(2.0, offset, offset + V2(16.0, 16.0), z, color::RED);
                ctx.draw_line(2.0, offset + V2(0.0, 16.0), offset + V2(16.0, 0.0), z, color::RED);
            }
        }
    }
}

#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Fx {
    PathOk,
    PathBlocked,
}

#[derive(Eq, PartialEq)]
pub enum GameMode {
    RealTime,
    Paused,
    Rogue(Entity),
}

pub struct GameState {
    world: World,

    selected: HashSet<Entity>,
    mode: GameMode,

    screen_offset: V2<f32>,
    scroll_delta: V2<f32>,
    pub proj: Projection,
    mouse_cell: V2<i32>,
    drag_rect: Option<Rect<f32>>,

    effects: Vec<Box<Effect>>,
}

impl GameState {
    pub fn new(world: World) -> GameState {
        let screen_offset = V2(320.0f32, 0.0f32);
        GameState {
            world: world,
            selected: HashSet::new(),
            mode: GameMode::RealTime,
            screen_offset: screen_offset,
            scroll_delta: V2(0.0, 0.0),
            proj: Projection::new(V2(16.0, 8.0), V2(-16.0, 8.0)).unwrap()
                .view_offset(screen_offset),
            mouse_cell: V2(-1, -1),
            drag_rect: None,
            effects: Vec::new(),
        }
    }

    pub fn render(&self, ctx: &mut Canvas) {
        let screen_rect = Rect(V2(-32.0f32, -32.0f32), V2(640.0f32 + 64.0, 360.0f32 + 64.0));

        let mut sprites = Vec::new();

        for pt in self.proj.inv_project_rectangle(&screen_rect).iter() {
            let pos = self.proj.project(pt);
            Kernel::new(|p| self.world.terrain_at(p), pt.map(|x| x as i32)).render(
                |layer, spr, fore, back| {
                    sprites.push(Sprite::new_spr(spr, fore, back, pos, layer));
                });
        }

        for &e in self.world.ecs.iter() {
            if let Some(spr) = cmd::sprite(&self.world, e, &self.proj) {
                // Highlight reticle on focused unit.
                if self.selected.contains(&e) {
                    for s in unit_focus_sprites(spr.pos).into_iter() {
                        sprites.push(s);
                    }
                }
                sprites.push(spr);
            }
        }

        for f in self.effects.iter() {
            sprites.push(f.sprite(&self.proj));
        }

        sprites.sort_by(|a, b| a.cmp(&b));
        for spr in sprites.iter() {
            spr.drawable.draw(ctx, spr.pos, 0.5);
        }

        if let Some(rect) = self.drag_rect {
            ctx.draw_rect(&rect, 0.2, color::CYAN);
        }
    }

    pub fn update(&mut self) {
        if self.mode == GameMode::RealTime {
            self.world.update_active();
        } else {
            self.world.update_standby();
        }

        match self.mode {
            GameMode::Rogue(rogue) => {
                let proj = Projection::new(V2(16.0, 8.0), V2(-16.0, 8.0)).unwrap();
                let spr = cmd::sprite(&self.world, rogue, &proj).unwrap();
                self.screen_offset = -spr.pos + V2(320.0, 180.0);
            }
            _ => { self.screen_offset = self.screen_offset - self.scroll_delta; }
        }

        self.proj = Projection::new(V2(16.0, 8.0), V2(-16.0, 8.0)).unwrap()
            .view_offset(self.screen_offset);

        for e in self.effects.iter_mut() {
            e.update();
        }

        self.effects.retain(|e| e.is_alive());
    }

    fn go_rogue(&mut self) -> Option<Entity> {
        if let GameMode::Rogue(rogue) = self.mode { return Some(rogue); }

        if let Some(&rogue) = self.selected.iter().next() {
            self.selected.clear();
            self.mode = GameMode::Rogue(rogue);
            Some(rogue)
        } else {
            None
        }
    }

    fn toggle_pause(&mut self) {
        match self.mode {
            GameMode::Rogue(_) | GameMode::Paused => self.mode = GameMode::RealTime,
            GameMode::RealTime => self.mode = GameMode::Paused,
        }
    }

    fn rogue_step(&mut self, dir: Dir6) {
        if let Some(rogue) = self.go_rogue() {
            while !cmd::ready_to_act(&self.world, rogue) { self.world.update_active(); }
            cmd::step(&mut self.world, rogue, dir);
        }
    }

    fn add_effect(&mut self, effect: Box<Effect>) {
        self.effects.push(effect);
    }

    pub fn process_event(&mut self, evt: Event) {
        let scroll_speed = 8f32;
        match evt {
            Event::KeyPress(k) => {
                match k {
                    Key::J => { self.scroll_delta.0 = -1.0 * scroll_speed; }
                    Key::L => { self.scroll_delta.0 =  1.0 * scroll_speed; }
                    Key::I => { self.scroll_delta.1 = -1.0 * scroll_speed; }
                    Key::K => { self.scroll_delta.1 =  1.0 * scroll_speed; }
                    Key::Space => { self.toggle_pause(); }

                    Key::W => { self.rogue_step(Dir6::North); }
                    Key::E => { self.rogue_step(Dir6::NorthEast); }
                    Key::D => { self.rogue_step(Dir6::SouthEast); }
                    Key::S => { self.rogue_step(Dir6::South); }
                    Key::A => { self.rogue_step(Dir6::SouthWest); }
                    Key::Q => { self.rogue_step(Dir6::NorthWest); }
                    _ => {}
                }
            }

            Event::KeyRelease(k) => {
                match k {
                    Key::J => { self.scroll_delta.0 = 0.0; }
                    Key::L => { self.scroll_delta.0 = 0.0; }
                    Key::I => { self.scroll_delta.1 = 0.0; }
                    Key::K => { self.scroll_delta.1 = 0.0; }
                    _ => {}
                }
            }

            Event::MouseDrag(MouseButton::Left, p1, p2) => {
                self.drag_rect = Some(Rect::from_points(p1, p2))
            }

            Event::MouseMove(pos) => {
                self.mouse_cell = self.proj.inv_project(pos).map(|x| x.floor() as i32);
            }

            Event::MouseClick(MouseButton::Left) => {
                self.selected.clear();
                if let Some(p) = cmd::mob_at(&self.world, self.mouse_cell) {
                    self.selected.insert(p);
                }
            }

            Event::MouseClick(MouseButton::Right) => {
                let mut path_found = None;
                for &p in self.selected.iter() {
                    if cmd::is_player(&self.world, p) {
                        let cmd_pathed = cmd::move_to(&mut self.world, p, self.mouse_cell);
                        path_found = path_found.map_or(
                            Some(cmd_pathed), |x| Some(x || cmd_pathed));
                    }
                }

                let pos = self.mouse_cell;
                match path_found {
                    Some(true) =>
                        self.add_effect(Box::new(Effect::new(Fx::PathOk, pos))),
                    Some(false) =>
                        self.add_effect(Box::new(Effect::new(Fx::PathBlocked, pos))),
                    _ => {}
                }
            }

            Event::MouseDragEnd(MouseButton::Left, p1, p2) => {
                let scale = (p2.0 - p1.0).abs().max((p2.1 - p1.1).abs());
                // Must have dragged some distance before we go from looking
                // at the single click to looking at the dragged rectangle.
                if scale > 12.0 {
                    self.selected.clear();
                    self.drag_rect = None;

                    let cell_rect = Rect(V2(-8f32, -8f32), V2(16f32, 8f32));
                    let select_rect = Rect::from_points(p1, p2);

                    for pt in self.proj.inv_project_rectangle(&select_rect).iter() {
                        if !select_rect.intersects(&(cell_rect + self.proj.project(pt))) { continue; }

                        let pos = pt.map(|x| x.floor() as i32);

                        match cmd::mob_at(&self.world, pos) {
                            Some(e) if cmd::is_player(&self.world, e) => {
                                self.selected.insert(e);
                            }
                            _ => {}
                        }
                    }
                }
            }

            _ => {}
        }
    }
}

fn main() {
    let mut world = World::new();
    let tmx = include_str!("../assets/hexworld.tmx");
    world.load(&tiled::parse(tmx.as_bytes()).unwrap());
    let mut game = GameState::new(world);

    let mut builder = CanvasBuilder::new()
        .set_size(640, 360)
        .set_frame_interval(0.033f64)
        ;
    Spr::init(&mut builder);
    let mut ctx = builder.build();

    loop {
        match ctx.next_event() {
            Event::RenderFrame => {
                game.update();
                game.render(&mut ctx);
            }

            Event::Quit => { return; }

            Event::KeyPress(Key::F12) => {
                ctx.save_screenshot(&"azag");
            }

            Event::KeyPress(Key::Escape) => {
                return;
            }

            e => game.process_event(e),
        }
    }
}
<commit_msg>Effect draw tweaking<commit_after>/*! Hex map display demo */

extern crate num;
extern crate rustc_serialize;
extern crate image;
extern crate tiled;

#[macro_use] extern crate calx_ecs;
extern crate calx;

mod cmd;
mod render;
mod spr;
mod world;

use std::convert::{Into};
use std::collections::{HashSet};
use calx_ecs::{Entity};
use calx::backend::{CanvasBuilder, Canvas, CanvasUtil, Event, MouseButton, Key};
use calx::{V2, Rect, Rgba, color, Dir6};
use calx::{Projection, Kernel, KernelTerrain};

use spr::Spr;
use render::RenderTerrain;
use world::World;

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum Terrain {
    Floor,
    Grass,
    Water,
    Tree,
    Wall,
    Door,
    Window,
    Magma,
    Rock,
    Void,
}

impl Terrain {
    pub fn new(id: u8) -> Terrain {
        // Tiled indexes start from 1.
        let id = id - 1;
        assert!(id <= Terrain::Rock as u8);
        unsafe {
            std::mem::transmute(id)
        }
    }

    pub fn can_walk(self) -> bool {
        use Terrain::*;
        match self {
            Floor | Grass | Door => true,
            _ => false,
        }
    }
}

impl KernelTerrain for Terrain {
    fn is_wall(&self) -> bool {
        use Terrain::*;
        match *self {
            Wall | Door | Window => true,
            _ => false
        }
    }

    fn is_block(&self) -> bool { *self == Terrain::Rock }
}

pub trait Drawable {
    fn draw(&self, ctx: &mut Canvas, offset: V2<f32>, z: f32);
}

pub struct SprDrawable {
    pub spr: Spr,
    pub fore: Rgba,
    pub back: Rgba,
}

impl SprDrawable {
    pub fn new<A: Into<Rgba>, B: Into<Rgba>>(spr: Spr, fore: A, back: B) -> SprDrawable {
        SprDrawable {
            spr: spr,
            fore: fore.into(),
            back: back.into(),
        }
    }
}

impl Drawable for SprDrawable {
    fn draw(&self, ctx: &mut Canvas, offset: V2<f32>, z: f32) {
        ctx.draw_image(self.spr.get(), offset, z, self.fore, self.back);
    }
}

pub struct Sprite {
    pub drawable: Box<Drawable>,
    pub pos: V2<f32>,

    sort_key: (i8, i32),
}

impl Sprite {
    pub fn new(drawable: Box<Drawable>, pos: V2<f32>, layer: i8) -> Sprite {
        // Fix some numerical inaccuracy noise.
        let pos = pos.map(|x| x.round());
        Sprite {
            drawable: drawable,
            pos: pos,
            sort_key: (layer, -pos.1 as i32)
        }
    }

    pub fn new_spr<A: Into<Rgba>, B: Into<Rgba>>(
        spr: Spr, fore: A, back: B, pos: V2<f32>, layer: i8) -> Sprite {
        Sprite::new(Box::new(SprDrawable::new(spr, fore, back)), pos, layer)
    }

    #[inline]
    pub fn cmp(&self, other: &Sprite) -> std::cmp::Ordering {
        // Cmp backwards, draw order is from large (far away) to small (close by) values.
        other.sort_key.cmp(&self.sort_key)
    }
}

fn unit_focus_sprites(screen_pos: V2<f32>) -> Vec<Sprite> {
    let mut ret = Vec::new();
    let color = color::LIME;
    for idx in 0..6 {
        ret.push(Sprite::new_spr(Spr::EdgeNW + idx, color, color::BLACK, screen_pos, 0));
    }
    ret
}

#[derive(Copy, Clone)]
pub struct Effect {
    pub kind: Fx,
    pub life: u32,
    pub pos: V2<i32>,
}

impl Effect {
    pub fn new(kind: Fx, map_pos: V2<i32>) -> Effect {
        // TODO: Lifetime parametrization.
        Effect {
            kind: kind,
            life: 10,
            pos: map_pos,
        }
    }

    pub fn update(&mut self) { if self.life > 0 { self.life -= 1; } }
    pub fn is_alive(&self) -> bool { self.life > 0 }

    pub fn sprite(&self, proj: &Projection) -> Sprite {
        // XXX: Cloning self, inefficient.
        Sprite::new(Box::new(*self), proj.project(self.pos.map(|x| (x as f32))), -4)
    }
}

impl Drawable for Effect {
    fn draw(&self, ctx: &mut Canvas, offset: V2<f32>, z: f32) {
        match self.kind {
            Fx::PathOk => {
                ctx.draw_rect(&Rect(offset + V2(-8.0, 0.0), V2(17.0, 16.0)), z, color::LIME);
            }
            Fx::PathBlocked => {
                ctx.draw_line(2.0, offset + V2(-8.0, 0.0), offset + V2(8.0, 16.0), z, color::RED);
                ctx.draw_line(2.0, offset + V2(-8.0, 16.0), offset + V2(8.0, 0.0), z, color::RED);
            }
        }
    }
}

#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Fx {
    PathOk,
    PathBlocked,
}

#[derive(Eq, PartialEq)]
pub enum GameMode {
    RealTime,
    Paused,
    Rogue(Entity),
}

pub struct GameState {
    world: World,

    selected: HashSet<Entity>,
    mode: GameMode,

    screen_offset: V2<f32>,
    scroll_delta: V2<f32>,
    pub proj: Projection,
    mouse_cell: V2<i32>,
    drag_rect: Option<Rect<f32>>,

    effects: Vec<Box<Effect>>,
}

impl GameState {
    pub fn new(world: World) -> GameState {
        let screen_offset = V2(320.0f32, 0.0f32);
        GameState {
            world: world,
            selected: HashSet::new(),
            mode: GameMode::RealTime,
            screen_offset: screen_offset,
            scroll_delta: V2(0.0, 0.0),
            proj: Projection::new(V2(16.0, 8.0), V2(-16.0, 8.0)).unwrap()
                .view_offset(screen_offset),
            mouse_cell: V2(-1, -1),
            drag_rect: None,
            effects: Vec::new(),
        }
    }

    pub fn render(&self, ctx: &mut Canvas) {
        let screen_rect = Rect(V2(-32.0f32, -32.0f32), V2(640.0f32 + 64.0, 360.0f32 + 64.0));

        let mut sprites = Vec::new();

        for pt in self.proj.inv_project_rectangle(&screen_rect).iter() {
            let pos = self.proj.project(pt);
            Kernel::new(|p| self.world.terrain_at(p), pt.map(|x| x as i32)).render(
                |layer, spr, fore, back| {
                    sprites.push(Sprite::new_spr(spr, fore, back, pos, layer));
                });
        }

        for &e in self.world.ecs.iter() {
            if let Some(spr) = cmd::sprite(&self.world, e, &self.proj) {
                // Highlight reticle on focused unit.
                if self.selected.contains(&e) {
                    for s in unit_focus_sprites(spr.pos).into_iter() {
                        sprites.push(s);
                    }
                }
                sprites.push(spr);
            }
        }

        for f in self.effects.iter() {
            sprites.push(f.sprite(&self.proj));
        }

        sprites.sort_by(|a, b| a.cmp(&b));
        for spr in sprites.iter() {
            spr.drawable.draw(ctx, spr.pos, 0.5);
        }

        if let Some(rect) = self.drag_rect {
            ctx.draw_rect(&rect, 0.2, color::CYAN);
        }
    }

    pub fn update(&mut self) {
        if self.mode == GameMode::RealTime {
            self.world.update_active();
        } else {
            self.world.update_standby();
        }

        match self.mode {
            GameMode::Rogue(rogue) => {
                let proj = Projection::new(V2(16.0, 8.0), V2(-16.0, 8.0)).unwrap();
                let spr = cmd::sprite(&self.world, rogue, &proj).unwrap();
                self.screen_offset = -spr.pos + V2(320.0, 180.0);
            }
            _ => { self.screen_offset = self.screen_offset - self.scroll_delta; }
        }

        self.proj = Projection::new(V2(16.0, 8.0), V2(-16.0, 8.0)).unwrap()
            .view_offset(self.screen_offset);

        for e in self.effects.iter_mut() {
            e.update();
        }

        self.effects.retain(|e| e.is_alive());
    }

    fn go_rogue(&mut self) -> Option<Entity> {
        if let GameMode::Rogue(rogue) = self.mode { return Some(rogue); }

        if let Some(&rogue) = self.selected.iter().next() {
            self.selected.clear();
            self.mode = GameMode::Rogue(rogue);
            Some(rogue)
        } else {
            None
        }
    }

    fn toggle_pause(&mut self) {
        match self.mode {
            GameMode::Rogue(_) | GameMode::Paused => self.mode = GameMode::RealTime,
            GameMode::RealTime => self.mode = GameMode::Paused,
        }
    }

    fn rogue_step(&mut self, dir: Dir6) {
        if let Some(rogue) = self.go_rogue() {
            while !cmd::ready_to_act(&self.world, rogue) { self.world.update_active(); }
            cmd::step(&mut self.world, rogue, dir);
        }
    }

    fn add_effect(&mut self, effect: Box<Effect>) {
        self.effects.push(effect);
    }

    pub fn process_event(&mut self, evt: Event) {
        let scroll_speed = 8f32;
        match evt {
            Event::KeyPress(k) => {
                match k {
                    Key::J => { self.scroll_delta.0 = -1.0 * scroll_speed; }
                    Key::L => { self.scroll_delta.0 =  1.0 * scroll_speed; }
                    Key::I => { self.scroll_delta.1 = -1.0 * scroll_speed; }
                    Key::K => { self.scroll_delta.1 =  1.0 * scroll_speed; }
                    Key::Space => { self.toggle_pause(); }

                    Key::W => { self.rogue_step(Dir6::North); }
                    Key::E => { self.rogue_step(Dir6::NorthEast); }
                    Key::D => { self.rogue_step(Dir6::SouthEast); }
                    Key::S => { self.rogue_step(Dir6::South); }
                    Key::A => { self.rogue_step(Dir6::SouthWest); }
                    Key::Q => { self.rogue_step(Dir6::NorthWest); }
                    _ => {}
                }
            }

            Event::KeyRelease(k) => {
                match k {
                    Key::J => { self.scroll_delta.0 = 0.0; }
                    Key::L => { self.scroll_delta.0 = 0.0; }
                    Key::I => { self.scroll_delta.1 = 0.0; }
                    Key::K => { self.scroll_delta.1 = 0.0; }
                    _ => {}
                }
            }

            Event::MouseDrag(MouseButton::Left, p1, p2) => {
                self.drag_rect = Some(Rect::from_points(p1, p2))
            }

            Event::MouseMove(pos) => {
                self.mouse_cell = self.proj.inv_project(pos).map(|x| x.floor() as i32);
            }

            Event::MouseClick(MouseButton::Left) => {
                self.selected.clear();
                if let Some(p) = cmd::mob_at(&self.world, self.mouse_cell) {
                    self.selected.insert(p);
                }
            }

            Event::MouseClick(MouseButton::Right) => {
                let mut path_found = None;
                for &p in self.selected.iter() {
                    if cmd::is_player(&self.world, p) {
                        let cmd_pathed = cmd::move_to(&mut self.world, p, self.mouse_cell);
                        path_found = path_found.map_or(
                            Some(cmd_pathed), |x| Some(x || cmd_pathed));
                    }
                }

                let pos = self.mouse_cell;
                match path_found {
                    Some(true) =>
                        self.add_effect(Box::new(Effect::new(Fx::PathOk, pos))),
                    Some(false) =>
                        self.add_effect(Box::new(Effect::new(Fx::PathBlocked, pos))),
                    _ => {}
                }
            }

            Event::MouseDragEnd(MouseButton::Left, p1, p2) => {
                let scale = (p2.0 - p1.0).abs().max((p2.1 - p1.1).abs());
                // Must have dragged some distance before we go from looking
                // at the single click to looking at the dragged rectangle.
                if scale > 12.0 {
                    self.selected.clear();
                    self.drag_rect = None;

                    let cell_rect = Rect(V2(-8f32, -8f32), V2(16f32, 8f32));
                    let select_rect = Rect::from_points(p1, p2);

                    for pt in self.proj.inv_project_rectangle(&select_rect).iter() {
                        if !select_rect.intersects(&(cell_rect + self.proj.project(pt))) { continue; }

                        let pos = pt.map(|x| x.floor() as i32);

                        match cmd::mob_at(&self.world, pos) {
                            Some(e) if cmd::is_player(&self.world, e) => {
                                self.selected.insert(e);
                            }
                            _ => {}
                        }
                    }
                }
            }

            _ => {}
        }
    }
}

fn main() {
    let mut world = World::new();
    let tmx = include_str!("../assets/hexworld.tmx");
    world.load(&tiled::parse(tmx.as_bytes()).unwrap());
    let mut game = GameState::new(world);

    let mut builder = CanvasBuilder::new()
        .set_size(640, 360)
        .set_frame_interval(0.033f64)
        ;
    Spr::init(&mut builder);
    let mut ctx = builder.build();

    loop {
        match ctx.next_event() {
            Event::RenderFrame => {
                game.update();
                game.render(&mut ctx);
            }

            Event::Quit => { return; }

            Event::KeyPress(Key::F12) => {
                ctx.save_screenshot(&"azag");
            }

            Event::KeyPress(Key::Escape) => {
                return;
            }

            e => game.process_event(e),
        }
    }
}
<|endoftext|>


<commit_before><commit_msg>Fix second part of Median Segments test<commit_after><|endoftext|>


<commit_before>use crate::homectl_core::device::{Device, DeviceState, Light};
use byteorder::{ByteOrder, LittleEndian};
use num_traits::pow::Pow;
use palette::Hsv;
use std::{error::Error, net::SocketAddr};

#[derive(Clone)]
pub struct LifxState {
    pub hue: u16,
    pub sat: u16,
    pub bri: u16,
    pub power: u16,
    pub label: String,
    pub addr: SocketAddr,
    pub transition: Option<u16>,
}

#[derive(Clone)]
pub enum LifxMsg {
    Get(SocketAddr),
    SetColor(LifxState),
    State(LifxState),
    SetPower(LifxState),
    Unknown,
}

pub fn lifx_msg_type_to_u16(msg_type: LifxMsg) -> u16 {
    match msg_type {
        LifxMsg::Get(_) => 101,
        LifxMsg::SetColor(_) => 102,
        LifxMsg::State(_) => 107,
        LifxMsg::SetPower(_) => 117,
        LifxMsg::Unknown => panic!("Cannot convert LifxMsg::Unknown to u16"),
    }
}

fn mk_lifx_msg_payload(lifx_msg: LifxMsg) -> Option<Vec<u8>> {
    match lifx_msg {
        LifxMsg::SetPower(state) => {
            let mut buf: [u8; 16 + 32] = [0; 16 + 32];

            LittleEndian::write_u16(&mut buf, state.power);

            state
                .transition
                .map(|t| LittleEndian::write_u16(&mut buf[2..], t));

            Some(buf.to_vec())
        }
        LifxMsg::SetColor(state) => {
            let mut buf: [u8; 8 + 16 * 4 + 32] = [0; 8 + 16 * 4 + 32];

            LittleEndian::write_u16(&mut buf[1..], state.hue);
            LittleEndian::write_u16(&mut buf[3..], state.sat);
            LittleEndian::write_u16(&mut buf[5..], state.bri);
            LittleEndian::write_u16(&mut buf[7..], 6500); // lifx requires this weird color temperature parameter?

            state
                .transition
                .map(|t| LittleEndian::write_u16(&mut buf[9..], t));

            Some(buf.to_vec())
        }
        _ => None,
    }
}

pub fn mk_lifx_udp_msg(lifx_msg: LifxMsg) -> Vec<u8> {
    // frame
    // https://lan.developer.lifx.com/docs/header-description#frame
    let mut frame: [u8; 8] = [0; 8];
    let protocol = 1024;
    let origin = 0;
    let tagged = 1;
    let addressable = 1;

    LittleEndian::write_u16(&mut frame, 0); // size to be filled in later
    LittleEndian::write_u16(
        &mut frame[2..],
        protocol | (origin << 14) | (tagged << 13) | (addressable << 12),
    );
    LittleEndian::write_u16(&mut frame[1..], 4);

    // frame address
    // https://lan.developer.lifx.com/docs/header-description#frame-address
    let mut frame_address: [u8; 16] = [0; 16];
    let ack_required = 0;
    let res_required = match lifx_msg {
        LifxMsg::Get(_) => 1,
        _ => 0,
    };

    frame_address[14] = (ack_required << 1) | res_required;

    // protocol header
    // https://lan.developer.lifx.com/docs/header-description#protocol-header
    let mut protocol_header: [u8; 12] = [0; 12];
    let msg_type = lifx_msg_type_to_u16(lifx_msg.clone());
    LittleEndian::write_u16(&mut protocol_header[8..], msg_type);

    let payload = mk_lifx_msg_payload(lifx_msg.clone());
    let payload_size = payload.clone().map(|p| p.len()).unwrap_or(0);
    let msg_size = frame.len() + frame_address.len() + protocol_header.len() + payload_size;

    // we now know the total size - write it into the beginning of the frame header
    LittleEndian::write_u16(&mut frame, msg_size as u16);

    let mut msg: Vec<u8> = vec![];
    msg.append(&mut frame.to_vec());
    msg.append(&mut frame_address.to_vec());
    msg.append(&mut protocol_header.to_vec());

    match payload {
        Some(payload) => {
            msg.append(&mut payload.to_vec());
        }
        None => {}
    };

    msg
}

pub fn read_lifx_msg(buf: &[u8], addr: SocketAddr) -> LifxMsg {
    let msg_type = LittleEndian::read_u16(&buf[32..]);
    let payload = &buf[36..];

    match msg_type {
        107 => {
            // State (107) message, response to Get (101)
            // https://lan.developer.lifx.com/docs/light-messages#section-state-107

            let hue = LittleEndian::read_u16(&payload);
            let sat = LittleEndian::read_u16(&payload[2..]);
            let bri = LittleEndian::read_u16(&payload[4..]);

            let power = LittleEndian::read_u16(&payload[10..]);

            let label = std::str::from_utf8(&payload[12..(12 + 32)])
                .unwrap_or("Unknown")
                .to_owned()
                .replace("\0", "");

            let state = LifxState {
                hue,
                sat,
                bri,
                power,
                label,
                addr,
                transition: None,
            };

            LifxMsg::State(state)
        }
        _ => LifxMsg::Unknown,
    }
}

pub fn from_lifx_state(lifx_state: LifxState, integration_id: String) -> Device {
    let hue = from_lifx_hue((f32::from(lifx_state.hue) / 65535.0) * 360.0);
    let sat = f32::from(lifx_state.sat) / 65535.0;
    let bri = f32::from(lifx_state.bri) / 65535.0;

    let power = lifx_state.power == 65535;

    let color = Hsv::new(hue, sat, bri);

    let state = DeviceState::Light(Light {
        power,
        brightness: None,
        color: Some(color.into()),
    });

    let device = Device {
        id: lifx_state.addr.to_string(),
        name: lifx_state.label.clone(),
        integration_id: integration_id.clone(),
        scene: None,
        state,
    };

    device
}

pub fn to_lifx_state(device: Device) -> Result<LifxState, Box<dyn Error>> {
    let light_state = match device.state {
        DeviceState::Light(Light {
            brightness,
            color,
            power,
        }) => Ok(Light {
            power,
            brightness,
            color,
        }),
        _ => Err("Unsupported device state"),
    }?;

    let color = light_state
        .color
        .map(|color| color.into())
        .unwrap_or(Hsv::new(0.0, 255.0, 255.0));

    let hue = ((to_lifx_hue(color.hue.to_positive_degrees()) / 360.0) * 65535.0).floor() as u16;
    let sat = (color.saturation * 65535.0).floor() as u16;
    let bri = (color.value * 65535.0).floor() as u16;

    let power = if light_state.power { 65535 } else { 0 };

    Ok(LifxState {
        hue,
        sat,
        bri,
        power,
        label: device.name,
        addr: device.id.parse()?,
        transition: Some(500),
    })
}

// NOTE: this is complete trial-and-error, but seems to produce a wider range of
// yellow hues which matches my other HA systems better.
pub fn to_lifx_hue(h: f32) -> f32 {
    if h > 0.0 && h < 60.0 {
        let p = h / 60.0;
        f32::pow(p, 1.3) * 60.0
    } else {
        h
    }
}

pub fn from_lifx_hue(h: f32) -> f32 {
    if h > 0.0 && h < 60.0 {
        let p = h / 60.0;
        f32::pow(p, 1.0 / 1.3) * 60.0
    } else {
        h
    }
}
<commit_msg>fixes to lifx hue<commit_after>use crate::homectl_core::device::{Device, DeviceState, Light};
use byteorder::{ByteOrder, LittleEndian};
use num_traits::pow::Pow;
use palette::Hsv;
use std::{error::Error, net::SocketAddr};

#[derive(Clone)]
pub struct LifxState {
    pub hue: u16,
    pub sat: u16,
    pub bri: u16,
    pub power: u16,
    pub label: String,
    pub addr: SocketAddr,
    pub transition: Option<u16>,
}

#[derive(Clone)]
pub enum LifxMsg {
    Get(SocketAddr),
    SetColor(LifxState),
    State(LifxState),
    SetPower(LifxState),
    Unknown,
}

pub fn lifx_msg_type_to_u16(msg_type: LifxMsg) -> u16 {
    match msg_type {
        LifxMsg::Get(_) => 101,
        LifxMsg::SetColor(_) => 102,
        LifxMsg::State(_) => 107,
        LifxMsg::SetPower(_) => 117,
        LifxMsg::Unknown => panic!("Cannot convert LifxMsg::Unknown to u16"),
    }
}

fn mk_lifx_msg_payload(lifx_msg: LifxMsg) -> Option<Vec<u8>> {
    match lifx_msg {
        LifxMsg::SetPower(state) => {
            let mut buf: [u8; 16 + 32] = [0; 16 + 32];

            LittleEndian::write_u16(&mut buf, state.power);

            state
                .transition
                .map(|t| LittleEndian::write_u16(&mut buf[2..], t));

            Some(buf.to_vec())
        }
        LifxMsg::SetColor(state) => {
            let mut buf: [u8; 8 + 16 * 4 + 32] = [0; 8 + 16 * 4 + 32];

            LittleEndian::write_u16(&mut buf[1..], state.hue);
            LittleEndian::write_u16(&mut buf[3..], state.sat);
            LittleEndian::write_u16(&mut buf[5..], state.bri);
            LittleEndian::write_u16(&mut buf[7..], 6500); // lifx requires this weird color temperature parameter?

            state
                .transition
                .map(|t| LittleEndian::write_u16(&mut buf[9..], t));

            Some(buf.to_vec())
        }
        _ => None,
    }
}

pub fn mk_lifx_udp_msg(lifx_msg: LifxMsg) -> Vec<u8> {
    // frame
    // https://lan.developer.lifx.com/docs/header-description#frame
    let mut frame: [u8; 8] = [0; 8];
    let protocol = 1024;
    let origin = 0;
    let tagged = 1;
    let addressable = 1;

    LittleEndian::write_u16(&mut frame, 0); // size to be filled in later
    LittleEndian::write_u16(
        &mut frame[2..],
        protocol | (origin << 14) | (tagged << 13) | (addressable << 12),
    );
    LittleEndian::write_u16(&mut frame[1..], 4);

    // frame address
    // https://lan.developer.lifx.com/docs/header-description#frame-address
    let mut frame_address: [u8; 16] = [0; 16];
    let ack_required = 0;
    let res_required = match lifx_msg {
        LifxMsg::Get(_) => 1,
        _ => 0,
    };

    frame_address[14] = (ack_required << 1) | res_required;

    // protocol header
    // https://lan.developer.lifx.com/docs/header-description#protocol-header
    let mut protocol_header: [u8; 12] = [0; 12];
    let msg_type = lifx_msg_type_to_u16(lifx_msg.clone());
    LittleEndian::write_u16(&mut protocol_header[8..], msg_type);

    let payload = mk_lifx_msg_payload(lifx_msg.clone());
    let payload_size = payload.clone().map(|p| p.len()).unwrap_or(0);
    let msg_size = frame.len() + frame_address.len() + protocol_header.len() + payload_size;

    // we now know the total size - write it into the beginning of the frame header
    LittleEndian::write_u16(&mut frame, msg_size as u16);

    let mut msg: Vec<u8> = vec![];
    msg.append(&mut frame.to_vec());
    msg.append(&mut frame_address.to_vec());
    msg.append(&mut protocol_header.to_vec());

    match payload {
        Some(payload) => {
            msg.append(&mut payload.to_vec());
        }
        None => {}
    };

    msg
}

pub fn read_lifx_msg(buf: &[u8], addr: SocketAddr) -> LifxMsg {
    let msg_type = LittleEndian::read_u16(&buf[32..]);
    let payload = &buf[36..];

    match msg_type {
        107 => {
            // State (107) message, response to Get (101)
            // https://lan.developer.lifx.com/docs/light-messages#section-state-107

            let hue = LittleEndian::read_u16(&payload);
            let sat = LittleEndian::read_u16(&payload[2..]);
            let bri = LittleEndian::read_u16(&payload[4..]);

            let power = LittleEndian::read_u16(&payload[10..]);

            let label = std::str::from_utf8(&payload[12..(12 + 32)])
                .unwrap_or("Unknown")
                .to_owned()
                .replace("\0", "");

            let state = LifxState {
                hue,
                sat,
                bri,
                power,
                label,
                addr,
                transition: None,
            };

            LifxMsg::State(state)
        }
        _ => LifxMsg::Unknown,
    }
}

pub fn from_lifx_state(lifx_state: LifxState, integration_id: String) -> Device {
    let hue = from_lifx_hue((f32::from(lifx_state.hue) / 65535.0) * 360.0);
    let sat = f32::from(lifx_state.sat) / 65535.0;
    let bri = f32::from(lifx_state.bri) / 65535.0;

    let power = lifx_state.power == 65535;

    let color = Hsv::new(hue, sat, bri);

    let state = DeviceState::Light(Light {
        power,
        brightness: None,
        color: Some(color.into()),
    });

    let device = Device {
        id: lifx_state.addr.to_string(),
        name: lifx_state.label.clone(),
        integration_id: integration_id.clone(),
        scene: None,
        state,
    };

    device
}

pub fn to_lifx_state(device: Device) -> Result<LifxState, Box<dyn Error>> {
    let light_state = match device.state {
        DeviceState::Light(Light {
            brightness,
            color,
            power,
        }) => Ok(Light {
            power,
            brightness,
            color,
        }),
        _ => Err("Unsupported device state"),
    }?;

    let color = light_state
        .color
        .map(|color| color.into())
        .unwrap_or(Hsv::new(0.0, 255.0, 255.0));

    let hue = ((to_lifx_hue(color.hue.to_positive_degrees()) / 360.0) * 65535.0).floor() as u16;
    let sat = (color.saturation * 65535.0).floor() as u16;
    let bri = (color.value * 65535.0).floor() as u16;

    let power = if light_state.power { 65535 } else { 0 };

    Ok(LifxState {
        hue,
        sat,
        bri,
        power,
        label: device.name,
        addr: device.id.parse()?,
        transition: Some(500),
    })
}

// NOTE: this is complete trial-and-error, but seems to produce a wider range of
// yellow hues which matches my other HA systems better.
pub fn to_lifx_hue(h: f32) -> f32 {
    if h > 0.0 && h < 60.0 {
        let p = h / 60.0;
        f32::pow(p, 1.0 / 2.0) * 60.0
    } else {
        h
    }
}

pub fn from_lifx_hue(h: f32) -> f32 {
    if h > 0.0 && h < 60.0 {
        let p = h / 60.0;
        f32::pow(p, 2.0 / 1.0) * 60.0
    } else {
        h
    }
}
<|endoftext|>


<commit_before>// Copyright 2014 The Gfx-rs Developers.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use syntax::{ast, ast_util, ext};
use syntax::ext::build::AstBuilder;
use syntax::ext::deriving::generic;
use syntax::codemap;
use syntax::parse::token;
use syntax::ptr::P;

#[deriving(PartialEq, Show)]
enum ParamType {
    ParamUniform,
    ParamBlock,
    ParamTexture,
}

#[deriving(Show)]
enum ParamError {
    ErrorDeprecatedTexture,
}

/// Classify variable types (`i32`, `TextureParam`, etc) into the `ParamType`
fn classify(node: &ast::Ty_) -> Result<ParamType, ParamError> {
    match *node {
        ast::TyPath(ref path, _, _) => match path.segments.last() {
            Some(segment) => match segment.identifier.name.as_str() {
                "RawBufferHandle" => Ok(ParamBlock),
                "TextureParam" => Ok(ParamTexture),
                "TextureHandle" => Err(ErrorDeprecatedTexture),
                _ => Ok(ParamUniform),
            },
            None => Ok(ParamUniform),
        },
        _ => Ok(ParamUniform),
    }
}

/// Generates the the method body for `gfx::shade::ParamValues::create_link`
fn method_create(cx: &mut ext::base::ExtCtxt, span: codemap::Span,
                 substr: &generic::Substructure,
                 link_name: &str,
                 path_root: ast::Ident) -> P<ast::Expr> {
    let link_ident = cx.ident_of(link_name);
    match *substr.fields {
        generic::StaticStruct(definition, generic::Named(ref fields)) => {
            let init_expr = cx.expr_struct_ident(
                span, link_ident,
                fields.iter().map(|&(fname, fspan)| {
                    cx.field_imm(fspan, fname, cx.expr_none(fspan))
                }).collect()
            );
            let class_info: Vec<(ParamType, P<ast::Expr>)> = definition.fields.iter()
                    .zip(fields.iter()).scan((), |_, (def, &(fname, fspan))|
                match classify(&def.node.ty.node) {
                    Ok(c) => {
                        let name = match super::find_name(cx, span, def.node.attrs.as_slice()) {
                            Some(name) => name,
                            None => token::get_ident(fname),
                        };
                        Some((c, cx.expr_str(fspan, name)))
                    },
                    Err(e) => {
                        cx.span_err(fspan, format!(
                            "Unrecognized parameter ({}) type {}",
                            fname.as_str(), e
                            ).as_slice()
                        );
                        None
                    },
                }
            ).collect();
            let gen_arms = |ptype: ParamType, var: ast::Ident| -> Vec<ast::Arm> {
                class_info.iter().zip(fields.iter())
                          .filter(|&(&(class, _), _)| class == ptype)
                          .map(|(&(_, ref name_expr), &(fname, fspan))|
                    cx.arm(fspan,
                        vec![cx.pat_lit(fspan, name_expr.clone())],
                        quote_expr!(cx,
                            out.$fname = Some(i as $path_root::gfx::shade::$var)
                        )
                    )
                ).collect()
            };
            let uniform_arms = gen_arms(ParamUniform, cx.ident_of("VarUniform"));
            let block_arms = gen_arms(ParamBlock, cx.ident_of("VarBlock"));
            let texture_arms = gen_arms(ParamTexture, cx.ident_of("VarTexture"));
            let input = &substr.nonself_args[1];
            quote_expr!(cx, {
                let mut out = $init_expr;
                for (i, u) in $input.uniforms.iter().enumerate() {
                    let _ = i; // suppress warning about unused i
                    match u.name.as_slice() {
                        $uniform_arms
                        _ => return Err($path_root::gfx::shade::MissingUniform(u.name.clone())),
                    }
                }
                for (i, b) in $input.blocks.iter().enumerate() {
                    let _ = i; // suppress warning about unused i
                    match b.name.as_slice() {
                        $block_arms
                        _ => return Err($path_root::gfx::shade::MissingBlock(b.name.clone())),
                    }
                }
                for (i, t) in $input.textures.iter().enumerate() {
                    let _ = i; // suppress warning about unused i
                    match t.name.as_slice() {
                        $texture_arms
                        _ => return Err($path_root::gfx::shade::MissingTexture(t.name.clone())),
                    }
                }
                Ok(out)
            })
        },
        _ => {
            cx.span_err(span, "Unable to implement `ShaderParam::create_link()` on a non-structure");
            cx.expr_lit(span, ast::LitNil)
        },
    }
}

/// Generates the the method body for `gfx::shade::ParamValues::fill_params`
fn method_fill(cx: &mut ext::base::ExtCtxt, span: codemap::Span,
               substr: &generic::Substructure,
               definition: P<ast::StructDef>,
               path_root: ast::Ident)
               -> P<ast::Expr> {
    match *substr.fields {
        generic::Struct(ref fields) => {
            let out = &substr.nonself_args[1];
            let max_num = cx.expr_uint(span, fields.len());
            let mut calls = vec![
                quote_stmt!(cx, $out.uniforms.reserve($max_num);),
                quote_stmt!(cx, $out.blocks.reserve($max_num);),
                quote_stmt!(cx, $out.textures.reserve($max_num);),
            ];
            calls.extend(definition.fields.iter().zip(fields.iter())
                                   .map(|(def, f)| {
                let value_id = &f.self_;
                let var_id = cx.expr_field_access(
                    span,
                    substr.nonself_args[0].clone(),
                    f.name.unwrap()
                    );
                match classify(&def.node.ty.node) {
                    Ok(ParamUniform) => quote_stmt!(cx,
                        $var_id.map_or((), |id| {
                            if $out.uniforms.len() <= id as uint {
                                unsafe { $out.uniforms.set_len(id as uint + 1) }
                            }
                            *$out.uniforms.get_mut(id as uint) = $value_id.to_uniform()
                        })
                    ),
                    Ok(ParamBlock)   => quote_stmt!(cx,
                        $var_id.map_or((), |id| {
                            if $out.blocks.len() <= id as uint {
                                unsafe { $out.blocks.set_len(id as uint + 1) }
                            }
                            *$out.blocks.get_mut(id as uint) = {$value_id}
                        })
                    ),
                    Ok(ParamTexture) => quote_stmt!(cx,
                        $var_id.map_or((), |id| {
                            if $out.textures.len() <= id as uint {
                                unsafe { $out.textures.set_len(id as uint + 1) }
                            }
                            *$out.textures.get_mut(id as uint) = {$value_id}
                        })
                    ),
                    Err(_) => {
                        cx.span_err(span, format!(
                            "Invalid uniform: {}",
                            f.name.unwrap().as_str(),
                            ).as_slice()
                        );
                        cx.stmt_expr(cx.expr_uint(span, 0))
                    },
                }
            }));
            let view = cx.view_use_simple(
                span,
                ast::Inherited,
                cx.path(span, vec![
                    cx.ident_of("self"),
                    path_root,
                    cx.ident_of("gfx"),
                    cx.ident_of("shade"),
                    cx.ident_of("ToUniform"),
                ])
            );
            cx.expr_block(cx.block_all(span, vec![view], calls, None))
        },
        _ => {
            cx.span_err(span, "Unable to implement `ShaderParam::bind()` on a non-structure");
            cx.expr_lit(span, ast::LitNil)
        }
    }
}

/// A helper function that translates variable type (`i32`, `TextureHandle`, etc)
/// into the corresponding shader var id type (`VarUniform`, `VarBlock`, or `VarTexture`)
fn node_to_var_type(cx: &mut ext::base::ExtCtxt,
                    span: codemap::Span, node: &ast::Ty_,
                    path_root: ast::Ident) -> P<ast::Ty> {
    let id = match classify(node) {
        Ok(ParamUniform) => "VarUniform",
        Ok(ParamBlock)   => "VarBlock",
        Ok(ParamTexture) => "VarTexture",
        Err(ErrorDeprecatedTexture) => {
            cx.span_err(span, "Use gfx::shade::TextureParam for texture vars instead of gfx::shade::TextureHandle");
            ""
        },
    };
    cx.ty_option(cx.ty_path(
        cx.path(span, vec![
            path_root,
            cx.ident_of("gfx"),
            cx.ident_of("shade"),
            cx.ident_of(id),
        ]),
        None
    ))
}

/// Extract all deriving() attributes into a separate array
fn copy_deriving(attribs: &[ast::Attribute]) -> Vec<ast::Attribute> {
    attribs.iter().filter(|at| {
        match at.node.value.node {
            ast::MetaList(ref s, _) => s.get() == "deriving",
            _ => false,
        }
    }).map(|at| at.clone()).collect()
}

/// Decorator for `shader_param` attribute
pub fn expand(context: &mut ext::base::ExtCtxt, span: codemap::Span,
              meta_item: &ast::MetaItem, item: &ast::Item,
              push: |P<ast::Item>|) {
    // Insert the `gfx` reexport module
    let path_root = super::extern_crate_hack(context, span, |i| push(i));

    // constructing the Link struct
    let (base_def, link_def) = match item.node {
        ast::ItemStruct(ref definition, ref generics) => {
            if generics.lifetimes.len() > 0 {
                context.bug("Generics are not allowed in ShaderParam struct");
            }
            (definition, ast::StructDef {
                fields: definition.fields.iter()
                    .map(|f| codemap::Spanned {
                        node: ast::StructField_ {
                            kind: f.node.kind,
                            id: f.node.id,
                            ty: node_to_var_type(context, f.span, &f.node.ty.node, path_root),
                            attrs: Vec::new(),
                        },
                        span: f.span,
                    }).collect(),
                ctor_id: None,
                super_struct: None,
                is_virtual: false,
            })
        },
        _ => {
            context.span_err(span, "Only free-standing named structs allowed to derive ShaderParam");
            return;
        }
    };
    let link_name = format!("_{}Link", item.ident.as_str());
    let link_ident = context.ident_of(link_name.as_slice());
    let link_ty = box generic::ty::Literal(
        generic::ty::Path::new_local(link_name.as_slice())
    );
    // Almost `context.item_struct(span, link_ident, link_def)` but with visibility
    push(P(ast::Item {
        ident: link_ident,
        attrs: copy_deriving(item.attrs.as_slice()),
        id: ast::DUMMY_NODE_ID,
        node: ast::ItemStruct(
            P(link_def),
            ast_util::empty_generics()
        ),
        vis: item.vis,
        span: span,
    }));
    // constructing the `Batch` implementation typedef
    match meta_item.node {
        ast::MetaList(_, ref items) if items.len() <= 2 => {
            let batch_names = ["RefBatch", "OwnedBatch"];
            for (&batch, param) in batch_names.iter().zip(items.iter()) {
                match param.node {
                    ast::MetaWord(ref shell_name) => {
                        // pub type $shell_ident = hack::gfx::batch::RefBatch<$link_ident, $self_ident>
                        let path = context.ty_path(
                            context.path_all(span, false,
                                vec![
                                    path_root,
                                    context.ident_of("gfx"),
                                    context.ident_of("batch"),
                                    context.ident_of(batch),
                                ],
                                Vec::new(),
                                vec![
                                    context.ty_ident(span, link_ident),
                                    context.ty_ident(span, item.ident)
                                ]
                            ), None
                        );
                        push(P(ast::Item {
                            ident: context.ident_of(shell_name.get()),
                            attrs: Vec::new(),
                            id: ast::DUMMY_NODE_ID,
                            node: ast::ItemTy(path, ast_util::empty_generics()),
                            vis: item.vis,
                            span: span,
                        }))
                    },
                    _ => {
                        context.span_err(meta_item.span,
                            "The new batch name has to be a word")
                    }
                }
            }
        },
        _ => {
            context.span_err(meta_item.span,
                "Invalid argument. Please specify the typedef for your `Program`\n\
                as `#[shader_param(MyLightBatch, MyHeavyBatch)]`")
        }
    }
    // deriving ShaderParam
    let trait_def = generic::TraitDef {
        span: span,
        attributes: Vec::new(),
        path: generic::ty::Path {
            path: vec![super::EXTERN_CRATE_HACK, "gfx", "shade", "ShaderParam"],
            lifetime: None,
            params: vec![link_ty.clone()],
            global: false,
        },
        additional_bounds: Vec::new(),
        generics: generic::ty::LifetimeBounds::empty(),
        methods: vec![
            generic::MethodDef {
                name: "create_link",
                generics: generic::ty::LifetimeBounds::empty(),
                explicit_self: None,
                args: vec![
                    generic::ty::Literal(generic::ty::Path {
                        path: vec!["Option"],
                        lifetime: None,
                        params: vec![
                            box generic::ty::Ptr(
                                box generic::ty::Self,
                                generic::ty::Borrowed(None, ast::MutImmutable)
                            ),
                        ],
                        global: false,
                    }),
                    generic::ty::Ptr(
                        box generic::ty::Literal(generic::ty::Path::new(
                            vec![super::EXTERN_CRATE_HACK, "gfx", "ProgramInfo"])),
                        generic::ty::Borrowed(None, ast::MutImmutable)
                    ),
                ],
                ret_ty: generic::ty::Literal(
                    generic::ty::Path {
                        path: vec!["Result"],
                        lifetime: None,
                        params: vec![
                            link_ty.clone(),
                            box generic::ty::Literal(generic::ty::Path::new(
                                vec![super::EXTERN_CRATE_HACK, "gfx", "shade", "ParameterError"]
                            ))
                        ],
                        global: false,
                    },
                ),
                attributes: Vec::new(),
                combine_substructure: generic::combine_substructure(|cx, span, sub|
                    method_create(cx, span, sub, link_name.as_slice(), path_root)
                ),
            },
            generic::MethodDef {
                name: "fill_params",
                generics: generic::ty::LifetimeBounds::empty(),
                explicit_self: Some(Some(generic::ty::Borrowed(
                    None, ast::MutImmutable
                ))),
                args: vec![
                    generic::ty::Ptr(
                        link_ty.clone(),
                        generic::ty::Borrowed(None, ast::MutImmutable)
                    ),
                    generic::ty::Literal(
                        generic::ty::Path::new(vec![super::EXTERN_CRATE_HACK, "gfx", "shade", "ParamValues"]),
                    ),
                ],
                ret_ty: generic::ty::Tuple(Vec::new()),
                attributes: Vec::new(),
                combine_substructure: generic::combine_substructure(|cx, span, sub|
                    method_fill(cx, span, sub, base_def.clone(), path_root)
                ),
            },
        ],
    };
    let fixup = |item| {
        push(super::fixup_extern_crate_paths(item, path_root))
    };
    trait_def.expand(context, meta_item, item, fixup);
}
<commit_msg>Update to rustc 0.13.0-dev 38517d0eb<commit_after>// Copyright 2014 The Gfx-rs Developers.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use syntax::{ast, ast_util, ext};
use syntax::ext::build::AstBuilder;
use syntax::ext::deriving::generic;
use syntax::codemap;
use syntax::parse::token;
use syntax::ptr::P;

#[deriving(PartialEq, Show)]
enum ParamType {
    ParamUniform,
    ParamBlock,
    ParamTexture,
}

#[deriving(Show)]
enum ParamError {
    ErrorDeprecatedTexture,
}

/// Classify variable types (`i32`, `TextureParam`, etc) into the `ParamType`
fn classify(node: &ast::Ty_) -> Result<ParamType, ParamError> {
    match *node {
        ast::TyPath(ref path, _, _) => match path.segments.last() {
            Some(segment) => match segment.identifier.name.as_str() {
                "RawBufferHandle" => Ok(ParamBlock),
                "TextureParam" => Ok(ParamTexture),
                "TextureHandle" => Err(ErrorDeprecatedTexture),
                _ => Ok(ParamUniform),
            },
            None => Ok(ParamUniform),
        },
        _ => Ok(ParamUniform),
    }
}

/// Generates the the method body for `gfx::shade::ParamValues::create_link`
fn method_create(cx: &mut ext::base::ExtCtxt, span: codemap::Span,
                 substr: &generic::Substructure,
                 link_name: &str,
                 path_root: ast::Ident) -> P<ast::Expr> {
    let link_ident = cx.ident_of(link_name);
    match *substr.fields {
        generic::StaticStruct(definition, generic::Named(ref fields)) => {
            let init_expr = cx.expr_struct_ident(
                span, link_ident,
                fields.iter().map(|&(fname, fspan)| {
                    cx.field_imm(fspan, fname, cx.expr_none(fspan))
                }).collect()
            );
            let class_info: Vec<(ParamType, P<ast::Expr>)> = definition.fields.iter()
                    .zip(fields.iter()).scan((), |_, (def, &(fname, fspan))|
                match classify(&def.node.ty.node) {
                    Ok(c) => {
                        let name = match super::find_name(cx, span, def.node.attrs.as_slice()) {
                            Some(name) => name,
                            None => token::get_ident(fname),
                        };
                        Some((c, cx.expr_str(fspan, name)))
                    },
                    Err(e) => {
                        cx.span_err(fspan, format!(
                            "Unrecognized parameter ({}) type {}",
                            fname.as_str(), e
                            ).as_slice()
                        );
                        None
                    },
                }
            ).collect();
            let gen_arms = |ptype: ParamType, var: ast::Ident| -> Vec<ast::Arm> {
                class_info.iter().zip(fields.iter())
                          .filter(|&(&(class, _), _)| class == ptype)
                          .map(|(&(_, ref name_expr), &(fname, fspan))|
                    cx.arm(fspan,
                        vec![cx.pat_lit(fspan, name_expr.clone())],
                        quote_expr!(cx,
                            out.$fname = Some(i as $path_root::gfx::shade::$var)
                        )
                    )
                ).collect()
            };
            let uniform_arms = gen_arms(ParamUniform, cx.ident_of("VarUniform"));
            let block_arms = gen_arms(ParamBlock, cx.ident_of("VarBlock"));
            let texture_arms = gen_arms(ParamTexture, cx.ident_of("VarTexture"));
            let input = &substr.nonself_args[1];
            quote_expr!(cx, {
                let mut out = $init_expr;
                for (i, u) in $input.uniforms.iter().enumerate() {
                    let _ = i; // suppress warning about unused i
                    match u.name.as_slice() {
                        $uniform_arms
                        _ => return Err($path_root::gfx::shade::MissingUniform(u.name.clone())),
                    }
                }
                for (i, b) in $input.blocks.iter().enumerate() {
                    let _ = i; // suppress warning about unused i
                    match b.name.as_slice() {
                        $block_arms
                        _ => return Err($path_root::gfx::shade::MissingBlock(b.name.clone())),
                    }
                }
                for (i, t) in $input.textures.iter().enumerate() {
                    let _ = i; // suppress warning about unused i
                    match t.name.as_slice() {
                        $texture_arms
                        _ => return Err($path_root::gfx::shade::MissingTexture(t.name.clone())),
                    }
                }
                Ok(out)
            })
        },
        _ => {
            cx.span_err(span, "Unable to implement `ShaderParam::create_link()` on a non-structure");
            cx.expr_lit(span, ast::LitNil)
        },
    }
}

/// Generates the the method body for `gfx::shade::ParamValues::fill_params`
fn method_fill(cx: &mut ext::base::ExtCtxt, span: codemap::Span,
               substr: &generic::Substructure,
               definition: P<ast::StructDef>,
               path_root: ast::Ident)
               -> P<ast::Expr> {
    match *substr.fields {
        generic::Struct(ref fields) => {
            let out = &substr.nonself_args[1];
            let max_num = cx.expr_uint(span, fields.len());
            let mut calls = vec![
                quote_stmt!(cx, $out.uniforms.reserve($max_num);),
                quote_stmt!(cx, $out.blocks.reserve($max_num);),
                quote_stmt!(cx, $out.textures.reserve($max_num);),
            ];
            calls.extend(definition.fields.iter().zip(fields.iter())
                                   .map(|(def, f)| {
                let value_id = &f.self_;
                let var_id = cx.expr_field_access(
                    span,
                    substr.nonself_args[0].clone(),
                    f.name.unwrap()
                    );
                match classify(&def.node.ty.node) {
                    Ok(ParamUniform) => quote_stmt!(cx,
                        $var_id.map_or((), |id| {
                            if $out.uniforms.len() <= id as uint {
                                unsafe { $out.uniforms.set_len(id as uint + 1) }
                            }
                            *$out.uniforms.get_mut(id as uint) = $value_id.to_uniform()
                        })
                    ),
                    Ok(ParamBlock)   => quote_stmt!(cx,
                        $var_id.map_or((), |id| {
                            if $out.blocks.len() <= id as uint {
                                unsafe { $out.blocks.set_len(id as uint + 1) }
                            }
                            *$out.blocks.get_mut(id as uint) = {$value_id}
                        })
                    ),
                    Ok(ParamTexture) => quote_stmt!(cx,
                        $var_id.map_or((), |id| {
                            if $out.textures.len() <= id as uint {
                                unsafe { $out.textures.set_len(id as uint + 1) }
                            }
                            *$out.textures.get_mut(id as uint) = {$value_id}
                        })
                    ),
                    Err(_) => {
                        cx.span_err(span, format!(
                            "Invalid uniform: {}",
                            f.name.unwrap().as_str(),
                            ).as_slice()
                        );
                        cx.stmt_expr(cx.expr_uint(span, 0))
                    },
                }
            }));
            let view = cx.view_use_simple(
                span,
                ast::Inherited,
                cx.path(span, vec![
                    cx.ident_of("self"),
                    path_root,
                    cx.ident_of("gfx"),
                    cx.ident_of("shade"),
                    cx.ident_of("ToUniform"),
                ])
            );
            cx.expr_block(cx.block_all(span, vec![view], calls, None))
        },
        _ => {
            cx.span_err(span, "Unable to implement `ShaderParam::bind()` on a non-structure");
            cx.expr_lit(span, ast::LitNil)
        }
    }
}

/// A helper function that translates variable type (`i32`, `TextureHandle`, etc)
/// into the corresponding shader var id type (`VarUniform`, `VarBlock`, or `VarTexture`)
fn node_to_var_type(cx: &mut ext::base::ExtCtxt,
                    span: codemap::Span, node: &ast::Ty_,
                    path_root: ast::Ident) -> P<ast::Ty> {
    let id = match classify(node) {
        Ok(ParamUniform) => "VarUniform",
        Ok(ParamBlock)   => "VarBlock",
        Ok(ParamTexture) => "VarTexture",
        Err(ErrorDeprecatedTexture) => {
            cx.span_err(span, "Use gfx::shade::TextureParam for texture vars instead of gfx::shade::TextureHandle");
            ""
        },
    };
    cx.ty_option(cx.ty_path(
        cx.path(span, vec![
            path_root,
            cx.ident_of("gfx"),
            cx.ident_of("shade"),
            cx.ident_of(id),
        ]),
        None
    ))
}

/// Extract all deriving() attributes into a separate array
fn copy_deriving(attribs: &[ast::Attribute]) -> Vec<ast::Attribute> {
    attribs.iter().filter(|at| {
        match at.node.value.node {
            ast::MetaList(ref s, _) => s.get() == "deriving",
            _ => false,
        }
    }).map(|at| at.clone()).collect()
}

/// Decorator for `shader_param` attribute
pub fn expand(context: &mut ext::base::ExtCtxt, span: codemap::Span,
              meta_item: &ast::MetaItem, item: &ast::Item,
              push: |P<ast::Item>|) {
    // Insert the `gfx` reexport module
    let path_root = super::extern_crate_hack(context, span, |i| push(i));

    // constructing the Link struct
    let (base_def, link_def) = match item.node {
        ast::ItemStruct(ref definition, ref generics) => {
            if generics.lifetimes.len() > 0 {
                context.bug("Generics are not allowed in ShaderParam struct");
            }
            (definition, ast::StructDef {
                fields: definition.fields.iter()
                    .map(|f| codemap::Spanned {
                        node: ast::StructField_ {
                            kind: f.node.kind,
                            id: f.node.id,
                            ty: node_to_var_type(context, f.span, &f.node.ty.node, path_root),
                            attrs: Vec::new(),
                        },
                        span: f.span,
                    }).collect(),
                ctor_id: None,
            })
        },
        _ => {
            context.span_err(span, "Only free-standing named structs allowed to derive ShaderParam");
            return;
        }
    };
    let link_name = format!("_{}Link", item.ident.as_str());
    let link_ident = context.ident_of(link_name.as_slice());
    let link_ty = box generic::ty::Literal(
        generic::ty::Path::new_local(link_name.as_slice())
    );
    // Almost `context.item_struct(span, link_ident, link_def)` but with visibility
    push(P(ast::Item {
        ident: link_ident,
        attrs: copy_deriving(item.attrs.as_slice()),
        id: ast::DUMMY_NODE_ID,
        node: ast::ItemStruct(
            P(link_def),
            ast_util::empty_generics()
        ),
        vis: item.vis,
        span: span,
    }));
    // constructing the `Batch` implementation typedef
    match meta_item.node {
        ast::MetaList(_, ref items) if items.len() <= 2 => {
            let batch_names = ["RefBatch", "OwnedBatch"];
            for (&batch, param) in batch_names.iter().zip(items.iter()) {
                match param.node {
                    ast::MetaWord(ref shell_name) => {
                        // pub type $shell_ident = hack::gfx::batch::RefBatch<$link_ident, $self_ident>
                        let path = context.ty_path(
                            context.path_all(span, false,
                                vec![
                                    path_root,
                                    context.ident_of("gfx"),
                                    context.ident_of("batch"),
                                    context.ident_of(batch),
                                ],
                                Vec::new(),
                                vec![
                                    context.ty_ident(span, link_ident),
                                    context.ty_ident(span, item.ident)
                                ]
                            ), None
                        );
                        push(P(ast::Item {
                            ident: context.ident_of(shell_name.get()),
                            attrs: Vec::new(),
                            id: ast::DUMMY_NODE_ID,
                            node: ast::ItemTy(path, ast_util::empty_generics()),
                            vis: item.vis,
                            span: span,
                        }))
                    },
                    _ => {
                        context.span_err(meta_item.span,
                            "The new batch name has to be a word")
                    }
                }
            }
        },
        _ => {
            context.span_err(meta_item.span,
                "Invalid argument. Please specify the typedef for your `Program`\n\
                as `#[shader_param(MyLightBatch, MyHeavyBatch)]`")
        }
    }
    // deriving ShaderParam
    let trait_def = generic::TraitDef {
        span: span,
        attributes: Vec::new(),
        path: generic::ty::Path {
            path: vec![super::EXTERN_CRATE_HACK, "gfx", "shade", "ShaderParam"],
            lifetime: None,
            params: vec![link_ty.clone()],
            global: false,
        },
        additional_bounds: Vec::new(),
        generics: generic::ty::LifetimeBounds::empty(),
        methods: vec![
            generic::MethodDef {
                name: "create_link",
                generics: generic::ty::LifetimeBounds::empty(),
                explicit_self: None,
                args: vec![
                    generic::ty::Literal(generic::ty::Path {
                        path: vec!["Option"],
                        lifetime: None,
                        params: vec![
                            box generic::ty::Ptr(
                                box generic::ty::Self,
                                generic::ty::Borrowed(None, ast::MutImmutable)
                            ),
                        ],
                        global: false,
                    }),
                    generic::ty::Ptr(
                        box generic::ty::Literal(generic::ty::Path::new(
                            vec![super::EXTERN_CRATE_HACK, "gfx", "ProgramInfo"])),
                        generic::ty::Borrowed(None, ast::MutImmutable)
                    ),
                ],
                ret_ty: generic::ty::Literal(
                    generic::ty::Path {
                        path: vec!["Result"],
                        lifetime: None,
                        params: vec![
                            link_ty.clone(),
                            box generic::ty::Literal(generic::ty::Path::new(
                                vec![super::EXTERN_CRATE_HACK, "gfx", "shade", "ParameterError"]
                            ))
                        ],
                        global: false,
                    },
                ),
                attributes: Vec::new(),
                combine_substructure: generic::combine_substructure(|cx, span, sub|
                    method_create(cx, span, sub, link_name.as_slice(), path_root)
                ),
            },
            generic::MethodDef {
                name: "fill_params",
                generics: generic::ty::LifetimeBounds::empty(),
                explicit_self: Some(Some(generic::ty::Borrowed(
                    None, ast::MutImmutable
                ))),
                args: vec![
                    generic::ty::Ptr(
                        link_ty.clone(),
                        generic::ty::Borrowed(None, ast::MutImmutable)
                    ),
                    generic::ty::Literal(
                        generic::ty::Path::new(vec![super::EXTERN_CRATE_HACK, "gfx", "shade", "ParamValues"]),
                    ),
                ],
                ret_ty: generic::ty::Tuple(Vec::new()),
                attributes: Vec::new(),
                combine_substructure: generic::combine_substructure(|cx, span, sub|
                    method_fill(cx, span, sub, base_def.clone(), path_root)
                ),
            },
        ],
    };
    let fixup = |item| {
        push(super::fixup_extern_crate_paths(item, path_root))
    };
    trait_def.expand(context, meta_item, item, fixup);
}
<|endoftext|>


<commit_before>use crate::utils::*;
use matches::matches;
use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc::hir::*;
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::{declare_lint_pass, declare_tool_lint};

declare_clippy_lint! {
    /// **What it does:** Checks for `if` conditions that use blocks to contain an
    /// expression.
    ///
    /// **Why is this bad?** It isn't really Rust style, same as using parentheses
    /// to contain expressions.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// if { true } { /* ... */ }
    /// ```
    pub BLOCK_IN_IF_CONDITION_EXPR,
    style,
    "braces that can be eliminated in conditions, e.g., `if { true } ...`"
}

declare_clippy_lint! {
    /// **What it does:** Checks for `if` conditions that use blocks containing
    /// statements, or conditions that use closures with blocks.
    ///
    /// **Why is this bad?** Using blocks in the condition makes it hard to read.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```ignore
    /// if { let x = somefunc(); x } {}
    /// // or
    /// if somefunc(|x| { x == 47 }) {}
    /// ```
    pub BLOCK_IN_IF_CONDITION_STMT,
    style,
    "complex blocks in conditions, e.g., `if { let x = true; x } ...`"
}

declare_lint_pass!(BlockInIfCondition => [BLOCK_IN_IF_CONDITION_EXPR, BLOCK_IN_IF_CONDITION_STMT]);

struct ExVisitor<'a, 'tcx> {
    found_block: Option<&'tcx Expr>,
    cx: &'a LateContext<'a, 'tcx>,
}

impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> {
    fn visit_expr(&mut self, expr: &'tcx Expr) {
        if let ExprKind::Closure(_, _, eid, _, _) = expr.node {
            let body = self.cx.tcx.hir().body(eid);
            let ex = &body.value;
            if matches!(ex.node, ExprKind::Block(_, _)) && !body.value.span.from_expansion() {
                self.found_block = Some(ex);
                return;
            }
        }
        walk_expr(self, expr);
    }
    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
        NestedVisitorMap::None
    }
}

const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition";
const COMPLEX_BLOCK_MESSAGE: &str = "in an 'if' condition, avoid complex blocks or closures with blocks; \
                                     instead, move the block or closure higher and bind it with a 'let'";

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BlockInIfCondition {
    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
        if let Some((check, then, _)) = higher::if_block(&expr) {
            if let ExprKind::Block(block, _) = &check.node {
                if block.rules == DefaultBlock {
                    if block.stmts.is_empty() {
                        if let Some(ex) = &block.expr {
                            // don't dig into the expression here, just suggest that they remove
                            // the block
                            if expr.span.from_expansion() || differing_macro_contexts(expr.span, ex.span) {
                                return;
                            }
                            span_help_and_lint(
                                cx,
                                BLOCK_IN_IF_CONDITION_EXPR,
                                check.span,
                                BRACED_EXPR_MESSAGE,
                                &format!(
                                    "try\nif {} {} ... ",
                                    snippet_block(cx, ex.span, ".."),
                                    snippet_block(cx, then.span, "..")
                                ),
                            );
                        }
                    } else {
                        let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
                        if span.from_expansion() || differing_macro_contexts(expr.span, span) {
                            return;
                        }
                        // move block higher
                        span_help_and_lint(
                            cx,
                            BLOCK_IN_IF_CONDITION_STMT,
                            check.span,
                            COMPLEX_BLOCK_MESSAGE,
                            &format!(
                                "try\nlet res = {};\nif res {} ... ",
                                snippet_block(cx, block.span, ".."),
                                snippet_block(cx, then.span, "..")
                            ),
                        );
                    }
                }
            } else {
                let mut visitor = ExVisitor { found_block: None, cx };
                walk_expr(&mut visitor, check);
                if let Some(block) = visitor.found_block {
                    span_lint(cx, BLOCK_IN_IF_CONDITION_STMT, block.span, COMPLEX_BLOCK_MESSAGE);
                }
            }
        }
    }
}
<commit_msg>Allow block_in_if_{stmt,expr} in external macro<commit_after>use crate::utils::*;
use matches::matches;
use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc::hir::*;
use rustc::lint::{in_external_macro, LateContext, LateLintPass, LintArray, LintContext, LintPass};
use rustc::{declare_lint_pass, declare_tool_lint};

declare_clippy_lint! {
    /// **What it does:** Checks for `if` conditions that use blocks to contain an
    /// expression.
    ///
    /// **Why is this bad?** It isn't really Rust style, same as using parentheses
    /// to contain expressions.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// if { true } { /* ... */ }
    /// ```
    pub BLOCK_IN_IF_CONDITION_EXPR,
    style,
    "braces that can be eliminated in conditions, e.g., `if { true } ...`"
}

declare_clippy_lint! {
    /// **What it does:** Checks for `if` conditions that use blocks containing
    /// statements, or conditions that use closures with blocks.
    ///
    /// **Why is this bad?** Using blocks in the condition makes it hard to read.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```ignore
    /// if { let x = somefunc(); x } {}
    /// // or
    /// if somefunc(|x| { x == 47 }) {}
    /// ```
    pub BLOCK_IN_IF_CONDITION_STMT,
    style,
    "complex blocks in conditions, e.g., `if { let x = true; x } ...`"
}

declare_lint_pass!(BlockInIfCondition => [BLOCK_IN_IF_CONDITION_EXPR, BLOCK_IN_IF_CONDITION_STMT]);

struct ExVisitor<'a, 'tcx> {
    found_block: Option<&'tcx Expr>,
    cx: &'a LateContext<'a, 'tcx>,
}

impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> {
    fn visit_expr(&mut self, expr: &'tcx Expr) {
        if let ExprKind::Closure(_, _, eid, _, _) = expr.node {
            let body = self.cx.tcx.hir().body(eid);
            let ex = &body.value;
            if matches!(ex.node, ExprKind::Block(_, _)) && !body.value.span.from_expansion() {
                self.found_block = Some(ex);
                return;
            }
        }
        walk_expr(self, expr);
    }
    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
        NestedVisitorMap::None
    }
}

const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition";
const COMPLEX_BLOCK_MESSAGE: &str = "in an 'if' condition, avoid complex blocks or closures with blocks; \
                                     instead, move the block or closure higher and bind it with a 'let'";

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BlockInIfCondition {
    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
        if in_external_macro(cx.sess(), expr.span) {
            return;
        }
        if let Some((check, then, _)) = higher::if_block(&expr) {
            if let ExprKind::Block(block, _) = &check.node {
                if block.rules == DefaultBlock {
                    if block.stmts.is_empty() {
                        if let Some(ex) = &block.expr {
                            // don't dig into the expression here, just suggest that they remove
                            // the block
                            if expr.span.from_expansion() || differing_macro_contexts(expr.span, ex.span) {
                                return;
                            }
                            span_help_and_lint(
                                cx,
                                BLOCK_IN_IF_CONDITION_EXPR,
                                check.span,
                                BRACED_EXPR_MESSAGE,
                                &format!(
                                    "try\nif {} {} ... ",
                                    snippet_block(cx, ex.span, ".."),
                                    snippet_block(cx, then.span, "..")
                                ),
                            );
                        }
                    } else {
                        let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
                        if span.from_expansion() || differing_macro_contexts(expr.span, span) {
                            return;
                        }
                        // move block higher
                        span_help_and_lint(
                            cx,
                            BLOCK_IN_IF_CONDITION_STMT,
                            check.span,
                            COMPLEX_BLOCK_MESSAGE,
                            &format!(
                                "try\nlet res = {};\nif res {} ... ",
                                snippet_block(cx, block.span, ".."),
                                snippet_block(cx, then.span, "..")
                            ),
                        );
                    }
                }
            } else {
                let mut visitor = ExVisitor { found_block: None, cx };
                walk_expr(&mut visitor, check);
                if let Some(block) = visitor.found_block {
                    span_lint(cx, BLOCK_IN_IF_CONDITION_STMT, block.span, COMPLEX_BLOCK_MESSAGE);
                }
            }
        }
    }
}
<|endoftext|>