From fa122b2a786c439c959716be6d204d2f6750db6b Mon Sep 17 00:00:00 2001 From: Margaret Meyerhofer Date: Mon, 23 Jul 2012 10:05:14 -0700 Subject: [PATCH] Added the ability to iterate over the layout-box tree in parallel. --- src/servo/layout/base.rs | 81 ++++++++++++++-- src/servo/layout/display_list_builder.rs | 27 ++++-- src/servo/layout/layout_task.rs | 2 +- src/servo/layout/style/apply.rs | 37 +++---- src/servo/layout/style/style.rs | 12 ++- src/servo/layout/traverse.rs | 118 +++++++++++++++++++++++ src/servo/servo.rc | 1 + 7 files changed, 237 insertions(+), 41 deletions(-) create mode 100644 src/servo/layout/traverse.rs diff --git a/src/servo/layout/base.rs b/src/servo/layout/base.rs index 0211b3e0c9c3..e3127b2ab385 100644 --- a/src/servo/layout/base.rs +++ b/src/servo/layout/base.rs @@ -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; 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>; + + 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 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) diff --git a/src/servo/layout/display_list_builder.rs b/src/servo/layout/display_list_builder.rs index fd764da2770b..913da09a5831 100644 --- a/src/servo/layout/display_list_builder.rs +++ b/src/servo/layout/display_list_builder.rs @@ -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) 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) 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 })); - } } } diff --git a/src/servo/layout/layout_task.rs b/src/servo/layout/layout_task.rs index 039688d0d21f..82ec2bd29e04 100644 --- a/src/servo/layout/layout_task.rs +++ b/src/servo/layout/layout_task.rs @@ -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); diff --git a/src/servo/layout/style/apply.rs b/src/servo/layout/style/apply.rs index 1ba87737f970..606b3900b41a 100644 --- a/src/servo/layout/style/apply.rs +++ b/src/servo/layout/style/apply.rs @@ -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 { } } } - diff --git a/src/servo/layout/style/style.rs b/src/servo/layout/style/style.rs index 199d0bbc5ee5..49071d7ee116 100644 --- a/src/servo/layout/style/style.rs +++ b/src/servo/layout/style/style.rs @@ -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, 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. diff --git a/src/servo/layout/traverse.rs b/src/servo/layout/traverse.rs new file mode 100644 index 000000000000..85b5695f16e6 --- /dev/null +++ b/src/servo/layout/traverse.rs @@ -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 = { + 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 unsafe { + let new_box : *shared_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 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> = ~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); +} diff --git a/src/servo/servo.rc b/src/servo/servo.rc index 9669c09e4e5b..58883cb8c3c1 100755 --- a/src/servo/servo.rc +++ b/src/servo/servo.rc @@ -59,6 +59,7 @@ mod layout { mod inline; mod layout_task; mod text; + mod traverse; } mod parser {