<commit_before>use parking_lot::MutexGuard;
use std::cmp;

use ctxt::VM;
use gc::root::Slot;
use gc::space::Space;
use gc::swiper::card::CardTable;
use gc::swiper::crossing::CrossingMap;
use gc::swiper::large::LargeSpace;
use gc::swiper::old::{OldGen, OldGenProtected};
use gc::swiper::walk_region;
use gc::swiper::young::YoungGen;
use gc::{Address, GcReason, Region};
use object::Obj;

pub struct FullCollector<'a, 'ast: 'a> {
    vm: &'a VM<'ast>,
    heap: Region,
    young: &'a YoungGen,
    old: &'a OldGen,
    old_protected: MutexGuard<'a, OldGenProtected>,
    large_space: &'a LargeSpace,
    rootset: &'a [Slot],
    card_table: &'a CardTable,
    crossing_map: &'a CrossingMap,
    perm_space: &'a Space,

    old_top: Address,
    old_limit: Address,
    old_committed: Region,
    init_old_top: Vec<Address>,

    reason: GcReason,

    min_heap_size: usize,
    max_heap_size: usize,
}

impl<'a, 'ast> FullCollector<'a, 'ast> {
    pub fn new(
        vm: &'a VM<'ast>,
        heap: Region,
        young: &'a YoungGen,
        old: &'a OldGen,
        large_space: &'a LargeSpace,
        card_table: &'a CardTable,
        crossing_map: &'a CrossingMap,
        perm_space: &'a Space,
        rootset: &'a [Slot],
        reason: GcReason,
        min_heap_size: usize,
        max_heap_size: usize,
    ) -> FullCollector<'a, 'ast> {
        let old_total = old.total();

        FullCollector {
            vm: vm,
            heap: heap,
            young: young,
            old: old,
            old_protected: old.protected(),
            large_space: large_space,
            rootset: rootset,
            card_table: card_table,
            crossing_map: crossing_map,
            perm_space: perm_space,

            old_top: old_total.start,
            old_limit: old_total.end,
            old_committed: Default::default(),
            init_old_top: Vec::new(),

            reason: reason,

            min_heap_size: min_heap_size,
            max_heap_size: max_heap_size,
        }
    }

    pub fn collect(&mut self) {
        let dev_verbose = self.vm.args.flag_gc_dev_verbose;
        self.init_old_top = self.old_protected.regions.iter().map(|r| r.top()).collect();

        if dev_verbose {
            println!("Full GC: Phase 1 (marking)");
        }

        self.mark_live();

        if self.vm.args.flag_gc_verify {
            if dev_verbose {
                println!("Full GC: Phase 1 (verify marking start)");
            }

            verify_marking(
                self.young,
                &*self.old_protected,
                self.large_space,
                self.heap,
            );

            if dev_verbose {
                println!("Full GC: Phase 1 (verify marking end)");
            }
        }

        if dev_verbose {
            println!("Full GC: Phase 2 (compute forward)");
        }

        self.compute_forward();

        if dev_verbose {
            println!("Full GC: Phase 3 (update refs)");
        }

        self.update_references();

        if dev_verbose {
            println!("Full GC: Phase 4 (relocate)");
        }

        self.relocate();

        if dev_verbose {
            println!("Full GC: Phase 5 (large objects)");
        }

        self.update_large_objects();

        if dev_verbose {
            println!("Full GC: Phase 5 (large objects) finished.");
        }

        self.reset_cards();

        self.young.clear();
        self.young.protect_to();

        self.old_protected.update_single_region(self.old_top);
    }

    fn mark_live(&mut self) {
        let mut marking_stack: Vec<Address> = Vec::new();

        for root in self.rootset {
            let root_ptr = root.get();

            if self.heap.contains(root_ptr) {
                let root_obj = root_ptr.to_mut_obj();

                if !root_obj.header().is_marked_non_atomic() {
                    marking_stack.push(root_ptr);
                    root_obj.header_mut().mark_non_atomic();
                }
            } else {
                debug_assert!(root_ptr.is_null() || self.perm_space.contains(root_ptr));
            }
        }

        while marking_stack.len() > 0 {
            let object_addr = marking_stack.pop().expect("stack already empty");
            let object = object_addr.to_mut_obj();

            object.visit_reference_fields(|field| {
                let field_addr = field.get();

                if self.heap.contains(field_addr) {
                    let field_obj = field_addr.to_mut_obj();

                    if !field_obj.header().is_marked_non_atomic() {
                        marking_stack.push(field_addr);
                        field_obj.header_mut().mark_non_atomic();
                    }
                } else {
                    debug_assert!(field_addr.is_null() || self.perm_space.contains(field_addr));
                }
            });
        }
    }

    fn compute_forward(&mut self) {
        self.walk_old_and_young(|full, object, _address, object_size| {
            if object.header().is_marked_non_atomic() {
                let fwd = full.allocate(object_size);
                object.header_mut().set_fwdptr_non_atomic(fwd);
            }
        });

        self.old_protected.commit_single_region(self.old_top);
        self.old_committed = Region::new(self.old.total_start(), self.old_top);
    }

    fn update_references(&mut self) {
        self.walk_old_and_young(|full, object, _address, _| {
            if object.header().is_marked_non_atomic() {
                object.visit_reference_fields(|field| {
                    full.forward_reference(field);
                });
            }
        });

        for root in self.rootset {
            self.forward_reference(*root);
        }

        self.large_space.visit_objects(|object_start| {
            let object = object_start.to_mut_obj();

            if object.header().is_marked_non_atomic() {
                object.visit_reference_fields(|field| {
                    self.forward_reference(field);
                });
            }
        });
    }

    fn relocate(&mut self) {
        self.crossing_map.set_first_object(0.into(), 0);

        self.walk_old_and_young(|full, object, address, object_size| {
            if object.header().is_marked_non_atomic() {
                // get new location
                let dest = object.header().fwdptr_non_atomic();
                debug_assert!(full.old_committed.contains(dest));

                // determine location after relocated object
                let next_dest = dest.offset(object_size);
                debug_assert!(full.old_committed.valid_top(next_dest));

                if address != dest {
                    object.copy_to(dest, object_size);
                }

                // unmark object for next collection
                let dest_obj = dest.to_mut_obj();
                dest_obj.header_mut().unmark_non_atomic();

                full.old
                    .update_crossing(dest, next_dest, dest_obj.is_array_ref());
            }
        });
    }

    fn update_large_objects(&mut self) {
        self.large_space.remove_objects(|object_start| {
            let object = object_start.to_mut_obj();

            // reset cards for object, also do this for dead objects
            // to reset card entries to clean.
            if object.is_array_ref() {
                let object_end = object_start.offset(object.size());
                self.card_table.reset_region(object_start, object_end);
            } else {
                self.card_table.reset_addr(object_start);
            }

            if !object.header().is_marked_non_atomic() {
                // object is unmarked -> free it
                return false;
            }

            // unmark object for next collection
            object.header_mut().unmark_non_atomic();

            // keep object
            true
        });
    }

    fn reset_cards(&mut self) {
        let regions = self
            .old_protected
            .regions
            .iter()
            .map(|r| (r.start(), r.top()))
            .collect::<Vec<_>>();

        for ((start, top), init_top) in regions.into_iter().zip(&self.init_old_top) {
            let top = cmp::max(top, *init_top);
            self.card_table.reset_region(start, top);
        }
    }

    fn forward_reference(&mut self, slot: Slot) {
        let object_addr = slot.get();

        if self.heap.contains(object_addr) {
            debug_assert!(object_addr.to_obj().header().is_marked_non_atomic());

            if self.large_space.contains(object_addr) {
                // large objects do not move in memory
                return;
            }

            let fwd_addr = object_addr.to_obj().header().fwdptr_non_atomic();
            debug_assert!(self.heap.contains(fwd_addr));
            slot.set(fwd_addr);
        } else {
            debug_assert!(object_addr.is_null() || self.perm_space.contains(object_addr));
        }
    }

    fn walk_old_and_young<F>(&mut self, mut fct: F)
    where
        F: FnMut(&mut FullCollector, &mut Obj, Address, usize),
    {
        let old_regions = self
            .old_protected
            .regions
            .iter()
            .map(|r| r.active_region())
            .collect::<Vec<_>>();

        for old_region in old_regions {
            self.walk_region(old_region.start, old_region.end, &mut fct);
        }

        let used_region = self.young.eden_active();
        self.walk_region(used_region.start, used_region.end, &mut fct);

        let used_region = self.young.from_active();
        self.walk_region(used_region.start, used_region.end, &mut fct);

        // This is a bit strange at first: to-space might not be empty,
        // after too many survivors in the minor GC of the young gen.
        let used_region = self.young.to_active();
        self.walk_region(used_region.start, used_region.end, &mut fct);
    }

    fn walk_region<F>(&mut self, start: Address, end: Address, fct: &mut F)
    where
        F: FnMut(&mut FullCollector, &mut Obj, Address, usize),
    {
        let mut scan = start;

        while scan < end {
            let object = scan.to_mut_obj();

            if object.header().vtblptr().is_null() {
                scan = scan.add_ptr(1);
                continue;
            }

            let object_size = object.size();

            fct(self, object, scan, object_size);

            scan = scan.offset(object_size);
        }
    }

    fn allocate(&mut self, object_size: usize) -> Address {
        let addr = self.old_top;
        let next = self.old_top.offset(object_size);

        if next <= self.old_limit {
            self.old_top = next;
            return addr;
        }

        panic!("FAIL: Not enough space for objects in old generation.");
    }
}

pub fn verify_marking(
    young: &YoungGen,
    old_protected: &OldGenProtected,
    large: &LargeSpace,
    heap: Region,
) {
    for region in &old_protected.regions {
        let active = region.active_region();
        verify_marking_region(active, heap);
    }

    let eden = young.eden_active();
    verify_marking_region(eden, heap);

    let from = young.from_active();
    verify_marking_region(from, heap);

    let to = young.to_active();
    verify_marking_region(to, heap);

    large.visit_objects(|obj_address| {
        verify_marking_object(obj_address, heap);
    });
}

