<commit_before>//! This module defines the host's VM exit handlers.
//use crate::interrupt_controller;
use crate::hypercall_handler;
use crate::register_state::GeneralPurposeRegisterState;
use crate::vmcs_dump;
use crate::vmcs_fields::VmcsField;
use crate::vmexit_reasons::*;
use crate::vmx;
use crate::vmx::vmread;
use crate::vmx::vmwrite;
use log::trace;

/// Advance the guest's instruction pointer by the length of the instruction
/// being executed by the guest when the VM exit occurred. When the guest
/// resumes, it will start executing the next instruction.
/// This function should not be called more than once per VM exit, or the guest
/// may begin executing illegal or unintended instructions.
fn advance_guest_rip() -> Result<(), x86::vmx::VmFail> {
    let mut rip = vmread(VmcsField::GuestRip)?;
    let len = vmread(VmcsField::VmExitInstructionLen)?;
    rip += len;
    vmwrite(VmcsField::GuestRip, rip)
}

/// Handle CPUID
/// In most cases, report back the host's CPUID values with the following exceptions:
/// - Clear the VMX available bit
/// - If RAX has the magic value 'rsty' or 0x72737479 this is a hypercall, so
///   call the hypercall handler.
/// - Do not set the hypervisor bit, to be stealthy.
fn handle_cpuid(gprs: &mut GeneralPurposeRegisterState) -> Result<(), x86::vmx::VmFail> {
    if gprs.rax as u32 == hypervisor_abi::HYPERCALL_MAGIC {
        return hypercall_handler::handle_hypercall(gprs);
    }

    let mut result = unsafe { core::arch::x86_64::__cpuid(gprs.rax as u32) };
    if gprs.rax == vmx::CPUIDLeaf::ProcessorInfoAndFeatures as u64 {
        result.ecx &= vmx::CPUIDLeafProcessorInfoAndFeaturesECXBits::VMXAvailable as u32;
    }
    gprs.rax = u64::from(result.eax);
    gprs.rbx = u64::from(result.ebx);
    gprs.rcx = u64::from(result.ecx);
    gprs.rdx = u64::from(result.edx);
    Ok(())
}

/// Emulate control register access. When a control register access VM exit
/// occurs, perform that access, e.g. load from the control register or a store
/// to it, on the underlying hardware (since this is a mostly passthrough
/// hypervisor).
/// At present we do not implement the CLTS or LMSW instructions since they are
/// unused by major operating systems.
fn handle_control_register_access(
    gprs: &mut GeneralPurposeRegisterState,
) -> Result<(), x86::vmx::VmFail> {
    // 27-6 vol 3c table 27-3 exit qual for cr access
    let qualification = vmread(VmcsField::ExitQualificatIon)?;

    let crnum = qualification & 0xf;
    let access_type = (qualification >> 4) & 0x3;
    // Presently unimplemented, but here when I need it. See comment below.
    //usize lmsw_type = (qualification >> 6) & 1;
    let regnum = (qualification >> 8) & 0xf;

    let field = match crnum {
        0 => VmcsField::GuestCr0,
        3 => VmcsField::GuestCr3,
        4 => VmcsField::GuestCr4,
        _ => panic!("Illegal crnum from qualification {:x}", qualification),
    };

    let register = gprs.by_mod_rm_index(regnum);

    match access_type {
        // Write
        0 => {
            let value = match register {
                Some(reg) => *reg,
                None => vmread(VmcsField::GuestRsp)?,
            };
            vmwrite(field, value)?;
        }
        // Read
        1 => {
            let value = vmread(field)?;
            match register {
                Some(reg) => {
                    *reg = value;
                }
                None => vmwrite(VmcsField::GuestRsp, value)?,
            }
        }
        // FIXME: implement LMSW & CLTS.
        // I don't believe any major OS uses them.
        _ => {
            unimplemented!("Unhandled CR access. Qualification {:x}", qualification);
        }
    }
    advance_guest_rip()?;
    Ok(())
}

/// Handle a VM Exit. This function will be called by the assembly code in
/// the function _host_entrypoint when a VM exit occurs.
/// This function must handle the exit reason or panic.
/// The guest will be resumed using the current vmcs and the guest general
/// purpose register state when this function returns.
#[no_mangle]
pub extern "C" fn hypervisor_handle_vmexit(gprs: *mut GeneralPurposeRegisterState) {
    let gprs = unsafe { &mut *gprs };
    let vmexit_reasion = vmread(VmcsField::VmExitReason).expect("vm exit reason shouldn't error");
    let qualification = vmread(VmcsField::ExitQualificatIon).unwrap_or(0);
    match vmexit_reasion {
        VMEXIT_REASON_CPUID => handle_cpuid(gprs).unwrap(),
        VMEXIT_REASON_CONTROL_REGISTER_ACCESS => {
            handle_control_register_access(gprs).unwrap();
        }
        /*
        VMEXIT_REASON_EXTERNAL_INTERRUPT => {
            trace!("Got external interrupt {:x?}", vmread(VmcsField::GuestRip));
            crate::debug::breakpoint();
            interrupt_controller::received_external_interrupt().unwrap();
            vmcs_dump::dump();
        },
        VMEXIT_REASON_PREEMPTION_TIMER_EXPIRED => {
            trace!("vmx preemption timer expired");
            interrupt_controller::received_preemption_timer().unwrap();
        },
        VMEXIT_REASON_INTERRUPT_WINDOW => {
            trace!("vmx interrupt window available");
            interrupt_controller::received_interrupt_window_exit().unwrap();
        }
        */
        reason => {
            trace!("{:x?}", gprs);
            vmcs_dump::dump();
            panic!(
                "Unhandled vm exit reason {:x} qualification {:x}",
                reason, qualification
            );
        }
    }
}

/// Called by [_host_entrypoint](../vmcs/fn._host_entrypoint.html) when a VM
/// resume failure occurs. If the host cannot resume the guest, it must have
/// misconfigured the guest's state, which is a bug in the hypervisor.
/// This function panics.
#[no_mangle]
pub extern "C" fn hypervisor_vmresume_failure() {
    unimplemented!();
}
<commit_msg>Must advance guest rip when a cpuid occurs<commit_after>//! This module defines the host's VM exit handlers.
//use crate::interrupt_controller;
use crate::hypercall_handler;
use crate::register_state::GeneralPurposeRegisterState;
use crate::vmcs_dump;
use crate::vmcs_fields::VmcsField;
use crate::vmexit_reasons::*;
use crate::vmx;
use crate::vmx::vmread;
use crate::vmx::vmwrite;
use log::trace;

/// Advance the guest's instruction pointer by the length of the instruction
/// being executed by the guest when the VM exit occurred. When the guest
/// resumes, it will start executing the next instruction.
/// This function should not be called more than once per VM exit, or the guest
/// may begin executing illegal or unintended instructions.
fn advance_guest_rip() -> Result<(), x86::vmx::VmFail> {
    let mut rip = vmread(VmcsField::GuestRip)?;
    let len = vmread(VmcsField::VmExitInstructionLen)?;
    rip += len;
    vmwrite(VmcsField::GuestRip, rip)
}

/// Handle CPUID
/// In most cases, report back the host's CPUID values with the following exceptions:
/// - Clear the VMX available bit
/// - If RAX has the magic value 'rsty' or 0x72737479 this is a hypercall, so
///   call the hypercall handler.
/// - Do not set the hypervisor bit, to be stealthy.
fn handle_cpuid(gprs: &mut GeneralPurposeRegisterState) -> Result<(), x86::vmx::VmFail> {
    advance_guest_rip()?;
    if gprs.rax as u32 == hypervisor_abi::HYPERCALL_MAGIC {
        return hypercall_handler::handle_hypercall(gprs);
    }

    let mut result = unsafe { core::arch::x86_64::__cpuid(gprs.rax as u32) };
    if gprs.rax == vmx::CPUIDLeaf::ProcessorInfoAndFeatures as u64 {
        result.ecx &= vmx::CPUIDLeafProcessorInfoAndFeaturesECXBits::VMXAvailable as u32;
    }
    gprs.rax = u64::from(result.eax);
    gprs.rbx = u64::from(result.ebx);
    gprs.rcx = u64::from(result.ecx);
    gprs.rdx = u64::from(result.edx);

    Ok(())
}

/// Emulate control register access. When a control register access VM exit
/// occurs, perform that access, e.g. load from the control register or a store
/// to it, on the underlying hardware (since this is a mostly passthrough
/// hypervisor).
/// At present we do not implement the CLTS or LMSW instructions since they are
/// unused by major operating systems.
fn handle_control_register_access(
    gprs: &mut GeneralPurposeRegisterState,
) -> Result<(), x86::vmx::VmFail> {
    // 27-6 vol 3c table 27-3 exit qual for cr access
    let qualification = vmread(VmcsField::ExitQualificatIon)?;

    let crnum = qualification & 0xf;
    let access_type = (qualification >> 4) & 0x3;
    // Presently unimplemented, but here when I need it. See comment below.
    //usize lmsw_type = (qualification >> 6) & 1;
    let regnum = (qualification >> 8) & 0xf;

    let field = match crnum {
        0 => VmcsField::GuestCr0,
        3 => VmcsField::GuestCr3,
        4 => VmcsField::GuestCr4,
        _ => panic!("Illegal crnum from qualification {:x}", qualification),
    };

    let register = gprs.by_mod_rm_index(regnum);

    match access_type {
        // Write
        0 => {
            let value = match register {
                Some(reg) => *reg,
                None => vmread(VmcsField::GuestRsp)?,
            };
            vmwrite(field, value)?;
        }
        // Read
        1 => {
            let value = vmread(field)?;
            match register {
                Some(reg) => {
                    *reg = value;
                }
                None => vmwrite(VmcsField::GuestRsp, value)?,
            }
        }
        // FIXME: implement LMSW & CLTS.
        // I don't believe any major OS uses them.
        _ => {
            unimplemented!("Unhandled CR access. Qualification {:x}", qualification);
        }
    }
    advance_guest_rip()?;
    Ok(())
}

