diff options
Diffstat (limited to 'components/util')
-rw-r--r-- | components/util/Cargo.toml | 20 | ||||
-rw-r--r-- | components/util/atom.rs | 43 | ||||
-rw-r--r-- | components/util/cache.rs | 279 | ||||
-rw-r--r-- | components/util/debug_utils.rs | 33 | ||||
-rw-r--r-- | components/util/geometry.rs | 304 | ||||
-rw-r--r-- | components/util/lib.rs | 44 | ||||
-rw-r--r-- | components/util/logical_geometry.rs | 1023 | ||||
-rw-r--r-- | components/util/memory.rs | 209 | ||||
-rw-r--r-- | components/util/namespace.rs | 43 | ||||
-rw-r--r-- | components/util/opts.rs | 235 | ||||
-rw-r--r-- | components/util/range.rs | 355 | ||||
-rw-r--r-- | components/util/smallvec.rs | 530 | ||||
-rw-r--r-- | components/util/sort.rs | 101 | ||||
-rw-r--r-- | components/util/str.rs | 111 | ||||
-rw-r--r-- | components/util/task.rs | 40 | ||||
-rw-r--r-- | components/util/time.rs | 241 | ||||
-rw-r--r-- | components/util/vec.rs | 124 | ||||
-rw-r--r-- | components/util/workqueue.rs | 291 |
18 files changed, 4026 insertions, 0 deletions
diff --git a/components/util/Cargo.toml b/components/util/Cargo.toml new file mode 100644 index 00000000000..be6662b76f6 --- /dev/null +++ b/components/util/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "util" +version = "0.0.1" +authors = ["The Servo Project Developers"] + +[lib] +name = "util" +path = "lib.rs" + +[dependencies.azure] +git = "https://github.com/servo/rust-azure" + +[dependencies.geom] +git = "https://github.com/servo/rust-geom" + +[dependencies.task_info] +path = "../../support/rust-task_info" + +[dependencies.string_cache] +git = "https://github.com/servo/string-cache" diff --git a/components/util/atom.rs b/components/util/atom.rs new file mode 100644 index 00000000000..49cb047768e --- /dev/null +++ b/components/util/atom.rs @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Provides a wrapper around the Atom type in the string cache +//! crate. It's needed so that it can implement the Encodable +//! trait which is required by Servo. + +use serialize::{Encoder, Encodable}; +use std::fmt; +use std::hash::Hash; +use string_cache::atom; + +#[deriving(Clone, Eq, Hash, PartialEq)] +pub struct Atom { + atom: atom::Atom, +} + +impl Atom { + #[inline(always)] + pub fn from_slice(slice: &str) -> Atom { + Atom { + atom: atom::Atom::from_slice(slice) + } + } + + #[inline(always)] + pub fn as_slice<'t>(&'t self) -> &'t str { + self.atom.as_slice() + } +} + +impl fmt::Show for Atom { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:s}", self.atom.as_slice()) + } +} + +impl<E, S: Encoder<E>> Encodable<S, E> for Atom { + fn encode(&self, _s: &mut S) -> Result<(), E> { + Ok(()) + } +} diff --git a/components/util/cache.rs b/components/util/cache.rs new file mode 100644 index 00000000000..1b159cea8c1 --- /dev/null +++ b/components/util/cache.rs @@ -0,0 +1,279 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::hashmap::HashMap; +use rand::Rng; +use std::hash::{Hash, sip}; +use std::rand::task_rng; +use std::slice::Items; + +#[cfg(test)] +use std::cell::Cell; + +pub trait Cache<K: PartialEq, V: Clone> { + fn insert(&mut self, key: K, value: V); + fn find(&mut self, key: &K) -> Option<V>; + fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V; + fn evict_all(&mut self); +} + +pub struct MonoCache<K, V> { + entry: Option<(K,V)>, +} + +impl<K: Clone + PartialEq, V: Clone> MonoCache<K,V> { + pub fn new(_size: uint) -> MonoCache<K,V> { + MonoCache { entry: None } + } +} + +impl<K: Clone + PartialEq, V: Clone> Cache<K,V> for MonoCache<K,V> { + fn insert(&mut self, key: K, value: V) { + self.entry = Some((key, value)); + } + + fn find(&mut self, key: &K) -> Option<V> { + match self.entry { + None => None, + Some((ref k, ref v)) => if *k == *key { Some(v.clone()) } else { None } + } + } + + fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V { + match self.find(key) { + Some(value) => value, + None => { + let value = blk(key); + self.entry = Some((key.clone(), value.clone())); + value + } + } + } + + fn evict_all(&mut self) { + self.entry = None; + } +} + +#[test] +fn test_monocache() { + let mut cache: MonoCache<uint,Cell<&str>> = MonoCache::new(10); + let one = Cell::new("one"); + let two = Cell::new("two"); + cache.insert(1, one); + + assert!(cache.find(&1).is_some()); + assert!(cache.find(&2).is_none()); + cache.find_or_create(&2, |_v| { two }); + assert!(cache.find(&2).is_some()); + assert!(cache.find(&1).is_none()); +} + +pub struct HashCache<K, V> { + entries: HashMap<K, V>, +} + +impl<K: Clone + PartialEq + Eq + Hash, V: Clone> HashCache<K,V> { + pub fn new() -> HashCache<K, V> { + HashCache { + entries: HashMap::new(), + } + } +} + +impl<K: Clone + PartialEq + Eq + Hash, V: Clone> Cache<K,V> for HashCache<K,V> { + fn insert(&mut self, key: K, value: V) { + self.entries.insert(key, value); + } + + fn find(&mut self, key: &K) -> Option<V> { + match self.entries.find(key) { + Some(v) => Some(v.clone()), + None => None, + } + } + + fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V { + self.entries.find_or_insert_with(key.clone(), blk).clone() + } + + fn evict_all(&mut self) { + self.entries.clear(); + } +} + +#[test] +fn test_hashcache() { + let mut cache: HashCache<uint, Cell<&str>> = HashCache::new(); + let one = Cell::new("one"); + let two = Cell::new("two"); + + cache.insert(1, one); + assert!(cache.find(&1).is_some()); + assert!(cache.find(&2).is_none()); + + cache.find_or_create(&2, |_v| { two }); + assert!(cache.find(&1).is_some()); + assert!(cache.find(&2).is_some()); +} + +pub struct LRUCache<K, V> { + entries: Vec<(K, V)>, + cache_size: uint, +} + +impl<K: Clone + PartialEq, V: Clone> LRUCache<K,V> { + pub fn new(size: uint) -> LRUCache<K, V> { + LRUCache { + entries: vec!(), + cache_size: size, + } + } + + #[inline] + pub fn touch(&mut self, pos: uint) -> V { + let last_index = self.entries.len() - 1; + if pos != last_index { + let entry = self.entries.remove(pos); + self.entries.push(entry.unwrap()); + } + self.entries[last_index].ref1().clone() + } + + pub fn iter<'a>(&'a self) -> Items<'a,(K,V)> { + self.entries.iter() + } +} + +impl<K: Clone + PartialEq, V: Clone> Cache<K,V> for LRUCache<K,V> { + fn insert(&mut self, key: K, val: V) { + if self.entries.len() == self.cache_size { + self.entries.remove(0); + } + self.entries.push((key, val)); + } + + fn find(&mut self, key: &K) -> Option<V> { + match self.entries.iter().position(|&(ref k, _)| *k == *key) { + Some(pos) => Some(self.touch(pos)), + None => None, + } + } + + fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V { + match self.entries.iter().position(|&(ref k, _)| *k == *key) { + Some(pos) => self.touch(pos), + None => { + let val = blk(key); + self.insert(key.clone(), val.clone()); + val + } + } + } + + fn evict_all(&mut self) { + self.entries.clear(); + } +} + +pub struct SimpleHashCache<K,V> { + entries: Vec<Option<(K,V)>>, + k0: u64, + k1: u64, +} + +impl<K:Clone+PartialEq+Hash,V:Clone> SimpleHashCache<K,V> { + pub fn new(cache_size: uint) -> SimpleHashCache<K,V> { + let mut r = task_rng(); + SimpleHashCache { + entries: Vec::from_elem(cache_size, None), + k0: r.gen(), + k1: r.gen(), + } + } + + #[inline] + fn to_bucket(&self, h: uint) -> uint { + h % self.entries.len() + } + + #[inline] + fn bucket_for_key<Q:Hash>(&self, key: &Q) -> uint { + self.to_bucket(sip::hash_with_keys(self.k0, self.k1, key) as uint) + } + + #[inline] + pub fn find_equiv<'a,Q:Hash+Equiv<K>>(&'a self, key: &Q) -> Option<&'a V> { + let bucket_index = self.bucket_for_key(key); + match self.entries[bucket_index] { + Some((ref existing_key, ref value)) if key.equiv(existing_key) => Some(value), + _ => None, + } + } +} + +impl<K:Clone+PartialEq+Hash,V:Clone> Cache<K,V> for SimpleHashCache<K,V> { + fn insert(&mut self, key: K, value: V) { + let bucket_index = self.bucket_for_key(&key); + *self.entries.get_mut(bucket_index) = Some((key, value)); + } + + fn find(&mut self, key: &K) -> Option<V> { + let bucket_index = self.bucket_for_key(key); + match self.entries[bucket_index] { + Some((ref existing_key, ref value)) if existing_key == key => Some((*value).clone()), + _ => None, + } + } + + fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V { + match self.find(key) { + Some(value) => return value, + None => {} + } + let value = blk(key); + self.insert((*key).clone(), value.clone()); + value + } + + fn evict_all(&mut self) { + for slot in self.entries.mut_iter() { + *slot = None + } + } +} + +#[test] +fn test_lru_cache() { + let one = Cell::new("one"); + let two = Cell::new("two"); + let three = Cell::new("three"); + let four = Cell::new("four"); + + // Test normal insertion. + let mut cache: LRUCache<uint,Cell<&str>> = LRUCache::new(2); // (_, _) (cache is empty) + cache.insert(1, one); // (1, _) + cache.insert(2, two); // (1, 2) + cache.insert(3, three); // (2, 3) + + assert!(cache.find(&1).is_none()); // (2, 3) (no change) + assert!(cache.find(&3).is_some()); // (2, 3) + assert!(cache.find(&2).is_some()); // (3, 2) + + // Test that LRU works (this insertion should replace 3, not 2). + cache.insert(4, four); // (2, 4) + + assert!(cache.find(&1).is_none()); // (2, 4) (no change) + assert!(cache.find(&2).is_some()); // (4, 2) + assert!(cache.find(&3).is_none()); // (4, 2) (no change) + assert!(cache.find(&4).is_some()); // (2, 4) (no change) + + // Test find_or_create. + cache.find_or_create(&1, |_| { one }); // (4, 1) + + assert!(cache.find(&1).is_some()); // (4, 1) (no change) + assert!(cache.find(&2).is_none()); // (4, 1) (no change) + assert!(cache.find(&3).is_none()); // (4, 1) (no change) + assert!(cache.find(&4).is_some()); // (1, 4) +} diff --git a/components/util/debug_utils.rs b/components/util/debug_utils.rs new file mode 100644 index 00000000000..e8d6cd31fea --- /dev/null +++ b/components/util/debug_utils.rs @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io; +use std::io::Writer; +use std::mem; +use std::mem::size_of; +use std::slice::raw::buf_as_slice; + +fn hexdump_slice(buf: &[u8]) { + let mut stderr = io::stderr(); + stderr.write(b" ").unwrap(); + for (i, &v) in buf.iter().enumerate() { + let output = format!("{:02X} ", v as uint); + stderr.write(output.as_bytes()).unwrap(); + match i % 16 { + 15 => { stderr.write(b"\n ").unwrap(); }, + 7 => { stderr.write(b" ").unwrap(); }, + _ => () + } + stderr.flush().unwrap(); + } + stderr.write(b"\n").unwrap(); +} + +pub fn hexdump<T>(obj: &T) { + unsafe { + let buf: *const u8 = mem::transmute(obj); + debug!("dumping at {:p}", buf); + buf_as_slice(buf, size_of::<T>(), hexdump_slice); + } +} diff --git a/components/util/geometry.rs b/components/util/geometry.rs new file mode 100644 index 00000000000..c87e98e38b7 --- /dev/null +++ b/components/util/geometry.rs @@ -0,0 +1,304 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use geom::length::Length; +use geom::point::Point2D; +use geom::rect::Rect; +use geom::size::Size2D; + +use serialize::{Encodable, Encoder}; +use std::default::Default; +use std::num::{NumCast, One, Zero}; +use std::fmt; + +// Units for use with geom::length and geom::scale_factor. + +/// A normalized "pixel" at the default resolution for the display. +/// +/// Like the CSS "px" unit, the exact physical size of this unit may vary between devices, but it +/// should approximate a device-independent reference length. This unit corresponds to Android's +/// "density-independent pixel" (dip), Mac OS X's "point", and Windows "device-independent pixel." +/// +/// The relationship between DevicePixel and ScreenPx is defined by the OS. On most low-dpi +/// screens, one ScreenPx is equal to one DevicePixel. But on high-density screens it can be +/// some larger number. For example, by default on Apple "retina" displays, one ScreenPx equals +/// two DevicePixels. On Android "MDPI" displays, one ScreenPx equals 1.5 device pixels. +/// +/// The ratio between ScreenPx and DevicePixel for a given display be found by calling +/// `servo::windowing::WindowMethods::hidpi_factor`. +pub enum ScreenPx {} + +/// One CSS "px" in the coordinate system of the "initial viewport": +/// http://www.w3.org/TR/css-device-adapt/#initial-viewport +/// +/// ViewportPx is equal to ScreenPx times a "page zoom" factor controlled by the user. This is +/// the desktop-style "full page" zoom that enlarges content but then reflows the layout viewport +/// so it still exactly fits the visible area. +/// +/// At the default zoom level of 100%, one PagePx is equal to one ScreenPx. However, if the +/// document is zoomed in or out then this scale may be larger or smaller. +#[deriving(Encodable)] +pub enum ViewportPx {} + +/// One CSS "px" in the root coordinate system for the content document. +/// +/// PagePx is equal to ViewportPx multiplied by a "viewport zoom" factor controlled by the user. +/// This is the mobile-style "pinch zoom" that enlarges content without reflowing it. When the +/// viewport zoom is not equal to 1.0, then the layout viewport is no longer the same physical size +/// as the viewable area. +#[deriving(Encodable)] +pub enum PagePx {} + +// In summary, the hierarchy of pixel units and the factors to convert from one to the next: +// +// DevicePixel +// / hidpi_ratio => ScreenPx +// / desktop_zoom => ViewportPx +// / pinch_zoom => PagePx + +// An Au is an "App Unit" and represents 1/60th of a CSS pixel. It was +// originally proposed in 2002 as a standard unit of measure in Gecko. +// See https://bugzilla.mozilla.org/show_bug.cgi?id=177805 for more info. +// +// FIXME: Implement Au using Length and ScaleFactor instead of a custom type. +#[deriving(Clone, PartialEq, PartialOrd, Eq, Ord, Zero)] +pub struct Au(pub i32); + +impl Default for Au { + #[inline] + fn default() -> Au { + Au(0) + } +} + +impl<E, S: Encoder<E>> Encodable<S, E> for Au { + fn encode(&self, e: &mut S) -> Result<(), E> { + e.emit_f64(to_frac_px(*self)) + } +} + +impl fmt::Show for Au { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}px", to_frac_px(*self)) + }} + +impl Add<Au,Au> for Au { + #[inline] + fn add(&self, other: &Au) -> Au { + let Au(s) = *self; + let Au(o) = *other; + Au(s + o) + } +} + +impl Sub<Au,Au> for Au { + #[inline] + fn sub(&self, other: &Au) -> Au { + let Au(s) = *self; + let Au(o) = *other; + Au(s - o) + } + +} + +impl Mul<Au,Au> for Au { + #[inline] + fn mul(&self, other: &Au) -> Au { + let Au(s) = *self; + let Au(o) = *other; + Au(s * o) + } +} + +impl Div<Au,Au> for Au { + #[inline] + fn div(&self, other: &Au) -> Au { + let Au(s) = *self; + let Au(o) = *other; + Au(s / o) + } +} + +impl Rem<Au,Au> for Au { + #[inline] + fn rem(&self, other: &Au) -> Au { + let Au(s) = *self; + let Au(o) = *other; + Au(s % o) + } +} + +impl Neg<Au> for Au { + #[inline] + fn neg(&self) -> Au { + let Au(s) = *self; + Au(-s) + } +} + +impl One for Au { + #[inline] + fn one() -> Au { Au(1) } +} + +impl Num for Au {} + +#[inline] +pub fn min(x: Au, y: Au) -> Au { if x < y { x } else { y } } +#[inline] +pub fn max(x: Au, y: Au) -> Au { if x > y { x } else { y } } + +impl NumCast for Au { + #[inline] + fn from<T:ToPrimitive>(n: T) -> Option<Au> { + Some(Au(n.to_i32().unwrap())) + } +} + +impl ToPrimitive for Au { + #[inline] + fn to_i64(&self) -> Option<i64> { + let Au(s) = *self; + Some(s as i64) + } + + #[inline] + fn to_u64(&self) -> Option<u64> { + let Au(s) = *self; + Some(s as u64) + } + + #[inline] + fn to_f32(&self) -> Option<f32> { + let Au(s) = *self; + s.to_f32() + } + + #[inline] + fn to_f64(&self) -> Option<f64> { + let Au(s) = *self; + s.to_f64() + } +} + +impl Au { + /// FIXME(pcwalton): Workaround for lack of cross crate inlining of newtype structs! + #[inline] + pub fn new(value: i32) -> Au { + Au(value) + } + + #[inline] + pub fn scale_by(self, factor: f64) -> Au { + let Au(s) = self; + Au(((s as f64) * factor) as i32) + } + + #[inline] + pub fn from_px(px: int) -> Au { + NumCast::from(px * 60).unwrap() + } + + #[inline] + pub fn from_page_px(px: Length<PagePx, f32>) -> Au { + NumCast::from(px.get() * 60f32).unwrap() + } + + #[inline] + pub fn to_nearest_px(&self) -> int { + let Au(s) = *self; + ((s as f64) / 60f64).round() as int + } + + #[inline] + pub fn to_snapped(&self) -> Au { + let Au(s) = *self; + let res = s % 60i32; + return if res >= 30i32 { return Au(s - res + 60i32) } + else { return Au(s - res) }; + } + + #[inline] + pub fn from_frac32_px(px: f32) -> Au { + Au((px * 60f32) as i32) + } + + #[inline] + pub fn from_pt(pt: f64) -> Au { + from_frac_px(pt_to_px(pt)) + } + + #[inline] + pub fn from_frac_px(px: f64) -> Au { + Au((px * 60f64) as i32) + } + + #[inline] + pub fn min(x: Au, y: Au) -> Au { + let Au(xi) = x; + let Au(yi) = y; + if xi < yi { x } else { y } + } + + #[inline] + pub fn max(x: Au, y: Au) -> Au { + let Au(xi) = x; + let Au(yi) = y; + if xi > yi { x } else { y } + } +} + +// assumes 72 points per inch, and 96 px per inch +pub fn pt_to_px(pt: f64) -> f64 { + pt / 72f64 * 96f64 +} + +// assumes 72 points per inch, and 96 px per inch +pub fn px_to_pt(px: f64) -> f64 { + px / 96f64 * 72f64 +} + +pub fn from_frac_px(px: f64) -> Au { + Au((px * 60f64) as i32) +} + +pub fn from_px(px: int) -> Au { + NumCast::from(px * 60).unwrap() +} + +pub fn to_px(au: Au) -> int { + let Au(a) = au; + (a / 60) as int +} + +pub fn to_frac_px(au: Au) -> f64 { + let Au(a) = au; + (a as f64) / 60f64 +} + +// assumes 72 points per inch, and 96 px per inch +pub fn from_pt(pt: f64) -> Au { + from_px((pt / 72f64 * 96f64) as int) +} + +// assumes 72 points per inch, and 96 px per inch +pub fn to_pt(au: Au) -> f64 { + let Au(a) = au; + (a as f64) / 60f64 * 72f64 / 96f64 +} + +/// Returns true if the rect contains the given point. Points on the top or left sides of the rect +/// are considered inside the rectangle, while points on the right or bottom sides of the rect are +/// not considered inside the rectangle. +pub fn rect_contains_point<T:PartialOrd + Add<T,T>>(rect: Rect<T>, point: Point2D<T>) -> bool { + point.x >= rect.origin.x && point.x < rect.origin.x + rect.size.width && + point.y >= rect.origin.y && point.y < rect.origin.y + rect.size.height +} + +/// A helper function to convert a rect of `f32` pixels to a rect of app units. +pub fn f32_rect_to_au_rect(rect: Rect<f32>) -> Rect<Au> { + Rect(Point2D(Au::from_frac32_px(rect.origin.x), Au::from_frac32_px(rect.origin.y)), + Size2D(Au::from_frac32_px(rect.size.width), Au::from_frac32_px(rect.size.height))) +} + diff --git a/components/util/lib.rs b/components/util/lib.rs new file mode 100644 index 00000000000..d05efb735b6 --- /dev/null +++ b/components/util/lib.rs @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![feature(macro_rules,unsafe_destructor)] + +#![feature(phase)] +#[phase(plugin, link)] +extern crate log; + +extern crate debug; +extern crate alloc; +extern crate azure; +extern crate collections; +extern crate geom; +extern crate getopts; +extern crate layers; +extern crate libc; +extern crate native; +extern crate rand; +extern crate rustrt; +extern crate serialize; +extern crate sync; +#[cfg(target_os="macos")] +extern crate task_info; +extern crate std_time = "time"; +extern crate string_cache; + +pub mod atom; +pub mod cache; +pub mod debug_utils; +pub mod geometry; +pub mod logical_geometry; +pub mod memory; +pub mod namespace; +pub mod opts; +pub mod range; +pub mod smallvec; +pub mod sort; +pub mod str; +pub mod task; +pub mod time; +pub mod vec; +pub mod workqueue; diff --git a/components/util/logical_geometry.rs b/components/util/logical_geometry.rs new file mode 100644 index 00000000000..a16dd6a5c8d --- /dev/null +++ b/components/util/logical_geometry.rs @@ -0,0 +1,1023 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// Geometry in flow-relative space. + +use geom::{Size2D, Point2D, SideOffsets2D, Rect}; +use std::cmp::{min, max}; +use std::fmt::{Show, Formatter, FormatError}; +use std::num::Zero; + +bitflags!( + #[deriving(Encodable)] + flags WritingMode: u8 { + static FlagRTL = 1 << 0, + static FlagVertical = 1 << 1, + static FlagVerticalLR = 1 << 2, + static FlagSidewaysLeft = 1 << 3 + } +) + +impl WritingMode { + #[inline] + pub fn is_vertical(&self) -> bool { + self.intersects(FlagVertical) + } + + /// Asuming .is_vertical(), does the block direction go left to right? + #[inline] + pub fn is_vertical_lr(&self) -> bool { + self.intersects(FlagVerticalLR) + } + + /// Asuming .is_vertical(), does the inline direction go top to bottom? + #[inline] + pub fn is_inline_tb(&self) -> bool { + !(self.intersects(FlagSidewaysLeft) ^ self.intersects(FlagRTL)) + } + + #[inline] + pub fn is_bidi_ltr(&self) -> bool { + !self.intersects(FlagRTL) + } + + #[inline] + pub fn is_sideways_left(&self) -> bool { + self.intersects(FlagSidewaysLeft) + } +} + +impl Show for WritingMode { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + if self.is_vertical() { + try!(write!(formatter, "V")); + if self.is_vertical_lr() { + try!(write!(formatter, " LR")); + } else { + try!(write!(formatter, " RL")); + } + if self.intersects(FlagSidewaysLeft) { + try!(write!(formatter, " SidewaysL")); + } + } else { + try!(write!(formatter, "H")); + } + if self.is_bidi_ltr() { + write!(formatter, " LTR") + } else { + write!(formatter, " RTL") + } + } +} + + +/// Wherever logical geometry is used, the writing mode is known based on context: +/// every method takes a `mode` parameter. +/// However, this context is easy to get wrong. +/// In debug builds only, logical geometry objects store their writing mode +/// (in addition to taking it as a parameter to methods) and check it. +/// In non-debug builds, make this storage zero-size and the checks no-ops. +#[cfg(ndebug)] +#[deriving(Encodable, PartialEq, Eq, Clone)] +struct DebugWritingMode; + +#[cfg(not(ndebug))] +#[deriving(Encodable, PartialEq, Eq, Clone)] +struct DebugWritingMode { + mode: WritingMode +} + +#[cfg(ndebug)] +impl DebugWritingMode { + #[inline] + fn check(&self, _other: WritingMode) {} + + #[inline] + fn check_debug(&self, _other: DebugWritingMode) {} + + #[inline] + fn new(_mode: WritingMode) -> DebugWritingMode { + DebugWritingMode + } +} + +#[cfg(not(ndebug))] +impl DebugWritingMode { + #[inline] + fn check(&self, other: WritingMode) { + assert!(self.mode == other) + } + + #[inline] + fn check_debug(&self, other: DebugWritingMode) { + assert!(self.mode == other.mode) + } + + #[inline] + fn new(mode: WritingMode) -> DebugWritingMode { + DebugWritingMode { mode: mode } + } +} + +impl Show for DebugWritingMode { + #[cfg(ndebug)] + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + write!(formatter, "?") + } + + #[cfg(not(ndebug))] + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + self.mode.fmt(formatter) + } +} + + +/// A 2D size in flow-relative dimensions +#[deriving(Encodable, PartialEq, Eq, Clone)] +pub struct LogicalSize<T> { + pub inline: T, // inline-size, a.k.a. logical width, a.k.a. measure + pub block: T, // block-size, a.k.a. logical height, a.k.a. extent + debug_writing_mode: DebugWritingMode, +} + +impl<T: Show> Show for LogicalSize<T> { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + write!(formatter, "LogicalSize[{}, {}, {}]", + self.debug_writing_mode, self.inline, self.block) + } +} + +// Can not implement the Zero trait: its zero() method does not have the `mode` parameter. +impl<T: Zero> LogicalSize<T> { + #[inline] + pub fn zero(mode: WritingMode) -> LogicalSize<T> { + LogicalSize { + inline: Zero::zero(), + block: Zero::zero(), + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.inline.is_zero() && self.block.is_zero() + } +} + +impl<T: Copy> LogicalSize<T> { + #[inline] + pub fn new(mode: WritingMode, inline: T, block: T) -> LogicalSize<T> { + LogicalSize { + inline: inline, + block: block, + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn from_physical(mode: WritingMode, size: Size2D<T>) -> LogicalSize<T> { + if mode.is_vertical() { + LogicalSize::new(mode, size.height, size.width) + } else { + LogicalSize::new(mode, size.width, size.height) + } + } + + #[inline] + pub fn width(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.block + } else { + self.inline + } + } + + #[inline] + pub fn set_width(&mut self, mode: WritingMode, width: T) { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.block = width + } else { + self.inline = width + } + } + + #[inline] + pub fn height(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.inline + } else { + self.block + } + } + + #[inline] + pub fn set_height(&mut self, mode: WritingMode, height: T) { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.inline = height + } else { + self.block = height + } + } + + #[inline] + pub fn to_physical(&self, mode: WritingMode) -> Size2D<T> { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + Size2D { width: self.block, height: self.inline } + } else { + Size2D { width: self.inline, height: self.block } + } + } + + #[inline] + pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalSize<T> { + if mode_from == mode_to { + self.debug_writing_mode.check(mode_from); + *self + } else { + LogicalSize::from_physical(mode_to, self.to_physical(mode_from)) + } + } +} + +impl<T: Add<T, T>> Add<LogicalSize<T>, LogicalSize<T>> for LogicalSize<T> { + #[inline] + fn add(&self, other: &LogicalSize<T>) -> LogicalSize<T> { + self.debug_writing_mode.check_debug(other.debug_writing_mode); + LogicalSize { + debug_writing_mode: self.debug_writing_mode, + inline: self.inline + other.inline, + block: self.block + other.block, + } + } +} + +impl<T: Sub<T, T>> Sub<LogicalSize<T>, LogicalSize<T>> for LogicalSize<T> { + #[inline] + fn sub(&self, other: &LogicalSize<T>) -> LogicalSize<T> { + self.debug_writing_mode.check_debug(other.debug_writing_mode); + LogicalSize { + debug_writing_mode: self.debug_writing_mode, + inline: self.inline - other.inline, + block: self.block - other.block, + } + } +} + + +/// A 2D point in flow-relative dimensions +#[deriving(PartialEq, Encodable, Eq, Clone)] +pub struct LogicalPoint<T> { + pub i: T, /// inline-axis coordinate + pub b: T, /// block-axis coordinate + debug_writing_mode: DebugWritingMode, +} + +impl<T: Show> Show for LogicalPoint<T> { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + write!(formatter, "LogicalPoint[{}, {}, {}]", + self.debug_writing_mode, self.i, self.b) + } +} + +// Can not implement the Zero trait: its zero() method does not have the `mode` parameter. +impl<T: Zero> LogicalPoint<T> { + #[inline] + pub fn zero(mode: WritingMode) -> LogicalPoint<T> { + LogicalPoint { + i: Zero::zero(), + b: Zero::zero(), + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.i.is_zero() && self.b.is_zero() + } +} + +impl<T: Copy> LogicalPoint<T> { + #[inline] + pub fn new(mode: WritingMode, i: T, b: T) -> LogicalPoint<T> { + LogicalPoint { + i: i, + b: b, + debug_writing_mode: DebugWritingMode::new(mode), + } + } +} + +impl<T: Copy + Sub<T, T>> LogicalPoint<T> { + #[inline] + pub fn from_physical(mode: WritingMode, point: Point2D<T>, container_size: Size2D<T>) + -> LogicalPoint<T> { + if mode.is_vertical() { + LogicalPoint { + i: if mode.is_inline_tb() { point.y } else { container_size.height - point.y }, + b: if mode.is_vertical_lr() { point.x } else { container_size.width - point.x }, + debug_writing_mode: DebugWritingMode::new(mode), + } + } else { + LogicalPoint { + i: if mode.is_bidi_ltr() { point.x } else { container_size.width - point.x }, + b: point.y, + debug_writing_mode: DebugWritingMode::new(mode), + } + } + } + + #[inline] + pub fn x(&self, mode: WritingMode, container_size: Size2D<T>) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_vertical_lr() { self.b } else { container_size.width - self.b } + } else { + if mode.is_bidi_ltr() { self.i } else { container_size.width - self.i } + } + } + + #[inline] + pub fn set_x(&mut self, mode: WritingMode, x: T, container_size: Size2D<T>) { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.b = if mode.is_vertical_lr() { x } else { container_size.width - x } + } else { + self.i = if mode.is_bidi_ltr() { x } else { container_size.width - x } + } + } + + #[inline] + pub fn y(&self, mode: WritingMode, container_size: Size2D<T>) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_inline_tb() { self.i } else { container_size.height - self.i } + } else { + self.b + } + } + + #[inline] + pub fn set_y(&mut self, mode: WritingMode, y: T, container_size: Size2D<T>) { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.i = if mode.is_inline_tb() { y } else { container_size.height - y } + } else { + self.b = y + } + } + + #[inline] + pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Point2D<T> { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + Point2D { + x: if mode.is_vertical_lr() { self.b } else { container_size.width - self.b }, + y: if mode.is_inline_tb() { self.i } else { container_size.height - self.i } + } + } else { + Point2D { + x: if mode.is_bidi_ltr() { self.i } else { container_size.width - self.i }, + y: self.b + } + } + } + + #[inline] + pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode, container_size: Size2D<T>) + -> LogicalPoint<T> { + if mode_from == mode_to { + self.debug_writing_mode.check(mode_from); + *self + } else { + LogicalPoint::from_physical( + mode_to, self.to_physical(mode_from, container_size), container_size) + } + } +} + +impl<T: Add<T,T>> LogicalPoint<T> { + /// This doesn’t really makes sense, + /// but happens when dealing with mutliple origins. + #[inline] + pub fn add_point(&self, other: &LogicalPoint<T>) -> LogicalPoint<T> { + self.debug_writing_mode.check_debug(other.debug_writing_mode); + LogicalPoint { + debug_writing_mode: self.debug_writing_mode, + i: self.i + other.i, + b: self.b + other.b, + } + } +} + +impl<T: Add<T,T>> Add<LogicalSize<T>, LogicalPoint<T>> for LogicalPoint<T> { + #[inline] + fn add(&self, other: &LogicalSize<T>) -> LogicalPoint<T> { + self.debug_writing_mode.check_debug(other.debug_writing_mode); + LogicalPoint { + debug_writing_mode: self.debug_writing_mode, + i: self.i + other.inline, + b: self.b + other.block, + } + } +} + +impl<T: Sub<T,T>> Sub<LogicalSize<T>, LogicalPoint<T>> for LogicalPoint<T> { + #[inline] + fn sub(&self, other: &LogicalSize<T>) -> LogicalPoint<T> { + self.debug_writing_mode.check_debug(other.debug_writing_mode); + LogicalPoint { + debug_writing_mode: self.debug_writing_mode, + i: self.i - other.inline, + b: self.b - other.block, + } + } +} + + +/// A "margin" in flow-relative dimensions +/// Represents the four sides of the margins, borders, or padding of a CSS box, +/// or a combination of those. +/// A positive "margin" can be added to a rectangle to obtain a bigger rectangle. +#[deriving(Encodable, PartialEq, Eq, Clone)] +pub struct LogicalMargin<T> { + pub block_start: T, + pub inline_end: T, + pub block_end: T, + pub inline_start: T, + debug_writing_mode: DebugWritingMode, +} + +impl<T: Show> Show for LogicalMargin<T> { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + write!(formatter, + "LogicalMargin[{}, block_start: {}, inline_end: {}, \ + block_end: {}, inline_start: {}]", + self.debug_writing_mode, self.block_start, + self.inline_end, self.block_end, self.inline_start) + } +} + +impl<T: Zero> LogicalMargin<T> { + #[inline] + pub fn zero(mode: WritingMode) -> LogicalMargin<T> { + LogicalMargin { + block_start: Zero::zero(), + inline_end: Zero::zero(), + block_end: Zero::zero(), + inline_start: Zero::zero(), + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.block_start.is_zero() && + self.inline_end.is_zero() && + self.block_end.is_zero() && + self.inline_start.is_zero() + } +} + +impl<T: Copy> LogicalMargin<T> { + #[inline] + pub fn new(mode: WritingMode, block_start: T, inline_end: T, block_end: T, inline_start: T) + -> LogicalMargin<T> { + LogicalMargin { + block_start: block_start, + inline_end: inline_end, + block_end: block_end, + inline_start: inline_start, + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn new_all_same(mode: WritingMode, value: T) -> LogicalMargin<T> { + LogicalMargin::new(mode, value, value, value, value) + } + + #[inline] + pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D<T>) -> LogicalMargin<T> { + let block_start; + let inline_end; + let block_end; + let inline_start; + if mode.is_vertical() { + if mode.is_vertical_lr() { + block_start = offsets.left; + block_end = offsets.right; + } else { + block_start = offsets.right; + block_end = offsets.left; + } + if mode.is_inline_tb() { + inline_start = offsets.top; + inline_end = offsets.bottom; + } else { + inline_start = offsets.bottom; + inline_end = offsets.top; + } + } else { + block_start = offsets.top; + block_end = offsets.bottom; + if mode.is_bidi_ltr() { + inline_start = offsets.left; + inline_end = offsets.right; + } else { + inline_start = offsets.right; + inline_end = offsets.left; + } + } + LogicalMargin::new(mode, block_start, inline_end, block_end, inline_start) + } + + #[inline] + pub fn top(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_inline_tb() { self.inline_start } else { self.inline_end } + } else { + self.block_start + } + } + + #[inline] + pub fn set_top(&mut self, mode: WritingMode, top: T) { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_inline_tb() { self.inline_start = top } else { self.inline_end = top } + } else { + self.block_start = top + } + } + + #[inline] + pub fn right(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_vertical_lr() { self.block_end } else { self.block_start } + } else { + if mode.is_bidi_ltr() { self.inline_end } else { self.inline_start } + } + } + + #[inline] + pub fn set_right(&mut self, mode: WritingMode, right: T) { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_vertical_lr() { self.block_end = right } else { self.block_start = right } + } else { + if mode.is_bidi_ltr() { self.inline_end = right } else { self.inline_start = right } + } + } + + #[inline] + pub fn bottom(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_inline_tb() { self.inline_end } else { self.inline_start } + } else { + self.block_end + } + } + + #[inline] + pub fn set_bottom(&mut self, mode: WritingMode, bottom: T) { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_inline_tb() { self.inline_end = bottom } else { self.inline_start = bottom } + } else { + self.block_end = bottom + } + } + + #[inline] + pub fn left(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_vertical_lr() { self.block_start } else { self.block_end } + } else { + if mode.is_bidi_ltr() { self.inline_start } else { self.inline_end } + } + } + + #[inline] + pub fn set_left(&mut self, mode: WritingMode, left: T) { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_vertical_lr() { self.block_start = left } else { self.block_end = left } + } else { + if mode.is_bidi_ltr() { self.inline_start = left } else { self.inline_end = left } + } + } + + #[inline] + pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> { + self.debug_writing_mode.check(mode); + let top; + let right; + let bottom; + let left; + if mode.is_vertical() { + if mode.is_vertical_lr() { + left = self.block_start; + right = self.block_end; + } else { + right = self.block_start; + left = self.block_end; + } + if mode.is_inline_tb() { + top = self.inline_start; + bottom = self.inline_end; + } else { + bottom = self.inline_start; + top = self.inline_end; + } + } else { + top = self.block_start; + bottom = self.block_end; + if mode.is_bidi_ltr() { + left = self.inline_start; + right = self.inline_end; + } else { + right = self.inline_start; + left = self.inline_end; + } + } + SideOffsets2D::new(top, right, bottom, left) + } + + #[inline] + pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> { + if mode_from == mode_to { + self.debug_writing_mode.check(mode_from); + *self + } else { + LogicalMargin::from_physical(mode_to, self.to_physical(mode_from)) + } + } +} + +impl<T: Add<T, T>> LogicalMargin<T> { + #[inline] + pub fn inline_start_end(&self) -> T { + self.inline_start + self.inline_end + } + + #[inline] + pub fn block_start_end(&self) -> T { + self.block_start + self.block_end + } + + #[inline] + pub fn top_bottom(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.inline_start_end() + } else { + self.block_start_end() + } + } + + #[inline] + pub fn left_right(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.block_start_end() + } else { + self.inline_start_end() + } + } +} + +impl<T: Add<T, T>> Add<LogicalMargin<T>, LogicalMargin<T>> for LogicalMargin<T> { + #[inline] + fn add(&self, other: &LogicalMargin<T>) -> LogicalMargin<T> { + self.debug_writing_mode.check_debug(other.debug_writing_mode); + LogicalMargin { + debug_writing_mode: self.debug_writing_mode, + block_start: self.block_start + other.block_start, + inline_end: self.inline_end + other.inline_end, + block_end: self.block_end + other.block_end, + inline_start: self.inline_start + other.inline_start, + } + } +} + +impl<T: Sub<T, T>> Sub<LogicalMargin<T>, LogicalMargin<T>> for LogicalMargin<T> { + #[inline] + fn sub(&self, other: &LogicalMargin<T>) -> LogicalMargin<T> { + self.debug_writing_mode.check_debug(other.debug_writing_mode); + LogicalMargin { + debug_writing_mode: self.debug_writing_mode, + block_start: self.block_start - other.block_start, + inline_end: self.inline_end - other.inline_end, + block_end: self.block_end - other.block_end, + inline_start: self.inline_start - other.inline_start, + } + } +} + + +/// A rectangle in flow-relative dimensions +#[deriving(Encodable, PartialEq, Eq, Clone)] +pub struct LogicalRect<T> { + pub start: LogicalPoint<T>, + pub size: LogicalSize<T>, + debug_writing_mode: DebugWritingMode, +} + +impl<T: Show> Show for LogicalRect<T> { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + write!(formatter, + "LogicalRect[{}, inline_start: {}, block_start: {}, \ + inline: {}, block: {}]", + self.debug_writing_mode, self.start.i, self.start.b, + self.size.inline, self.size.block) + } +} + +impl<T: Zero> LogicalRect<T> { + #[inline] + pub fn zero(mode: WritingMode) -> LogicalRect<T> { + LogicalRect { + start: LogicalPoint::zero(mode), + size: LogicalSize::zero(mode), + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.start.is_zero() && self.size.is_zero() + } +} + +impl<T: Copy> LogicalRect<T> { + #[inline] + pub fn new(mode: WritingMode, inline_start: T, block_start: T, inline: T, block: T) + -> LogicalRect<T> { + LogicalRect { + start: LogicalPoint::new(mode, inline_start, block_start), + size: LogicalSize::new(mode, inline, block), + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn from_point_size(mode: WritingMode, start: LogicalPoint<T>, size: LogicalSize<T>) + -> LogicalRect<T> { + start.debug_writing_mode.check(mode); + size.debug_writing_mode.check(mode); + LogicalRect { + start: start, + size: size, + debug_writing_mode: DebugWritingMode::new(mode), + } + } +} + +impl<T: Copy + Add<T, T> + Sub<T, T>> LogicalRect<T> { + #[inline] + pub fn from_physical(mode: WritingMode, rect: Rect<T>, container_size: Size2D<T>) + -> LogicalRect<T> { + let inline_start; + let block_start; + let inline; + let block; + if mode.is_vertical() { + inline = rect.size.height; + block = rect.size.width; + if mode.is_vertical_lr() { + block_start = rect.origin.x; + } else { + block_start = container_size.width - (rect.origin.x + rect.size.width); + } + if mode.is_inline_tb() { + inline_start = rect.origin.y; + } else { + inline_start = container_size.height - (rect.origin.y + rect.size.height); + } + } else { + inline = rect.size.width; + block = rect.size.height; + block_start = rect.origin.y; + if mode.is_bidi_ltr() { + inline_start = rect.origin.x; + } else { + inline_start = container_size.width - (rect.origin.x + rect.size.width); + } + } + LogicalRect { + start: LogicalPoint::new(mode, inline_start, block_start), + size: LogicalSize::new(mode, inline, block), + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn inline_end(&self) -> T { + self.start.i + self.size.inline + } + + #[inline] + pub fn block_end(&self) -> T { + self.start.b + self.size.block + } + + #[inline] + pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Rect<T> { + self.debug_writing_mode.check(mode); + let x; + let y; + let width; + let height; + if mode.is_vertical() { + width = self.size.block; + height = self.size.inline; + if mode.is_vertical_lr() { + x = self.start.b; + } else { + x = container_size.width - self.block_end(); + } + if mode.is_inline_tb() { + y = self.start.i; + } else { + y = container_size.height - self.inline_end(); + } + } else { + width = self.size.inline; + height = self.size.block; + y = self.start.b; + if mode.is_bidi_ltr() { + x = self.start.i; + } else { + x = container_size.width - self.inline_end(); + } + } + Rect { + origin: Point2D { x: x, y: y }, + size: Size2D { width: width, height: height }, + } + } + + #[inline] + pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode, container_size: Size2D<T>) + -> LogicalRect<T> { + if mode_from == mode_to { + self.debug_writing_mode.check(mode_from); + *self + } else { + LogicalRect::from_physical( + mode_to, self.to_physical(mode_from, container_size), container_size) + } + } + + pub fn translate(&self, offset: &LogicalPoint<T>) -> LogicalRect<T> { + LogicalRect { + start: self.start + LogicalSize { + inline: offset.i, + block: offset.b, + debug_writing_mode: offset.debug_writing_mode, + }, + size: self.size, + debug_writing_mode: self.debug_writing_mode, + } + } +} + +impl<T: Copy + Ord + Add<T, T> + Sub<T, T>> LogicalRect<T> { + #[inline] + pub fn union(&self, other: &LogicalRect<T>) -> LogicalRect<T> { + self.debug_writing_mode.check_debug(other.debug_writing_mode); + + let inline_start = min(self.start.i, other.start.i); + let block_start = min(self.start.b, other.start.b); + LogicalRect { + start: LogicalPoint { + i: inline_start, + b: block_start, + debug_writing_mode: self.debug_writing_mode, + }, + size: LogicalSize { + inline: max(self.inline_end(), other.inline_end()) - inline_start, + block: max(self.block_end(), other.block_end()) - block_start, + debug_writing_mode: self.debug_writing_mode, + }, + debug_writing_mode: self.debug_writing_mode, + } + } +} + +impl<T: Add<T, T> + Sub<T, T>> Add<LogicalMargin<T>, LogicalRect<T>> for LogicalRect<T> { + #[inline] + fn add(&self, other: &LogicalMargin<T>) -> LogicalRect<T> { + self.debug_writing_mode.check_debug(other.debug_writing_mode); + LogicalRect { + start: LogicalPoint { + // Growing a rectangle on the start side means pushing its + // start point on the negative direction. + i: self.start.i - other.inline_start, + b: self.start.b - other.block_start, + debug_writing_mode: self.debug_writing_mode, + }, + size: LogicalSize { + inline: self.size.inline + other.inline_start_end(), + block: self.size.block + other.block_start_end(), + debug_writing_mode: self.debug_writing_mode, + }, + debug_writing_mode: self.debug_writing_mode, + } + } +} + + +impl<T: Add<T, T> + Sub<T, T>> Sub<LogicalMargin<T>, LogicalRect<T>> for LogicalRect<T> { + #[inline] + fn sub(&self, other: &LogicalMargin<T>) -> LogicalRect<T> { + self.debug_writing_mode.check_debug(other.debug_writing_mode); + LogicalRect { + start: LogicalPoint { + // Shrinking a rectangle on the start side means pushing its + // start point on the positive direction. + i: self.start.i + other.inline_start, + b: self.start.b + other.block_start, + debug_writing_mode: self.debug_writing_mode, + }, + size: LogicalSize { + inline: self.size.inline - other.inline_start_end(), + block: self.size.block - other.block_start_end(), + debug_writing_mode: self.debug_writing_mode, + }, + debug_writing_mode: self.debug_writing_mode, + } + } +} + +#[cfg(test)] +fn modes() -> [WritingMode, ..10] { + [ + WritingMode::empty(), + FlagVertical, + FlagVertical | FlagVerticalLR, + FlagVertical | FlagVerticalLR | FlagSidewaysLeft, + FlagVertical | FlagSidewaysLeft, + FlagRTL, + FlagVertical | FlagRTL, + FlagVertical | FlagVerticalLR | FlagRTL, + FlagVertical | FlagVerticalLR | FlagSidewaysLeft | FlagRTL, + FlagVertical | FlagSidewaysLeft | FlagRTL, + ] +} + +#[test] +fn test_size_round_trip() { + let physical = Size2D(1u32, 2u32); + for &mode in modes().iter() { + let logical = LogicalSize::from_physical(mode, physical); + assert!(logical.to_physical(mode) == physical); + assert!(logical.width(mode) == 1); + assert!(logical.height(mode) == 2); + } +} + +#[test] +fn test_point_round_trip() { + let physical = Point2D(1u32, 2u32); + let container = Size2D(100, 200); + for &mode in modes().iter() { + let logical = LogicalPoint::from_physical(mode, physical, container); + assert!(logical.to_physical(mode, container) == physical); + assert!(logical.x(mode, container) == 1); + assert!(logical.y(mode, container) == 2); + } +} + +#[test] +fn test_margin_round_trip() { + let physical = SideOffsets2D::new(1u32, 2u32, 3u32, 4u32); + for &mode in modes().iter() { + let logical = LogicalMargin::from_physical(mode, physical); + assert!(logical.to_physical(mode) == physical); + assert!(logical.top(mode) == 1); + assert!(logical.right(mode) == 2); + assert!(logical.bottom(mode) == 3); + assert!(logical.left(mode) == 4); + } +} + +#[test] +fn test_rect_round_trip() { + let physical = Rect(Point2D(1u32, 2u32), Size2D(3u32, 4u32)); + let container = Size2D(100, 200); + for &mode in modes().iter() { + let logical = LogicalRect::from_physical(mode, physical, container); + assert!(logical.to_physical(mode, container) == physical); + } +} diff --git a/components/util/memory.rs b/components/util/memory.rs new file mode 100644 index 00000000000..25aa13c8316 --- /dev/null +++ b/components/util/memory.rs @@ -0,0 +1,209 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Memory profiling functions. + +use libc::{c_char,c_int,c_void,size_t}; +use std::io::timer::sleep; +#[cfg(target_os="linux")] +use std::io::File; +use std::mem::size_of; +#[cfg(target_os="linux")] +use std::os::page_size; +use std::ptr::mut_null; +use task::spawn_named; +#[cfg(target_os="macos")] +use task_info::task_basic_info::{virtual_size,resident_size}; + +pub struct MemoryProfilerChan(pub Sender<MemoryProfilerMsg>); + +impl MemoryProfilerChan { + pub fn send(&self, msg: MemoryProfilerMsg) { + let MemoryProfilerChan(ref c) = *self; + c.send(msg); + } +} + +pub enum MemoryProfilerMsg { + /// Message used to force print the memory profiling metrics. + PrintMsg, + /// Tells the memory profiler to shut down. + ExitMsg, +} + +pub struct MemoryProfiler { + pub port: Receiver<MemoryProfilerMsg>, +} + +impl MemoryProfiler { + pub fn create(period: Option<f64>) -> MemoryProfilerChan { + let (chan, port) = channel(); + match period { + Some(period) => { + let period = (period * 1000f64) as u64; + let chan = chan.clone(); + spawn_named("Memory profiler timer", proc() { + loop { + sleep(period); + if chan.send_opt(PrintMsg).is_err() { + break; + } + } + }); + // Spawn the memory profiler. + spawn_named("Memory profiler", proc() { + let memory_profiler = MemoryProfiler::new(port); + memory_profiler.start(); + }); + } + None => { + // No-op to handle messages when the memory profiler is + // inactive. + spawn_named("Memory profiler", proc() { + loop { + match port.recv_opt() { + Err(_) | Ok(ExitMsg) => break, + _ => {} + } + } + }); + } + } + + MemoryProfilerChan(chan) + } + + pub fn new(port: Receiver<MemoryProfilerMsg>) -> MemoryProfiler { + MemoryProfiler { + port: port + } + } + + pub fn start(&self) { + loop { + match self.port.recv_opt() { + Ok(msg) => { + if !self.handle_msg(msg) { + break + } + } + _ => break + } + } + } + + fn handle_msg(&self, msg: MemoryProfilerMsg) -> bool { + match msg { + PrintMsg => { + self.handle_print_msg(); + true + }, + ExitMsg => false + } + } + + fn print_measurement(path: &str, nbytes: Option<u64>) { + match nbytes { + Some(nbytes) => { + let mebi = 1024f64 * 1024f64; + println!("{:16s}: {:12.2f}", path, (nbytes as f64) / mebi); + } + None => { + println!("{:16s}: {:>12s}", path, "???"); + } + } + } + + fn handle_print_msg(&self) { + println!("{:16s}: {:12s}", "_category_", "_size (MiB)_"); + + // Virtual and physical memory usage, as reported by the OS. + MemoryProfiler::print_measurement("vsize", get_vsize()); + MemoryProfiler::print_measurement("resident", get_resident()); + + // The descriptions of the jemalloc measurements are taken directly + // from the jemalloc documentation. + + // Total number of bytes allocated by the application. + MemoryProfiler::print_measurement("heap-allocated", get_jemalloc_stat("stats.allocated")); + + // Total number of bytes in active pages allocated by the application. + // This is a multiple of the page size, and greater than or equal to + // |stats.allocated|. + MemoryProfiler::print_measurement("heap-active", get_jemalloc_stat("stats.active")); + + // Total number of bytes in chunks mapped on behalf of the application. + // This is a multiple of the chunk size, and is at least as large as + // |stats.active|. This does not include inactive chunks. + MemoryProfiler::print_measurement("heap-mapped", get_jemalloc_stat("stats.mapped")); + + println!(""); + } +} + +extern { + fn je_mallctl(name: *const c_char, oldp: *mut c_void, oldlenp: *mut size_t, + newp: *mut c_void, newlen: size_t) -> c_int; +} + +fn get_jemalloc_stat(name: &'static str) -> Option<u64> { + let mut old: size_t = 0; + let c_name = name.to_c_str(); + let oldp = &mut old as *mut _ as *mut c_void; + let mut oldlen = size_of::<size_t>() as size_t; + let rv: c_int; + unsafe { + rv = je_mallctl(c_name.unwrap(), oldp, &mut oldlen, mut_null(), 0); + } + if rv == 0 { Some(old as u64) } else { None } +} + +// Like std::macros::try!, but for Option<>. +macro_rules! option_try( + ($e:expr) => (match $e { Some(e) => e, None => return None }) +) + +#[cfg(target_os="linux")] +fn get_proc_self_statm_field(field: uint) -> Option<u64> { + let mut f = File::open(&Path::new("/proc/self/statm")); + match f.read_to_string() { + Ok(contents) => { + let s = option_try!(contents.as_slice().words().nth(field)); + let npages: u64 = option_try!(from_str(s)); + Some(npages * (page_size() as u64)) + } + Err(_) => None + } +} + +#[cfg(target_os="linux")] +fn get_vsize() -> Option<u64> { + get_proc_self_statm_field(0) +} + +#[cfg(target_os="linux")] +fn get_resident() -> Option<u64> { + get_proc_self_statm_field(1) +} + +#[cfg(target_os="macos")] +fn get_vsize() -> Option<u64> { + virtual_size() +} + +#[cfg(target_os="macos")] +fn get_resident() -> Option<u64> { + resident_size() +} + +#[cfg(not(target_os="linux"), not(target_os = "macos"))] +fn get_vsize() -> Option<u64> { + None +} + +#[cfg(not(target_os="linux"), not(target_os = "macos"))] +fn get_resident() -> Option<u64> { + None +} + diff --git a/components/util/namespace.rs b/components/util/namespace.rs new file mode 100644 index 00000000000..1f32ae83017 --- /dev/null +++ b/components/util/namespace.rs @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[deriving(PartialEq, Clone, Encodable)] +pub enum Namespace { + Null, + HTML, + XML, + XMLNS, + XLink, + SVG, + MathML, + Other(String) +} + +impl Namespace { + /// Empty string for "no namespace" + pub fn from_str(url: &str) -> Namespace { + match url { + "http://www.w3.org/1999/xhtml" => HTML, + "http://www.w3.org/XML/1998/namespace" => XML, + "http://www.w3.org/2000/xmlns/" => XMLNS, + "http://www.w3.org/1999/xlink" => XLink, + "http://www.w3.org/2000/svg" => SVG, + "http://www.w3.org/1998/Math/MathML" => MathML, + "" => Null, + ns => Other(ns.to_string()) + } + } + pub fn to_str<'a>(&'a self) -> &'a str { + match *self { + Null => "", + HTML => "http://www.w3.org/1999/xhtml", + XML => "http://www.w3.org/XML/1998/namespace", + XMLNS => "http://www.w3.org/2000/xmlns/", + XLink => "http://www.w3.org/1999/xlink", + SVG => "http://www.w3.org/2000/svg", + MathML => "http://www.w3.org/1998/Math/MathML", + Other(ref x) => x.as_slice() + } + } +} diff --git a/components/util/opts.rs b/components/util/opts.rs new file mode 100644 index 00000000000..40da68632d8 --- /dev/null +++ b/components/util/opts.rs @@ -0,0 +1,235 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Configuration options for a single run of the servo application. Created +//! from command line arguments. + +use geometry::ScreenPx; + +use azure::azure_hl::{BackendType, CairoBackend, CoreGraphicsBackend}; +use azure::azure_hl::{CoreGraphicsAcceleratedBackend, Direct2DBackend, SkiaBackend}; +use geom::scale_factor::ScaleFactor; +use layers::geometry::DevicePixel; +use getopts; +use std::cmp; +use std::io; +use std::os; +use std::rt; + +/// Global flags for Servo, currently set on the command line. +#[deriving(Clone)] +pub struct Opts { + /// The initial URLs to load. + pub urls: Vec<String>, + + /// The rendering backend to use (`-r`). + pub render_backend: BackendType, + + /// How many threads to use for CPU rendering (`-t`). + /// + /// FIXME(pcwalton): This is not currently used. All rendering is sequential. + pub n_render_threads: uint, + + /// True to use CPU painting, false to use GPU painting via Skia-GL (`-c`). Note that + /// compositing is always done on the GPU. + pub cpu_painting: bool, + + /// The maximum size of each tile in pixels (`-s`). + pub tile_size: uint, + + /// The ratio of device pixels per px at the default scale. If unspecified, will use the + /// platform default setting. + pub device_pixels_per_px: Option<ScaleFactor<ScreenPx, DevicePixel, f32>>, + + /// `None` to disable the time profiler or `Some` with an interval in seconds to enable it and + /// cause it to produce output on that interval (`-p`). + pub time_profiler_period: Option<f64>, + + /// `None` to disable the memory profiler or `Some` with an interval in seconds to enable it + /// and cause it to produce output on that interval (`-m`). + pub memory_profiler_period: Option<f64>, + + /// Enable experimental web features (`-e`). + pub enable_experimental: bool, + + /// The number of threads to use for layout (`-y`). Defaults to 1, which results in a recursive + /// sequential algorithm. + pub layout_threads: uint, + + /// True to exit after the page load (`-x`). + pub exit_after_load: bool, + + pub output_file: Option<String>, + pub headless: bool, + pub hard_fail: bool, + + /// True if we should bubble intrinsic widths sequentially (`-b`). If this is true, then + /// intrinsic widths are computed as a separate pass instead of during flow construction. You + /// may wish to turn this flag on in order to benchmark style recalculation against other + /// browser engines. + pub bubble_inline_sizes_separately: bool, + + /// True if we should show borders on all layers and tiles for + /// debugging purposes (`--show-debug-borders`). + pub show_debug_borders: bool, + + /// If set with --disable-text-aa, disable antialiasing on fonts. This is primarily useful for reftests + /// where pixel perfect results are required when using fonts such as the Ahem + /// font for layout tests. + pub enable_text_antialiasing: bool, + + /// True if each step of layout is traced to an external JSON file + /// for debugging purposes. Settings this implies sequential layout + /// and render. + pub trace_layout: bool, +} + +fn print_usage(app: &str, opts: &[getopts::OptGroup]) { + let message = format!("Usage: {} [ options ... ] [URL]\n\twhere options include", app); + println!("{}", getopts::usage(message.as_slice(), opts)); +} + +fn args_fail(msg: &str) { + io::stderr().write_line(msg).unwrap(); + os::set_exit_status(1); +} + +pub fn from_cmdline_args(args: &[String]) -> Option<Opts> { + let app_name = args[0].to_string(); + let args = args.tail(); + + let opts = vec!( + getopts::optflag("c", "cpu", "CPU rendering"), + getopts::optopt("o", "output", "Output file", "output.png"), + getopts::optopt("r", "rendering", "Rendering backend", "direct2d|core-graphics|core-graphics-accelerated|cairo|skia."), + getopts::optopt("s", "size", "Size of tiles", "512"), + getopts::optopt("", "device-pixel-ratio", "Device pixels per px", ""), + getopts::optflag("e", "experimental", "Enable experimental web features"), + getopts::optopt("t", "threads", "Number of render threads", "1"), + getopts::optflagopt("p", "profile", "Profiler flag and output interval", "10"), + getopts::optflagopt("m", "memory-profile", "Memory profiler flag and output interval", "10"), + getopts::optflag("x", "exit", "Exit after load flag"), + getopts::optopt("y", "layout-threads", "Number of threads to use for layout", "1"), + getopts::optflag("z", "headless", "Headless mode"), + getopts::optflag("f", "hard-fail", "Exit on task failure instead of displaying about:failure"), + getopts::optflag("b", "bubble-widths", "Bubble intrinsic widths separately like other engines"), + getopts::optflag("", "show-debug-borders", "Show debugging borders on layers and tiles."), + getopts::optflag("", "disable-text-aa", "Disable antialiasing for text rendering."), + getopts::optflag("", "trace-layout", "Write layout trace to external file for debugging."), + getopts::optflag("h", "help", "Print this message") + ); + + let opt_match = match getopts::getopts(args, opts.as_slice()) { + Ok(m) => m, + Err(f) => { + args_fail(format!("{}", f).as_slice()); + return None; + } + }; + + if opt_match.opt_present("h") || opt_match.opt_present("help") { + print_usage(app_name.as_slice(), opts.as_slice()); + return None; + }; + + let urls = if opt_match.free.is_empty() { + print_usage(app_name.as_slice(), opts.as_slice()); + args_fail("servo asks that you provide 1 or more URLs"); + return None; + } else { + opt_match.free.clone() + }; + + let render_backend = match opt_match.opt_str("r") { + Some(backend_str) => { + if "direct2d" == backend_str.as_slice() { + Direct2DBackend + } else if "core-graphics" == backend_str.as_slice() { + CoreGraphicsBackend + } else if "core-graphics-accelerated" == backend_str.as_slice() { + CoreGraphicsAcceleratedBackend + } else if "cairo" == backend_str.as_slice() { + CairoBackend + } else if "skia" == backend_str.as_slice() { + SkiaBackend + } else { + fail!("unknown backend type") + } + } + None => SkiaBackend + }; + + let tile_size: uint = match opt_match.opt_str("s") { + Some(tile_size_str) => from_str(tile_size_str.as_slice()).unwrap(), + None => 512, + }; + + let device_pixels_per_px = opt_match.opt_str("device-pixel-ratio").map(|dppx_str| + ScaleFactor(from_str(dppx_str.as_slice()).unwrap()) + ); + + let mut n_render_threads: uint = match opt_match.opt_str("t") { + Some(n_render_threads_str) => from_str(n_render_threads_str.as_slice()).unwrap(), + None => 1, // FIXME: Number of cores. + }; + + // If only the flag is present, default to a 5 second period for both profilers. + let time_profiler_period = opt_match.opt_default("p", "5").map(|period| { + from_str(period.as_slice()).unwrap() + }); + let memory_profiler_period = opt_match.opt_default("m", "5").map(|period| { + from_str(period.as_slice()).unwrap() + }); + + let cpu_painting = opt_match.opt_present("c"); + + let mut layout_threads: uint = match opt_match.opt_str("y") { + Some(layout_threads_str) => from_str(layout_threads_str.as_slice()).unwrap(), + None => cmp::max(rt::default_sched_threads() * 3 / 4, 1), + }; + + let mut bubble_inline_sizes_separately = opt_match.opt_present("b"); + + let trace_layout = opt_match.opt_present("trace-layout"); + if trace_layout { + n_render_threads = 1; + layout_threads = 1; + bubble_inline_sizes_separately = true; + } + + Some(Opts { + urls: urls, + render_backend: render_backend, + n_render_threads: n_render_threads, + cpu_painting: cpu_painting, + tile_size: tile_size, + device_pixels_per_px: device_pixels_per_px, + time_profiler_period: time_profiler_period, + memory_profiler_period: memory_profiler_period, + enable_experimental: opt_match.opt_present("e"), + layout_threads: layout_threads, + exit_after_load: opt_match.opt_present("x"), + output_file: opt_match.opt_str("o"), + headless: opt_match.opt_present("z"), + hard_fail: opt_match.opt_present("f"), + bubble_inline_sizes_separately: bubble_inline_sizes_separately, + show_debug_borders: opt_match.opt_present("show-debug-borders"), + enable_text_antialiasing: !opt_match.opt_present("disable-text-aa"), + trace_layout: trace_layout, + }) +} + +static mut EXPERIMENTAL_ENABLED: bool = false; + +pub fn set_experimental_enabled(new_value: bool) { + unsafe { + EXPERIMENTAL_ENABLED = new_value; + } +} + +pub fn experimental_enabled() -> bool { + unsafe { + EXPERIMENTAL_ENABLED + } +} diff --git a/components/util/range.rs b/components/util/range.rs new file mode 100644 index 00000000000..ffea08e6264 --- /dev/null +++ b/components/util/range.rs @@ -0,0 +1,355 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::cmp::{max, min}; +use std::iter; +use std::fmt; +use std::num; +use std::num::{Bounded, Zero}; + +/// An index type to be used by a `Range` +pub trait RangeIndex: Copy + + Clone + + fmt::Show + + PartialEq + + PartialOrd + + Eq + + Ord + + Add<Self, Self> + + Sub<Self, Self> + + Neg<Self> + + Zero {} + +pub trait IntRangeIndex<T>: RangeIndex + Copy { + fn new(x: T) -> Self; + fn get(self) -> T; +} + +impl RangeIndex for int {} + +impl IntRangeIndex<int> for int { + #[inline] + fn new(x: int) -> int { x } + + #[inline] + fn get(self) -> int { self } +} + +/// Implements a range index type with operator overloads +#[macro_export] +macro_rules! int_range_index { + ($(#[$attr:meta])* struct $Self:ident($T:ty)) => ( + #[deriving(Clone, PartialEq, PartialOrd, Eq, Ord, Show, Zero)] + $(#[$attr])* + pub struct $Self(pub $T); + + impl $Self { + #[inline] + pub fn to_uint(self) -> uint { + self.get() as uint + } + } + + impl RangeIndex for $Self {} + + impl IntRangeIndex<$T> for $Self { + #[inline] + fn new(x: $T) -> $Self { + $Self(x) + } + + #[inline] + fn get(self) -> $T { + match self { $Self(x) => x } + } + } + + impl Add<$Self, $Self> for $Self { + #[inline] + fn add(&self, other: &$Self) -> $Self { + $Self(self.get() + other.get()) + } + } + + impl Sub<$Self, $Self> for $Self { + #[inline] + fn sub(&self, other: &$Self) -> $Self { + $Self(self.get() - other.get()) + } + } + + impl Neg<$Self> for $Self { + #[inline] + fn neg(&self) -> $Self { + $Self(-self.get()) + } + } + ) +} + +#[deriving(Show)] +pub enum RangeRelation<I> { + OverlapsBegin(/* overlap */ I), + OverlapsEnd(/* overlap */ I), + ContainedBy, + Contains, + Coincides, + EntirelyBefore, + EntirelyAfter +} + +/// A range of indices +#[deriving(Clone, Encodable)] +pub struct Range<I> { + begin: I, + length: I, +} + +impl<I: RangeIndex> fmt::Show for Range<I> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{} .. {})", self.begin(), self.end()) + } +} + +/// An iterator over each index in a range +pub struct EachIndex<T, I> { + it: iter::Range<T>, +} + +pub fn each_index<T: Int, I: IntRangeIndex<T>>(start: I, stop: I) -> EachIndex<T, I> { + EachIndex { it: iter::range(start.get(), stop.get()) } +} + +impl<T: Int, I: IntRangeIndex<T>> Iterator<I> for EachIndex<T, I> { + #[inline] + fn next(&mut self) -> Option<I> { + self.it.next().map(|i| IntRangeIndex::new(i)) + } + + #[inline] + fn size_hint(&self) -> (uint, Option<uint>) { + self.it.size_hint() + } +} + +impl<I: RangeIndex> Range<I> { + /// Create a new range from beginning and length offsets. This could be + /// denoted as `[begin, begin + length)`. + /// + /// ~~~ + /// |-- begin ->|-- length ->| + /// | | | + /// <- o - - - - - +============+ - - - -> + /// ~~~ + #[inline] + pub fn new(begin: I, length: I) -> Range<I> { + Range { begin: begin, length: length } + } + + #[inline] + pub fn empty() -> Range<I> { + Range::new(num::zero(), num::zero()) + } + + /// The index offset to the beginning of the range. + /// + /// ~~~ + /// |-- begin ->| + /// | | + /// <- o - - - - - +============+ - - - -> + /// ~~~ + #[inline] + pub fn begin(&self) -> I { self.begin } + + /// The index offset from the beginning to the end of the range. + /// + /// ~~~ + /// |-- length ->| + /// | | + /// <- o - - - - - +============+ - - - -> + /// ~~~ + #[inline] + pub fn length(&self) -> I { self.length } + + /// The index offset to the end of the range. + /// + /// ~~~ + /// |--------- end --------->| + /// | | + /// <- o - - - - - +============+ - - - -> + /// ~~~ + #[inline] + pub fn end(&self) -> I { self.begin + self.length } + + /// `true` if the index is between the beginning and the end of the range. + /// + /// ~~~ + /// false true false + /// | | | + /// <- o - - + - - +=====+======+ - + - -> + /// ~~~ + #[inline] + pub fn contains(&self, i: I) -> bool { + i >= self.begin() && i < self.end() + } + + /// `true` if the offset from the beginning to the end of the range is zero. + #[inline] + pub fn is_empty(&self) -> bool { + self.length().is_zero() + } + + /// Shift the entire range by the supplied index delta. + /// + /// ~~~ + /// |-- delta ->| + /// | | + /// <- o - +============+ - - - - - | - - - -> + /// | + /// <- o - - - - - - - +============+ - - - -> + /// ~~~ + #[inline] + pub fn shift_by(&mut self, delta: I) { + self.begin = self.begin + delta; + } + + /// Extend the end of the range by the supplied index delta. + /// + /// ~~~ + /// |-- delta ->| + /// | | + /// <- o - - - - - +====+ - - - - - | - - - -> + /// | + /// <- o - - - - - +================+ - - - -> + /// ~~~ + #[inline] + pub fn extend_by(&mut self, delta: I) { + self.length = self.length + delta; + } + + /// Move the end of the range to the target index. + /// + /// ~~~ + /// target + /// | + /// <- o - - - - - +====+ - - - - - | - - - -> + /// | + /// <- o - - - - - +================+ - - - -> + /// ~~~ + #[inline] + pub fn extend_to(&mut self, target: I) { + self.length = target - self.begin; + } + + /// Adjust the beginning offset and the length by the supplied deltas. + #[inline] + pub fn adjust_by(&mut self, begin_delta: I, length_delta: I) { + self.begin = self.begin + begin_delta; + self.length = self.length + length_delta; + } + + /// Set the begin and length values. + #[inline] + pub fn reset(&mut self, begin: I, length: I) { + self.begin = begin; + self.length = length; + } + + #[inline] + pub fn intersect(&self, other: &Range<I>) -> Range<I> { + let begin = max(self.begin(), other.begin()); + let end = min(self.end(), other.end()); + + if end < begin { + Range::empty() + } else { + Range::new(begin, end - begin) + } + } + + /// Computes the relationship between two ranges (`self` and `other`), + /// from the point of view of `self`. So, 'EntirelyBefore' means + /// that the `self` range is entirely before `other` range. + #[inline] + pub fn relation_to_range(&self, other: &Range<I>) -> RangeRelation<I> { + if other.begin() > self.end() { + return EntirelyBefore; + } + if self.begin() > other.end() { + return EntirelyAfter; + } + if self.begin() == other.begin() && self.end() == other.end() { + return Coincides; + } + if self.begin() <= other.begin() && self.end() >= other.end() { + return Contains; + } + if self.begin() >= other.begin() && self.end() <= other.end() { + return ContainedBy; + } + if self.begin() < other.begin() && self.end() < other.end() { + let overlap = self.end() - other.begin(); + return OverlapsBegin(overlap); + } + if self.begin() > other.begin() && self.end() > other.end() { + let overlap = other.end() - self.begin(); + return OverlapsEnd(overlap); + } + fail!("relation_to_range(): didn't classify self={}, other={}", + self, other); + } +} + +/// Methods for `Range`s with indices based on integer values +impl<T: Int, I: IntRangeIndex<T>> Range<I> { + /// Returns an iterater that increments over `[begin, end)`. + #[inline] + pub fn each_index(&self) -> EachIndex<T, I> { + each_index(self.begin(), self.end()) + } + + #[inline] + pub fn is_valid_for_string(&self, s: &str) -> bool { + let s_len = s.len(); + match num::cast::<uint, T>(s_len) { + Some(len) => { + let len = IntRangeIndex::new(len); + self.begin() < len + && self.end() <= len + && self.length() <= len + }, + None => { + debug!("Range<T>::is_valid_for_string: string length (len={}) is longer than the \ + max value for the range index (max={})", s_len, + { + let max: T = Bounded::max_value(); + let val: I = IntRangeIndex::new(max); + val + }); + false + }, + } + } + + #[inline] + pub fn repair_after_coalesced_range(&mut self, other: &Range<I>) { + let relation = self.relation_to_range(other); + debug!("repair_after_coalesced_range: possibly repairing range {}", *self); + debug!("repair_after_coalesced_range: relation of original range and coalesced range {}: {}", + *other, relation); + let _1: I = IntRangeIndex::new(num::one::<T>()); + match relation { + EntirelyBefore => {}, + EntirelyAfter => { self.shift_by(-other.length()); }, + Coincides | ContainedBy => { self.reset(other.begin(), _1); }, + Contains => { self.extend_by(-other.length()); }, + OverlapsBegin(overlap) => { self.extend_by(_1 - overlap); }, + OverlapsEnd(overlap) => { + let len = self.length() - overlap + _1; + self.reset(other.begin(), len); + } + }; + debug!("repair_after_coalesced_range: new range: ---- {}", *self); + } +} diff --git a/components/util/smallvec.rs b/components/util/smallvec.rs new file mode 100644 index 00000000000..4b926c78701 --- /dev/null +++ b/components/util/smallvec.rs @@ -0,0 +1,530 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Small vectors in various sizes. These store a certain number of elements inline and fall back +//! to the heap for larger allocations. + +use i = std::mem::init; +use std::cmp; +use std::intrinsics; +use std::kinds::marker::ContravariantLifetime; +use std::mem; +use std::ptr; +use std::raw::Slice; +use rustrt::local_heap; +use alloc::heap; + +// Generic code for all small vectors + +pub trait VecLike<T> { + fn vec_len(&self) -> uint; + fn vec_push(&mut self, value: T); + + fn vec_mut_slice<'a>(&'a mut self, start: uint, end: uint) -> &'a mut [T]; + + #[inline] + fn vec_mut_slice_from<'a>(&'a mut self, start: uint) -> &'a mut [T] { + let len = self.vec_len(); + self.vec_mut_slice(start, len) + } +} + +impl<T> VecLike<T> for Vec<T> { + #[inline] + fn vec_len(&self) -> uint { + self.len() + } + + #[inline] + fn vec_push(&mut self, value: T) { + self.push(value); + } + + #[inline] + fn vec_mut_slice<'a>(&'a mut self, start: uint, end: uint) -> &'a mut [T] { + self.mut_slice(start, end) + } +} + +trait SmallVecPrivate<T> { + unsafe fn set_len(&mut self, new_len: uint); + unsafe fn set_cap(&mut self, new_cap: uint); + fn data(&self, index: uint) -> *const T; + fn mut_data(&mut self, index: uint) -> *mut T; + unsafe fn ptr(&self) -> *const T; + unsafe fn mut_ptr(&mut self) -> *mut T; + unsafe fn set_ptr(&mut self, new_ptr: *mut T); +} + +pub trait SmallVec<T> : SmallVecPrivate<T> { + fn inline_size(&self) -> uint; + fn len(&self) -> uint; + fn cap(&self) -> uint; + + fn spilled(&self) -> bool { + self.cap() > self.inline_size() + } + + fn begin(&self) -> *const T { + unsafe { + if self.spilled() { + self.ptr() + } else { + self.data(0) + } + } + } + + fn end(&self) -> *const T { + unsafe { + self.begin().offset(self.len() as int) + } + } + + fn iter<'a>(&'a self) -> SmallVecIterator<'a,T> { + SmallVecIterator { + ptr: self.begin(), + end: self.end(), + lifetime: ContravariantLifetime::<'a>, + } + } + + fn mut_iter<'a>(&'a mut self) -> SmallVecMutIterator<'a,T> { + unsafe { + SmallVecMutIterator { + ptr: mem::transmute(self.begin()), + end: mem::transmute(self.end()), + lifetime: ContravariantLifetime::<'a>, + } + } + } + + /// NB: For efficiency reasons (avoiding making a second copy of the inline elements), this + /// actually clears out the original array instead of moving it. + fn move_iter<'a>(&'a mut self) -> SmallVecMoveIterator<'a,T> { + unsafe { + let iter = mem::transmute(self.iter()); + let ptr_opt = if self.spilled() { + Some(mem::transmute(self.ptr())) + } else { + None + }; + let inline_size = self.inline_size(); + self.set_cap(inline_size); + self.set_len(0); + SmallVecMoveIterator { + allocation: ptr_opt, + cap: inline_size, + iter: iter, + lifetime: ContravariantLifetime::<'a>, + } + } + } + + fn push(&mut self, value: T) { + let cap = self.cap(); + if self.len() == cap { + self.grow(cmp::max(cap * 2, 1)) + } + unsafe { + let end: &mut T = mem::transmute(self.end()); + ptr::write(end, value); + let len = self.len(); + self.set_len(len + 1) + } + } + + fn push_all_move<V:SmallVec<T>>(&mut self, mut other: V) { + for value in other.move_iter() { + self.push(value) + } + } + + fn pop(&mut self) -> Option<T> { + if self.len() == 0 { + return None + } + + unsafe { + let mut value: T = mem::uninitialized(); + let last_index = self.len() - 1; + + if (last_index as int) < 0 { + fail!("overflow") + } + let end_ptr = self.begin().offset(last_index as int); + + mem::swap(&mut value, mem::transmute::<*const T,&mut T>(end_ptr)); + self.set_len(last_index); + Some(value) + } + } + + fn grow(&mut self, new_cap: uint) { + unsafe { + let new_alloc: *mut T = mem::transmute(heap::allocate(mem::size_of::<T>() * + new_cap, + mem::min_align_of::<T>())); + ptr::copy_nonoverlapping_memory(new_alloc, self.begin(), self.len()); + + if self.spilled() { + if intrinsics::owns_managed::<T>() { + local_heap::local_free(self.ptr() as *mut u8) + } else { + heap::deallocate(self.mut_ptr() as *mut u8, + mem::size_of::<T>() * self.cap(), + mem::min_align_of::<T>()) + } + } else { + let mut_begin: *mut T = mem::transmute(self.begin()); + intrinsics::set_memory(mut_begin, 0, self.len()) + } + + self.set_ptr(new_alloc); + self.set_cap(new_cap) + } + } + + fn get<'a>(&'a self, index: uint) -> &'a T { + if index >= self.len() { + self.fail_bounds_check(index) + } + unsafe { + mem::transmute(self.begin().offset(index as int)) + } + } + + fn get_mut<'a>(&'a mut self, index: uint) -> &'a mut T { + if index >= self.len() { + self.fail_bounds_check(index) + } + unsafe { + mem::transmute(self.begin().offset(index as int)) + } + } + + fn slice<'a>(&'a self, start: uint, end: uint) -> &'a [T] { + assert!(start <= end); + assert!(end <= self.len()); + unsafe { + mem::transmute(Slice { + data: self.begin().offset(start as int), + len: (end - start) + }) + } + } + + fn as_slice<'a>(&'a self) -> &'a [T] { + self.slice(0, self.len()) + } + + fn as_mut_slice<'a>(&'a mut self) -> &'a mut [T] { + let len = self.len(); + self.mut_slice(0, len) + } + + fn mut_slice<'a>(&'a mut self, start: uint, end: uint) -> &'a mut [T] { + assert!(start <= end); + assert!(end <= self.len()); + unsafe { + mem::transmute(Slice { + data: self.begin().offset(start as int), + len: (end - start) + }) + } + } + + fn mut_slice_from<'a>(&'a mut self, start: uint) -> &'a mut [T] { + let len = self.len(); + self.mut_slice(start, len) + } + + fn fail_bounds_check(&self, index: uint) { + fail!("index {} beyond length ({})", index, self.len()) + } +} + +pub struct SmallVecIterator<'a,T> { + ptr: *const T, + end: *const T, + lifetime: ContravariantLifetime<'a> +} + +impl<'a,T> Iterator<&'a T> for SmallVecIterator<'a,T> { + #[inline] + fn next(&mut self) -> Option<&'a T> { + unsafe { + if self.ptr == self.end { + return None + } + let old = self.ptr; + self.ptr = if mem::size_of::<T>() == 0 { + mem::transmute(self.ptr as uint + 1) + } else { + self.ptr.offset(1) + }; + Some(mem::transmute(old)) + } + } +} + +pub struct SmallVecMutIterator<'a,T> { + ptr: *mut T, + end: *mut T, + lifetime: ContravariantLifetime<'a>, +} + +impl<'a,T> Iterator<&'a mut T> for SmallVecMutIterator<'a,T> { + #[inline] + fn next(&mut self) -> Option<&'a mut T> { + unsafe { + if self.ptr == self.end { + return None + } + let old = self.ptr; + self.ptr = if mem::size_of::<T>() == 0 { + mem::transmute(self.ptr as uint + 1) + } else { + self.ptr.offset(1) + }; + Some(mem::transmute(old)) + } + } +} + +pub struct SmallVecMoveIterator<'a,T> { + allocation: Option<*mut u8>, + cap: uint, + iter: SmallVecIterator<'static,T>, + lifetime: ContravariantLifetime<'a>, +} + +impl<'a,T> Iterator<T> for SmallVecMoveIterator<'a,T> { + #[inline] + fn next(&mut self) -> Option<T> { + unsafe { + match self.iter.next() { + None => None, + Some(reference) => { + // Zero out the values as we go so they don't get double-freed. + let reference: &mut T = mem::transmute(reference); + Some(mem::replace(reference, mem::zeroed())) + } + } + } + } +} + +#[unsafe_destructor] +impl<'a,T> Drop for SmallVecMoveIterator<'a,T> { + fn drop(&mut self) { + // Destroy the remaining elements. + for _ in *self {} + + match self.allocation { + None => {} + Some(allocation) => { + unsafe { + if intrinsics::owns_managed::<T>() { + local_heap::local_free(allocation as *mut u8) + } else { + heap::deallocate(allocation as *mut u8, + mem::size_of::<T>() * self.cap, + mem::min_align_of::<T>()) + } + } + } + } + } +} + +// Concrete implementations + +macro_rules! def_small_vector( + ($name:ident, $size:expr) => ( + pub struct $name<T> { + len: uint, + cap: uint, + ptr: *const T, + data: [T, ..$size], + } + + impl<T> SmallVecPrivate<T> for $name<T> { + unsafe fn set_len(&mut self, new_len: uint) { + self.len = new_len + } + unsafe fn set_cap(&mut self, new_cap: uint) { + self.cap = new_cap + } + fn data(&self, index: uint) -> *const T { + let ptr: *const T = &self.data[index]; + ptr + } + fn mut_data(&mut self, index: uint) -> *mut T { + let ptr: *mut T = &mut self.data[index]; + ptr + } + unsafe fn ptr(&self) -> *const T { + self.ptr + } + unsafe fn mut_ptr(&mut self) -> *mut T { + mem::transmute(self.ptr) + } + unsafe fn set_ptr(&mut self, new_ptr: *mut T) { + self.ptr = mem::transmute(new_ptr) + } + } + + impl<T> SmallVec<T> for $name<T> { + fn inline_size(&self) -> uint { + $size + } + fn len(&self) -> uint { + self.len + } + fn cap(&self) -> uint { + self.cap + } + } + + impl<T> VecLike<T> for $name<T> { + #[inline] + fn vec_len(&self) -> uint { + self.len() + } + + #[inline] + fn vec_push(&mut self, value: T) { + self.push(value); + } + + #[inline] + fn vec_mut_slice<'a>(&'a mut self, start: uint, end: uint) -> &'a mut [T] { + self.mut_slice(start, end) + } + } + + impl<T> $name<T> { + #[inline] + pub fn new() -> $name<T> { + unsafe { + $name { + len: 0, + cap: $size, + ptr: ptr::null(), + data: mem::zeroed(), + } + } + } + } + ) +) + +def_small_vector!(SmallVec1, 1) +def_small_vector!(SmallVec2, 2) +def_small_vector!(SmallVec4, 4) +def_small_vector!(SmallVec8, 8) +def_small_vector!(SmallVec16, 16) +def_small_vector!(SmallVec24, 24) +def_small_vector!(SmallVec32, 32) + +macro_rules! def_small_vector_drop_impl( + ($name:ident, $size:expr) => ( + #[unsafe_destructor] + impl<T> Drop for $name<T> { + fn drop(&mut self) { + if !self.spilled() { + return + } + + unsafe { + let ptr = self.mut_ptr(); + for i in range(0, self.len()) { + *ptr.offset(i as int) = mem::uninitialized(); + } + + if intrinsics::owns_managed::<T>() { + local_heap::local_free(self.ptr() as *mut u8) + } else { + heap::deallocate(self.mut_ptr() as *mut u8, + mem::size_of::<T>() * self.cap(), + mem::min_align_of::<T>()) + } + } + } + } + ) +) + +def_small_vector_drop_impl!(SmallVec1, 1) +def_small_vector_drop_impl!(SmallVec2, 2) +def_small_vector_drop_impl!(SmallVec4, 4) +def_small_vector_drop_impl!(SmallVec8, 8) +def_small_vector_drop_impl!(SmallVec16, 16) +def_small_vector_drop_impl!(SmallVec24, 24) +def_small_vector_drop_impl!(SmallVec32, 32) + +macro_rules! def_small_vector_clone_impl( + ($name:ident) => ( + impl<T:Clone> Clone for $name<T> { + fn clone(&self) -> $name<T> { + let mut new_vector = $name::new(); + for element in self.iter() { + new_vector.push((*element).clone()) + } + new_vector + } + } + ) +) + +def_small_vector_clone_impl!(SmallVec1) +def_small_vector_clone_impl!(SmallVec2) +def_small_vector_clone_impl!(SmallVec4) +def_small_vector_clone_impl!(SmallVec8) +def_small_vector_clone_impl!(SmallVec16) +def_small_vector_clone_impl!(SmallVec24) +def_small_vector_clone_impl!(SmallVec32) + +#[cfg(test)] +pub mod tests { + use smallvec::{SmallVec, SmallVec2, SmallVec16}; + + // We heap allocate all these strings so that double frees will show up under valgrind. + + #[test] + pub fn test_inline() { + let mut v = SmallVec16::new(); + v.push("hello".to_string()); + v.push("there".to_string()); + assert_eq!(v.as_slice(), &["hello".to_string(), "there".to_string()]); + } + + #[test] + pub fn test_spill() { + let mut v = SmallVec2::new(); + v.push("hello".to_string()); + v.push("there".to_string()); + v.push("burma".to_string()); + v.push("shave".to_string()); + assert_eq!(v.as_slice(), &["hello".to_string(), "there".to_string(), "burma".to_string(), "shave".to_string()]); + } + + #[test] + pub fn test_double_spill() { + let mut v = SmallVec2::new(); + v.push("hello".to_string()); + v.push("there".to_string()); + v.push("burma".to_string()); + v.push("shave".to_string()); + v.push("hello".to_string()); + v.push("there".to_string()); + v.push("burma".to_string()); + v.push("shave".to_string()); + assert_eq!(v.as_slice(), &[ + "hello".to_string(), "there".to_string(), "burma".to_string(), "shave".to_string(), "hello".to_string(), "there".to_string(), "burma".to_string(), "shave".to_string(), + ]); + } +} + diff --git a/components/util/sort.rs b/components/util/sort.rs new file mode 100644 index 00000000000..32dc52f6574 --- /dev/null +++ b/components/util/sort.rs @@ -0,0 +1,101 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! In-place sorting. + +fn quicksort_helper<T>(arr: &mut [T], left: int, right: int, compare: fn(&T, &T) -> Ordering) { + if right <= left { + return + } + + let mut i: int = left - 1; + let mut j: int = right; + let mut p: int = i; + let mut q: int = j; + unsafe { + let v: *mut T = &mut arr[right as uint]; + loop { + i += 1; + while compare(&arr[i as uint], &*v) == Less { + i += 1 + } + j -= 1; + while compare(&*v, &arr[j as uint]) == Less { + if j == left { + break + } + j -= 1; + } + if i >= j { + break + } + arr.swap(i as uint, j as uint); + if compare(&arr[i as uint], &*v) == Equal { + p += 1; + arr.swap(p as uint, i as uint) + } + if compare(&*v, &arr[j as uint]) == Equal { + q -= 1; + arr.swap(j as uint, q as uint) + } + } + } + + arr.swap(i as uint, right as uint); + j = i - 1; + i += 1; + let mut k: int = left; + while k < p { + arr.swap(k as uint, j as uint); + k += 1; + j -= 1; + assert!(k < arr.len() as int); + } + k = right - 1; + while k > q { + arr.swap(i as uint, k as uint); + k -= 1; + i += 1; + assert!(k != 0); + } + + quicksort_helper(arr, left, j, compare); + quicksort_helper(arr, i, right, compare); +} + +/// An in-place quicksort. +/// +/// The algorithm is from Sedgewick and Bentley, "Quicksort is Optimal": +/// http://www.cs.princeton.edu/~rs/talks/QuicksortIsOptimal.pdf +pub fn quicksort_by<T>(arr: &mut [T], compare: fn(&T, &T) -> Ordering) { + if arr.len() <= 1 { + return + } + + let len = arr.len(); + quicksort_helper(arr, 0, (len - 1) as int, compare); +} + +#[cfg(test)] +pub mod test { + use std::rand; + use std::rand::Rng; + + use sort; + + #[test] + pub fn random() { + let mut rng = rand::task_rng(); + for _ in range(0u32, 50000u32) { + let len: uint = rng.gen(); + let mut v: Vec<int> = rng.gen_iter::<int>().take((len % 32) + 1).collect(); + fn compare_ints(a: &int, b: &int) -> Ordering { a.cmp(b) } + sort::quicksort_by(v.as_mut_slice(), compare_ints); + for i in range(0, v.len() - 1) { + assert!(v.get(i) <= v.get(i + 1)) + } + } + } +} + diff --git a/components/util/str.rs b/components/util/str.rs new file mode 100644 index 00000000000..9d07cf80b99 --- /dev/null +++ b/components/util/str.rs @@ -0,0 +1,111 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::iter::Filter; +use std::str::CharSplits; + +pub type DOMString = String; +pub type StaticCharVec = &'static [char]; +pub type StaticStringVec = &'static [&'static str]; + +pub fn null_str_as_empty(s: &Option<DOMString>) -> DOMString { + // We don't use map_default because it would allocate "".to_string() even for Some. + match *s { + Some(ref s) => s.clone(), + None => "".to_string() + } +} + +pub fn null_str_as_empty_ref<'a>(s: &'a Option<DOMString>) -> &'a str { + match *s { + Some(ref s) => s.as_slice(), + None => "" + } +} + +pub fn is_whitespace(s: &str) -> bool { + s.chars().all(|c| match c { + '\u0020' | '\u0009' | '\u000D' | '\u000A' => true, + _ => false + }) +} + +/// A "space character" according to: +/// +/// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html# +/// space-character +pub static HTML_SPACE_CHARACTERS: StaticCharVec = &[ + '\u0020', + '\u0009', + '\u000a', + '\u000c', + '\u000d', +]; + +pub fn split_html_space_chars<'a>(s: &'a str) -> Filter<'a, &'a str, CharSplits<'a, StaticCharVec>> { + s.split(HTML_SPACE_CHARACTERS).filter(|&split| !split.is_empty()) +} + +/// Shared implementation to parse an integer according to +/// <http://www.whatwg.org/html/#rules-for-parsing-integers> or +/// <http://www.whatwg.org/html/#rules-for-parsing-non-negative-integers>. +fn do_parse_integer<T: Iterator<char>>(input: T) -> Option<i64> { + fn is_ascii_digit(c: &char) -> bool { + match *c { + '0'..'9' => true, + _ => false, + } + } + + + let mut input = input.skip_while(|c| { + HTML_SPACE_CHARACTERS.iter().any(|s| s == c) + }).peekable(); + + let sign = match input.peek() { + None => return None, + Some(&'-') => { + input.next(); + -1 + }, + Some(&'+') => { + input.next(); + 1 + }, + Some(_) => 1, + }; + + match input.peek() { + Some(c) if is_ascii_digit(c) => (), + _ => return None, + } + + let value = input.take_while(is_ascii_digit).map(|d| { + d as i64 - '0' as i64 + }).fold(Some(0i64), |accumulator, d| { + accumulator.and_then(|accumulator| { + accumulator.checked_mul(&10) + }).and_then(|accumulator| { + accumulator.checked_add(&d) + }) + }); + + return value.and_then(|value| value.checked_mul(&sign)); +} + +/// Parse an integer according to +/// <http://www.whatwg.org/html/#rules-for-parsing-integers>. +pub fn parse_integer<T: Iterator<char>>(input: T) -> Option<i32> { + do_parse_integer(input).and_then(|result| { + result.to_i32() + }) +} + +/// Parse an integer according to +/// <http://www.whatwg.org/html/#rules-for-parsing-non-negative-integers>. +pub fn parse_unsigned_integer<T: Iterator<char>>(input: T) -> Option<u32> { + do_parse_integer(input).and_then(|result| { + result.to_u32() + }) +} diff --git a/components/util/task.rs b/components/util/task.rs new file mode 100644 index 00000000000..b3e03771610 --- /dev/null +++ b/components/util/task.rs @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::str::IntoMaybeOwned; +use std::task; +use std::comm::Sender; +use std::task::TaskBuilder; +use native::task::NativeTaskBuilder; + +pub fn spawn_named<S: IntoMaybeOwned<'static>>(name: S, f: proc():Send) { + let builder = task::TaskBuilder::new().named(name); + builder.spawn(f); +} + +/// Arrange to send a particular message to a channel if the task built by +/// this `TaskBuilder` fails. +pub fn spawn_named_with_send_on_failure<T: Send>(name: &'static str, + f: proc(): Send, + msg: T, + dest: Sender<T>, + native: bool) { + let future_result = if native { + TaskBuilder::new().named(name).native().try_future(f) + } else { + TaskBuilder::new().named(name).try_future(f) + }; + + let watched_name = name.to_string(); + let watcher_name = format!("{:s}Watcher", watched_name); + TaskBuilder::new().named(watcher_name).spawn(proc() { + match future_result.unwrap() { + Ok(()) => (), + Err(..) => { + debug!("{:s} failed, notifying constellation", name); + dest.send(msg); + } + } + }); +} diff --git a/components/util/time.rs b/components/util/time.rs new file mode 100644 index 00000000000..4f282aa2648 --- /dev/null +++ b/components/util/time.rs @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Timing functions. + +use std_time::precise_time_ns; +use collections::treemap::TreeMap; +use std::comm::{Sender, channel, Receiver}; +use std::f64; +use std::iter::AdditiveIterator; +use std::io::timer::sleep; +use task::{spawn_named}; + +// front-end representation of the profiler used to communicate with the profiler +#[deriving(Clone)] +pub struct TimeProfilerChan(pub Sender<TimeProfilerMsg>); + +impl TimeProfilerChan { + pub fn send(&self, msg: TimeProfilerMsg) { + let TimeProfilerChan(ref c) = *self; + c.send(msg); + } +} + +pub enum TimeProfilerMsg { + /// Normal message used for reporting time + TimeMsg(TimeProfilerCategory, f64), + /// Message used to force print the profiling metrics + PrintMsg, + /// Tells the profiler to shut down. + ExitMsg, +} + +#[repr(u32)] +#[deriving(PartialEq, Clone, PartialOrd, Eq, Ord)] +pub enum TimeProfilerCategory { + CompositingCategory, + LayoutQueryCategory, + LayoutPerformCategory, + LayoutStyleRecalcCategory, + LayoutSelectorMatchCategory, + LayoutTreeBuilderCategory, + LayoutDamagePropagateCategory, + LayoutMainCategory, + LayoutParallelWarmupCategory, + LayoutShapingCategory, + LayoutDispListBuildCategory, + GfxRegenAvailableFontsCategory, + RenderingDrawingCategory, + RenderingPrepBuffCategory, + RenderingCategory, + // FIXME(rust#8803): workaround for lack of CTFE function on enum types to return length + NumBuckets, +} + +impl TimeProfilerCategory { + // convenience function to not have to cast every time + pub fn num_buckets() -> uint { + NumBuckets as uint + } + + // enumeration of all TimeProfilerCategory types + fn empty_buckets() -> TimeProfilerBuckets { + let mut buckets = TreeMap::new(); + buckets.insert(CompositingCategory, vec!()); + buckets.insert(LayoutQueryCategory, vec!()); + buckets.insert(LayoutPerformCategory, vec!()); + buckets.insert(LayoutStyleRecalcCategory, vec!()); + buckets.insert(LayoutSelectorMatchCategory, vec!()); + buckets.insert(LayoutTreeBuilderCategory, vec!()); + buckets.insert(LayoutMainCategory, vec!()); + buckets.insert(LayoutParallelWarmupCategory, vec!()); + buckets.insert(LayoutShapingCategory, vec!()); + buckets.insert(LayoutDamagePropagateCategory, vec!()); + buckets.insert(LayoutDispListBuildCategory, vec!()); + buckets.insert(GfxRegenAvailableFontsCategory, vec!()); + buckets.insert(RenderingDrawingCategory, vec!()); + buckets.insert(RenderingPrepBuffCategory, vec!()); + buckets.insert(RenderingCategory, vec!()); + + buckets + } + + // some categories are subcategories of LayoutPerformCategory + // and should be printed to indicate this + pub fn format(self) -> String { + let padding = match self { + LayoutStyleRecalcCategory | + LayoutMainCategory | + LayoutDispListBuildCategory | + LayoutShapingCategory | + LayoutDamagePropagateCategory => "+ ", + LayoutParallelWarmupCategory | + LayoutSelectorMatchCategory | + LayoutTreeBuilderCategory => "| + ", + _ => "" + }; + format!("{:s}{:?}", padding, self) + } +} + +type TimeProfilerBuckets = TreeMap<TimeProfilerCategory, Vec<f64>>; + +// back end of the profiler that handles data aggregation and performance metrics +pub struct TimeProfiler { + pub port: Receiver<TimeProfilerMsg>, + buckets: TimeProfilerBuckets, + pub last_msg: Option<TimeProfilerMsg>, +} + +impl TimeProfiler { + pub fn create(period: Option<f64>) -> TimeProfilerChan { + let (chan, port) = channel(); + match period { + Some(period) => { + let period = (period * 1000f64) as u64; + let chan = chan.clone(); + spawn_named("Time profiler timer", proc() { + loop { + sleep(period); + if chan.send_opt(PrintMsg).is_err() { + break; + } + } + }); + // Spawn the time profiler. + spawn_named("Time profiler", proc() { + let mut profiler = TimeProfiler::new(port); + profiler.start(); + }); + } + None => { + // No-op to handle messages when the time profiler is inactive. + spawn_named("Time profiler", proc() { + loop { + match port.recv_opt() { + Err(_) | Ok(ExitMsg) => break, + _ => {} + } + } + }); + } + } + + TimeProfilerChan(chan) + } + + pub fn new(port: Receiver<TimeProfilerMsg>) -> TimeProfiler { + TimeProfiler { + port: port, + buckets: TimeProfilerCategory::empty_buckets(), + last_msg: None, + } + } + + pub fn start(&mut self) { + loop { + let msg = self.port.recv_opt(); + match msg { + Ok(msg) => { + if !self.handle_msg(msg) { + break + } + } + _ => break + } + } + } + + fn handle_msg(&mut self, msg: TimeProfilerMsg) -> bool { + match msg { + TimeMsg(category, t) => self.buckets.find_mut(&category).unwrap().push(t), + PrintMsg => match self.last_msg { + // only print if more data has arrived since the last printout + Some(TimeMsg(..)) => self.print_buckets(), + _ => () + }, + ExitMsg => return false, + }; + self.last_msg = Some(msg); + true + } + + fn print_buckets(&mut self) { + println!("{:39s} {:15s} {:15s} {:15s} {:15s} {:15s}", + "_category_", "_mean (ms)_", "_median (ms)_", + "_min (ms)_", "_max (ms)_", "_bucket size_"); + for (category, data) in self.buckets.mut_iter() { + data.sort_by(|a, b| { + if a < b { + Less + } else { + Greater + } + }); + let data_len = data.len(); + if data_len > 0 { + let (mean, median, min, max) = + (data.iter().map(|&x|x).sum() / (data_len as f64), + (*data)[data_len / 2], + data.iter().fold(f64::INFINITY, |a, &b| a.min(b)), + data.iter().fold(-f64::INFINITY, |a, &b| a.max(b))); + println!("{:-35s}: {:15.4f} {:15.4f} {:15.4f} {:15.4f} {:15u}", + category.format(), mean, median, min, max, data_len); + } + } + println!(""); + } +} + + +pub fn profile<T>(category: TimeProfilerCategory, + time_profiler_chan: TimeProfilerChan, + callback: || -> T) + -> T { + let start_time = precise_time_ns(); + let val = callback(); + let end_time = precise_time_ns(); + let ms = (end_time - start_time) as f64 / 1000000f64; + time_profiler_chan.send(TimeMsg(category, ms)); + return val; +} + +pub fn time<T>(msg: &str, callback: || -> T) -> T{ + let start_time = precise_time_ns(); + let val = callback(); + let end_time = precise_time_ns(); + let ms = (end_time - start_time) as f64 / 1000000f64; + if ms >= 5f64 { + debug!("{:s} took {} ms", msg, ms); + } + return val; +} + +// ensure that the order of the buckets matches the order of the enum categories +#[test] +fn check_order() { + let buckets = TimeProfilerCategory::empty_buckets(); + assert!(buckets.len() == NumBuckets as uint); +} diff --git a/components/util/vec.rs b/components/util/vec.rs new file mode 100644 index 00000000000..b8d24687d28 --- /dev/null +++ b/components/util/vec.rs @@ -0,0 +1,124 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::cmp::{PartialOrd, PartialEq}; + +/// FIXME(pcwalton): Workaround for lack of unboxed closures. This is called in +/// performance-critical code, so a closure is insufficient. +pub trait Comparator<K,T> { + fn compare(&self, key: &K, value: &T) -> Ordering; +} + +pub trait BinarySearchMethods<'a, T: Ord + PartialOrd + PartialEq> { + fn binary_search(&self, key: &T) -> Option<&'a T>; + fn binary_search_index(&self, key: &T) -> Option<uint>; +} + +pub trait FullBinarySearchMethods<T> { + fn binary_search_index_by<K,C:Comparator<K,T>>(&self, key: &K, cmp: C) -> Option<uint>; +} + +impl<'a, T: Ord + PartialOrd + PartialEq> BinarySearchMethods<'a, T> for &'a [T] { + fn binary_search(&self, key: &T) -> Option<&'a T> { + self.binary_search_index(key).map(|i| &self[i]) + } + + fn binary_search_index(&self, key: &T) -> Option<uint> { + self.binary_search_index_by(key, DefaultComparator) + } +} + +impl<'a, T> FullBinarySearchMethods<T> for &'a [T] { + fn binary_search_index_by<K,C:Comparator<K,T>>(&self, key: &K, cmp: C) -> Option<uint> { + if self.len() == 0 { + return None; + } + + let mut low : int = 0; + let mut high : int = (self.len() as int) - 1; + + while low <= high { + // http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html + let mid = ((low as uint) + (high as uint)) >> 1; + let midv = &self[mid]; + + match cmp.compare(key, midv) { + Greater => low = (mid as int) + 1, + Less => high = (mid as int) - 1, + Equal => return Some(mid), + } + } + return None; + } +} + +struct DefaultComparator; + +impl<T:PartialEq + PartialOrd + Ord> Comparator<T,T> for DefaultComparator { + fn compare(&self, key: &T, value: &T) -> Ordering { + (*key).cmp(value) + } +} + +#[cfg(test)] +fn test_find_all_elems<T: PartialEq + PartialOrd + Eq + Ord>(arr: &[T]) { + let mut i = 0; + while i < arr.len() { + assert!(test_match(&arr[i], arr.binary_search(&arr[i]))); + i += 1; + } +} + +#[cfg(test)] +fn test_miss_all_elems<T: PartialEq + PartialOrd + Eq + Ord>(arr: &[T], misses: &[T]) { + let mut i = 0; + while i < misses.len() { + let res = arr.binary_search(&misses[i]); + debug!("{:?} == {:?} ?", misses[i], res); + assert!(!test_match(&misses[i], arr.binary_search(&misses[i]))); + i += 1; + } +} + +#[cfg(test)] +fn test_match<T: PartialEq>(b: &T, a: Option<&T>) -> bool { + match a { + None => false, + Some(t) => t == b + } +} + +#[test] +fn should_find_all_elements() { + let arr_odd = [1u32, 2, 4, 6, 7, 8, 9]; + let arr_even = [1u32, 2, 5, 6, 7, 8, 9, 42]; + let arr_double = [1u32, 1, 2, 2, 6, 8, 22]; + let arr_one = [234986325u32]; + let arr_two = [3044u32, 8393]; + let arr_three = [12u32, 23, 34]; + + test_find_all_elems(arr_odd); + test_find_all_elems(arr_even); + test_find_all_elems(arr_double); + test_find_all_elems(arr_one); + test_find_all_elems(arr_two); + test_find_all_elems(arr_three); +} + +#[test] +fn should_not_find_missing_elements() { + let arr_odd = [1u32, 2, 4, 6, 7, 8, 9]; + let arr_even = [1u32, 2, 5, 6, 7, 8, 9, 42]; + let arr_double = [1u32, 1, 2, 2, 6, 8, 22]; + let arr_one = [234986325u32]; + let arr_two = [3044u32, 8393]; + let arr_three = [12u32, 23, 34]; + + test_miss_all_elems(arr_odd, [-22, 0, 3, 5, 34938, 10, 11, 12]); + test_miss_all_elems(arr_even, [-1, 0, 3, 34938, 10, 11, 12]); + test_miss_all_elems(arr_double, [-1, 0, 3, 4, 34938, 10, 11, 12, 234, 234, 33]); + test_miss_all_elems(arr_one, [-1, 0, 3, 34938, 10, 11, 12, 234, 234, 33]); + test_miss_all_elems(arr_two, [-1, 0, 3, 34938, 10, 11, 12, 234, 234, 33]); + test_miss_all_elems(arr_three, [-2, 0, 1, 2, 3, 34938, 10, 11, 234, 234, 33]); +} diff --git a/components/util/workqueue.rs b/components/util/workqueue.rs new file mode 100644 index 00000000000..5b27b4a5dab --- /dev/null +++ b/components/util/workqueue.rs @@ -0,0 +1,291 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! A work queue for scheduling units of work across threads in a fork-join fashion. +//! +//! Data associated with queues is simply a pair of unsigned integers. It is expected that a +//! higher-level API on top of this could allow safe fork-join parallelism. + +use native::task::NativeTaskBuilder; +use rand::{Rng, XorShiftRng}; +use std::mem; +use std::rand::weak_rng; +use std::sync::atomics::{AtomicUint, SeqCst}; +use std::sync::deque::{Abort, BufferPool, Data, Empty, Stealer, Worker}; +use std::task::TaskBuilder; + +/// A unit of work. +/// +/// # Type parameters +/// +/// - `QueueData`: global custom data for the entire work queue. +/// - `WorkData`: custom data specific to each unit of work. +pub struct WorkUnit<QueueData, WorkData> { + /// The function to execute. + pub fun: extern "Rust" fn(WorkData, &mut WorkerProxy<QueueData, WorkData>), + /// Arbitrary data. + pub data: WorkData, +} + +/// Messages from the supervisor to the worker. +enum WorkerMsg<QueueData, WorkData> { + /// Tells the worker to start work. + StartMsg(Worker<WorkUnit<QueueData, WorkData>>, *mut AtomicUint, *const QueueData), + /// Tells the worker to stop. It can be restarted again with a `StartMsg`. + StopMsg, + /// Tells the worker thread to terminate. + ExitMsg, +} + +/// Messages to the supervisor. +enum SupervisorMsg<QueueData, WorkData> { + FinishedMsg, + ReturnDequeMsg(uint, Worker<WorkUnit<QueueData, WorkData>>), +} + +/// Information that the supervisor thread keeps about the worker threads. +struct WorkerInfo<QueueData, WorkData> { + /// The communication channel to the workers. + chan: Sender<WorkerMsg<QueueData, WorkData>>, + /// The worker end of the deque, if we have it. + deque: Option<Worker<WorkUnit<QueueData, WorkData>>>, + /// The thief end of the work-stealing deque. + thief: Stealer<WorkUnit<QueueData, WorkData>>, +} + +/// Information specific to each worker thread that the thread keeps. +struct WorkerThread<QueueData, WorkData> { + /// The index of this worker. + index: uint, + /// The communication port from the supervisor. + port: Receiver<WorkerMsg<QueueData, WorkData>>, + /// The communication channel on which messages are sent to the supervisor. + chan: Sender<SupervisorMsg<QueueData, WorkData>>, + /// The thief end of the work-stealing deque for all other workers. + other_deques: Vec<Stealer<WorkUnit<QueueData, WorkData>>>, + /// The random number generator for this worker. + rng: XorShiftRng, +} + +static SPIN_COUNT: uint = 1000; + +impl<QueueData: Send, WorkData: Send> WorkerThread<QueueData, WorkData> { + /// The main logic. This function starts up the worker and listens for + /// messages. + fn start(&mut self) { + loop { + // Wait for a start message. + let (mut deque, ref_count, queue_data) = match self.port.recv() { + StartMsg(deque, ref_count, queue_data) => (deque, ref_count, queue_data), + StopMsg => fail!("unexpected stop message"), + ExitMsg => return, + }; + + // We're off! + // + // FIXME(pcwalton): Can't use labeled break or continue cross-crate due to a Rust bug. + loop { + // FIXME(pcwalton): Nasty workaround for the lack of labeled break/continue + // cross-crate. + let mut work_unit = unsafe { + mem::uninitialized() + }; + match deque.pop() { + Some(work) => work_unit = work, + None => { + // Become a thief. + let mut i = 0; + let mut should_continue = true; + loop { + let victim = (self.rng.next_u32() as uint) % self.other_deques.len(); + match self.other_deques.get_mut(victim).steal() { + Empty | Abort => { + // Continue. + } + Data(work) => { + work_unit = work; + break + } + } + + if i == SPIN_COUNT { + match self.port.try_recv() { + Ok(StopMsg) => { + should_continue = false; + break + } + Ok(ExitMsg) => return, + Ok(_) => fail!("unexpected message"), + _ => {} + } + + i = 0 + } else { + i += 1 + } + } + + if !should_continue { + break + } + } + } + + // At this point, we have some work. Perform it. + let mut proxy = WorkerProxy { + worker: &mut deque, + ref_count: ref_count, + queue_data: queue_data, + }; + (work_unit.fun)(work_unit.data, &mut proxy); + + // The work is done. Now decrement the count of outstanding work items. If this was + // the last work unit in the queue, then send a message on the channel. + unsafe { + if (*ref_count).fetch_sub(1, SeqCst) == 1 { + self.chan.send(FinishedMsg) + } + } + } + + // Give the deque back to the supervisor. + self.chan.send(ReturnDequeMsg(self.index, deque)) + } + } +} + +/// A handle to the work queue that individual work units have. +pub struct WorkerProxy<'a, QueueData, WorkData> { + worker: &'a mut Worker<WorkUnit<QueueData, WorkData>>, + ref_count: *mut AtomicUint, + queue_data: *const QueueData, +} + +impl<'a, QueueData, WorkData: Send> WorkerProxy<'a, QueueData, WorkData> { + /// Enqueues a block into the work queue. + #[inline] + pub fn push(&mut self, work_unit: WorkUnit<QueueData, WorkData>) { + unsafe { + drop((*self.ref_count).fetch_add(1, SeqCst)); + } + self.worker.push(work_unit); + } + + /// Retrieves the queue user data. + #[inline] + pub fn user_data<'a>(&'a self) -> &'a QueueData { + unsafe { + mem::transmute(self.queue_data) + } + } +} + +/// A work queue on which units of work can be submitted. +pub struct WorkQueue<QueueData, WorkData> { + /// Information about each of the workers. + workers: Vec<WorkerInfo<QueueData, WorkData>>, + /// A port on which deques can be received from the workers. + port: Receiver<SupervisorMsg<QueueData, WorkData>>, + /// The amount of work that has been enqueued. + work_count: uint, + /// Arbitrary user data. + pub data: QueueData, +} + +impl<QueueData: Send, WorkData: Send> WorkQueue<QueueData, WorkData> { + /// Creates a new work queue and spawns all the threads associated with + /// it. + pub fn new(task_name: &'static str, thread_count: uint, user_data: QueueData) -> WorkQueue<QueueData, WorkData> { + // Set up data structures. + let (supervisor_chan, supervisor_port) = channel(); + let (mut infos, mut threads) = (vec!(), vec!()); + for i in range(0, thread_count) { + let (worker_chan, worker_port) = channel(); + let pool = BufferPool::new(); + let (worker, thief) = pool.deque(); + infos.push(WorkerInfo { + chan: worker_chan, + deque: Some(worker), + thief: thief, + }); + threads.push(WorkerThread { + index: i, + port: worker_port, + chan: supervisor_chan.clone(), + other_deques: vec!(), + rng: weak_rng(), + }); + } + + // Connect workers to one another. + for i in range(0, thread_count) { + for j in range(0, thread_count) { + if i != j { + threads.get_mut(i).other_deques.push(infos[j].thief.clone()) + } + } + assert!(threads.get(i).other_deques.len() == thread_count - 1) + } + + // Spawn threads. + for thread in threads.move_iter() { + TaskBuilder::new().named(task_name).native().spawn(proc() { + let mut thread = thread; + thread.start() + }) + } + + WorkQueue { + workers: infos, + port: supervisor_port, + work_count: 0, + data: user_data, + } + } + + /// Enqueues a block into the work queue. + #[inline] + pub fn push(&mut self, work_unit: WorkUnit<QueueData, WorkData>) { + match self.workers.get_mut(0).deque { + None => { + fail!("tried to push a block but we don't have the deque?!") + } + Some(ref mut deque) => deque.push(work_unit), + } + self.work_count += 1 + } + + /// Synchronously runs all the enqueued tasks and waits for them to complete. + pub fn run(&mut self) { + // Tell the workers to start. + let mut work_count = AtomicUint::new(self.work_count); + for worker in self.workers.mut_iter() { + worker.chan.send(StartMsg(worker.deque.take_unwrap(), &mut work_count, &self.data)) + } + + // Wait for the work to finish. + drop(self.port.recv()); + self.work_count = 0; + + // Tell everyone to stop. + for worker in self.workers.iter() { + worker.chan.send(StopMsg) + } + + // Get our deques back. + for _ in range(0, self.workers.len()) { + match self.port.recv() { + ReturnDequeMsg(index, deque) => self.workers.get_mut(index).deque = Some(deque), + FinishedMsg => fail!("unexpected finished message!"), + } + } + } + + pub fn shutdown(&mut self) { + for worker in self.workers.iter() { + worker.chan.send(ExitMsg) + } + } +} + |