fn verify_marking_region(region: Region, heap: Region) {
    walk_region(region, |_obj, obj_address, _size| {
        verify_marking_object(obj_address, heap);
    });
}

fn verify_marking_object(obj_address: Address, heap: Region) {
    let obj = obj_address.to_mut_obj();

    if obj.header().is_marked_non_atomic() {
        obj.visit_reference_fields(|field| {
            let object_addr = field.get();

            if heap.contains(object_addr) {
                assert!(object_addr.to_obj().header().is_marked_non_atomic());
            }
        });
    }
}
<commit_msg>full: reuse walk_region function<commit_after>use parking_lot::MutexGuard;
use std::cmp;

use ctxt::VM;
use gc::root::Slot;
use gc::space::Space;
use gc::swiper::card::CardTable;
use gc::swiper::crossing::CrossingMap;
use gc::swiper::large::LargeSpace;
use gc::swiper::old::{OldGen, OldGenProtected};
use gc::swiper::walk_region;
use gc::swiper::young::YoungGen;
use gc::{Address, GcReason, Region};
use object::Obj;

pub struct FullCollector<'a, 'ast: 'a> {
    vm: &'a VM<'ast>,
    heap: Region,
    young: &'a YoungGen,
    old: &'a OldGen,
    old_protected: MutexGuard<'a, OldGenProtected>,
    large_space: &'a LargeSpace,
    rootset: &'a [Slot],
    card_table: &'a CardTable,
    crossing_map: &'a CrossingMap,
    perm_space: &'a Space,

    old_top: Address,
    old_limit: Address,
    old_committed: Region,
    init_old_top: Vec<Address>,

    reason: GcReason,

    min_heap_size: usize,
    max_heap_size: usize,
}

impl<'a, 'ast> FullCollector<'a, 'ast> {
    pub fn new(
        vm: &'a VM<'ast>,
        heap: Region,
        young: &'a YoungGen,
        old: &'a OldGen,
        large_space: &'a LargeSpace,
        card_table: &'a CardTable,
        crossing_map: &'a CrossingMap,
        perm_space: &'a Space,
        rootset: &'a [Slot],
        reason: GcReason,
        min_heap_size: usize,
        max_heap_size: usize,
    ) -> FullCollector<'a, 'ast> {
        let old_total = old.total();

        FullCollector {
            vm: vm,
            heap: heap,
            young: young,
            old: old,
            old_protected: old.protected(),
            large_space: large_space,
            rootset: rootset,
            card_table: card_table,
            crossing_map: crossing_map,
            perm_space: perm_space,

            old_top: old_total.start,
            old_limit: old_total.end,
            old_committed: Default::default(),
            init_old_top: Vec::new(),

            reason: reason,

            min_heap_size: min_heap_size,
            max_heap_size: max_heap_size,
        }
    }

    pub fn collect(&mut self) {
        let dev_verbose = self.vm.args.flag_gc_dev_verbose;
        self.init_old_top = self.old_protected.regions.iter().map(|r| r.top()).collect();

        if dev_verbose {
            println!("Full GC: Phase 1 (marking)");
        }

        self.mark_live();

        if self.vm.args.flag_gc_verify {
            if dev_verbose {
                println!("Full GC: Phase 1 (verify marking start)");
            }

            verify_marking(
                self.young,
                &*self.old_protected,
                self.large_space,
                self.heap,
            );

            if dev_verbose {
                println!("Full GC: Phase 1 (verify marking end)");
            }
        }

        if dev_verbose {
            println!("Full GC: Phase 2 (compute forward)");
        }

        self.compute_forward();

        if dev_verbose {
            println!("Full GC: Phase 3 (update refs)");
        }

        self.update_references();

        if dev_verbose {
            println!("Full GC: Phase 4 (relocate)");
        }

        self.relocate();

        if dev_verbose {
            println!("Full GC: Phase 5 (large objects)");
        }

        self.update_large_objects();

        if dev_verbose {
            println!("Full GC: Phase 5 (large objects) finished.");
        }

        self.reset_cards();

        self.young.clear();
        self.young.protect_to();

        self.old_protected.update_single_region(self.old_top);
    }

    fn mark_live(&mut self) {
        let mut marking_stack: Vec<Address> = Vec::new();

        for root in self.rootset {
            let root_ptr = root.get();

            if self.heap.contains(root_ptr) {
                let root_obj = root_ptr.to_mut_obj();

                if !root_obj.header().is_marked_non_atomic() {
                    marking_stack.push(root_ptr);
                    root_obj.header_mut().mark_non_atomic();
                }
            } else {
                debug_assert!(root_ptr.is_null() || self.perm_space.contains(root_ptr));
            }
        }

        while marking_stack.len() > 0 {
            let object_addr = marking_stack.pop().expect("stack already empty");
            let object = object_addr.to_mut_obj();

            object.visit_reference_fields(|field| {
                let field_addr = field.get();

                if self.heap.contains(field_addr) {
                    let field_obj = field_addr.to_mut_obj();

                    if !field_obj.header().is_marked_non_atomic() {
                        marking_stack.push(field_addr);
                        field_obj.header_mut().mark_non_atomic();
                    }
                } else {
                    debug_assert!(field_addr.is_null() || self.perm_space.contains(field_addr));
                }
            });
        }
    }

    fn compute_forward(&mut self) {
        self.walk_old_and_young(|full, object, _address, object_size| {
            if object.header().is_marked_non_atomic() {
                let fwd = full.allocate(object_size);
                object.header_mut().set_fwdptr_non_atomic(fwd);
            }
        });

        self.old_protected.commit_single_region(self.old_top);
        self.old_committed = Region::new(self.old.total_start(), self.old_top);
    }

    fn update_references(&mut self) {
        self.walk_old_and_young(|full, object, _address, _| {
            if object.header().is_marked_non_atomic() {
                object.visit_reference_fields(|field| {
                    full.forward_reference(field);
                });
            }
        });

        for root in self.rootset {
            self.forward_reference(*root);
        }

        self.large_space.visit_objects(|object_start| {
            let object = object_start.to_mut_obj();

            if object.header().is_marked_non_atomic() {
                object.visit_reference_fields(|field| {
                    self.forward_reference(field);
                });
            }
        });
    }

    fn relocate(&mut self) {
        self.crossing_map.set_first_object(0.into(), 0);

        self.walk_old_and_young(|full, object, address, object_size| {
            if object.header().is_marked_non_atomic() {
                // get new location
                let dest = object.header().fwdptr_non_atomic();
                debug_assert!(full.old_committed.contains(dest));

                // determine location after relocated object
                let next_dest = dest.offset(object_size);
                debug_assert!(full.old_committed.valid_top(next_dest));

                if address != dest {
                    object.copy_to(dest, object_size);
                }

                // unmark object for next collection
                let dest_obj = dest.to_mut_obj();
                dest_obj.header_mut().unmark_non_atomic();

                full.old
                    .update_crossing(dest, next_dest, dest_obj.is_array_ref());
            }
        });
    }

    fn update_large_objects(&mut self) {
        self.large_space.remove_objects(|object_start| {
            let object = object_start.to_mut_obj();

            // reset cards for object, also do this for dead objects
            // to reset card entries to clean.
            if object.is_array_ref() {
                let object_end = object_start.offset(object.size());
                self.card_table.reset_region(object_start, object_end);
            } else {
                self.card_table.reset_addr(object_start);
            }

            if !object.header().is_marked_non_atomic() {
                // object is unmarked -> free it
                return false;
            }

            // unmark object for next collection
            object.header_mut().unmark_non_atomic();

            // keep object
            true
        });
    }

    fn reset_cards(&mut self) {
        let regions = self
            .old_protected
            .regions
            .iter()
            .map(|r| (r.start(), r.top()))
            .collect::<Vec<_>>();

        for ((start, top), init_top) in regions.into_iter().zip(&self.init_old_top) {
            let top = cmp::max(top, *init_top);
            self.card_table.reset_region(start, top);
        }
    }

    fn forward_reference(&mut self, slot: Slot) {
        let object_addr = slot.get();

        if self.heap.contains(object_addr) {
            debug_assert!(object_addr.to_obj().header().is_marked_non_atomic());

            if self.large_space.contains(object_addr) {
                // large objects do not move in memory
                return;
            }

            let fwd_addr = object_addr.to_obj().header().fwdptr_non_atomic();
            debug_assert!(self.heap.contains(fwd_addr));
            slot.set(fwd_addr);
        } else {
            debug_assert!(object_addr.is_null() || self.perm_space.contains(object_addr));
        }
    }

    fn walk_old_and_young<F>(&mut self, mut fct: F)
    where
        F: FnMut(&mut FullCollector, &mut Obj, Address, usize),
    {
        let old_regions = self
            .old_protected
            .regions
            .iter()
            .map(|r| r.active_region())
            .collect::<Vec<_>>();

        for old_region in old_regions {
            walk_region(old_region, |obj, addr, size| {
                fct(self, obj, addr, size);
            });
        }

        let used_region = self.young.eden_active();
        walk_region(used_region, |obj, addr, size| {
            fct(self, obj, addr, size);
        });

        let used_region = self.young.from_active();
        walk_region(used_region, |obj, addr, size| {
            fct(self, obj, addr, size);
        });

        // This is a bit strange at first: to-space might not be empty,
        // after too many survivors in the minor GC of the young gen.
        let used_region = self.young.to_active();
        walk_region(used_region, |obj, addr, size| {
            fct(self, obj, addr, size);
        });
    }

    fn allocate(&mut self, object_size: usize) -> Address {
        let addr = self.old_top;
        let next = self.old_top.offset(object_size);

        if next <= self.old_limit {
            self.old_top = next;
            return addr;
        }

        panic!("FAIL: Not enough space for objects in old generation.");
    }
}