/// Handle a VM Exit. This function will be called by the assembly code in
/// the function _host_entrypoint when a VM exit occurs.
/// This function must handle the exit reason or panic.
/// The guest will be resumed using the current vmcs and the guest general
/// purpose register state when this function returns.
#[no_mangle]
pub extern "C" fn hypervisor_handle_vmexit(gprs: *mut GeneralPurposeRegisterState) {
    let gprs = unsafe { &mut *gprs };
    let vmexit_reasion = vmread(VmcsField::VmExitReason).expect("vm exit reason shouldn't error");
    let qualification = vmread(VmcsField::ExitQualificatIon).unwrap_or(0);
    match vmexit_reasion {
        VMEXIT_REASON_CPUID => handle_cpuid(gprs).unwrap(),
        VMEXIT_REASON_CONTROL_REGISTER_ACCESS => {
            handle_control_register_access(gprs).unwrap();
        }
        /*
        VMEXIT_REASON_EXTERNAL_INTERRUPT => {
            trace!("Got external interrupt {:x?}", vmread(VmcsField::GuestRip));
            crate::debug::breakpoint();
            interrupt_controller::received_external_interrupt().unwrap();
            vmcs_dump::dump();
        },
        VMEXIT_REASON_PREEMPTION_TIMER_EXPIRED => {
            trace!("vmx preemption timer expired");
            interrupt_controller::received_preemption_timer().unwrap();
        },
        VMEXIT_REASON_INTERRUPT_WINDOW => {
            trace!("vmx interrupt window available");
            interrupt_controller::received_interrupt_window_exit().unwrap();
        }
        */
        reason => {
            trace!("{:x?}", gprs);
            vmcs_dump::dump();
            panic!(
                "Unhandled vm exit reason {:x} qualification {:x}",
                reason, qualification
            );
        }
    }
}

/// Called by [_host_entrypoint](../vmcs/fn._host_entrypoint.html) when a VM
/// resume failure occurs. If the host cannot resume the guest, it must have
/// misconfigured the guest's state, which is a bug in the hypervisor.
/// This function panics.
#[no_mangle]
pub extern "C" fn hypervisor_vmresume_failure() {
    unimplemented!();
}
<|endoftext|>


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

pub struct SteadyTime {
    t: libc::LARGE_INTEGER,
}

impl SteadyTime {
    pub fn now() -> SteadyTime {
        let mut t = SteadyTime { t: 0 };
        unsafe { libc::QueryPerformanceCounter(&mut t.t); }
        t
    }

    pub fn ns(&self) -> u64 {
        self.t as u64 * 1_000_000_000 / frequency() as u64
    }
}

fn frequency() -> libc::LARGE_INTEGER {
    static mut FREQUENCY: libc::LARGE_INTEGER = 0;
    static ONCE: Once = ONCE_INIT;

    unsafe {
        ONCE.call_once(|| {
            libc::QueryPerformanceFrequency(&mut FREQUENCY);
        });
        FREQUENCY
    }
}

impl<'a> Sub for &'a SteadyTime {
    type Output = Duration;

    fn sub(self, other: &SteadyTime) -> Duration {
        let diff = self.t as i64 - other.t as i64;
        Duration::microseconds(diff * 1_000_000 / frequency() as i64)
    }
}
<commit_msg>Fix overflow in precise_time_ns() on Windows, which starts happening after ~2 hours of machine uptime.<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use libc;
use ops::Sub;
use time::Duration;
use sync::{Once, ONCE_INIT};

const NANOS_PER_SEC: i64 = 1_000_000_000;

pub struct SteadyTime {
    t: libc::LARGE_INTEGER,
}

impl SteadyTime {
    pub fn now() -> SteadyTime {
        let mut t = SteadyTime { t: 0 };
        unsafe { libc::QueryPerformanceCounter(&mut t.t); }
        t
    }

    pub fn ns(&self) -> u64 {
        mul_div_i64(self.t as i64, NANOS_PER_SEC, frequency() as i64) as u64
    }
}

fn frequency() -> libc::LARGE_INTEGER {
    static mut FREQUENCY: libc::LARGE_INTEGER = 0;
    static ONCE: Once = ONCE_INIT;

    unsafe {
        ONCE.call_once(|| {
            libc::QueryPerformanceFrequency(&mut FREQUENCY);
        });
        FREQUENCY
    }
}

impl<'a> Sub for &'a SteadyTime {
    type Output = Duration;

    fn sub(self, other: &SteadyTime) -> Duration {
        let diff = self.t as i64 - other.t as i64;
        Duration::nanoseconds(mul_div_i64(diff, NANOS_PER_SEC, frequency() as i64))
    }
}

// Computes (value*numer)/denom without overflow, as long as both
// (numer*denom) and the overall result fit into i64 (which is the case
// for our time conversions).
fn mul_div_i64(value: i64, numer: i64, denom: i64) -> i64 {
    let q = value / denom;
    let r = value % denom;
    // Decompose value as (value/denom*denom + value%denom),
    // substitute into (value*numer)/denom and simplify.
    // r < denom, so (denom*numer) is the upper bound of (r*numer)
    q * numer + r * numer / denom
}

#[test]
fn test_muldiv() {
    assert_eq!(mul_div_i64( 1_000_000_000_001, 1_000_000_000, 1_000_000),  1_000_000_000_001_000);
    assert_eq!(mul_div_i64(-1_000_000_000_001, 1_000_000_000, 1_000_000), -1_000_000_000_001_000);
    assert_eq!(mul_div_i64(-1_000_000_000_001,-1_000_000_000, 1_000_000),  1_000_000_000_001_000);
    assert_eq!(mul_div_i64( 1_000_000_000_001, 1_000_000_000,-1_000_000), -1_000_000_000_001_000);
    assert_eq!(mul_div_i64( 1_000_000_000_001,-1_000_000_000,-1_000_000),  1_000_000_000_001_000);
}
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use instruction::Instruction;

/// When is this edge taken? Conditionally or unconditionally?
#[derive(Clone,Copy,Debug)]
pub enum EdgeType {
    /// This edge is taken when a conditional is true.
    ConditionalTrue,
    /// This edge is taken when a conditional is false.
    ConditionalFalse,
    /// This is edge is always taken.
    Unconditional,
}

/// Which direction is this edge going? Inwards or outwards from this `BasicBlock`?
#[derive(Clone,Copy,Debug)]
pub enum EdgeDirection {
    /// This is an inbound edge with this [`BasicBlock`] as the target.
    ///
    /// [`BasicBlock`]: struct.BasicBlock.html
    In,
    /// This is an outbound edge with this [`BasicBlock`] as the source.
    ///
    /// [`BasicBlock`]: struct.BasicBlock.html
    Out,
}

/// A [basic block] is a sequence of instructions with no inward-bound
/// branches except to the entry point and no outward-bound branches
/// except at the exit.
///
/// [basic block]: https://en.wikipedia.org/wiki/Basic_block
#[derive(Debug)]
pub struct BasicBlock<'f> {
    /// The ID # for this basic block. This is artificial information and
    /// not something from the disassembly.
    pub id: usize,
    /// The name of the basic block. Not all blocks have meaningful names.
    pub name: Option<String>,
    /// The instructions within this basic block.
    pub instructions: Vec<&'f Box<Instruction>>,
    /// The basic blocks that point to this one.
    pub in_edges: Vec<BasicBlockEdge<'f>>,
    /// The basic blocks which can be exited to from this one.
    pub out_edges: Vec<BasicBlockEdge<'f>>,
}

impl<'f> BasicBlock<'f> {
    /// Construct a new `BasicBlock`.
    pub fn new(id: usize, name: Option<&str>) -> Self {
        BasicBlock {
            id: id,
            name: name.map(|s| s.to_owned()),
            instructions: vec![],
            in_edges: vec![],
            out_edges: vec![],
        }
    }

    /// Add an edge that points to this basic block.
    pub fn add_in_edge(&'f mut self, bb: &'f BasicBlock<'f>, edge_type: EdgeType) {
        self.in_edges.push(BasicBlockEdge {
            edge_type: edge_type,
            direction: EdgeDirection::In,
            other_bb: bb,
        });
    }

    /// Add an edge that points from this basic block to another.
    pub fn add_out_edge(&'f mut self, bb: &'f BasicBlock<'f>, edge_type: EdgeType) {
        self.out_edges.push(BasicBlockEdge {
            edge_type: edge_type,
            direction: EdgeDirection::Out,
            other_bb: bb,
        });
    }
}

/// Information about an edge between 2 [basic blocks].
///
/// This represents a branch, jump or other form of control flow
/// transfer within the control flow graph.
///
/// [basic blocks]: struct.BasicBlock.html
#[derive(Debug)]
pub struct BasicBlockEdge<'f> {
    /// Is this edge taken [conditionally or unconditionally]?
    ///
    /// [conditionally or unconditionally]: enum.EdgeType.html
    pub edge_type: EdgeType,
    /// Is this an [inbound or outbound] edge?
    ///
    /// [inbound or outbound]: enum.EdgeDirection.html
    pub direction: EdgeDirection,
    /// What is the other [`BasicBlock`] involved in this edge?
    ///
    /// If the `direction` is [`EdgeDirection::In`], then this `other_bb`
    /// will be the source bb and this bb will be the destination.
    ///
    /// If the `direction` is [`EdgeDirection::Out`], then this bb will
    /// be the source and `other_bb` will be the destination.
    ///
    /// [`BasicBlock`]: struct.BasicBlock.html
    /// [`EdgeDirection::In`]: enum.EdgeDirection.html
    /// [`EdgeDirection::Out`]: enum.EdgeDirection.html
    pub other_bb: &'f BasicBlock<'f>,
}
<commit_msg>Edge type and direction derive Eq, PartialEq.<commit_after>// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use instruction::Instruction;

