From 21d919e2d2a38802ba96003597eadb68342d71ef Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Tue, 29 Jun 2021 12:33:24 +0200 Subject: [PATCH] Put incremental compilation behind feature --- Cargo.toml | 3 +- src/cache.rs | 3 ++ src/layout/background.rs | 3 +- src/layout/fixed.rs | 3 +- src/layout/grid.rs | 3 +- src/layout/image.rs | 3 +- src/layout/incremental.rs | 6 ++++ src/layout/mod.rs | 59 +++++++++++++++++++++++-------- src/layout/pad.rs | 3 +- src/layout/par.rs | 6 ++-- src/layout/stack.rs | 6 ++-- tests/typeset.rs | 73 ++++++++++++++++++++------------------- 12 files changed, 111 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b18b0af56..2cb23c4e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,10 @@ authors = ["The Typst Project Developers"] edition = "2018" [features] -default = ["cli", "fs"] +default = ["cli", "fs", "layout-cache"] cli = ["anyhow", "fs", "same-file"] fs = ["dirs", "memmap2", "same-file", "walkdir"] +layout-cache = [] [workspace] members = ["bench"] diff --git a/src/cache.rs b/src/cache.rs index aa9c10a01..2aa276aa4 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -2,6 +2,7 @@ use crate::font::FontCache; use crate::image::ImageCache; +#[cfg(feature = "layout-cache")] use crate::layout::LayoutCache; use crate::loading::Loader; @@ -12,6 +13,7 @@ pub struct Cache { /// Caches decoded images. pub image: ImageCache, /// Caches layouting artifacts. + #[cfg(feature = "layout-cache")] pub layout: LayoutCache, } @@ -21,6 +23,7 @@ impl Cache { Self { font: FontCache::new(loader), image: ImageCache::new(), + #[cfg(feature = "layout-cache")] layout: LayoutCache::new(), } } diff --git a/src/layout/background.rs b/src/layout/background.rs index 8390a7568..013a887a8 100644 --- a/src/layout/background.rs +++ b/src/layout/background.rs @@ -1,7 +1,8 @@ use super::*; /// A node that places a rectangular filled background behind its child. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] pub struct BackgroundNode { /// The kind of shape to use as a background. pub shape: BackgroundShape, diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs index 732351682..c1d1ac5e4 100644 --- a/src/layout/fixed.rs +++ b/src/layout/fixed.rs @@ -1,7 +1,8 @@ use super::*; /// A node that can fix its child's width and height. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] pub struct FixedNode { /// The fixed width, if any. pub width: Option, diff --git a/src/layout/grid.rs b/src/layout/grid.rs index 33fce0642..b0bf225f7 100644 --- a/src/layout/grid.rs +++ b/src/layout/grid.rs @@ -1,7 +1,8 @@ use super::*; /// A node that arranges its children in a grid. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] pub struct GridNode { /// The `main` and `cross` directions of this grid. /// diff --git a/src/layout/image.rs b/src/layout/image.rs index 9ba8cd822..20a521ffc 100644 --- a/src/layout/image.rs +++ b/src/layout/image.rs @@ -4,7 +4,8 @@ use crate::image::ImageId; use ::image::GenericImageView; /// An image node. -#[derive(Debug, Copy, Clone, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] pub struct ImageNode { /// The id of the image file. pub id: ImageId, diff --git a/src/layout/incremental.rs b/src/layout/incremental.rs index 9eda04026..1dd90f8ed 100644 --- a/src/layout/incremental.rs +++ b/src/layout/incremental.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "layout-cache")] use std::collections::{hash_map::Entry, HashMap}; use std::ops::Deref; @@ -5,6 +6,7 @@ use super::*; /// Caches layouting artifacts. #[derive(Default, Debug, Clone)] +#[cfg(feature = "layout-cache")] pub struct LayoutCache { /// Maps from node hashes to the resulting frames and regions in which the /// frames are valid. The right hand side of the hash map is a vector of @@ -15,6 +17,7 @@ pub struct LayoutCache { age: usize, } +#[cfg(feature = "layout-cache")] impl LayoutCache { /// Create a new, empty layout cache. pub fn new() -> Self { @@ -100,6 +103,7 @@ impl LayoutCache { /// Cached frames from past layouting. #[derive(Debug, Clone)] +#[cfg(feature = "layout-cache")] pub struct FramesEntry { /// The cached frames for a node. pub frames: Vec>>, @@ -112,6 +116,7 @@ pub struct FramesEntry { temperature: [usize; 5], } +#[cfg(feature = "layout-cache")] impl FramesEntry { /// Construct a new instance. pub fn new(frames: Vec>>, level: usize) -> Self { @@ -205,6 +210,7 @@ impl Constraints { } } + #[cfg(feature = "layout-cache")] fn check(&self, regions: &Regions) -> bool { if self.expand != regions.expand { return false; diff --git a/src/layout/mod.rs b/src/layout/mod.rs index dc819a164..7ba556a11 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -24,9 +24,12 @@ pub use stack::*; use std::any::Any; use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; +use std::hash::Hash; +#[cfg(feature = "layout-cache")] +use std::hash::Hasher; use std::rc::Rc; +#[cfg(feature = "layout-cache")] use fxhash::FxHasher64; use crate::cache::Cache; @@ -35,7 +38,12 @@ use crate::loading::Loader; /// Layout a tree into a collection of frames. pub fn layout(loader: &mut dyn Loader, cache: &mut Cache, tree: &Tree) -> Vec> { - tree.layout(&mut LayoutContext { loader, cache, level: 0 }) + tree.layout(&mut LayoutContext { + loader, + cache, + #[cfg(feature = "layout-cache")] + level: 0, + }) } /// A tree of layout nodes. @@ -77,22 +85,35 @@ impl PageRun { /// A wrapper around a dynamic layouting node. pub struct AnyNode { node: Box, + #[cfg(feature = "layout-cache")] hash: u64, } impl AnyNode { /// Create a new instance from any node that satisifies the required bounds. + #[cfg(feature = "layout-cache")] pub fn new(node: T) -> Self where T: Layout + Debug + Clone + PartialEq + Hash + 'static, { - let mut state = FxHasher64::default(); - node.type_id().hash(&mut state); - node.hash(&mut state); - let hash = state.finish(); + let hash = { + let mut state = FxHasher64::default(); + node.type_id().hash(&mut state); + node.hash(&mut state); + state.finish() + }; Self { node: Box::new(node), hash } } + + /// Create a new instance from any node that satisifies the required bounds. + #[cfg(not(feature = "layout-cache"))] + pub fn new(node: T) -> Self + where + T: Layout + Debug + Clone + PartialEq + 'static, + { + Self { node: Box::new(node) } + } } impl Layout for AnyNode { @@ -101,15 +122,20 @@ impl Layout for AnyNode { ctx: &mut LayoutContext, regions: &Regions, ) -> Vec>> { - ctx.level += 1; - let frames = - ctx.cache.layout.get(self.hash, regions.clone()).unwrap_or_else(|| { - let frames = self.node.layout(ctx, regions); - ctx.cache.layout.insert(self.hash, frames.clone(), ctx.level - 1); - frames - }); - ctx.level -= 1; - frames + #[cfg(feature = "layout-cache")] + { + ctx.level += 1; + let frames = + ctx.cache.layout.get(self.hash, regions.clone()).unwrap_or_else(|| { + let frames = self.node.layout(ctx, regions); + ctx.cache.layout.insert(self.hash, frames.clone(), ctx.level - 1); + frames + }); + ctx.level -= 1; + frames + } + #[cfg(not(feature = "layout-cache"))] + self.node.layout(ctx, regions) } } @@ -117,6 +143,7 @@ impl Clone for AnyNode { fn clone(&self) -> Self { Self { node: self.node.dyn_clone(), + #[cfg(feature = "layout-cache")] hash: self.hash, } } @@ -128,6 +155,7 @@ impl PartialEq for AnyNode { } } +#[cfg(feature = "layout-cache")] impl Hash for AnyNode { fn hash(&self, state: &mut H) { state.write_u64(self.hash); @@ -184,6 +212,7 @@ pub struct LayoutContext<'a> { /// A cache for loaded fonts and artifacts from past layouting. pub cache: &'a mut Cache, /// How deeply nested the current layout tree position is. + #[cfg(feature = "layout-cache")] pub level: usize, } diff --git a/src/layout/pad.rs b/src/layout/pad.rs index 9461f3ff2..75ed366ce 100644 --- a/src/layout/pad.rs +++ b/src/layout/pad.rs @@ -1,7 +1,8 @@ use super::*; /// A node that adds padding to its child. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] pub struct PadNode { /// The amount of padding. pub padding: Sides, diff --git a/src/layout/par.rs b/src/layout/par.rs index 45eefe29f..464853e02 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -11,7 +11,8 @@ use crate::util::{RangeExt, SliceExt}; type Range = std::ops::Range; /// A node that arranges its children into a paragraph. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] pub struct ParNode { /// The inline direction of this paragraph. pub dir: Dir, @@ -22,7 +23,8 @@ pub struct ParNode { } /// A child of a paragraph node. -#[derive(Clone, PartialEq, Hash)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] pub enum ParChild { /// Spacing between other nodes. Spacing(Length), diff --git a/src/layout/stack.rs b/src/layout/stack.rs index fa5fd5840..d8e30b2a3 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -3,7 +3,8 @@ use decorum::N64; use super::*; /// A node that stacks its children. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] pub struct StackNode { /// The `main` and `cross` directions of this stack. /// @@ -19,7 +20,8 @@ pub struct StackNode { } /// A child of a stack node. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] pub enum StackChild { /// Spacing between other nodes. Spacing(Length), diff --git a/tests/typeset.rs b/tests/typeset.rs index 31da3ce0f..36439f52e 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -272,46 +272,49 @@ fn test_part( } } - let reference_cache = cache.layout.clone(); - for level in 0 .. reference_cache.levels() { - cache.layout = reference_cache.clone(); - cache.layout.retain(|x| x == level); - if cache.layout.frames.is_empty() { - continue; + #[cfg(feature = "layout-cache")] + { + let reference_cache = cache.layout.clone(); + for level in 0 .. reference_cache.levels() { + cache.layout = reference_cache.clone(); + cache.layout.retain(|x| x == level); + if cache.layout.frames.is_empty() { + continue; + } + + cache.layout.turnaround(); + + let cached_result = layout(loader, cache, &executed.output); + + let misses = cache + .layout + .frames + .iter() + .flat_map(|(_, e)| e) + .filter(|e| e.level == level && !e.hit() && e.age() == 2) + .count(); + + if misses > 0 { + ok = false; + println!( + " Recompilation had {} cache misses on level {} (Subtest {}) ❌", + misses, level, i + ); + } + + if cached_result != layouted { + ok = false; + println!( + " Recompilation of subtest {} differs from clean pass ❌", + i + ); + } } + cache.layout = reference_cache; cache.layout.turnaround(); - - let cached_result = layout(loader, cache, &executed.output); - - let misses = cache - .layout - .frames - .iter() - .flat_map(|(_, e)| e) - .filter(|e| e.level == level && !e.hit() && e.age() == 2) - .count(); - - if misses > 0 { - ok = false; - println!( - " Recompilation had {} cache misses on level {} (Subtest {}) ❌", - misses, level, i - ); - } - - if cached_result != layouted { - ok = false; - println!( - " Recompilation of subtest {} differs from clean pass ❌", - i - ); - } } - cache.layout = reference_cache; - cache.layout.turnaround(); - if !compare_ref { layouted.clear(); }