pub fn verify_marking(
    young: &YoungGen,
    old_protected: &OldGenProtected,
    large: &LargeSpace,
    heap: Region,
) {
    for region in &old_protected.regions {
        let active = region.active_region();
        verify_marking_region(active, heap);
    }

    let eden = young.eden_active();
    verify_marking_region(eden, heap);

    let from = young.from_active();
    verify_marking_region(from, heap);

    let to = young.to_active();
    verify_marking_region(to, heap);

    large.visit_objects(|obj_address| {
        verify_marking_object(obj_address, heap);
    });
}

fn verify_marking_region(region: Region, heap: Region) {
    walk_region(region, |_obj, obj_address, _size| {
        verify_marking_object(obj_address, heap);
    });
}

fn verify_marking_object(obj_address: Address, heap: Region) {
    let obj = obj_address.to_mut_obj();

    if obj.header().is_marked_non_atomic() {
        obj.visit_reference_fields(|field| {
            let object_addr = field.get();

            if heap.contains(object_addr) {
                assert!(object_addr.to_obj().header().is_marked_non_atomic());
            }
        });
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 The Rouille developers
// 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. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use std::f32;
use std::str::FromStr;
use std::str::Split;

/// Returns the preferred value amongst a priority header.
///
/// This function takes the value of a priority header and a list of elements that can be handled
/// by the server, and returns the index within that list of the element with the highest priority.
///
/// If multiple elements in `handled` match and have the same priority, the first one is returned.
///
/// # Example
///
/// ```
/// use rouille::input::priority_header_preferred;
///
/// let header = "text/plain; q=1.2, image/png; q=2.0";
/// let handled = ["image/gif", "image/png", "text/plain"];
/// assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(1));
/// ```
pub fn priority_header_preferred<'a, I>(input: &'a str, elements: I) -> Option<usize>
    where I: Iterator<Item = &'a str>
{
    let mut result = (None, f32::NEG_INFINITY);

    for (index, req_elem) in elements.enumerate() {
        for (header_elem, prio) in parse_priority_header(input) {
            if prio <= result.1 {
                continue;
            }

            if req_elem == header_elem {
                result = (Some(index), prio);
                continue;
            }

            let (req_elem_left, req_elem_right) = {
                let mut parts = req_elem.split('/');
                let left = parts.next();
                let right = parts.next();
                (left, right)
            };

            let (header_elem_left, header_elem_right) = {
                let mut parts = header_elem.split('/');
                let left = parts.next();
                let right = parts.next();
                (left, right)
            };

            if req_elem_left == Some("*") || header_elem_left == Some("*") {
                if req_elem_right == header_elem_right || req_elem_right == Some("*") ||
                    header_elem_right == Some("*")
                {
                    result = (Some(index), prio);
                    continue;
                }
            }

            if req_elem_right == Some("*") || header_elem_right == Some("*") {
                if req_elem_left == header_elem_left || req_elem_left == Some("*") ||
                    header_elem_left == Some("*")
                {
                    result = (Some(index), prio);
                    continue;
                }
            }
        }
    }

    result.0
}

/// Parses the value of a header that has values with priorities. Suitable for
/// `Accept-*`, `TE`, etc.
///
/// # Example
///
/// ```
/// use rouille::input::parse_priority_header;
///
/// let mut iter = parse_priority_header("text/plain, image/png; q=1.5");
///
/// assert_eq!(iter.next().unwrap(), ("text/plain", 1.0));
/// assert_eq!(iter.next().unwrap(), ("image/png", 1.5));
/// assert_eq!(iter.next(), None);
/// ```
#[inline]
pub fn parse_priority_header(input: &str) -> PriorityHeaderIter {
    PriorityHeaderIter {
        iter: input.split(','),
    }
}

/// Iterator to the elements of a priority header.
///
/// Created with [`parse_priority_header`](fn.parse_priority_header.html).
pub struct PriorityHeaderIter<'a> {
    iter: Split<'a, char>,
}

impl<'a> Iterator for PriorityHeaderIter<'a> {
    type Item = (&'a str, f32);

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let elem = match self.iter.next() {
                Some(n) => n,
                None => return None,
            };

            let mut params = elem.split(';');

            let t = match params.next() {
                Some(t) => t.trim(),
                None => continue
            };

            let mut value = 1.0f32;

            for p in params {
                let trimmed_p = p.trim_left();
                if trimmed_p.starts_with("q=") {
                    if let Ok(val) = FromStr::from_str(&trimmed_p[2..].trim()) {
                        value = val; break;
                    }
                }
            }

            return Some((t, value));
        }
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        let (_, len) = self.iter.size_hint();
        (0, len)
    }
}

#[cfg(test)]
mod tests {
    use super::parse_priority_header;
    use super::priority_header_preferred;

    #[test]
    fn parse_basic() {
        let mut iter = parse_priority_header("text/plain; q=1.5, */*");
        assert_eq!(iter.next().unwrap(), ("text/plain", 1.5));
        assert_eq!(iter.next().unwrap(), ("*/*", 1.0));
        assert_eq!(iter.next(), None);
    }

    #[test]
    fn parse_white_spaces() {
        let mut iter = parse_priority_header("   text/plain   ;  q=   1.5  ,    */*   ");
        assert_eq!(iter.next().unwrap(), ("text/plain", 1.5));
        assert_eq!(iter.next().unwrap(), ("*/*", 1.0));
        assert_eq!(iter.next(), None);
    }

    #[test]
    fn preferred_basic() {
        let header = "text/plain; q=1.2, image/png; q=2.0";
        let handled = ["image/gif", "image/png", "text/plain"];
        assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(1));
    }

    #[test]
    fn preferred_multimatch_first() {
        let header = "text/plain";
        let handled = ["text/plain", "text/plain"];
        assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(0));
    }

    #[test]
    fn preferred_wildcard_header() {
        let header = "text/plain; q=1.2, */*";
        let handled = ["image/gif"];
        assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(0));
    }

    #[test]
    fn preferred_wildcard_header_left() {
        let header = "text/*; q=2.0, */*";
        let handled = ["image/gif", "text/html"];
        assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(1));
    }

    #[test]
    fn preferred_empty() {
        let header = "*/*";
        let handled = [];
        assert_eq!(priority_header_preferred(header, handled.iter().cloned()), None);
    }
}
<commit_msg>Replace str::trim_start with str::trim_left<commit_after>// Copyright (c) 2016 The Rouille developers
// 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. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use std::f32;
use std::str::FromStr;
use std::str::Split;

/// Returns the preferred value amongst a priority header.
///
/// This function takes the value of a priority header and a list of elements that can be handled
/// by the server, and returns the index within that list of the element with the highest priority.
///
/// If multiple elements in `handled` match and have the same priority, the first one is returned.
///
/// # Example
///
/// ```
/// use rouille::input::priority_header_preferred;
///
/// let header = "text/plain; q=1.2, image/png; q=2.0";
/// let handled = ["image/gif", "image/png", "text/plain"];
/// assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(1));
/// ```
pub fn priority_header_preferred<'a, I>(input: &'a str, elements: I) -> Option<usize>
    where I: Iterator<Item = &'a str>
{
    let mut result = (None, f32::NEG_INFINITY);

    for (index, req_elem) in elements.enumerate() {
        for (header_elem, prio) in parse_priority_header(input) {
            if prio <= result.1 {
                continue;
            }

            if req_elem == header_elem {
                result = (Some(index), prio);
                continue;
            }

            let (req_elem_left, req_elem_right) = {
                let mut parts = req_elem.split('/');
                let left = parts.next();
                let right = parts.next();
                (left, right)
            };

            let (header_elem_left, header_elem_right) = {
                let mut parts = header_elem.split('/');
                let left = parts.next();
                let right = parts.next();
                (left, right)
            };

            if req_elem_left == Some("*") || header_elem_left == Some("*") {
                if req_elem_right == header_elem_right || req_elem_right == Some("*") ||
                    header_elem_right == Some("*")
                {
                    result = (Some(index), prio);
                    continue;
                }
            }

            if req_elem_right == Some("*") || header_elem_right == Some("*") {
                if req_elem_left == header_elem_left || req_elem_left == Some("*") ||
                    header_elem_left == Some("*")
                {
                    result = (Some(index), prio);
                    continue;
                }
            }
        }
    }

    result.0
}

/// Parses the value of a header that has values with priorities. Suitable for
/// `Accept-*`, `TE`, etc.
///
/// # Example
///
/// ```
/// use rouille::input::parse_priority_header;
///
/// let mut iter = parse_priority_header("text/plain, image/png; q=1.5");
///
/// assert_eq!(iter.next().unwrap(), ("text/plain", 1.0));
/// assert_eq!(iter.next().unwrap(), ("image/png", 1.5));
/// assert_eq!(iter.next(), None);
/// ```
#[inline]
pub fn parse_priority_header(input: &str) -> PriorityHeaderIter {
    PriorityHeaderIter {
        iter: input.split(','),
    }
}

/// Iterator to the elements of a priority header.
///
/// Created with [`parse_priority_header`](fn.parse_priority_header.html).
pub struct PriorityHeaderIter<'a> {
    iter: Split<'a, char>,
}

impl<'a> Iterator for PriorityHeaderIter<'a> {
    type Item = (&'a str, f32);

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let elem = match self.iter.next() {
                Some(n) => n,
                None => return None,
            };

            let mut params = elem.split(';');

            let t = match params.next() {
                Some(t) => t.trim(),
                None => continue
            };

            let mut value = 1.0f32;

            for p in params {
                let trimmed_p = p.trim_start();
                if trimmed_p.starts_with("q=") {
                    if let Ok(val) = FromStr::from_str(&trimmed_p[2..].trim()) {
                        value = val; break;
                    }
                }
            }

            return Some((t, value));
        }
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        let (_, len) = self.iter.size_hint();
        (0, len)
    }
}

#[cfg(test)]
mod tests {
    use super::parse_priority_header;
    use super::priority_header_preferred;