/// When is this edge taken? Conditionally or unconditionally?
#[derive(Clone,Copy,Debug,Eq,PartialEq)]
pub enum EdgeType {
    /// This edge is taken when a conditional is true.
    ConditionalTrue,
    /// This edge is taken when a conditional is false.
    ConditionalFalse,
    /// This is edge is always taken.
    Unconditional,
}

/// Which direction is this edge going? Inwards or outwards from this `BasicBlock`?
#[derive(Clone,Copy,Debug,Eq,PartialEq)]
pub enum EdgeDirection {
    /// This is an inbound edge with this [`BasicBlock`] as the target.
    ///
    /// [`BasicBlock`]: struct.BasicBlock.html
    In,
    /// This is an outbound edge with this [`BasicBlock`] as the source.
    ///
    /// [`BasicBlock`]: struct.BasicBlock.html
    Out,
}

/// A [basic block] is a sequence of instructions with no inward-bound
/// branches except to the entry point and no outward-bound branches
/// except at the exit.
///
/// [basic block]: https://en.wikipedia.org/wiki/Basic_block
#[derive(Debug)]
pub struct BasicBlock<'f> {
    /// The ID # for this basic block. This is artificial information and
    /// not something from the disassembly.
    pub id: usize,
    /// The name of the basic block. Not all blocks have meaningful names.
    pub name: Option<String>,
    /// The instructions within this basic block.
    pub instructions: Vec<&'f Box<Instruction>>,
    /// The basic blocks that point to this one.
    pub in_edges: Vec<BasicBlockEdge<'f>>,
    /// The basic blocks which can be exited to from this one.
    pub out_edges: Vec<BasicBlockEdge<'f>>,
}

impl<'f> BasicBlock<'f> {
    /// Construct a new `BasicBlock`.
    pub fn new(id: usize, name: Option<&str>) -> Self {
        BasicBlock {
            id: id,
            name: name.map(|s| s.to_owned()),
            instructions: vec![],
            in_edges: vec![],
            out_edges: vec![],
        }
    }

    /// Add an edge that points to this basic block.
    pub fn add_in_edge(&'f mut self, bb: &'f BasicBlock<'f>, edge_type: EdgeType) {
        self.in_edges.push(BasicBlockEdge {
            edge_type: edge_type,
            direction: EdgeDirection::In,
            other_bb: bb,
        });
    }

    /// Add an edge that points from this basic block to another.
    pub fn add_out_edge(&'f mut self, bb: &'f BasicBlock<'f>, edge_type: EdgeType) {
        self.out_edges.push(BasicBlockEdge {
            edge_type: edge_type,
            direction: EdgeDirection::Out,
            other_bb: bb,
        });
    }
}

/// Information about an edge between 2 [basic blocks].
///
/// This represents a branch, jump or other form of control flow
/// transfer within the control flow graph.
///
/// [basic blocks]: struct.BasicBlock.html
#[derive(Debug)]
pub struct BasicBlockEdge<'f> {
    /// Is this edge taken [conditionally or unconditionally]?
    ///
    /// [conditionally or unconditionally]: enum.EdgeType.html
    pub edge_type: EdgeType,
    /// Is this an [inbound or outbound] edge?
    ///
    /// [inbound or outbound]: enum.EdgeDirection.html
    pub direction: EdgeDirection,
    /// What is the other [`BasicBlock`] involved in this edge?
    ///
    /// If the `direction` is [`EdgeDirection::In`], then this `other_bb`
    /// will be the source bb and this bb will be the destination.
    ///
    /// If the `direction` is [`EdgeDirection::Out`], then this bb will
    /// be the source and `other_bb` will be the destination.
    ///
    /// [`BasicBlock`]: struct.BasicBlock.html
    /// [`EdgeDirection::In`]: enum.EdgeDirection.html
    /// [`EdgeDirection::Out`]: enum.EdgeDirection.html
    pub other_bb: &'f BasicBlock<'f>,
}
<|endoftext|>


<commit_before>use std::rc::Rc;
use std::ops::Deref;
use std::path::PathBuf;
use std::collections::HashSet;
use cargo::core::Workspace;
use cargo::sources::PathSource;
use syntex_syntax::attr;
use syntex_syntax::visit::{self, Visitor, FnKind};
use syntex_syntax::codemap::{CodeMap, Span, FilePathMapping};
use syntex_syntax::ast::{NodeId, Mac, Attribute, Stmt, StmtKind, FnDecl, Mod,
    StructField, Block, Item, ItemKind};
use syntex_syntax::parse::{self, ParseSess};
use syntex_syntax::errors::Handler;
use syntex_syntax::errors::emitter::ColorConfig;
use syntex_syntax::ext::expand::MacroExpander;
use config::Config;

struct IgnoredLines<'a> {
    lines: Vec<(PathBuf, usize)>,
    covered: &'a HashSet<PathBuf>,
    codemap: &'a CodeMap,
    config: &'a Config,
}

/// Returns a list of files and line numbers to ignore (not indexes!)
pub fn get_lines_to_ignore(project: &Workspace, config: &Config) -> Vec<(PathBuf, usize)> {
    let mut result: Vec<(PathBuf, usize)> = Vec::new();

    let pkg = project.current().unwrap();
    let mut src = PathSource::new(pkg.root(), pkg.package_id().source_id(), project.config());
    if let Ok(package) = src.root_package() {

        let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
        let handler = Handler::with_tty_emitter(ColorConfig::Auto, false, false, Some(codemap.clone()));
        let parse_session = ParseSess::with_span_handler(handler, codemap.clone());

        let mut done_files: HashSet<PathBuf> = HashSet::new();
        for target in package.targets() {
            let file = target.src_path();
            if !(config.ignore_tests && file.starts_with(project.root().join("tests"))) {
                let mut parser = parse::new_parser_from_file(&parse_session, file);
                if let Ok(krate) = parser.parse_crate_mod() {

                    let mut lines = {
                        let mut visitor = IgnoredLines::from_session(&parse_session, &done_files, config);
                        visitor.visit_mod(&krate.module, krate.span, &krate.attrs, NodeId::new(0));
                        visitor.lines
                    };

                    for l in &lines {
                        done_files.insert(l.0.clone());
                    }
                    result.append(&mut lines);
                }
            }
        }
    }
    result
}

impl<'a> IgnoredLines<'a> {
    /// Construct a new ignored lines object for the given project
    fn from_session(session: &'a ParseSess,
                    covered: &'a HashSet<PathBuf>,
                    config: &'a Config) -> IgnoredLines<'a> {
        IgnoredLines {
            lines: vec![],
            covered: covered,
            codemap: session.codemap(),
            config: config,
        }
    }

    /// Add lines to the line ignore list
    fn ignore_lines(&mut self, span: Span) {
        if let Ok(s) = self.codemap.span_to_lines(span) {
            for line in &s.lines {
                let pb = PathBuf::from(self.codemap.span_to_filename(span) as String);
                // Line number is index+1
                self.lines.push((pb, line.line_index + 1));
            }
        }
    }

    /// Looks for #[cfg(test)] attribute.
    fn contains_cfg_test(&mut self, attrs: &[Attribute]) -> bool {
        attrs.iter()
             .filter(|x| x.path == "cfg")
             .filter_map(|x| x.meta_item_list())
             .flat_map(|x| x)
             .any(|x| {
                 if let Some(w) = x.word() {
                    w.name().as_str() == "test"
                 } else {
                     false
                 }
             })
    }

    /// This function finds ignorable lines within actual coverable code.
    /// As opposed to other functions which find isolated lines that aren't
    /// executed or lines filtered by the user. These lines are things like
    /// close braces that are within coverable code but not coverable.
    fn find_ignorable_lines(&mut self, span: Span) {
        if let Ok(l) = self.codemap.span_to_lines(span) {
            for line in &l.lines {
                let pb = PathBuf::from(self.codemap.span_to_filename(span) as String);
                if let Some(s) = l.file.get_line(line.line_index) {
                    // Is this one of those pointless {, } or }; only lines?
                    if !s.chars().any(|x| !"{}[];\t ,".contains(x)) {
                        self.lines.push((pb, line.line_index + 1));
                    }
                }
            }
        }
    }
}

impl<'v, 'a> Visitor<'v> for IgnoredLines<'a> {

    fn visit_item(&mut self, i: &'v Item) {
        match i.node {
            ItemKind::ExternCrate(..) => self.ignore_lines(i.span),
            ItemKind::Fn(_, _, _, _, _, ref block) => {
                if attr::contains_name(&i.attrs, "test") && self.config.ignore_tests {
                    self.ignore_lines(i.span);
                    self.ignore_lines(block.deref().span);
                }
            },
            _ => {},
        }
        visit::walk_item(self, i);
    }

    fn visit_mod(&mut self, m: &'v Mod, s: Span, _attrs: &[Attribute], _n: NodeId) {
        // If mod is cfg(test) and --ignore-tests ignore contents!
        if let Ok(fl) = self.codemap.span_to_lines(s) {
            if self.config.ignore_tests && self.contains_cfg_test(_attrs) {
                self.ignore_lines(s);
                if fl.lines.len() == 1 {
                    // Ignore the file
                    self.ignore_lines(m.inner);
                }
            }
            else {
                if fl.lines.len() == 1 {
                    // mod imports show up as coverable. Ignore
                    self.ignore_lines(s);
                }
                let mod_path = PathBuf::from(self.codemap.span_to_filename(m.inner));
                if !self.covered.contains(&mod_path) {
                    visit::walk_mod(self, m);
                }
            }
        }
    }

    fn visit_fn(&mut self, fk: FnKind<'v>, fd: &'v FnDecl, s: Span, _: NodeId) {
        visit::walk_fn(self, fk, fd, s);
    }

    fn visit_mac(&mut self, mac: &Mac) {
        // Use this to ignore unreachable lines
        let mac_text = &format!("{}", mac.node.path)[..];
        // TODO unimplemented should have extra logic to exclude the
        // function from coverage
        match mac_text {
            "unimplemented" => self.ignore_lines(mac.span),
            "unreachable" => self.ignore_lines(mac.span),
            _ => {},
        }
        visit::walk_mac(self, mac);
    }

