Added the ability to iterate over the layout-box tree in parallel.

This commit is contained in:
Margaret Meyerhofer 2012-07-23 10:05:14 -07:00
parent f2c97474c5
commit fa122b2a78
7 changed files with 237 additions and 41 deletions

View file

@ -4,23 +4,23 @@ import dom::base::{Element, ElementKind, HTMLDivElement, HTMLImageElement, Node,
import dom::base::{NodeKind};
import dom::rcu;
import dom::rcu::ReaderMethods;
import dom::style::Unit;
import gfx::geometry;
import gfx::geometry::{au, zero_size_au};
import geom::point::Point2D;
import geom::rect::Rect;
import geom::size::Size2D;
import image::base::image;
import image::base::{image, load};
import layout::block::block_layout_methods;
import layout::inline::inline_layout_methods;
import util::tree;
import util::color::{Color, css_colors};
import text::text_box;
import style::style::SpecifiedStyle;
import style::style::{SpecifiedStyle, default_style_methods};
import text::text_layout_methods;
import vec::{push, push_all};
import future::future;
import arc::arc;
import arc::{arc, clone};
enum BoxKind {
BlockBox,
@ -30,12 +30,35 @@ enum BoxKind {
}
class Appearance {
let mut background_image: option<~future<~arc<~image>>>;
let mut background_image: option<ImageHolder>;
let mut background_color: Color;
let mut width: Unit;
let mut height: Unit;
new() {
new(kind: NodeKind) {
self.background_image = none;
self.background_color = css_colors::black();
self.background_color = kind.default_color();
self.width = kind.default_width();
self.height = kind.default_height();
}
// This will be very unhappy if it is getting run in parallel with
// anything trying to read the background image
fn get_image() -> option<~arc<~image>> {
let mut image = none;
// Do a dance where we swap the ImageHolder out before we can
// get the image out of it because we can't alt over it
// because holder.get_image() is not pure.
if (self.background_image).is_some() {
let mut temp = none;
temp <-> self.background_image;
let holder <- option::unwrap(temp);
image = some(holder.get_image());
self.background_image = some(holder);
}
ret image;
}
}
@ -47,11 +70,52 @@ class Box {
let appearance: Appearance;
new(node: Node, kind: BoxKind) {
self.appearance = node.read(|n| Appearance(*n.kind));
self.tree = tree::empty();
self.node = node;
self.kind = kind;
self.bounds = geometry::zero_rect_au();
self.appearance = Appearance();
}
}
#[doc="A class to store image data. The image will be loaded once,
the first time it is requested, and an arc will be stored. Clones of
this arc are given out on demand."]
class ImageHolder {
// Invariant: at least one of url and image is not none, except
// occasionally while get_image is being called
let mut url : option<~str>;
let mut image : option<arc<~image>>;
new(-url : ~str) {
self.url = some(url);
self.image = none;
}
// This function should not be called by two tasks at the same time
fn get_image() -> ~arc<~image> {
// If this is the first time we've called this function, load
// the image and store it for the future
if self.image.is_none() {
assert self.url.is_some();
let mut temp = none;
temp <-> self.url;
let url = option::unwrap(temp);
let image = load(url);
self.image = some(arc(~image));
}
// Temporarily swap out the arc of the image so we can clone
// it without breaking purity, then put it back and return the
// clone. This is not threadsafe.
let mut temp = none;
temp <-> self.image;
let im_arc = option::unwrap(temp);
self.image = some(clone(&im_arc));
ret ~im_arc;
}
}
@ -74,6 +138,7 @@ impl NodeTreeReadMethods of tree::ReadMethods<Node> for NTree {
}
enum BTree { BTree }
impl BoxTreeReadMethods of tree::ReadMethods<@Box> for BTree {
fn each_child(node: @Box, f: fn(&&@Box) -> bool) {
tree::each_child(self, node, f)

View file

@ -1,15 +1,17 @@
export build_display_list;
import base::{Box, TextBox, BTree, BoxTreeReadMethods};
import base::{Box, TextBox, BTree, BoxTreeReadMethods, ImageHolder};
import box_builder::box_builder_methods;
import dl = display_list;
import dom::base::{Text, NodeScope};
import dom::rcu::Scope;
import either::{left, right};
import geom::point::Point2D;
import geom::rect::Rect;
import geom::size::Size2D;
import gfx::geometry::{au, au_to_px, box, px_to_au};
import gfx::renderer;
import image::base::load;
import text::text_layout_methods;
import util::color::methods;
import util::tree;
@ -70,8 +72,8 @@ fn box_to_display_items(list: dl::display_list, box: @Box, origin: Point2D<au>)
let bounds = Rect(origin, copy box.bounds.size);
let col = box.appearance.background_color;
alt (box.kind, copy box.appearance.background_image) {
(TextBox(subbox), _) {
alt box.kind {
TextBox(subbox) {
let run = copy subbox.run;
assert run.is_some();
list.push(dl::display_item({
@ -82,24 +84,29 @@ fn box_to_display_items(list: dl::display_list, box: @Box, origin: Point2D<au>)
item_type: dl::display_item_text(run.get()),
bounds: bounds
}));
ret;
}
(_, some(image)) {
_ {
// Fall through
}
};
// Check if there is a background image, if not set the background color.
let image = box.appearance.get_image();
if image.is_some() {
let display_item = dl::display_item({
item_type: do future::with(*image) |image| {
dl::display_item_image(~arc::clone(&*image))
},
item_type: dl::display_item_image(option::unwrap(image)),
bounds: bounds
});
list.push(display_item);
}
(_, none) {
} else {
#debug("Assigning color %? to box with bounds %?", col, bounds);
let col = box.appearance.background_color;
list.push(dl::display_item({
item_type: dl::display_item_solid_color(col.red, col.green, col.blue),
bounds: bounds
}));
}
}
}

View file

@ -48,7 +48,7 @@ fn Layout(renderer: Renderer) -> Layout {
let this_box = node.construct_boxes();
this_box.dump();
this_box.apply_style_for_subtree();
this_box.apply_css_style();
this_box.reflow(px_to_au(800));
let dlist = build_display_list(this_box);

View file

@ -2,23 +2,25 @@
import dom::base::{Element, HTMLImageElement, Node};
import dom::rcu::ReaderMethods;
import either::right;
import image::base::load;
import base::{Box, BTree, NTree, LayoutData, BoxTreeReadMethods, SpecifiedStyle};
import base::{Box, BTree, NTree, LayoutData, BoxTreeReadMethods, SpecifiedStyle, ImageHolder};
import style::{default_style_methods, style_methods};
import future_spawn = future::spawn;
import traverse::top_down_traversal;
trait ApplyStyleBoxMethods {
fn apply_style_for_subtree();
fn apply_css_style();
fn apply_style();
}
#[doc="A wrapper so the function can be passed around by name."]
fn apply_style_wrapper(box : @Box) {
box.apply_style();
}
impl ApplyStyleBoxMethods of ApplyStyleBoxMethods for @Box {
fn apply_style_for_subtree() {
self.apply_style();
for BTree.each_child(self) |child| {
child.apply_style_for_subtree();
}
fn apply_css_style() {
top_down_traversal(self, apply_style_wrapper);
}
#[doc="Applies CSS style to a layout box.
@ -42,19 +44,13 @@ impl ApplyStyleBoxMethods of ApplyStyleBoxMethods for @Box {
alt element.kind {
~HTMLImageElement(*) {
alt element.get_attr(~"src") {
some(url) {
let url = element.get_attr(~"src");
if url.is_some() {
// FIXME: Some sort of BASE HREF support!
// FIXME: Parse URLs!
#debug("loading image from %s", url);
self.appearance.background_image = some(~do future_spawn |copy url| {
~arc::arc(~load(url))
});
}
none {
/* Ignore. */
}
}
self.appearance.background_image = some(ImageHolder(option::unwrap(url)))
};
}
_ { /* Ignore. */ }
}
@ -64,4 +60,3 @@ impl ApplyStyleBoxMethods of ApplyStyleBoxMethods for @Box {
}
}
}

View file

@ -2,7 +2,7 @@
import arc::{arc, get, clone};
import dom::style::{DisplayType, DisBlock, DisInline, DisNone, Stylesheet, Unit};
import dom::style::{DisplayType, DisBlock, DisInline, DisNone, Stylesheet, Unit, Auto};
import dom::base::{Element, HTMLDivElement, HTMLHeadElement, HTMLImageElement, Node, NodeKind};
import dom::base::{Text};
import dom::rcu::ReaderMethods;
@ -22,6 +22,8 @@ type SpecifiedStyle = {mut background_color : option<Color>,
trait default_style_methods {
fn default_color() -> Color;
fn default_display_type() -> DisplayType;
fn default_width() -> Unit;
fn default_height() -> Unit;
}
#[doc="Default stylesfor various attributes in case they don't get initialized from css selectors"]
@ -49,6 +51,14 @@ impl default_style_methods of default_style_methods for NodeKind {
}
}
}
fn default_width() -> Unit {
Auto
}
fn default_height() -> Unit {
Auto
}
}
#[doc="Create a specified style that can be used to initialize a node before selector matching.

View file

@ -0,0 +1,118 @@
#[doc = "Interface for running tree-based traversals over layout boxes"]
import base::{Box, BTree, NodeMethods};
import intrinsic::tydesc;
export full_traversal;
export top_down_traversal;
export bottom_up_traversal;
// The underlying representation of an @T. We don't actually care
// what it is, just that we can transform to and from this
// representation to send boxes across task boundaries.
type shared_box<T> = {
mut refcount : uint,
// These are generic unsafe pointers, not just *ints
foo : *int,
bar : *int,
baz : *int,
payload : T
};
#[doc="Transform and @ into its underlying representation. The reference count stays constant."]
fn unwrap_box(-b : @Box) -> *shared_box<Box> unsafe {
let new_box : *shared_box<Box> = unsafe::transmute(b);
ret new_box;
}
#[doc="Transform an underlying representation back to an @. The reference count stays constant."]
fn rewrap_box(-b : *shared_box<Box>) -> @Box unsafe {
let new_box : @Box = unsafe::transmute(b);
ret new_box;
}
#[doc="
Iterate down and then up a tree of layout boxes in parallel and apply
the given functions to each box. Each box applies the first function,
spawns a task to complete all of its children in parallel, waits for
them to finish, and then applies the second function.
# Arguments
* `root` - The current top of the tree, the functions will be applied to it and its children.
* `top-down` - A function that is applied to each node after it is applied to that node's parent.
* `bottom-up` - A function that is applied to each node after it is applied to that node's
children
"]
fn traverse_helper(-root : @Box, -top_down : fn~(@Box), -bottom_up : fn~(@Box)) {
top_down(root);
do listen |ack_chan| {
let mut count = 0;
// For each child we will send it off to another task and then
// recurse. It is safe to send these boxes across tasks
// because root still holds a reference to the children so
// they will not be destroyed from the other task. Also the
// current task will block until all of it's children return,
// so the original owner of the @-box will not exit while the
// children are still live.
for BTree.each_child(root) |kid| {
count += 1;
// Unwrap the box so we can send it out of this task
let unwrapped = unwrap_box(copy kid);
// Hide the box in an option so we can get it across the
// task boundary without copying it
let swappable : ~mut option<*shared_box<Box>> = ~mut some(unwrapped);
do task::spawn || {
// Get the box out of the option and into the new task
let mut swapped_in = none;
swapped_in <-> *swappable;
// Retrieve the original @Box and recurse
let new_kid = rewrap_box(option::unwrap(swapped_in));
traverse_helper(new_kid, copy top_down, copy bottom_up);
ack_chan.send(());
}
}
// wait for all the children to finish before preceding
for count.times() { ack_chan.recv(); }
}
bottom_up(root);
}
#[doc="A noneffectful function to be used if only one pass is required."]
fn nop(box : @Box) {
ret;
}
#[doc="
Iterate in parallel over the boxes in a tree, applying one function
to a parent before recursing on its children and one after.
"]
fn full_traversal(+root : @Box, -top_down : fn~(@Box), -bottom_up : fn~(@Box)) {
traverse_helper(root, top_down, bottom_up);
}
#[doc="
Iterate in parallel over the boxes in a tree, applying the given
function to a parent before its children.
"]
fn top_down_traversal(+root : @Box, -top_down : fn~(@Box)) {
traverse_helper(root, top_down, nop);
}
#[doc="
Iterate in parallel over the boxes in a tree, applying the given
function to a parent after its children.
"]
fn bottom_up_traversal(+root : @Box, -bottom_up : fn~(@Box)) {
traverse_helper(root, nop, bottom_up);
}

View file

@ -59,6 +59,7 @@ mod layout {
mod inline;
mod layout_task;
mod text;
mod traverse;
}
mod parser {