    #[test]
    fn parse_basic() {
        let mut iter = parse_priority_header("text/plain; q=1.5, */*");
        assert_eq!(iter.next().unwrap(), ("text/plain", 1.5));
        assert_eq!(iter.next().unwrap(), ("*/*", 1.0));
        assert_eq!(iter.next(), None);
    }

    #[test]
    fn parse_white_spaces() {
        let mut iter = parse_priority_header("   text/plain   ;  q=   1.5  ,    */*   ");
        assert_eq!(iter.next().unwrap(), ("text/plain", 1.5));
        assert_eq!(iter.next().unwrap(), ("*/*", 1.0));
        assert_eq!(iter.next(), None);
    }

    #[test]
    fn preferred_basic() {
        let header = "text/plain; q=1.2, image/png; q=2.0";
        let handled = ["image/gif", "image/png", "text/plain"];
        assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(1));
    }

    #[test]
    fn preferred_multimatch_first() {
        let header = "text/plain";
        let handled = ["text/plain", "text/plain"];
        assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(0));
    }

    #[test]
    fn preferred_wildcard_header() {
        let header = "text/plain; q=1.2, */*";
        let handled = ["image/gif"];
        assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(0));
    }

    #[test]
    fn preferred_wildcard_header_left() {
        let header = "text/*; q=2.0, */*";
        let handled = ["image/gif", "text/html"];
        assert_eq!(priority_header_preferred(header, handled.iter().cloned()), Some(1));
    }

    #[test]
    fn preferred_empty() {
        let header = "*/*";
        let handled = [];
        assert_eq!(priority_header_preferred(header, handled.iter().cloned()), None);
    }
}
<|endoftext|>


<commit_before>use im::HashSet;
use lsp_types::{
    DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams, Location, ReferenceParams,
    TextDocumentPositionParams,
};

use crate::{
    compiler::{
        ast_to_hir::AstToHir,
        cst::{CstDb, CstKind},
        hir::{self, Body, Expression, HirDb, Lambda},
    },
    database::Database,
    input::Input,
};

use super::utils::{LspPositionConversion, TupleToPosition};

pub fn find_references(db: &Database, params: ReferenceParams) -> Option<Vec<Location>> {
    let position = params.text_document_position;
    let references = find(db, position.clone(), params.context.include_declaration)?
        .into_iter()
        .map(|it| Location {
            uri: position.text_document.uri.clone(),
            range: it.range,
        })
        .collect();
    Some(references)
}

pub fn find_document_highlights(
    db: &Database,
    params: DocumentHighlightParams,
) -> Option<Vec<DocumentHighlight>> {
    find(db, params.text_document_position_params, true)
}

fn find(
    db: &Database,
    params: TextDocumentPositionParams,
    include_declaration: bool,
) -> Option<Vec<DocumentHighlight>> {
    let input: Input = params.text_document.uri.clone().into();
    let position = params.position;
    let offset = db.offset_from_lsp(input.clone(), position.line, position.character);

    let origin_cst = db.find_cst_by_offset(input.clone(), offset);
    match origin_cst.kind {
        CstKind::Identifier { .. } => {}
        _ => return None,
    }

    let origin_hir_id = db.cst_to_hir_id(input.clone(), origin_cst.id)?;
    Some(db.references(input, origin_hir_id, include_declaration))
}

#[salsa::query_group(ReferencesDbStorage)]
pub trait ReferencesDb: HirDb + LspPositionConversion {
    fn references(
        &self,
        input: Input,
        id: hir::Id,
        include_declaration: bool,
    ) -> Vec<DocumentHighlight>;
}

fn references(
    db: &dyn ReferencesDb,
    input: Input,
    id: hir::Id,
    include_declaration: bool,
) -> Vec<DocumentHighlight> {
    let (hir, _) = db.hir(input.clone()).unwrap();

    let mut context = Context::new(db, input, id, include_declaration);
    context.visit_body(hir.as_ref());
    context.references
}

struct Context<'a> {
    db: &'a dyn ReferencesDb,
    input: Input,
    id: hir::Id,
    include_declaration: bool,
    discovered_references: HashSet<hir::Id>,
    references: Vec<DocumentHighlight>,
}
impl<'a> Context<'a> {
    fn new(db: &'a dyn ReferencesDb, input: Input, id: hir::Id, include_declaration: bool) -> Self {
        Self {
            db,
            input,
            id,
            include_declaration,
            discovered_references: HashSet::new(),
            references: vec![],
        }
    }

    fn visit_body(&mut self, body: &Body) {
        if body.identifiers.contains_key(&self.id) {
            self.add_reference(self.id.clone(), DocumentHighlightKind::WRITE);
        }
        for (id, expression) in &body.expressions {
            self.visit_expression(id.to_owned(), expression);
        }
    }
    fn visit_expressions(&mut self, ids: &[hir::Id]) {
        for id in ids {
            self.visit_id(id.to_owned());
        }
    }
    fn visit_id(&mut self, id: hir::Id) {
        let expression = self
            .db
            .find_expression(self.input.clone(), id.to_owned())
            .unwrap();
        self.visit_expression(id, &expression);
    }
    fn visit_expression(&mut self, id: hir::Id, expression: &Expression) {
        match expression {
            Expression::Int(_) => {}
            Expression::Text(_) => {}
            Expression::Reference(target) => {
                if target == &self.id {
                    self.add_reference(id, DocumentHighlightKind::READ);
                }
            }
            Expression::Symbol(_) => {}
            Expression::Struct(entries) => {
                for (key_id, value_id) in entries {
                    self.visit_id(key_id.to_owned());
                    self.visit_id(value_id.to_owned());
                }
            }
            Expression::Lambda(Lambda { body, .. }) => {
                // We don't need to visit the parameters: They can only be the
                // declaration of an identifier and don't reference it any other
                // way. Therfore, we already visit them in [visit_body].
                self.visit_body(body);
            }
            Expression::Body(body) => {
                self.visit_body(body);
            }
            Expression::Call {
                function,
                arguments,
            } => {
                if function == &self.id {
                    self.add_reference(id, DocumentHighlightKind::READ);
                }
                self.visit_expressions(arguments);
            }
            Expression::Error => {}
        }
    }

    fn add_reference(&mut self, id: hir::Id, kind: DocumentHighlightKind) {
        if id == self.id && !self.include_declaration {
            return;
        }

        if self.discovered_references.contains(&id) {
            return;
        }
        self.discovered_references.insert(id.clone());

        let span = self.db.hir_id_to_span(self.input.clone(), id).unwrap();
        self.references.push(DocumentHighlight {
            range: lsp_types::Range {
                start: self
                    .db
                    .offset_to_lsp(self.input.clone(), span.start)
                    .to_position(),
                end: self
                    .db
                    .offset_to_lsp(self.input.clone(), span.end)
                    .to_position(),
            },
            kind: Some(kind),
        });
    }
}
<commit_msg>refactor(compiler): rename LSP reference internal function for consistency<commit_after>use im::HashSet;
use lsp_types::{
    DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams, Location, ReferenceParams,
    TextDocumentPositionParams,
};

use crate::{
    compiler::{
        ast_to_hir::AstToHir,
        cst::{CstDb, CstKind},
        hir::{self, Body, Expression, HirDb, Lambda},
    },
    database::Database,
    input::Input,
};

use super::utils::{LspPositionConversion, TupleToPosition};

pub fn find_references(db: &Database, params: ReferenceParams) -> Option<Vec<Location>> {
    let position = params.text_document_position;
    let references = find(db, position.clone(), params.context.include_declaration)?
        .into_iter()
        .map(|it| Location {
            uri: position.text_document.uri.clone(),
            range: it.range,
        })
        .collect();
    Some(references)
}

pub fn find_document_highlights(
    db: &Database,
    params: DocumentHighlightParams,
) -> Option<Vec<DocumentHighlight>> {
    find(db, params.text_document_position_params, true)
}

fn find(
    db: &Database,
    params: TextDocumentPositionParams,
    include_declaration: bool,
) -> Option<Vec<DocumentHighlight>> {
    let input: Input = params.text_document.uri.clone().into();
    let position = params.position;
    let offset = db.offset_from_lsp(input.clone(), position.line, position.character);

    let origin_cst = db.find_cst_by_offset(input.clone(), offset);
    match origin_cst.kind {
        CstKind::Identifier { .. } => {}
        _ => return None,
    }

    let origin_hir_id = db.cst_to_hir_id(input.clone(), origin_cst.id)?;
    Some(db.references(input, origin_hir_id, include_declaration))
}

#[salsa::query_group(ReferencesDbStorage)]
pub trait ReferencesDb: HirDb + LspPositionConversion {
    fn references(
        &self,
        input: Input,
        id: hir::Id,
        include_declaration: bool,
    ) -> Vec<DocumentHighlight>;
}

fn references(
    db: &dyn ReferencesDb,
    input: Input,
    id: hir::Id,
    include_declaration: bool,
) -> Vec<DocumentHighlight> {
    let (hir, _) = db.hir(input.clone()).unwrap();

    let mut context = Context::new(db, input, id, include_declaration);
    context.visit_body(hir.as_ref());
    context.references
}

struct Context<'a> {
    db: &'a dyn ReferencesDb,
    input: Input,
    id: hir::Id,
    include_declaration: bool,
    discovered_references: HashSet<hir::Id>,
    references: Vec<DocumentHighlight>,
}
impl<'a> Context<'a> {
    fn new(db: &'a dyn ReferencesDb, input: Input, id: hir::Id, include_declaration: bool) -> Self {
        Self {
            db,
            input,
            id,
            include_declaration,
            discovered_references: HashSet::new(),
            references: vec![],
        }
    }