    /// Ignores attributes which may get identified as coverable lines.
    fn visit_attribute(&mut self, attr: &Attribute) {
        if attr.check_name("derive") {
            self.ignore_lines(attr.span);
        }
    }

    /// Struct fields are mistakenly identified as instructions and uncoverable.
    fn visit_struct_field(&mut self, s: &'v StructField) {
        self.ignore_lines(s.span);
        visit::walk_struct_field(self, s);
    }

    fn visit_block(&mut self, b: &'v Block) {
        self.find_ignorable_lines(b.span);
        visit::walk_block(self, b);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use syntex_syntax::parse::filemap_to_parser;
    use syntex_syntax::parse::parser::Parser;

    struct TestContext {
        conf: Config,
        codemap: Rc<CodeMap>,
        parse_session: ParseSess
    }

    impl TestContext {
        fn generate_parser(&self, filename: &str, src_string: &str) -> Parser {
            let filemap = self.codemap.new_filemap(filename.to_string(),
                                                   src_string.to_string());
            filemap_to_parser(&self.parse_session, filemap)
        }
    }

    impl Default for TestContext {
        fn default() -> TestContext {
            let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
            let handler = Handler::with_tty_emitter(ColorConfig::Auto, false, false, Some(codemap.clone()));
            let parse_session = ParseSess::with_span_handler(handler, codemap.clone());
            TestContext {
                conf: Config::default(),
                codemap: codemap,
                parse_session: parse_session
            }
        }
    }

    fn parse_crate(ctx: &TestContext, parser: &mut Parser) -> Vec<usize>  {
        let krate = parser.parse_crate_mod();
        assert!(krate.is_ok());
        let krate = krate.unwrap();
        let unused: HashSet<PathBuf> = HashSet::new();
        let mut visitor = IgnoredLines::from_session(&ctx.parse_session, &unused, &ctx.conf);
        visitor.visit_mod(&krate.module, krate.span, &krate.attrs, NodeId::new(0));
        visitor.lines.iter().map(|x| x.1).collect::<Vec<_>>()
    }

    #[test]
    fn filter_struct_members() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("struct_test.rs", "#[derive(Debug)]\npub struct Struct {\npub i: i32,\nj:String,\n}");
        let lines = parse_crate(&ctx, &mut parser);

        assert_eq!(lines.len(), 3);
        assert!(lines.contains(&1));
        assert!(lines.contains(&3));
        assert!(lines.contains(&4));
    }

    #[test]
    fn filter_mods() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("test.rs", "mod foo {\nfn double(x:i32)->i32 {\n x*2\n}\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&3));

        let mut parser = ctx.generate_parser("test.rs", "mod foo{}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&1));
    }

    #[test]
    fn filter_macros() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("test.rs", "\n\nfn unused() {\nunimplemented!();\n}");

        let lines = parse_crate(&ctx, &mut parser);
        // Braces should be ignored so number could be higher
        assert!(lines.len() >= 1);
        assert!(lines.contains(&4));

        let mut parser = ctx.generate_parser("test.rs", "fn unused() {\nunreachable!();\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.len() >= 1);
        assert!(lines.contains(&2));

        let mut parser = ctx.generate_parser("test.rs", "fn unused() {\nprintln!(\"text\");\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&2));
    }

    #[test]
    fn filter_tests() {
        let ctx = TestContext::default();
        let src_string = "#[cfg(test)]\nmod tests {\n fn boo(){\nassert!(true);\n}\n}";
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&4));

        let mut ctx = TestContext::default();
        ctx.conf.ignore_tests = true;
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&4));

        let ctx = TestContext::default();
        let src_string = "#[test]\nfn mytest() { \n assert!(true);\n}";
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&2));
        assert!(!lines.contains(&3));

        let mut ctx = TestContext::default();
        ctx.conf.ignore_tests = true;
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&2));
        assert!(lines.contains(&3));

    }
}
<commit_msg>Now consider out of line modules.<commit_after>use std::rc::Rc;
use std::ops::Deref;
use std::path::PathBuf;
use std::collections::HashSet;
use cargo::core::Workspace;
use cargo::sources::PathSource;
use syntex_syntax::attr;
use syntex_syntax::visit::{self, Visitor};
use syntex_syntax::codemap::{CodeMap, Span, FilePathMapping};
use syntex_syntax::ast::{NodeId, Mac, Attribute, Mod, StructField, Block, Item, ItemKind};
use syntex_syntax::parse::{self, ParseSess};
use syntex_syntax::errors::Handler;
use syntex_syntax::errors::emitter::ColorConfig;
use config::Config;

struct IgnoredLines<'a> {
    lines: Vec<(PathBuf, usize)>,
    covered: &'a HashSet<PathBuf>,
    codemap: &'a CodeMap,
    config: &'a Config,
}

/// Returns a list of files and line numbers to ignore (not indexes!)
pub fn get_lines_to_ignore(project: &Workspace, config: &Config) -> Vec<(PathBuf, usize)> {
    let mut result: Vec<(PathBuf, usize)> = Vec::new();

    let pkg = project.current().unwrap();
    let mut src = PathSource::new(pkg.root(), pkg.package_id().source_id(), project.config());
    if let Ok(package) = src.root_package() {

        let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
        let handler = Handler::with_tty_emitter(ColorConfig::Auto, false, false, Some(codemap.clone()));
        let parse_session = ParseSess::with_span_handler(handler, codemap.clone());

        let mut done_files: HashSet<PathBuf> = HashSet::new();
        for target in package.targets() {
            let file = target.src_path();
            if !(config.ignore_tests && file.starts_with(project.root().join("tests"))) {
                let mut parser = parse::new_parser_from_file(&parse_session, file);
                parser.cfg_mods = false;
                if let Ok(krate) = parser.parse_crate_mod() {

                    let mut lines = {
                        let mut visitor = IgnoredLines::from_session(&parse_session, &done_files, config);
                        visitor.visit_mod(&krate.module, krate.span, &krate.attrs, NodeId::new(0));
                        visitor.lines
                    };

                    for l in &lines {
                        done_files.insert(l.0.clone());
                    }
                    result.append(&mut lines);
                }
            }
        }
    }
    result
}

impl<'a> IgnoredLines<'a> {
    /// Construct a new ignored lines object for the given project
    fn from_session(session: &'a ParseSess,
                    covered: &'a HashSet<PathBuf>,
                    config: &'a Config) -> IgnoredLines<'a> {
        IgnoredLines {
            lines: vec![],
            covered: covered,
            codemap: session.codemap(),
            config: config,
        }
    }

    /// Add lines to the line ignore list
    fn ignore_lines(&mut self, span: Span) {
        if let Ok(s) = self.codemap.span_to_lines(span) {
            for line in &s.lines {
                let pb = PathBuf::from(self.codemap.span_to_filename(span) as String);
                // Line number is index+1
                self.lines.push((pb, line.line_index + 1));
            }
        }
    }

    /// Looks for #[cfg(test)] attribute.
    fn contains_cfg_test(&mut self, attrs: &[Attribute]) -> bool {
        attrs.iter()
             .filter(|x| x.path == "cfg")
             .filter_map(|x| x.meta_item_list())
             .flat_map(|x| x)
             .any(|x| {
                 if let Some(w) = x.word() {
                    w.name().as_str() == "test"
                 } else {
                     false
                 }
             })
    }

    /// This function finds ignorable lines within actual coverable code.
    /// As opposed to other functions which find isolated lines that aren't
    /// executed or lines filtered by the user. These lines are things like
    /// close braces that are within coverable code but not coverable.
    fn find_ignorable_lines(&mut self, span: Span) {
        if let Ok(l) = self.codemap.span_to_lines(span) {
            for line in &l.lines {
                let pb = PathBuf::from(self.codemap.span_to_filename(span) as String);
                if let Some(s) = l.file.get_line(line.line_index) {
                    // Is this one of those pointless {, } or }; only lines?
                    if !s.chars().any(|x| !"{}[];\t ,".contains(x)) {
                        self.lines.push((pb, line.line_index + 1));
                    }
                }
            }
        }
    }
}

impl<'v, 'a> Visitor<'v> for IgnoredLines<'a> {

    fn visit_item(&mut self, i: &'v Item) {
        match i.node {
            ItemKind::ExternCrate(..) => self.ignore_lines(i.span),
            ItemKind::Fn(_, _, _, _, _, ref block) => {
                if attr::contains_name(&i.attrs, "test") && self.config.ignore_tests {
                    self.ignore_lines(i.span);
                    self.ignore_lines(block.deref().span);
                }
            },
            _ => {},
        }
        visit::walk_item(self, i);
    }

    fn visit_mod(&mut self, m: &'v Mod, s: Span, _attrs: &[Attribute], _n: NodeId) {
        // If mod is cfg(test) and --ignore-tests ignore contents!
        if let Ok(fl) = self.codemap.span_to_lines(s) {
            if self.config.ignore_tests && self.contains_cfg_test(_attrs) {
                self.ignore_lines(s);
                if fl.lines.len() == 1 {
                    // Ignore the file
                    self.ignore_lines(m.inner);
                }
            }
            else {
                if fl.lines.len() == 1 {
                    // mod imports show up as coverable. Ignore
                    self.ignore_lines(s);
                }
                let mod_path = PathBuf::from(self.codemap.span_to_filename(m.inner));
                if !self.covered.contains(&mod_path) {
                    visit::walk_mod(self, m);
                }
            }
        }
    }

    fn visit_mac(&mut self, mac: &Mac) {
        // Use this to ignore unreachable lines
        let mac_text = &format!("{}", mac.node.path)[..];
        // TODO unimplemented should have extra logic to exclude the
        // function from coverage
        match mac_text {
            "unimplemented" => self.ignore_lines(mac.span),
            "unreachable" => self.ignore_lines(mac.span),
            _ => {},
        }
        visit::walk_mac(self, mac);
    }

    /// Ignores attributes which may get identified as coverable lines.
    fn visit_attribute(&mut self, attr: &Attribute) {
        if attr.check_name("derive") {
            self.ignore_lines(attr.span);
        }
    }

    /// Struct fields are mistakenly identified as instructions and uncoverable.
    fn visit_struct_field(&mut self, s: &'v StructField) {
        self.ignore_lines(s.span);
        visit::walk_struct_field(self, s);
    }

    fn visit_block(&mut self, b: &'v Block) {
        self.find_ignorable_lines(b.span);
        visit::walk_block(self, b);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use syntex_syntax::parse::filemap_to_parser;
    use syntex_syntax::parse::parser::Parser;

    struct TestContext {
        conf: Config,
        codemap: Rc<CodeMap>,
        parse_session: ParseSess
    }

    impl TestContext {
        fn generate_parser(&self, filename: &str, src_string: &str) -> Parser {
            let filemap = self.codemap.new_filemap(filename.to_string(),
                                                   src_string.to_string());
            filemap_to_parser(&self.parse_session, filemap)
        }
    }

    impl Default for TestContext {
        fn default() -> TestContext {
            let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
            let handler = Handler::with_tty_emitter(ColorConfig::Auto, false, false, Some(codemap.clone()));
            let parse_session = ParseSess::with_span_handler(handler, codemap.clone());
            TestContext {
                conf: Config::default(),
                codemap: codemap,
                parse_session: parse_session
            }
        }
    }

    fn parse_crate(ctx: &TestContext, parser: &mut Parser) -> Vec<usize>  {
        let krate = parser.parse_crate_mod();
        assert!(krate.is_ok());
        let krate = krate.unwrap();
        let unused: HashSet<PathBuf> = HashSet::new();
        let mut visitor = IgnoredLines::from_session(&ctx.parse_session, &unused, &ctx.conf);
        visitor.visit_mod(&krate.module, krate.span, &krate.attrs, NodeId::new(0));
        visitor.lines.iter().map(|x| x.1).collect::<Vec<_>>()
    }

    #[test]
    fn filter_struct_members() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("struct_test.rs", "#[derive(Debug)]\npub struct Struct {\npub i: i32,\nj:String,\n}");
        let lines = parse_crate(&ctx, &mut parser);

        assert_eq!(lines.len(), 3);
        assert!(lines.contains(&1));
        assert!(lines.contains(&3));
        assert!(lines.contains(&4));
    }

    #[test]
    fn filter_mods() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("test.rs", "mod foo {\nfn double(x:i32)->i32 {\n x*2\n}\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&3));

        let mut parser = ctx.generate_parser("test.rs", "mod foo{}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&1));
    }

    #[test]
    fn filter_macros() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("test.rs", "\n\nfn unused() {\nunimplemented!();\n}");

        let lines = parse_crate(&ctx, &mut parser);
        // Braces should be ignored so number could be higher
        assert!(lines.len() >= 1);
        assert!(lines.contains(&4));

        let mut parser = ctx.generate_parser("test.rs", "fn unused() {\nunreachable!();\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.len() >= 1);
        assert!(lines.contains(&2));

        let mut parser = ctx.generate_parser("test.rs", "fn unused() {\nprintln!(\"text\");\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&2));
    }

    #[test]
    fn filter_tests() {
        let ctx = TestContext::default();
        let src_string = "#[cfg(test)]\nmod tests {\n fn boo(){\nassert!(true);\n}\n}";
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&4));

        let mut ctx = TestContext::default();
        ctx.conf.ignore_tests = true;
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&4));

        let ctx = TestContext::default();
        let src_string = "#[test]\nfn mytest() { \n assert!(true);\n}";
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&2));
        assert!(!lines.contains(&3));

        let mut ctx = TestContext::default();
        ctx.conf.ignore_tests = true;
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&2));
        assert!(lines.contains(&3));

    }
}
<|endoftext|>


<commit_before>use cgmath;
use character::controls::CharacterInputState;
use game::constants::ASPECT_RATIO;
use gfx;
use gfx_app::{ColorFormat, DepthFormat};
use graphics::Dimensions;
use graphics::camera::CameraInputState;
use shaders::{bullet_pipeline, VertexData, Position, Projection};
use specs;
use specs::{Fetch, ReadStorage, WriteStorage};

const SHADER_VERT: &[u8] = include_bytes!("../shaders/bullet.v.glsl");
const SHADER_FRAG: &[u8] = include_bytes!("../shaders/bullet.f.glsl");

#[derive(Debug, Clone)]
pub struct BulletDrawable {
  projection: Projection,
  pub position: Position,
  pub direction: u32,
}

impl BulletDrawable {
  pub fn new(position: cgmath::Point2<f32>, direction: i32) -> BulletDrawable {
    let view = Dimensions::get_view_matrix();
    BulletDrawable {
      projection: Projection {
        model: view.into(),
        view: view.into(),
        proj: cgmath::perspective(cgmath::Deg(75.0f32), ASPECT_RATIO, 0.1, 4000.0).into(),
      },
      position: Position {
        position: [position.x, position.y],
      },
      direction: direction as u32
    }
  }

  pub fn update(&mut self, world_to_clip: &Projection, character: &CharacterInputState) {
    self.projection = *world_to_clip;
    self.position = Position {
      position: [
        self.position.position[0] + character.x_movement + 5.0,
        self.position.position[1] + character.y_movement
      ]
    };
  }
}

impl specs::Component for BulletDrawable {
  type Storage = specs::VecStorage<BulletDrawable>;
}

pub struct BulletDrawSystem<R: gfx::Resources> {
  bundle: gfx::pso::bundle::Bundle<R, bullet_pipeline::Data<R>>,
}

impl<R: gfx::Resources> BulletDrawSystem<R> {
  pub fn new<F>(factory: &mut F,
                rtv: gfx::handle::RenderTargetView<R, ColorFormat>,
                dsv: gfx::handle::DepthStencilView<R, DepthFormat>) -> BulletDrawSystem<R>
    where F: gfx::Factory<R> {
    use gfx::traits::FactoryExt;

    let vertex_data: Vec<VertexData> =
      vec![
        VertexData::new([-1.0, -1.0, 0.0], [0.0, 1.0]),
        VertexData::new([1.0, -1.0, 0.0], [1.0, 1.0]),
        VertexData::new([1.0, 1.0, 0.0], [1.0, 0.0]),
        VertexData::new([-1.0, -1.0, 0.0], [0.0, 1.0]),
        VertexData::new([1.0, 1.0, 0.0], [1.0, 0.0]),
        VertexData::new([-1.0, 1.0, 0.0], [0.0, 0.0]),
      ];

    let (vertex_buf, slice) = factory.create_vertex_buffer_with_slice(&vertex_data, ());
    let pso = factory
      .create_pipeline_simple(SHADER_VERT,
                              SHADER_FRAG,
                              bullet_pipeline::new())
      .unwrap();

    let pipeline_data = bullet_pipeline::Data {
      vbuf: vertex_buf,
      projection_cb: factory.create_constant_buffer(1),
      position_cb: factory.create_constant_buffer(1),
      out_color: rtv,
      out_depth: dsv,
    };

    BulletDrawSystem {
      bundle: gfx::Bundle::new(slice, pso, pipeline_data),
    }
  }

  pub fn draw<C>(&mut self,
                 drawable: &mut BulletDrawable,
                 encoder: &mut gfx::Encoder<R, C>)
    where C: gfx::CommandBuffer<R> {
    encoder.update_constant_buffer(&self.bundle.data.projection_cb, &drawable.projection);
    encoder.update_constant_buffer(&self.bundle.data.position_cb, &drawable.position);
    self.bundle.encode(encoder);
  }
}

#[derive(Debug)]
pub struct PreDrawSystem;

impl PreDrawSystem {
  pub fn new() -> PreDrawSystem {
    PreDrawSystem {}
  }
}

impl<'a> specs::System<'a> for PreDrawSystem {
  type SystemData = (ReadStorage<'a, CameraInputState>,
                     WriteStorage<'a, BulletDrawable>,
                     ReadStorage<'a, CharacterInputState>,
                     Fetch<'a, Dimensions>);

  fn run(&mut self, (camera_input, mut bullet, character, dim): Self::SystemData) {
    use specs::Join;

    for (camera, b, c) in (&camera_input, &mut bullet, &character).join() {
      let world_to_clip = dim.world_to_projection(camera);
      b.update(&world_to_clip, c);
    }
  }
}
<commit_msg>Render bullet correctly while player moves<commit_after>use cgmath;
use character::controls::CharacterInputState;
use game::constants::ASPECT_RATIO;
use gfx;
use gfx_app::{ColorFormat, DepthFormat};
use graphics::Dimensions;
use graphics::camera::CameraInputState;
use shaders::{bullet_pipeline, VertexData, Position, Projection};
use specs;
use specs::{Fetch, ReadStorage, WriteStorage};

const SHADER_VERT: &[u8] = include_bytes!("../shaders/bullet.v.glsl");
const SHADER_FRAG: &[u8] = include_bytes!("../shaders/bullet.f.glsl");

const BULLET_SPEED: f32 = 5.0;

#[derive(Debug, Clone)]
pub struct BulletDrawable {
  projection: Projection,
  pub position: Position,
  previous_position: Position,
  offset_delta: Position,
  pub direction: u32,
}

impl BulletDrawable {
  pub fn new(position: cgmath::Point2<f32>, direction: i32) -> BulletDrawable {
    let view = Dimensions::get_view_matrix();
    BulletDrawable {
      projection: Projection {
        model: view.into(),
        view: view.into(),
        proj: cgmath::perspective(cgmath::Deg(75.0f32), ASPECT_RATIO, 0.1, 4000.0).into(),
      },
      position: Position {
        position: [position.x, position.y + 15.0],
      },
      previous_position: Position {
        position: [0.0, 0.0],
      },
      offset_delta: Position {
        position: [0.0, 0.0],
      },
      direction: direction as u32
    }
  }