    fn visit_body(&mut self, body: &Body) {
        if body.identifiers.contains_key(&self.id) {
            self.add_reference(self.id.clone(), DocumentHighlightKind::WRITE);
        }
        for (id, expression) in &body.expressions {
            self.visit_expression(id.to_owned(), expression);
        }
    }
    fn visit_ids(&mut self, ids: &[hir::Id]) {
        for id in ids {
            self.visit_id(id.to_owned());
        }
    }
    fn visit_id(&mut self, id: hir::Id) {
        let expression = self
            .db
            .find_expression(self.input.clone(), id.to_owned())
            .unwrap();
        self.visit_expression(id, &expression);
    }
    fn visit_expression(&mut self, id: hir::Id, expression: &Expression) {
        match expression {
            Expression::Int(_) => {}
            Expression::Text(_) => {}
            Expression::Reference(target) => {
                if target == &self.id {
                    self.add_reference(id, DocumentHighlightKind::READ);
                }
            }
            Expression::Symbol(_) => {}
            Expression::Struct(entries) => {
                for (key_id, value_id) in entries {
                    self.visit_id(key_id.to_owned());
                    self.visit_id(value_id.to_owned());
                }
            }
            Expression::Lambda(Lambda { body, .. }) => {
                // We don't need to visit the parameters: They can only be the
                // declaration of an identifier and don't reference it any other
                // way. Therfore, we already visit them in [visit_body].
                self.visit_body(body);
            }
            Expression::Body(body) => {
                self.visit_body(body);
            }
            Expression::Call {
                function,
                arguments,
            } => {
                if function == &self.id {
                    self.add_reference(id, DocumentHighlightKind::READ);
                }
                self.visit_ids(arguments);
            }
            Expression::Error => {}
        }
    }

    fn add_reference(&mut self, id: hir::Id, kind: DocumentHighlightKind) {
        if id == self.id && !self.include_declaration {
            return;
        }

        if self.discovered_references.contains(&id) {
            return;
        }
        self.discovered_references.insert(id.clone());

        let span = self.db.hir_id_to_span(self.input.clone(), id).unwrap();
        self.references.push(DocumentHighlight {
            range: lsp_types::Range {
                start: self
                    .db
                    .offset_to_lsp(self.input.clone(), span.start)
                    .to_position(),
                end: self
                    .db
                    .offset_to_lsp(self.input.clone(), span.end)
                    .to_position(),
            },
            kind: Some(kind),
        });
    }
}
<|endoftext|>


<commit_before>// Copyright 2017 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! GPU related things
//! depends on "gpu" feature

#[cfg(feature = "virgl_renderer_next")]
use std::collections::HashMap;
use std::env;
use std::path::PathBuf;

use serde::Deserialize;
use serde::Serialize;

use super::*;
use crate::crosvm::config::Config;

pub struct GpuCacheInfo<'a> {
    directory: Option<&'a str>,
    environment: Vec<(&'a str, &'a str)>,
}

pub fn get_gpu_cache_info<'a>(
    cache_dir: Option<&'a String>,
    cache_size: Option<&'a String>,
    sandbox: bool,
) -> GpuCacheInfo<'a> {
    let mut dir = None;
    let mut env = Vec::new();

    // TODO (renatopereyra): Remove deprecated env vars once all src/third_party/mesa* are updated.
    if let Some(cache_dir) = cache_dir {
        if !Path::new(cache_dir).exists() {
            warn!("shader caching dir {} does not exist", cache_dir);
            // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
            env.push(("MESA_GLSL_CACHE_DISABLE", "true"));

            env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
        } else if cfg!(any(target_arch = "arm", target_arch = "aarch64")) && sandbox {
            warn!("shader caching not yet supported on ARM with sandbox enabled");
            // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
            env.push(("MESA_GLSL_CACHE_DISABLE", "true"));

            env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
        } else {
            dir = Some(cache_dir.as_str());

            // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
            env.push(("MESA_GLSL_CACHE_DISABLE", "false"));
            env.push(("MESA_GLSL_CACHE_DIR", cache_dir.as_str()));

            env.push(("MESA_SHADER_CACHE_DISABLE", "false"));
            env.push(("MESA_SHADER_CACHE_DIR", cache_dir.as_str()));
            if let Some(cache_size) = cache_size {
                // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
                env.push(("MESA_GLSL_CACHE_MAX_SIZE", cache_size.as_str()));

                env.push(("MESA_SHADER_CACHE_MAX_SIZE", cache_size.as_str()));
            }
        }
    }

    GpuCacheInfo {
        directory: dir,
        environment: env,
    }
}

pub fn create_gpu_device(
    cfg: &Config,
    exit_evt_wrtube: &SendTube,
    resource_bridges: Vec<Tube>,
    wayland_socket_path: Option<&PathBuf>,
    x_display: Option<String>,
    #[cfg(feature = "virgl_renderer_next")] render_server_fd: Option<SafeDescriptor>,
    event_devices: Vec<EventDevice>,
) -> DeviceResult {
    let mut display_backends = vec![
        virtio::DisplayBackend::X(x_display),
        virtio::DisplayBackend::Stub,
    ];

    let wayland_socket_dirs = cfg
        .wayland_socket_paths
        .iter()
        .map(|(_name, path)| path.parent())
        .collect::<Option<Vec<_>>>()
        .ok_or_else(|| anyhow!("wayland socket path has no parent or file name"))?;

    if let Some(socket_path) = wayland_socket_path {
        display_backends.insert(
            0,
            virtio::DisplayBackend::Wayland(Some(socket_path.to_owned())),
        );
    }

    let dev = virtio::Gpu::new(
        exit_evt_wrtube
            .try_clone()
            .context("failed to clone tube")?,
        resource_bridges,
        display_backends,
        cfg.gpu_parameters.as_ref().unwrap(),
        #[cfg(feature = "virgl_renderer_next")]
        render_server_fd,
        event_devices,
        cfg.jail_config.is_some(),
        virtio::base_features(cfg.protection_type),
        cfg.wayland_socket_paths.clone(),
    );

    let jail = match gpu_jail(&cfg.jail_config, "gpu_device")? {
        Some(mut jail) => {
            // Prepare GPU shader disk cache directory.
            let (cache_dir, cache_size) = cfg
                .gpu_parameters
                .as_ref()
                .map(|params| (params.cache_path.as_ref(), params.cache_size.as_ref()))
                .unwrap();
            let cache_info = get_gpu_cache_info(cache_dir, cache_size, cfg.jail_config.is_some());

            if let Some(dir) = cache_info.directory {
                jail.mount_bind(dir, dir, true)?;
            }
            for (key, val) in cache_info.environment {
                env::set_var(key, val);
            }

            // Bind mount the wayland socket's directory into jail's root. This is necessary since
            // each new wayland context must open() the socket. If the wayland socket is ever
            // destroyed and remade in the same host directory, new connections will be possible
            // without restarting the wayland device.
            for dir in &wayland_socket_dirs {
                jail.mount_bind(dir, dir, true)?;
            }

            add_current_user_to_jail(&mut jail)?;

            Some(jail)
        }
        None => None,
    };

    Ok(VirtioDeviceStub {
        dev: Box::new(dev),
        jail,
    })
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct GpuRenderServerParameters {
    pub path: PathBuf,
    pub cache_path: Option<String>,
    pub cache_size: Option<String>,
}

#[cfg(feature = "virgl_renderer_next")]
fn get_gpu_render_server_environment(cache_info: Option<&GpuCacheInfo>) -> Result<Vec<String>> {
    let mut env = HashMap::<String, String>::new();
    let os_env_len = env::vars_os().count();

    if let Some(cache_info) = cache_info {
        env.reserve(os_env_len + cache_info.environment.len());
        for (key, val) in cache_info.environment.iter() {
            env.insert(key.to_string(), val.to_string());
        }
    } else {
        env.reserve(os_env_len);
    }

    for (key_os, val_os) in env::vars_os() {
        // minijail should accept OsStr rather than str...
        let into_string_err = |_| anyhow!("invalid environment key/val");
        let key = key_os.into_string().map_err(into_string_err)?;
        let val = val_os.into_string().map_err(into_string_err)?;
        env.entry(key).or_insert(val);
    }

    // TODO(b/237493180): workaround to enable ETC2 format emulation in RADV for ARCVM
    if !env.contains_key("radv_require_etc2") {
        env.insert("radv_require_etc2".to_string(), "true".to_string());
    }

    Ok(env.iter().map(|(k, v)| format!("{}={}", k, v)).collect())
}

#[cfg(feature = "virgl_renderer_next")]
pub fn start_gpu_render_server(
    cfg: &Config,
    render_server_parameters: &GpuRenderServerParameters,
) -> Result<(Minijail, SafeDescriptor)> {
    let (server_socket, client_socket) =
        UnixSeqpacket::pair().context("failed to create render server socket")?;

    let (jail, cache_info) = match gpu_jail(&cfg.jail_config, "gpu_render_server")? {
        Some(mut jail) => {
            let cache_info = get_gpu_cache_info(
                render_server_parameters.cache_path.as_ref(),
                render_server_parameters.cache_size.as_ref(),
                true,
            );

            if let Some(dir) = cache_info.directory {
                jail.mount_bind(dir, dir, true)?;
            }

            // bind mount /dev/log for syslog
            let log_path = Path::new("/dev/log");
            if log_path.exists() {
                jail.mount_bind(log_path, log_path, true)?;
            }

            // Run as root in the jail to keep capabilities after execve, which is needed for
            // mounting to work.  All capabilities will be dropped afterwards.
            add_current_user_as_root_to_jail(&mut jail)?;

            (jail, Some(cache_info))
        }
        None => (Minijail::new().context("failed to create jail")?, None),
    };

    let inheritable_fds = [
        server_socket.as_raw_descriptor(),
        libc::STDOUT_FILENO,
        libc::STDERR_FILENO,
    ];

    let cmd = &render_server_parameters.path;
    let cmd_str = cmd
        .to_str()
        .ok_or_else(|| anyhow!("invalid render server path"))?;
    let fd_str = server_socket.as_raw_descriptor().to_string();
    let args = [cmd_str, "--socket-fd", &fd_str];

    let env = Some(get_gpu_render_server_environment(cache_info.as_ref())?);
    let mut envp: Option<Vec<&str>> = None;
    if let Some(ref env) = env {
        envp = Some(env.iter().map(AsRef::as_ref).collect());
    }

    jail.run_command(minijail::Command::new_for_path(
        cmd,
        &inheritable_fds,
        &args,
        envp.as_deref(),
    )?)
    .context("failed to start gpu render server")?;

    Ok((jail, SafeDescriptor::from(client_socket)))
}
<commit_msg>crosvm: enable MESA_DISK_CACHE_DATABASE<commit_after>// Copyright 2017 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! GPU related things
//! depends on "gpu" feature

#[cfg(feature = "virgl_renderer_next")]
use std::collections::HashMap;
use std::env;
use std::path::PathBuf;

use serde::Deserialize;
use serde::Serialize;

use super::*;
use crate::crosvm::config::Config;

pub struct GpuCacheInfo<'a> {
    directory: Option<&'a str>,
    environment: Vec<(&'a str, &'a str)>,
}

pub fn get_gpu_cache_info<'a>(
    cache_dir: Option<&'a String>,
    cache_size: Option<&'a String>,
    sandbox: bool,
) -> GpuCacheInfo<'a> {
    let mut dir = None;
    let mut env = Vec::new();

    // TODO (renatopereyra): Remove deprecated env vars once all src/third_party/mesa* are updated.
    if let Some(cache_dir) = cache_dir {
        if !Path::new(cache_dir).exists() {
            warn!("shader caching dir {} does not exist", cache_dir);
            // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
            env.push(("MESA_GLSL_CACHE_DISABLE", "true"));

            env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
        } else if cfg!(any(target_arch = "arm", target_arch = "aarch64")) && sandbox {
            warn!("shader caching not yet supported on ARM with sandbox enabled");
            // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
            env.push(("MESA_GLSL_CACHE_DISABLE", "true"));

            env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
        } else {
            dir = Some(cache_dir.as_str());

            // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
            env.push(("MESA_GLSL_CACHE_DISABLE", "false"));
            env.push(("MESA_GLSL_CACHE_DIR", cache_dir.as_str()));

            env.push(("MESA_SHADER_CACHE_DISABLE", "false"));
            env.push(("MESA_SHADER_CACHE_DIR", cache_dir.as_str()));

            env.push(("MESA_DISK_CACHE_DATABASE", "1"));

            if let Some(cache_size) = cache_size {
                // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
                env.push(("MESA_GLSL_CACHE_MAX_SIZE", cache_size.as_str()));

                env.push(("MESA_SHADER_CACHE_MAX_SIZE", cache_size.as_str()));
            }
        }
    }

    GpuCacheInfo {
        directory: dir,
        environment: env,
    }
}