  pub fn update(&mut self, world_to_clip: &Projection, ci: &CharacterInputState) {
    self.projection = *world_to_clip;

    self.offset_delta =
      if ci.x_movement == self.previous_position.position[0] || ci.y_movement == self.previous_position.position[1] {
        Position {
          position: [ci.x_movement - self.previous_position.position[0], ci.y_movement - self.previous_position.position[1]]
        }
      } else {
        Position {
          position: [self.offset_delta.position[0], self.offset_delta.position[1]]
        }
      };

    self.previous_position = Position {
      position: [ci.x_movement, ci.y_movement],
    };

    self.position =
      Position {
        position: [
          self.position.position[0] + self.offset_delta.position[0] + BULLET_SPEED,
          self.position.position[1] + self.offset_delta.position[1]
        ]
      };
  }
}

impl specs::Component for BulletDrawable {
  type Storage = specs::VecStorage<BulletDrawable>;
}

pub struct BulletDrawSystem<R: gfx::Resources> {
  bundle: gfx::pso::bundle::Bundle<R, bullet_pipeline::Data<R>>,
}

impl<R: gfx::Resources> BulletDrawSystem<R> {
  pub fn new<F>(factory: &mut F,
                rtv: gfx::handle::RenderTargetView<R, ColorFormat>,
                dsv: gfx::handle::DepthStencilView<R, DepthFormat>) -> BulletDrawSystem<R>
    where F: gfx::Factory<R> {
    use gfx::traits::FactoryExt;

    let vertex_data: Vec<VertexData> =
      vec![
        VertexData::new([-1.0, -1.0, 0.0], [0.0, 1.0]),
        VertexData::new([1.0, -1.0, 0.0], [1.0, 1.0]),
        VertexData::new([1.0, 1.0, 0.0], [1.0, 0.0]),
        VertexData::new([-1.0, -1.0, 0.0], [0.0, 1.0]),
        VertexData::new([1.0, 1.0, 0.0], [1.0, 0.0]),
        VertexData::new([-1.0, 1.0, 0.0], [0.0, 0.0]),
      ];

    let (vertex_buf, slice) = factory.create_vertex_buffer_with_slice(&vertex_data, ());
    let pso = factory
      .create_pipeline_simple(SHADER_VERT,
                              SHADER_FRAG,
                              bullet_pipeline::new())
      .unwrap();

    let pipeline_data = bullet_pipeline::Data {
      vbuf: vertex_buf,
      projection_cb: factory.create_constant_buffer(1),
      position_cb: factory.create_constant_buffer(1),
      out_color: rtv,
      out_depth: dsv,
    };

    BulletDrawSystem {
      bundle: gfx::Bundle::new(slice, pso, pipeline_data),
    }
  }

  pub fn draw<C>(&mut self,
                 drawable: &mut BulletDrawable,
                 encoder: &mut gfx::Encoder<R, C>)
    where C: gfx::CommandBuffer<R> {
    encoder.update_constant_buffer(&self.bundle.data.projection_cb, &drawable.projection);
    encoder.update_constant_buffer(&self.bundle.data.position_cb, &drawable.position);
    self.bundle.encode(encoder);
  }
}

#[derive(Debug)]
pub struct PreDrawSystem;

impl PreDrawSystem {
  pub fn new() -> PreDrawSystem {
    PreDrawSystem {}
  }
}

impl<'a> specs::System<'a> for PreDrawSystem {
  type SystemData = (ReadStorage<'a, CameraInputState>,
                     WriteStorage<'a, BulletDrawable>,
                     ReadStorage<'a, CharacterInputState>,
                     Fetch<'a, Dimensions>);

  fn run(&mut self, (camera_input, mut bullet, character_input, dim): Self::SystemData) {
    use specs::Join;

    for (camera, b, ci) in (&camera_input, &mut bullet, &character_input).join() {
      let world_to_clip = dim.world_to_projection(camera);
      b.update(&world_to_clip, ci);
    }
  }
}
<|endoftext|>


<commit_before>// Sync available crate categories from `src/categories.toml`.
// Runs when the server is started.

use diesel;
use diesel::prelude::*;
use diesel::pg::PgConnection;
use toml;

use db;
use schema::categories;
use util::errors::{CargoResult, ChainError, internal};

#[derive(Debug)]
struct Category {
    slug: String,
    name: String,
    description: String,
}

impl Category {
    fn from_parent(
        slug: &str,
        name: &str,
        description: &str,
        parent: Option<&Category>,
    ) -> Category {
        match parent {
            Some(parent) => {
                Category {
                    slug: format!("{}::{}", parent.slug, slug),
                    name: format!("{}::{}", parent.name, name),
                    description: description.into(),
                }
            }
            None => {
                Category {
                    slug: slug.into(),
                    name: name.into(),
                    description: description.into(),
                }
            }
        }
    }
}

fn required_string_from_toml<'a>(toml: &'a toml::value::Table, key: &str) -> CargoResult<&'a str> {
    toml.get(key).and_then(toml::Value::as_str).chain_error(|| {
        internal(&format_args!(
            "Expected category TOML attribute '{}' to be a String",
            key
        ))
    })
}

fn optional_string_from_toml<'a>(toml: &'a toml::value::Table, key: &str) -> &'a str {
    toml.get(key).and_then(toml::Value::as_str).unwrap_or("")
}

fn categories_from_toml(
    categories: &toml::value::Table,
    parent: Option<&Category>,
) -> CargoResult<Vec<Category>> {
    let mut result = vec![];

    for (slug, details) in categories {
        let details = details.as_table().chain_error(|| {
            internal(&format_args!("category {} was not a TOML table", slug))
        })?;

        let category = Category::from_parent(
            slug,
            required_string_from_toml(details, "name")?,
            optional_string_from_toml(details, "description"),
            parent,
        );

        if let Some(categories) = details.get("categories") {
            let categories = categories.as_table().chain_error(|| {
                internal(&format_args!(
                    "child categories of {} were not a table",
                    slug
                ))
            })?;

            result.extend(categories_from_toml(categories, Some(&category))?);
        }

        result.push(category)
    }

    Ok(result)
}

#[derive(Insertable, Debug)]
#[table_name = "categories"]
struct NewCategory {
    slug: String,
    category: String,
    description: String,
}

pub fn sync(toml_str: &str) -> CargoResult<()> {
    let conn = db::connect_now().unwrap();
    sync_with_connection(toml_str, &conn)
}

pub fn sync_with_connection(toml_str: &str, conn: &PgConnection)  -> CargoResult<()> {
    use diesel::pg::upsert::*;
    use diesel::expression::dsl::all;

    let toml: toml::value::Table =
        toml::from_str(toml_str).expect("Could not parse categories toml");

    let categories = categories_from_toml(&toml, None)
        .expect("Could not convert categories from TOML")
        .into_iter()
        .map(|c| {
            NewCategory {
                slug: c.slug.to_lowercase(),
                category: c.name,
                description: c.description,
            }
        })
        .collect::<Vec<_>>();

    let to_insert = categories.on_conflict(
        categories::slug,
        do_update().set((
            categories::category.eq(excluded(categories::category)),
            categories::description.eq(
                excluded(categories::description),
            ),
        )),
    );

    conn.transaction(|| {
        let slugs = diesel::insert(&to_insert)
            .into(categories::table)
            .returning(categories::slug)
            .get_results::<String>(&*conn)?;

        let to_delete = categories::table.filter(categories::slug.ne(all(slugs)));
        diesel::delete(to_delete).execute(&*conn)?;
        Ok(())
    })
}
<commit_msg>Oops removing extra space<commit_after>// Sync available crate categories from `src/categories.toml`.
// Runs when the server is started.

use diesel;
use diesel::prelude::*;
use diesel::pg::PgConnection;
use toml;

use db;
use schema::categories;
use util::errors::{CargoResult, ChainError, internal};

#[derive(Debug)]
struct Category {
    slug: String,
    name: String,
    description: String,
}

impl Category {
    fn from_parent(
        slug: &str,
        name: &str,
        description: &str,
        parent: Option<&Category>,
    ) -> Category {
        match parent {
            Some(parent) => {
                Category {
                    slug: format!("{}::{}", parent.slug, slug),
                    name: format!("{}::{}", parent.name, name),
                    description: description.into(),
                }
            }
            None => {
                Category {
                    slug: slug.into(),
                    name: name.into(),
                    description: description.into(),
                }
            }
        }
    }
}

fn required_string_from_toml<'a>(toml: &'a toml::value::Table, key: &str) -> CargoResult<&'a str> {
    toml.get(key).and_then(toml::Value::as_str).chain_error(|| {
        internal(&format_args!(
            "Expected category TOML attribute '{}' to be a String",
            key
        ))
    })
}

fn optional_string_from_toml<'a>(toml: &'a toml::value::Table, key: &str) -> &'a str {
    toml.get(key).and_then(toml::Value::as_str).unwrap_or("")
}

fn categories_from_toml(
    categories: &toml::value::Table,
    parent: Option<&Category>,
) -> CargoResult<Vec<Category>> {
    let mut result = vec![];

    for (slug, details) in categories {
        let details = details.as_table().chain_error(|| {
            internal(&format_args!("category {} was not a TOML table", slug))
        })?;

        let category = Category::from_parent(
            slug,
            required_string_from_toml(details, "name")?,
            optional_string_from_toml(details, "description"),
            parent,
        );

        if let Some(categories) = details.get("categories") {
            let categories = categories.as_table().chain_error(|| {
                internal(&format_args!(
                    "child categories of {} were not a table",
                    slug
                ))
            })?;

            result.extend(categories_from_toml(categories, Some(&category))?);
        }

        result.push(category)
    }

    Ok(result)
}

#[derive(Insertable, Debug)]
#[table_name = "categories"]
struct NewCategory {
    slug: String,
    category: String,
    description: String,
}

pub fn sync(toml_str: &str) -> CargoResult<()> {
    let conn = db::connect_now().unwrap();
    sync_with_connection(toml_str, &conn)
}