pub fn create_gpu_device(
    cfg: &Config,
    exit_evt_wrtube: &SendTube,
    resource_bridges: Vec<Tube>,
    wayland_socket_path: Option<&PathBuf>,
    x_display: Option<String>,
    #[cfg(feature = "virgl_renderer_next")] render_server_fd: Option<SafeDescriptor>,
    event_devices: Vec<EventDevice>,
) -> DeviceResult {
    let mut display_backends = vec![
        virtio::DisplayBackend::X(x_display),
        virtio::DisplayBackend::Stub,
    ];

    let wayland_socket_dirs = cfg
        .wayland_socket_paths
        .iter()
        .map(|(_name, path)| path.parent())
        .collect::<Option<Vec<_>>>()
        .ok_or_else(|| anyhow!("wayland socket path has no parent or file name"))?;

    if let Some(socket_path) = wayland_socket_path {
        display_backends.insert(
            0,
            virtio::DisplayBackend::Wayland(Some(socket_path.to_owned())),
        );
    }

    let dev = virtio::Gpu::new(
        exit_evt_wrtube
            .try_clone()
            .context("failed to clone tube")?,
        resource_bridges,
        display_backends,
        cfg.gpu_parameters.as_ref().unwrap(),
        #[cfg(feature = "virgl_renderer_next")]
        render_server_fd,
        event_devices,
        cfg.jail_config.is_some(),
        virtio::base_features(cfg.protection_type),
        cfg.wayland_socket_paths.clone(),
    );

    let jail = match gpu_jail(&cfg.jail_config, "gpu_device")? {
        Some(mut jail) => {
            // Prepare GPU shader disk cache directory.
            let (cache_dir, cache_size) = cfg
                .gpu_parameters
                .as_ref()
                .map(|params| (params.cache_path.as_ref(), params.cache_size.as_ref()))
                .unwrap();
            let cache_info = get_gpu_cache_info(cache_dir, cache_size, cfg.jail_config.is_some());

            if let Some(dir) = cache_info.directory {
                jail.mount_bind(dir, dir, true)?;
            }
            for (key, val) in cache_info.environment {
                env::set_var(key, val);
            }

            // Bind mount the wayland socket's directory into jail's root. This is necessary since
            // each new wayland context must open() the socket. If the wayland socket is ever
            // destroyed and remade in the same host directory, new connections will be possible
            // without restarting the wayland device.
            for dir in &wayland_socket_dirs {
                jail.mount_bind(dir, dir, true)?;
            }

            add_current_user_to_jail(&mut jail)?;

            Some(jail)
        }
        None => None,
    };

    Ok(VirtioDeviceStub {
        dev: Box::new(dev),
        jail,
    })
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct GpuRenderServerParameters {
    pub path: PathBuf,
    pub cache_path: Option<String>,
    pub cache_size: Option<String>,
}

#[cfg(feature = "virgl_renderer_next")]
fn get_gpu_render_server_environment(cache_info: Option<&GpuCacheInfo>) -> Result<Vec<String>> {
    let mut env = HashMap::<String, String>::new();
    let os_env_len = env::vars_os().count();

    if let Some(cache_info) = cache_info {
        env.reserve(os_env_len + cache_info.environment.len());
        for (key, val) in cache_info.environment.iter() {
            env.insert(key.to_string(), val.to_string());
        }
    } else {
        env.reserve(os_env_len);
    }

    for (key_os, val_os) in env::vars_os() {
        // minijail should accept OsStr rather than str...
        let into_string_err = |_| anyhow!("invalid environment key/val");
        let key = key_os.into_string().map_err(into_string_err)?;
        let val = val_os.into_string().map_err(into_string_err)?;
        env.entry(key).or_insert(val);
    }

    // TODO(b/237493180): workaround to enable ETC2 format emulation in RADV for ARCVM
    if !env.contains_key("radv_require_etc2") {
        env.insert("radv_require_etc2".to_string(), "true".to_string());
    }

    Ok(env.iter().map(|(k, v)| format!("{}={}", k, v)).collect())
}

#[cfg(feature = "virgl_renderer_next")]
pub fn start_gpu_render_server(
    cfg: &Config,
    render_server_parameters: &GpuRenderServerParameters,
) -> Result<(Minijail, SafeDescriptor)> {
    let (server_socket, client_socket) =
        UnixSeqpacket::pair().context("failed to create render server socket")?;

    let (jail, cache_info) = match gpu_jail(&cfg.jail_config, "gpu_render_server")? {
        Some(mut jail) => {
            let cache_info = get_gpu_cache_info(
                render_server_parameters.cache_path.as_ref(),
                render_server_parameters.cache_size.as_ref(),
                true,
            );

            if let Some(dir) = cache_info.directory {
                jail.mount_bind(dir, dir, true)?;
            }

            // bind mount /dev/log for syslog
            let log_path = Path::new("/dev/log");
            if log_path.exists() {
                jail.mount_bind(log_path, log_path, true)?;
            }

            // Run as root in the jail to keep capabilities after execve, which is needed for
            // mounting to work.  All capabilities will be dropped afterwards.
            add_current_user_as_root_to_jail(&mut jail)?;

            (jail, Some(cache_info))
        }
        None => (Minijail::new().context("failed to create jail")?, None),
    };

    let inheritable_fds = [
        server_socket.as_raw_descriptor(),
        libc::STDOUT_FILENO,
        libc::STDERR_FILENO,
    ];

    let cmd = &render_server_parameters.path;
    let cmd_str = cmd
        .to_str()
        .ok_or_else(|| anyhow!("invalid render server path"))?;
    let fd_str = server_socket.as_raw_descriptor().to_string();
    let args = [cmd_str, "--socket-fd", &fd_str];

    let env = Some(get_gpu_render_server_environment(cache_info.as_ref())?);
    let mut envp: Option<Vec<&str>> = None;
    if let Some(ref env) = env {
        envp = Some(env.iter().map(AsRef::as_ref).collect());
    }

    jail.run_command(minijail::Command::new_for_path(
        cmd,
        &inheritable_fds,
        &args,
        envp.as_deref(),
    )?)
    .context("failed to start gpu render server")?;

    Ok((jail, SafeDescriptor::from(client_socket)))
}
<|endoftext|>


<commit_before>
use glium::{Program, Surface};

use glium::VertexBuffer;
use glium::index;
use glium;
use glutin;

use render::shader::ShaderPair;
use render::texture_array::{TextureDirectory, TextureArrayDimensions};

use input;
use HashMap;
use input::InputState;
use color::Color;

use glium::texture::srgb_texture2d_array::SrgbTexture2dArray;

use std::sync::mpsc::{channel, Receiver};

use notify::{RecommendedWatcher, Watcher, RecursiveMode, RawEvent};

use super::window;
use super::program;
use render::command::*;
use render::command::Command::*;

use dimensions::Dimensions;
use render::vertex::Vertex;

use font::*;

use {JamResult, JamError};

use std::hash::Hash;

use image;

pub struct Renderer<BufferKey> where BufferKey : Hash + Eq {
    pub shader_pair : ShaderPair,
    pub texture_directory: TextureDirectory,
    pub font_directory: FontDirectory,
    pub resource_file_watcher : RecommendedWatcher,
    pub resource_file_change_events: Receiver<RawEvent>,
    pub display: glium::Display,
    pub input_state: InputState,
    pub program : Option<Program>,
    pub texture : Option<(SrgbTexture2dArray, TextureArrayDimensions)>,
    pub vertex_buffers : HashMap<BufferKey, VertexBuffer<Vertex>>,
    pub last_dimensions : Dimensions,
    pub fonts: Vec<LoadedBitmapFont>,
}

fn dimensions_for(display : &glium::Display) -> Dimensions {
    let (width_pixels, height_pixels) = display.get_framebuffer_dimensions();

    let scale : f64 = display.get_window().map(|w| w.hidpi_factor() as f64).unwrap_or(1.0);

    Dimensions {
        pixels: (width_pixels, height_pixels),
        scale: scale,
    }
}

impl <BufferKey> Renderer<BufferKey> where BufferKey : Hash + Eq + Clone {
    pub fn new(shader_pair : ShaderPair, texture_directory: TextureDirectory, font_directory: FontDirectory, initial_dimensions: (u32, u32)) -> JamResult<Renderer<BufferKey>> { //
        let (tx, notifier_rx) = channel::<RawEvent>();

        let mut resource_file_watcher : RecommendedWatcher = Watcher::new_raw(tx).expect("a watcher");
        resource_file_watcher.watch(&shader_pair.vertex_path, RecursiveMode::Recursive).expect("watching shader vertex path");
        resource_file_watcher.watch(&shader_pair.fragment_path, RecursiveMode::Recursive).expect("watching shader fragment path");
        resource_file_watcher.watch(&texture_directory.path, RecursiveMode::Recursive).expect("watching texture directory path");

        let display = window::create_window("mah window", true, initial_dimensions)?;

        let dimensions = dimensions_for(&display);

        Ok(Renderer {
            shader_pair : shader_pair,
            texture_directory: texture_directory,
            font_directory: font_directory,
            resource_file_watcher : resource_file_watcher,
            resource_file_change_events: notifier_rx,
            display: display,
            input_state: InputState::default(),
            program : None,
            texture : None,
            vertex_buffers : HashMap::default(),
            last_dimensions : dimensions,
            fonts: Vec::new(),
        })
    }

    pub fn load_font(&mut self, font_description: &FontDescription) -> JamResult<()> {
        let found_font = self.fonts.iter().any(|lf| &lf.font.description == font_description); // { f.font.description == font_description }

        if found_font {
            Ok(())
        } else if let Some(dimensions) = self.texture_array_dimensions() {
            let mut full_path = self.font_directory.path.clone();
            full_path.push(font_description.family.clone());
            full_path.set_extension("ttf");

            println!("load font {:?} with full_path -> {:?}", font_description, full_path);

            let font = build_font(full_path.as_path(), font_description, dimensions.width)?;

            println!("we've loaded the font, unloading the texture");
            self.fonts.push(font);
            self.texture = None;

            Ok(())
        } else {
            Err(JamError::MustLoadTextureBeforeFont)
        }
    }

    pub fn get_font(&self, font_description: &FontDescription) -> Option<(&BitmapFont, u32)> {
        if let Some(dimensions) = self.texture_array_dimensions() {
            if let Some(font_position) = self.fonts.iter().position(|loaded_font| &loaded_font.font.description == font_description) {
                let font_count = self.fonts.len();
                let layer = dimensions.layers as usize - font_count + font_position;
                Some((&self.fonts[font_position].font, layer as u32))
            } else {
                None
            }
        } else {
            None
        }
    }

    pub fn texture_array_dimensions(&self) -> Option<TextureArrayDimensions> {
       self.texture.as_ref().map(|&(_, d)| d )
    }

    pub fn clear_fonts(&mut self) {
        if !self.fonts.is_empty() {
            self.fonts.clear();
        }
    }

    pub fn screenshot(&mut self) -> image::DynamicImage {
        let image: glium::texture::RawImage2d<u8> = self.display.read_front_buffer();
        let image = image::ImageBuffer::from_raw(image.width, image.height, image.data.into_owned()).unwrap();
        let image = image::DynamicImage::ImageRgba8(image).flipv();
        image
    }

    pub fn load_resources(&mut self) {
        let (reload_program, reload_texture) = check_reload(&self.resource_file_change_events, &self.shader_pair, &self.texture_directory);

        if reload_program || self.program.is_none() {
            let program_load_result = self.shader_pair.load().and_then(|shader_data| shader_data.load(&self.display));
            println!("program load result -> {:?}", program_load_result);
            self.program = program_load_result.ok();
        }

        if reload_texture || self.texture.is_none() {
            println!("reload texture");
            let texture_load_result = self.texture_directory.load().and_then(|mut texture_data| {
                for loaded_font in &self.fonts {
                    println!("copying in font -> {:?} to texture array",loaded_font.font.description);
                    texture_data.data.push(loaded_font.image.clone().into_raw());
                }
                let mut dimensions = texture_data.dimensions;
                dimensions.layers += self.fonts.len() as u32;
                texture_data.load(&self.display).map(|t| (t, dimensions))
            });
            println!("texture load result -> {:?}", texture_load_result);
            self.texture = texture_load_result.ok();
        }
    }

    pub fn begin(&mut self) -> (Dimensions, InputState) {
        self.load_resources();

        let events : Vec<glutin::Event> = self.display.poll_events().collect();
        self.input_state = input::produce(&self.input_state, &events);

        let new_dimensions = dimensions_for(&self.display);

        if new_dimensions != self.last_dimensions { // this is a fix for Glutin/WINIT, when changing windows with different scales it does (but same points size), it won't properly resize
            println!("resize detected from {:?} to {:?}", self.last_dimensions, new_dimensions);
            if Dimensions::approx_equal_point_size(new_dimensions, self.last_dimensions)  {
                println!("missed resize case detected");
                if let Some(window) = self.display.get_window() {
                    if let Some((w, h)) = window.get_inner_size_points() {
                        println!("jiggling a little");
                        window.set_inner_size(w+1, h);
                        window.set_inner_size(w, h);
                    }
                }
            }

            self.last_dimensions = new_dimensions;
        }

        (new_dimensions, self.input_state.clone())
    }

    pub fn render(&mut self, passes: Vec<Pass<BufferKey>>, clear_color: Color) -> JamResult<()> {
        if let (&Some(ref pr), &Some((ref tr, _))) = (&self.program, &self.texture) {
            let mut target = self.display.draw();

            target.clear_color_and_depth(clear_color.float_tup(), 1.0);

            let tex = tr.sampled().magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest);

            for pass in passes {
                let blend = program::draw_params_for_blend(pass.blend);
                if pass.clear_depth {
                    target.clear_depth(1.0);
                }

                for command in pass.commands {
                    // println!("received command -> {:?}", command);
                    match command {
                        Delete { key } => {
                            let _ = self.vertex_buffers.remove(&key);
                        },
                        DeleteMatching { pred } => {
                            let keys_to_delete : Vec<_>= self.vertex_buffers.keys().filter(|e| pred(e)).cloned().collect();
                            for key in keys_to_delete.iter() {
                                self.vertex_buffers.remove(key);
                            }
                        }
                        Update { key, vertices } => {
                            let new_vertex_buffer = VertexBuffer::persistent(&self.display,&vertices).unwrap();
                            self.vertex_buffers.insert(key, new_vertex_buffer);
                        },
                        Draw { key, uniforms } => {
                            if let Some(vertex_buffer) = self.vertex_buffers.get(&key) {
                                let uniforms = uniform! {
                                    u_matrix: uniforms.transform,
                                    u_texture_array: tex,
                                    u_color: uniforms.color.float_raw(),
                                    u_alpha_minimum: 0.01_f32,
                                };
                                target.draw(vertex_buffer, &index::NoIndices(index::PrimitiveType::TrianglesList), &pr, &uniforms, &blend).unwrap();
                            } else {
                                // println!("couldnt draw for {:?}", key);
                            }
                        },
                        DrawNew { key , vertices, uniforms } => {
                            let new_vertex_buffer = VertexBuffer::persistent(&self.display,&vertices).unwrap();

                            let uniforms = uniform! {
                                u_matrix: uniforms.transform,
                                u_texture_array: tex,
                                u_color: uniforms.color.float_raw(),
                                u_alpha_minimum: 0.01_f32,
                            };

                            target.draw(&new_vertex_buffer, &index::NoIndices(index::PrimitiveType::TrianglesList), &pr, &uniforms, &blend).unwrap();

                            if let Some(name) = key {
                                self.vertex_buffers.insert(name,new_vertex_buffer);
                            }
                        },
                    }
                }
            }
            target.finish().map_err(JamError::SwapBufferError)
        } else {
            Err(JamError::RenderingPipelineIncomplete)
        }
    }
}

pub fn check_reload(rx: &Receiver<RawEvent>, shader_pair:&ShaderPair, texture_directory: &TextureDirectory) -> (bool, bool) {
    let mut reload_program = false;
    let mut reload_texture = false;

    'fs: loop {
        match rx.try_recv() {
            Ok(RawEvent { path, op:_, cookie:_ }) => {
                if let Some(p) = path {
                    if shader_pair.contains(&p) {
                        reload_program = true;
                    } else if texture_directory.contains(&p) {
                        reload_texture = true;
                    } else {
                        use std::path;
                        let components: Vec<path::Component> = p.components().collect();
                        println!("fs event {:?} -> {:?}", p, components);
                    }
                }
            },
            Err(_) => break 'fs,
        }
    }

     (reload_program, reload_texture)
}<commit_msg>Clear colour now uses glium srgb based function<commit_after>
use glium::{Program, Surface};

use glium::VertexBuffer;
use glium::index;
use glium;
use glutin;

use render::shader::ShaderPair;
use render::texture_array::{TextureDirectory, TextureArrayDimensions};

use input;
use HashMap;
use input::InputState;
use color::Color;

use glium::texture::srgb_texture2d_array::SrgbTexture2dArray;

use std::sync::mpsc::{channel, Receiver};

use notify::{RecommendedWatcher, Watcher, RecursiveMode, RawEvent};

use super::window;
use super::program;
use render::command::*;
use render::command::Command::*;

use dimensions::Dimensions;
use render::vertex::Vertex;

use font::*;

use {JamResult, JamError};

use std::hash::Hash;

use image;

pub struct Renderer<BufferKey> where BufferKey : Hash + Eq {
    pub shader_pair : ShaderPair,
    pub texture_directory: TextureDirectory,
    pub font_directory: FontDirectory,
    pub resource_file_watcher : RecommendedWatcher,
    pub resource_file_change_events: Receiver<RawEvent>,
    pub display: glium::Display,
    pub input_state: InputState,
    pub program : Option<Program>,
    pub texture : Option<(SrgbTexture2dArray, TextureArrayDimensions)>,
    pub vertex_buffers : HashMap<BufferKey, VertexBuffer<Vertex>>,
    pub last_dimensions : Dimensions,
    pub fonts: Vec<LoadedBitmapFont>,
}

fn dimensions_for(display : &glium::Display) -> Dimensions {
    let (width_pixels, height_pixels) = display.get_framebuffer_dimensions();

    let scale : f64 = display.get_window().map(|w| w.hidpi_factor() as f64).unwrap_or(1.0);

    Dimensions {
        pixels: (width_pixels, height_pixels),
        scale: scale,
    }
}

impl <BufferKey> Renderer<BufferKey> where BufferKey : Hash + Eq + Clone {
    pub fn new(shader_pair : ShaderPair, texture_directory: TextureDirectory, font_directory: FontDirectory, initial_dimensions: (u32, u32)) -> JamResult<Renderer<BufferKey>> { //
        let (tx, notifier_rx) = channel::<RawEvent>();

        let mut resource_file_watcher : RecommendedWatcher = Watcher::new_raw(tx).expect("a watcher");
        resource_file_watcher.watch(&shader_pair.vertex_path, RecursiveMode::Recursive).expect("watching shader vertex path");
        resource_file_watcher.watch(&shader_pair.fragment_path, RecursiveMode::Recursive).expect("watching shader fragment path");
        resource_file_watcher.watch(&texture_directory.path, RecursiveMode::Recursive).expect("watching texture directory path");

        let display = window::create_window("mah window", true, initial_dimensions)?;

        let dimensions = dimensions_for(&display);

        Ok(Renderer {
            shader_pair : shader_pair,
            texture_directory: texture_directory,
            font_directory: font_directory,
            resource_file_watcher : resource_file_watcher,
            resource_file_change_events: notifier_rx,
            display: display,
            input_state: InputState::default(),
            program : None,
            texture : None,
            vertex_buffers : HashMap::default(),
            last_dimensions : dimensions,
            fonts: Vec::new(),
        })
    }

    pub fn load_font(&mut self, font_description: &FontDescription) -> JamResult<()> {
        let found_font = self.fonts.iter().any(|lf| &lf.font.description == font_description); // { f.font.description == font_description }

        if found_font {
            Ok(())
        } else if let Some(dimensions) = self.texture_array_dimensions() {
            let mut full_path = self.font_directory.path.clone();
            full_path.push(font_description.family.clone());
            full_path.set_extension("ttf");

            println!("load font {:?} with full_path -> {:?}", font_description, full_path);

            let font = build_font(full_path.as_path(), font_description, dimensions.width)?;

            println!("we've loaded the font, unloading the texture");
            self.fonts.push(font);
            self.texture = None;

            Ok(())
        } else {
            Err(JamError::MustLoadTextureBeforeFont)
        }
    }

    pub fn get_font(&self, font_description: &FontDescription) -> Option<(&BitmapFont, u32)> {
        if let Some(dimensions) = self.texture_array_dimensions() {
            if let Some(font_position) = self.fonts.iter().position(|loaded_font| &loaded_font.font.description == font_description) {
                let font_count = self.fonts.len();
                let layer = dimensions.layers as usize - font_count + font_position;
                Some((&self.fonts[font_position].font, layer as u32))
            } else {
                None
            }
        } else {
            None
        }
    }

    pub fn texture_array_dimensions(&self) -> Option<TextureArrayDimensions> {
       self.texture.as_ref().map(|&(_, d)| d )
    }

    pub fn clear_fonts(&mut self) {
        if !self.fonts.is_empty() {
            self.fonts.clear();
        }
    }

    pub fn screenshot(&mut self) -> image::DynamicImage {
        let image: glium::texture::RawImage2d<u8> = self.display.read_front_buffer();
        let image = image::ImageBuffer::from_raw(image.width, image.height, image.data.into_owned()).unwrap();
        let image = image::DynamicImage::ImageRgba8(image).flipv();
        image
    }

    pub fn load_resources(&mut self) {
        let (reload_program, reload_texture) = check_reload(&self.resource_file_change_events, &self.shader_pair, &self.texture_directory);

        if reload_program || self.program.is_none() {
            let program_load_result = self.shader_pair.load().and_then(|shader_data| shader_data.load(&self.display));
            println!("program load result -> {:?}", program_load_result);
            self.program = program_load_result.ok();
        }

        if reload_texture || self.texture.is_none() {
            println!("reload texture");
            let texture_load_result = self.texture_directory.load().and_then(|mut texture_data| {
                for loaded_font in &self.fonts {
                    println!("copying in font -> {:?} to texture array",loaded_font.font.description);
                    texture_data.data.push(loaded_font.image.clone().into_raw());
                }
                let mut dimensions = texture_data.dimensions;
                dimensions.layers += self.fonts.len() as u32;
                texture_data.load(&self.display).map(|t| (t, dimensions))
            });
            println!("texture load result -> {:?}", texture_load_result);
            self.texture = texture_load_result.ok();
        }
    }

    pub fn begin(&mut self) -> (Dimensions, InputState) {
        self.load_resources();

        let events : Vec<glutin::Event> = self.display.poll_events().collect();
        self.input_state = input::produce(&self.input_state, &events);

        let new_dimensions = dimensions_for(&self.display);

        if new_dimensions != self.last_dimensions { // this is a fix for Glutin/WINIT, when changing windows with different scales it does (but same points size), it won't properly resize
            println!("resize detected from {:?} to {:?}", self.last_dimensions, new_dimensions);
            if Dimensions::approx_equal_point_size(new_dimensions, self.last_dimensions)  {
                println!("missed resize case detected");
                if let Some(window) = self.display.get_window() {
                    if let Some((w, h)) = window.get_inner_size_points() {
                        println!("jiggling a little");
                        window.set_inner_size(w+1, h);
                        window.set_inner_size(w, h);
                    }
                }
            }

            self.last_dimensions = new_dimensions;
        }

        (new_dimensions, self.input_state.clone())
    }

    pub fn render(&mut self, passes: Vec<Pass<BufferKey>>, clear_color: Color) -> JamResult<()> {
        if let (&Some(ref pr), &Some((ref tr, _))) = (&self.program, &self.texture) {
            let mut target = self.display.draw();

            target.clear_color_srgb_and_depth(clear_color.float_tup(), 1.0);

            let tex = tr.sampled().magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest);

            for pass in passes {
                let blend = program::draw_params_for_blend(pass.blend);
                if pass.clear_depth {
                    target.clear_depth(1.0);
                }

                for command in pass.commands {
                    // println!("received command -> {:?}", command);
                    match command {
                        Delete { key } => {
                            let _ = self.vertex_buffers.remove(&key);
                        },
                        DeleteMatching { pred } => {
                            let keys_to_delete : Vec<_>= self.vertex_buffers.keys().filter(|e| pred(e)).cloned().collect();
                            for key in keys_to_delete.iter() {
                                self.vertex_buffers.remove(key);
                            }
                        }
                        Update { key, vertices } => {
                            let new_vertex_buffer = VertexBuffer::persistent(&self.display,&vertices).unwrap();
                            self.vertex_buffers.insert(key, new_vertex_buffer);
                        },
                        Draw { key, uniforms } => {
                            if let Some(vertex_buffer) = self.vertex_buffers.get(&key) {
                                let uniforms = uniform! {
                                    u_matrix: uniforms.transform,
                                    u_texture_array: tex,
                                    u_color: uniforms.color.float_raw(),
                                    u_alpha_minimum: 0.01_f32,
                                };
                                target.draw(vertex_buffer, &index::NoIndices(index::PrimitiveType::TrianglesList), &pr, &uniforms, &blend).unwrap();
                            } else {
                                // println!("couldnt draw for {:?}", key);
                            }
                        },
                        DrawNew { key , vertices, uniforms } => {
                            let new_vertex_buffer = VertexBuffer::persistent(&self.display,&vertices).unwrap();

                            let uniforms = uniform! {
                                u_matrix: uniforms.transform,
                                u_texture_array: tex,
                                u_color: uniforms.color.float_raw(),
                                u_alpha_minimum: 0.01_f32,
                            };

                            target.draw(&new_vertex_buffer, &index::NoIndices(index::PrimitiveType::TrianglesList), &pr, &uniforms, &blend).unwrap();

                            if let Some(name) = key {
                                self.vertex_buffers.insert(name,new_vertex_buffer);
                            }
                        },
                    }
                }
            }
            target.finish().map_err(JamError::SwapBufferError)
        } else {
            Err(JamError::RenderingPipelineIncomplete)
        }
    }
}

pub fn check_reload(rx: &Receiver<RawEvent>, shader_pair:&ShaderPair, texture_directory: &TextureDirectory) -> (bool, bool) {
    let mut reload_program = false;
    let mut reload_texture = false;

    'fs: loop {
        match rx.try_recv() {
            Ok(RawEvent { path, op:_, cookie:_ }) => {
                if let Some(p) = path {
                    if shader_pair.contains(&p) {
                        reload_program = true;
                    } else if texture_directory.contains(&p) {
                        reload_texture = true;
                    } else {
                        use std::path;
                        let components: Vec<path::Component> = p.components().collect();
                        println!("fs event {:?} -> {:?}", p, components);
                    }
                }
            },
            Err(_) => break 'fs,
        }
    }

     (reload_program, reload_texture)
}<|endoftext|>