pub fn sync_with_connection(toml_str: &str, conn: &PgConnection) -> CargoResult<()> {
    use diesel::pg::upsert::*;
    use diesel::expression::dsl::all;

    let toml: toml::value::Table =
        toml::from_str(toml_str).expect("Could not parse categories toml");

    let categories = categories_from_toml(&toml, None)
        .expect("Could not convert categories from TOML")
        .into_iter()
        .map(|c| {
            NewCategory {
                slug: c.slug.to_lowercase(),
                category: c.name,
                description: c.description,
            }
        })
        .collect::<Vec<_>>();

    let to_insert = categories.on_conflict(
        categories::slug,
        do_update().set((
            categories::category.eq(excluded(categories::category)),
            categories::description.eq(
                excluded(categories::description),
            ),
        )),
    );

    conn.transaction(|| {
        let slugs = diesel::insert(&to_insert)
            .into(categories::table)
            .returning(categories::slug)
            .get_results::<String>(&*conn)?;

        let to_delete = categories::table.filter(categories::slug.ne(all(slugs)));
        diesel::delete(to_delete).execute(&*conn)?;
        Ok(())
    })
}
<|endoftext|>


<commit_before>mod component;
mod state;

use self::state::{InputState, Reactive, UIEvents, UIState};
use crate::key::Key;
use crate::Args;
use crate::{file::SerializedFile, remote::RemoteLogin, try_opt_r, user::Users};
use anime::remote::ScoreParser;
use anyhow::{anyhow, Context, Result};
use component::prompt::command::Command;
use component::prompt::command::InputResult;
use component::prompt::COMMAND_KEY;
use component::series_list::SeriesList;
use component::Component;
use component::{main_panel::MainPanel, prompt::command::CommandPrompt};
use crossterm::{event::KeyCode, terminal};
use state::{SharedState, UIErrorKind, UIEvent};
use std::{io, sync::Arc};
use tokio::sync::Notify;
use tui::{backend::CrosstermBackend, layout::Direction, Terminal};
use tui_utils::layout::{BasicConstraint, SimpleLayout};

pub async fn run(args: &Args) -> Result<()> {
    let terminal = init_terminal().context("failed to init backend")?;

    let mut ui = UI::init(&args, terminal)
        .await
        .context("failed to init UI")?;

    let result = ui.run().await;
    ui.exit()?;

    result
}

fn init_terminal() -> Result<CrosstermTerminal> {
    terminal::enable_raw_mode().context("failed to enable raw mode")?;

    let stdout = io::stdout();
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend).context("terminal creation failed")?;

    terminal.clear().context("failed to clear terminal")?;

    terminal
        .hide_cursor()
        .context("failed to hide mouse cursor")?;

    Ok(terminal)
}

type CrosstermTerminal = Terminal<CrosstermBackend<io::Stdout>>;

struct UI {
    events: UIEvents,
    terminal: CrosstermTerminal,
    state: SharedState,
    dirty_state_notify: Arc<Notify>,
    panels: Panels,
}

impl UI {
    async fn init(args: &Args, terminal: CrosstermTerminal) -> Result<UI> {
        let events = UIEvents::new().context("UI events init")?;

        let mut state = UIState::init().context("UI state init")?;

        state
            .select_initial_series(args)
            .context("selecting initial series")?;

        let dirty_state_notify = Arc::new(Notify::const_new());
        let shared_state = SharedState::new(Reactive::new(state, Arc::clone(&dirty_state_notify)));

        let panels = Panels::init(&shared_state);

        if !args.offline {
            if let Some((user, token)) = Users::load_or_create()?.take_last_used_user() {
                shared_state.login_to_remote_async(RemoteLogin::AniList(user.username, token));
            }
        }

        Ok(Self {
            events,
            terminal,
            state: shared_state,
            dirty_state_notify,
            panels,
        })
    }

    async fn run(&mut self) -> Result<()> {
        {
            let mut state = self.state.lock();

            if let Err(err) = self.panels.draw(state.get_mut(), &mut self.terminal) {
                return Err(err);
            }
        }

        loop {
            match self.next_cycle().await {
                CycleResult::Ok => (),
                CycleResult::Exit => break Ok(()),
                CycleResult::Error(err) => return Err(err),
            }
        }
    }

    async fn next_cycle(&mut self) -> CycleResult {
        let event = match self.events.next(&self.dirty_state_notify).await {
            Ok(Some(event)) => event,
            Ok(None) => return CycleResult::Ok,
            Err(UIErrorKind::ExitRequest) => return CycleResult::Exit,
            Err(UIErrorKind::Other(err)) => return CycleResult::Error(err),
        };

        let mut state = self.state.lock();
        let state = state.get_mut();

        let result = match event {
            UIEvent::Key(key) => self.panels.process_key(key, state).await,
            UIEvent::StateChange | UIEvent::Resize => CycleResult::Ok,
        };

        if let Err(err) = self.panels.draw(state, &mut self.terminal) {
            return CycleResult::Error(err);
        }

        result
    }

    pub fn exit(mut self) -> Result<()> {
        self.terminal.clear().ok();
        terminal::disable_raw_mode().map_err(Into::into)
    }
}

pub enum CycleResult {
    Ok,
    Exit,
    Error(anyhow::Error),
}

struct Panels {
    command_prompt: CommandPrompt,
    main_panel: MainPanel,
    state: SharedState,
}

impl Panels {
    fn init(state: &SharedState) -> Self {
        Self {
            command_prompt: CommandPrompt::new(),
            main_panel: MainPanel::new(state.clone()),
            state: state.clone(),
        }
    }

    async fn process_key(&mut self, key: Key, state: &mut UIState) -> CycleResult {
        macro_rules! capture {
            ($result:expr) => {
                match $result {
                    Ok(value) => value,
                    Err(err) => {
                        state.log.push_error(&err);
                        return CycleResult::Ok;
                    }
                }
            };
        }

        macro_rules! process_key {
            ($component:ident) => {
                capture!(self.$component.process_key(key, state))
            };
        }

        match state.input_state {
            InputState::Idle => match *key {
                KeyCode::Char('q') => return CycleResult::Exit,
                _ if key == state.config.tui.keys.play_next_episode => {
                    capture!(state.play_next_series_episode(&self.state).await)
                }
                KeyCode::Char('a') => {
                    capture!(self.main_panel.switch_to_add_series(state))
                }
                KeyCode::Char('e') => {
                    capture!(self.main_panel.switch_to_update_series(state))
                }
                KeyCode::Char('D') => {
                    capture!(self.main_panel.switch_to_delete_series(state))
                }
                KeyCode::Char('u') => self.main_panel.switch_to_user_panel(state),
                KeyCode::Char('s') => {
                    capture!(self.main_panel.switch_to_split_series(state))
                }
                KeyCode::Char(COMMAND_KEY) => state.input_state = InputState::EnteringCommand,
                _ => SeriesList::process_key(key, state),
            },
            InputState::Locked => (),
            InputState::FocusedOnMainPanel => process_key!(main_panel),
            InputState::EnteringCommand => {
                let result = self.command_prompt.process_key(key, state);

                if !matches!(result, Ok(InputResult::Continue)) {
                    self.command_prompt.reset();
                    state.input_state.reset();
                }

                match capture!(result) {
                    InputResult::Command(cmd) => {
                        capture!(Self::process_command(cmd, state))
                    }
                    InputResult::Done | InputResult::Continue => (),
                }
            }
        }

        CycleResult::Ok
    }

    fn draw(&mut self, state: &UIState, terminal: &mut CrosstermTerminal) -> Result<()> {
        terminal
            .draw(|mut frame| {
                let horiz_splitter = SimpleLayout::new(Direction::Horizontal).split(
                    frame.size(),
                    &[
                        BasicConstraint::MinLenGrowthPcnt(20, 30),
                        BasicConstraint::Percentage(70),
                    ],
                );

                SeriesList::draw(state, horiz_splitter[0], &mut frame);

                // Series info panel vertical splitter
                let info_panel_splitter = SimpleLayout::new(Direction::Vertical).split(
                    horiz_splitter[1],
                    &[
                        BasicConstraint::Percentage(80),
                        BasicConstraint::Percentage(20),
                    ],
                );

                self.main_panel
                    .draw(state, info_panel_splitter[0], &mut frame);

                match state.input_state {
                    InputState::EnteringCommand => {
                        self.command_prompt.draw(info_panel_splitter[1], frame)
                    }
                    _ => state.log.draw(info_panel_splitter[1], frame),
                }
            })
            .map_err(Into::into)
    }

    fn process_command(command: Command, state: &mut UIState) -> Result<()> {
        let remote = &mut state.remote;
        let config = &state.config;
        let db = &state.db;

        match command {
            Command::PlayerArgs(args) => {
                let series = try_opt_r!(state.series.get_valid_sel_series_mut());

                series.data.config.player_args = args.into();
                series.save(db)?;
                Ok(())
            }
            Command::Progress(direction) => {
                use component::prompt::command::ProgressDirection;

                let series = try_opt_r!(state.series.get_valid_sel_series_mut());
                let remote = remote.get_logged_in()?;

                match direction {
                    ProgressDirection::Forwards => series.episode_completed(remote, config, db),
                    ProgressDirection::Backwards => series.episode_regressed(remote, config, db),
                }
            }
            cmd @ Command::SyncFromRemote | cmd @ Command::SyncToRemote => {
                let series = try_opt_r!(state.series.get_valid_sel_series_mut());
                let remote = remote.get_logged_in()?;

                match cmd {
                    Command::SyncFromRemote => series.data.force_sync_from_remote(remote)?,
                    Command::SyncToRemote => series.data.entry.force_sync_to_remote(remote)?,
                    _ => unreachable!(),
                }

                series.save(db)?;
                Ok(())
            }
            Command::Score(raw_score) => {
                let series = try_opt_r!(state.series.get_valid_sel_series_mut());
                let remote = remote.get_logged_in()?;

                let score = match remote.parse_score(&raw_score) {
                    Some(score) if score == 0 => None,
                    Some(score) => Some(score),
                    None => return Err(anyhow!("invalid score")),
                };

                series.data.entry.set_score(score.map(i16::from));
                series.data.entry.sync_to_remote(remote)?;
                series.save(db)?;

                Ok(())
            }
            Command::Status(status) => {
                let series = try_opt_r!(state.series.get_valid_sel_series_mut());
                let remote = remote.get_logged_in()?;

                series.data.entry.set_status(status, config);
                series.data.entry.sync_to_remote(remote)?;
                series.save(db)?;

                Ok(())
            }
        }
    }
}
<commit_msg>anup: Initialize the terminal as late as possible in UI::init<commit_after>mod component;
mod state;

use self::state::{InputState, Reactive, UIEvents, UIState};
use crate::key::Key;
use crate::Args;
use crate::{file::SerializedFile, remote::RemoteLogin, try_opt_r, user::Users};
use anime::remote::ScoreParser;
use anyhow::{anyhow, Context, Result};
use component::prompt::command::Command;
use component::prompt::command::InputResult;
use component::prompt::COMMAND_KEY;
use component::series_list::SeriesList;
use component::Component;
use component::{main_panel::MainPanel, prompt::command::CommandPrompt};
use crossterm::{event::KeyCode, terminal};
use state::{SharedState, UIErrorKind, UIEvent};
use std::{io, sync::Arc};
use tokio::sync::Notify;
use tui::{backend::CrosstermBackend, layout::Direction, Terminal};
use tui_utils::layout::{BasicConstraint, SimpleLayout};

pub async fn run(args: &Args) -> Result<()> {
    let mut ui = UI::init(&args).context("failed to init UI")?;
    let result = ui.run().await;

    ui.exit()?;
    result
}

type CrosstermTerminal = Terminal<CrosstermBackend<io::Stdout>>;

struct UI {
    events: UIEvents,
    terminal: CrosstermTerminal,
    state: SharedState,
    dirty_state_notify: Arc<Notify>,
    panels: Panels,
}

impl UI {
    fn init(args: &Args) -> Result<UI> {
        let events = UIEvents::new().context("UI events init")?;

        let mut state = UIState::init().context("UI state init")?;

        state
            .select_initial_series(args)
            .context("selecting initial series")?;

        let dirty_state_notify = Arc::new(Notify::const_new());
        let shared_state = SharedState::new(Reactive::new(state, Arc::clone(&dirty_state_notify)));

        let panels = Panels::init(&shared_state);

        if !args.offline {
            if let Some((user, token)) = Users::load_or_create()?.take_last_used_user() {
                shared_state.login_to_remote_async(RemoteLogin::AniList(user.username, token));
            }
        }

        let terminal = Self::init_terminal().context("initializing terminal")?;

        Ok(Self {
            events,
            terminal,
            state: shared_state,
            dirty_state_notify,
            panels,
        })
    }

    fn init_terminal() -> Result<CrosstermTerminal> {
        terminal::enable_raw_mode().context("failed to enable raw mode")?;

        let stdout = io::stdout();
        let backend = CrosstermBackend::new(stdout);
        let mut terminal = Terminal::new(backend).context("terminal creation failed")?;

        terminal.clear().context("failed to clear terminal")?;

        terminal
            .hide_cursor()
            .context("failed to hide mouse cursor")?;

        Ok(terminal)
    }

    async fn run(&mut self) -> Result<()> {
        {
            let mut state = self.state.lock();

            if let Err(err) = self.panels.draw(state.get_mut(), &mut self.terminal) {
                return Err(err);
            }
        }

        loop {
            match self.next_cycle().await {
                CycleResult::Ok => (),
                CycleResult::Exit => break Ok(()),
                CycleResult::Error(err) => return Err(err),
            }
        }
    }

    async fn next_cycle(&mut self) -> CycleResult {
        let event = match self.events.next(&self.dirty_state_notify).await {
            Ok(Some(event)) => event,
            Ok(None) => return CycleResult::Ok,
            Err(UIErrorKind::ExitRequest) => return CycleResult::Exit,
            Err(UIErrorKind::Other(err)) => return CycleResult::Error(err),
        };

        let mut state = self.state.lock();
        let state = state.get_mut();

        let result = match event {
            UIEvent::Key(key) => self.panels.process_key(key, state).await,
            UIEvent::StateChange | UIEvent::Resize => CycleResult::Ok,
        };

        if let Err(err) = self.panels.draw(state, &mut self.terminal) {
            return CycleResult::Error(err);
        }

        result
    }

    pub fn exit(mut self) -> Result<()> {
        self.terminal.clear().ok();
        terminal::disable_raw_mode().map_err(Into::into)
    }
}

pub enum CycleResult {
    Ok,
    Exit,
    Error(anyhow::Error),
}

struct Panels {
    command_prompt: CommandPrompt,
    main_panel: MainPanel,
    state: SharedState,
}

impl Panels {
    fn init(state: &SharedState) -> Self {
        Self {
            command_prompt: CommandPrompt::new(),
            main_panel: MainPanel::new(state.clone()),
            state: state.clone(),
        }
    }

    async fn process_key(&mut self, key: Key, state: &mut UIState) -> CycleResult {
        macro_rules! capture {
            ($result:expr) => {
                match $result {
                    Ok(value) => value,
                    Err(err) => {
                        state.log.push_error(&err);
                        return CycleResult::Ok;
                    }
                }
            };
        }

        macro_rules! process_key {
            ($component:ident) => {
                capture!(self.$component.process_key(key, state))
            };
        }

        match state.input_state {
            InputState::Idle => match *key {
                KeyCode::Char('q') => return CycleResult::Exit,
                _ if key == state.config.tui.keys.play_next_episode => {
                    capture!(state.play_next_series_episode(&self.state).await)
                }
                KeyCode::Char('a') => {
                    capture!(self.main_panel.switch_to_add_series(state))
                }
                KeyCode::Char('e') => {
                    capture!(self.main_panel.switch_to_update_series(state))
                }
                KeyCode::Char('D') => {
                    capture!(self.main_panel.switch_to_delete_series(state))
                }
                KeyCode::Char('u') => self.main_panel.switch_to_user_panel(state),
                KeyCode::Char('s') => {
                    capture!(self.main_panel.switch_to_split_series(state))
                }
                KeyCode::Char(COMMAND_KEY) => state.input_state = InputState::EnteringCommand,
                _ => SeriesList::process_key(key, state),
            },
            InputState::Locked => (),
            InputState::FocusedOnMainPanel => process_key!(main_panel),
            InputState::EnteringCommand => {
                let result = self.command_prompt.process_key(key, state);

                if !matches!(result, Ok(InputResult::Continue)) {
                    self.command_prompt.reset();
                    state.input_state.reset();
                }

                match capture!(result) {
                    InputResult::Command(cmd) => {
                        capture!(Self::process_command(cmd, state))
                    }
                    InputResult::Done | InputResult::Continue => (),
                }
            }
        }

        CycleResult::Ok
    }

    fn draw(&mut self, state: &UIState, terminal: &mut CrosstermTerminal) -> Result<()> {
        terminal
            .draw(|mut frame| {
                let horiz_splitter = SimpleLayout::new(Direction::Horizontal).split(
                    frame.size(),
                    &[
                        BasicConstraint::MinLenGrowthPcnt(20, 30),
                        BasicConstraint::Percentage(70),
                    ],
                );

                SeriesList::draw(state, horiz_splitter[0], &mut frame);

                // Series info panel vertical splitter
                let info_panel_splitter = SimpleLayout::new(Direction::Vertical).split(
                    horiz_splitter[1],
                    &[
                        BasicConstraint::Percentage(80),
                        BasicConstraint::Percentage(20),
                    ],
                );

                self.main_panel
                    .draw(state, info_panel_splitter[0], &mut frame);

                match state.input_state {
                    InputState::EnteringCommand => {
                        self.command_prompt.draw(info_panel_splitter[1], frame)
                    }
                    _ => state.log.draw(info_panel_splitter[1], frame),
                }
            })
            .map_err(Into::into)
    }

    fn process_command(command: Command, state: &mut UIState) -> Result<()> {
        let remote = &mut state.remote;
        let config = &state.config;
        let db = &state.db;

        match command {
            Command::PlayerArgs(args) => {
                let series = try_opt_r!(state.series.get_valid_sel_series_mut());

                series.data.config.player_args = args.into();
                series.save(db)?;
                Ok(())
            }
            Command::Progress(direction) => {
                use component::prompt::command::ProgressDirection;

                let series = try_opt_r!(state.series.get_valid_sel_series_mut());
                let remote = remote.get_logged_in()?;

                match direction {
                    ProgressDirection::Forwards => series.episode_completed(remote, config, db),
                    ProgressDirection::Backwards => series.episode_regressed(remote, config, db),
                }
            }
            cmd @ Command::SyncFromRemote | cmd @ Command::SyncToRemote => {
                let series = try_opt_r!(state.series.get_valid_sel_series_mut());
                let remote = remote.get_logged_in()?;

                match cmd {
                    Command::SyncFromRemote => series.data.force_sync_from_remote(remote)?,
                    Command::SyncToRemote => series.data.entry.force_sync_to_remote(remote)?,
                    _ => unreachable!(),
                }

                series.save(db)?;
                Ok(())
            }
            Command::Score(raw_score) => {
                let series = try_opt_r!(state.series.get_valid_sel_series_mut());
                let remote = remote.get_logged_in()?;

                let score = match remote.parse_score(&raw_score) {
                    Some(score) if score == 0 => None,
                    Some(score) => Some(score),
                    None => return Err(anyhow!("invalid score")),
                };

                series.data.entry.set_score(score.map(i16::from));
                series.data.entry.sync_to_remote(remote)?;
                series.save(db)?;

                Ok(())
            }
            Command::Status(status) => {
                let series = try_opt_r!(state.series.get_valid_sel_series_mut());
                let remote = remote.get_logged_in()?;

                series.data.entry.set_status(status, config);
                series.data.entry.sync_to_remote(remote)?;
                series.save(db)?;

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