From adb71ee040752f9348b0c9a511b2ab7e3710cb80 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 21 Jul 2021 20:35:02 +0200 Subject: [PATCH] Move and refactor --- src/eval/mod.rs | 27 +++--- src/layout/incremental.rs | 188 ++++++++++++++++++++------------------ src/layout/mod.rs | 162 +------------------------------- src/layout/par.rs | 2 +- src/layout/tree.rs | 158 ++++++++++++++++++++++++++++++++ src/lib.rs | 5 +- tests/typeset.rs | 11 ++- 7 files changed, 284 insertions(+), 269 deletions(-) create mode 100644 src/layout/tree.rs diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 34986ffa9..5b00819b9 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -43,6 +43,9 @@ pub fn eval(ctx: &mut Context, file: FileId, ast: Rc) -> Pass; + /// An evaluated module, ready for importing or execution. #[derive(Debug, Clone, PartialEq)] pub struct Module { @@ -52,20 +55,29 @@ pub struct Module { pub template: Template, } +/// Evaluate an expression. +pub trait Eval { + /// The output of evaluating the expression. + type Output; + + /// Evaluate the expression to the output value. + fn eval(&self, ctx: &mut EvalContext) -> Self::Output; +} + /// The context for evaluation. pub struct EvalContext<'a> { /// The loader from which resources (files and images) are loaded. pub loader: &'a dyn Loader, /// The cache for decoded images. pub images: &'a mut ImageCache, + /// The cache for loaded modules. + pub modules: &'a mut ModuleCache, /// The active scopes. pub scopes: Scopes<'a>, /// Evaluation diagnostics. pub diags: DiagSet, /// The stack of imported files that led to evaluation of the current file. pub route: Vec, - /// A map of loaded module. - pub modules: HashMap, } impl<'a> EvalContext<'a> { @@ -74,10 +86,10 @@ impl<'a> EvalContext<'a> { Self { loader: ctx.loader.as_ref(), images: &mut ctx.images, + modules: &mut ctx.modules, scopes: Scopes::new(Some(&ctx.std)), diags: DiagSet::new(), route: vec![file], - modules: HashMap::new(), } } @@ -184,15 +196,6 @@ impl<'a> EvalContext<'a> { } } -/// Evaluate an expression. -pub trait Eval { - /// The output of evaluating the expression. - type Output; - - /// Evaluate the expression to the output value. - fn eval(&self, ctx: &mut EvalContext) -> Self::Output; -} - impl Eval for Rc { type Output = Template; diff --git a/src/layout/incremental.rs b/src/layout/incremental.rs index ca4839b6f..e4496f06d 100644 --- a/src/layout/incremental.rs +++ b/src/layout/incremental.rs @@ -5,14 +5,16 @@ use std::ops::Deref; use super::*; /// Caches layouting artifacts. -#[derive(Default, Debug, Clone)] +/// +/// _This is only available when the `layout-cache` feature is enabled._ #[cfg(feature = "layout-cache")] +#[derive(Default, Debug, Clone)] 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 /// results because across one or more compilations, multiple different /// layouts of the same node may have been requested. - pub frames: HashMap>, + frames: HashMap>, /// In how many compilations this cache has been used. age: usize, } @@ -24,67 +26,42 @@ impl LayoutCache { Self::default() } - /// Clear the cache. - pub fn clear(&mut self) { - self.frames.clear(); + /// Whether the cache is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 } /// Amount of items in the cache. pub fn len(&self) -> usize { - self.frames.iter().map(|(_, e)| e.len()).sum() + self.frames.values().map(Vec::len).sum() } - /// Retains all elements for which the closure on the level returns `true`. - pub fn retain(&mut self, mut f: F) - where - F: FnMut(usize) -> bool, - { - for (_, entries) in self.frames.iter_mut() { - entries.retain(|entry| f(entry.level)); - } - } - - /// Prepare the cache for the next round of compilation - pub fn turnaround(&mut self) { - self.age += 1; - for entry in self.frames.iter_mut().flat_map(|(_, x)| x.iter_mut()) { - for i in 0 .. (entry.temperature.len() - 1) { - entry.temperature[i + 1] = entry.temperature[i]; - } - entry.temperature[0] = 0; - entry.age += 1; - } - } - - /// The amount of levels stored in the cache. + /// The number of levels stored in the cache. pub fn levels(&self) -> usize { - self.frames - .iter() - .flat_map(|(_, x)| x) - .map(|entry| entry.level + 1) - .max() - .unwrap_or(0) + self.entries().map(|entry| entry.level + 1).max().unwrap_or(0) } - /// Fetches the appropriate entry from the cache if there is any. + /// An iterator over all entries in the cache. + pub fn entries(&self) -> impl Iterator + '_ { + self.frames.values().flatten() + } + + /// Fetch matching cached frames if there are any. pub fn get( &mut self, hash: u64, regions: Regions, ) -> Option>>> { - self.frames.get_mut(&hash).and_then(|frames| { - for frame in frames { - let res = frame.check(regions.clone()); - if res.is_some() { - return res; - } + let entries = self.frames.get_mut(&hash)?; + for entry in entries { + if let Some(frames) = entry.check(regions.clone()) { + return Some(frames); } - - None - }) + } + None } - /// Inserts a new frame set into the cache. + /// Insert a new frame entry into the cache. pub fn insert( &mut self, hash: u64, @@ -99,16 +76,45 @@ impl LayoutCache { } } } + + /// Clear the cache. + pub fn clear(&mut self) { + self.frames.clear(); + } + + /// Retain all elements for which the closure on the level returns `true`. + pub fn retain(&mut self, mut f: F) + where + F: FnMut(usize) -> bool, + { + for entries in self.frames.values_mut() { + entries.retain(|entry| f(entry.level)); + } + } + + /// Prepare the cache for the next round of compilation. + pub fn turnaround(&mut self) { + self.age += 1; + for entry in self.frames.values_mut().flatten() { + for i in 0 .. (entry.temperature.len() - 1) { + entry.temperature[i + 1] = entry.temperature[i]; + } + entry.temperature[0] = 0; + entry.age += 1; + } + } } /// Cached frames from past layouting. -#[derive(Debug, Clone)] +/// +/// _This is only available when the `layout-cache` feature is enabled._ #[cfg(feature = "layout-cache")] +#[derive(Debug, Clone)] pub struct FramesEntry { /// The cached frames for a node. - pub frames: Vec>>, + frames: Vec>>, /// How nested the frame was in the context is was originally appearing in. - pub level: usize, + level: usize, /// For how long the element already exists. age: usize, /// How much the element was accessed during the last five compilations, the @@ -128,7 +134,8 @@ impl FramesEntry { } } - /// Checks if the cached [`Frame`] is valid for the given regions. + /// Checks if the cached frames are valid in the given regions and returns + /// them if so. pub fn check(&mut self, mut regions: Regions) -> Option>>> { for (i, frame) in self.frames.iter().enumerate() { if (i != 0 && !regions.next()) || !frame.constraints.check(®ions) { @@ -137,18 +144,25 @@ impl FramesEntry { } self.temperature[0] += 1; - Some(self.frames.clone()) } - /// Get the amount of compilation cycles this item has remained in the - /// cache. + /// How nested the frame was in the context is was originally appearing in. + pub fn level(&self) -> usize { + self.level + } + + /// The number of compilation cycles this item has remained in the cache. pub fn age(&self) -> usize { self.age } - /// Get the amount of consecutive cycles in which this item has not - /// been used. + /// Whether this element was used in the last compilation cycle. + pub fn hit(&self) -> bool { + self.temperature[0] != 0 + } + + /// The amount of consecutive cycles in which this item has not been used. pub fn cooldown(&self) -> usize { let mut cycle = 0; for &temp in &self.temperature[.. self.age] { @@ -157,13 +171,23 @@ impl FramesEntry { } cycle += 1; } - cycle } +} - /// Whether this element was used in the last compilation cycle. - pub fn hit(&self) -> bool { - self.temperature[0] != 0 +/// Carries an item that only applies to certain regions and the constraints +/// that describe these regions. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct Constrained { + pub item: T, + pub constraints: Constraints, +} + +impl Deref for Constrained { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.item } } @@ -194,6 +218,21 @@ impl Constraints { } } + #[cfg(feature = "layout-cache")] + fn check(&self, regions: &Regions) -> bool { + if self.expand != regions.expand { + return false; + } + + let base = regions.base.to_spec(); + let current = regions.current.to_spec(); + + current.eq_by(&self.min, |x, y| y.map_or(true, |y| x.fits(y))) + && current.eq_by(&self.max, |x, y| y.map_or(true, |y| x < &y)) + && current.eq_by(&self.exact, |x, y| y.map_or(true, |y| x.approx_eq(y))) + && base.eq_by(&self.base, |x, y| y.map_or(true, |y| x.approx_eq(y))) + } + /// Set the appropriate base constraints for (relative) width and height /// metrics, respectively. pub fn set_base_using_linears( @@ -210,21 +249,6 @@ impl Constraints { } } - #[cfg(feature = "layout-cache")] - fn check(&self, regions: &Regions) -> bool { - if self.expand != regions.expand { - return false; - } - - let base = regions.base.to_spec(); - let current = regions.current.to_spec(); - - current.eq_by(&self.min, |x, y| y.map_or(true, |y| x.fits(y))) - && current.eq_by(&self.max, |x, y| y.map_or(true, |y| x < &y)) - && current.eq_by(&self.exact, |x, y| y.map_or(true, |y| x.approx_eq(y))) - && base.eq_by(&self.base, |x, y| y.map_or(true, |y| x.approx_eq(y))) - } - /// Changes all constraints by adding the `size` to them if they are `Some`. pub fn mutate(&mut self, size: Size, regions: &Regions) { for spec in [ @@ -251,22 +275,6 @@ impl Constraints { } } -/// Carries an item that only applies to certain regions and the constraints -/// that describe these regions. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct Constrained { - pub item: T, - pub constraints: Constraints, -} - -impl Deref for Constrained { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.item - } -} - /// Extends length-related options by providing convenience methods for setting /// minimum and maximum lengths on them, even if they are `None`. pub trait OptionExt { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 7f8ee4ff2..22e6e8c8b 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -10,6 +10,7 @@ mod pad; mod par; mod shaping; mod stack; +mod tree; pub use self::image::*; pub use background::*; @@ -21,21 +22,16 @@ pub use pad::*; pub use par::*; pub use shaping::*; pub use stack::*; +pub use tree::*; -use std::any::Any; -use std::fmt::{self, Debug, Formatter}; 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::font::FontCache; use crate::geom::*; use crate::image::ImageCache; -use crate::loading::Loader; use crate::Context; /// Layout a tree into a collection of frames. @@ -44,157 +40,6 @@ pub fn layout(ctx: &mut Context, tree: &LayoutTree) -> Vec> { tree.layout(&mut ctx) } -/// A tree of layout nodes. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct LayoutTree { - /// Runs of pages with the same properties. - pub runs: Vec, -} - -impl LayoutTree { - /// Layout the tree into a collection of frames. - pub fn layout(&self, ctx: &mut LayoutContext) -> Vec> { - self.runs.iter().flat_map(|run| run.layout(ctx)).collect() - } -} - -/// A run of pages that all have the same properties. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct PageRun { - /// The size of each page. - pub size: Size, - /// The layout node that produces the actual pages (typically a - /// [`StackNode`]). - pub child: LayoutNode, -} - -impl PageRun { - /// Layout the page run. - pub fn layout(&self, ctx: &mut LayoutContext) -> Vec> { - // When one of the lengths is infinite the page fits its content along - // that axis. - let Size { width, height } = self.size; - let expand = Spec::new(width.is_finite(), height.is_finite()); - let regions = Regions::repeat(self.size, expand); - self.child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect() - } -} - -/// A dynamic layouting node. -pub struct LayoutNode { - node: Box, - #[cfg(feature = "layout-cache")] - hash: u64, -} - -impl LayoutNode { - /// 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 + Eq + PartialEq + Hash + 'static, - { - 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 + Eq + PartialEq + 'static, - { - Self { node: Box::new(node) } - } -} - -impl Layout for LayoutNode { - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - ) -> Vec>> { - #[cfg(feature = "layout-cache")] - { - ctx.level += 1; - let frames = ctx.layouts.get(self.hash, regions.clone()).unwrap_or_else(|| { - let frames = self.node.layout(ctx, regions); - ctx.layouts.insert(self.hash, frames.clone(), ctx.level - 1); - frames - }); - ctx.level -= 1; - frames - } - - #[cfg(not(feature = "layout-cache"))] - self.node.layout(ctx, regions) - } -} - -impl Debug for LayoutNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.node.fmt(f) - } -} - -impl Clone for LayoutNode { - fn clone(&self) -> Self { - Self { - node: self.node.dyn_clone(), - #[cfg(feature = "layout-cache")] - hash: self.hash, - } - } -} - -impl Eq for LayoutNode {} - -impl PartialEq for LayoutNode { - fn eq(&self, other: &Self) -> bool { - self.node.dyn_eq(other.node.as_ref()) - } -} - -#[cfg(feature = "layout-cache")] -impl Hash for LayoutNode { - fn hash(&self, state: &mut H) { - state.write_u64(self.hash); - } -} - -trait Bounds: Layout + Debug + 'static { - fn as_any(&self) -> &dyn Any; - fn dyn_eq(&self, other: &dyn Bounds) -> bool; - fn dyn_clone(&self) -> Box; -} - -impl Bounds for T -where - T: Layout + Debug + Eq + PartialEq + Clone + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } - - fn dyn_eq(&self, other: &dyn Bounds) -> bool { - if let Some(other) = other.as_any().downcast_ref::() { - self == other - } else { - false - } - } - - fn dyn_clone(&self) -> Box { - Box::new(self.clone()) - } -} - /// Layout a node. pub trait Layout { /// Layout the node into the given regions. @@ -207,8 +52,6 @@ pub trait Layout { /// The context for layouting. pub struct LayoutContext<'a> { - /// The loader from which fonts are loaded. - pub loader: &'a dyn Loader, /// The cache for parsed font faces. pub fonts: &'a mut FontCache, /// The cache for decoded imges. @@ -225,7 +68,6 @@ impl<'a> LayoutContext<'a> { /// Create a new layout context. pub fn new(ctx: &'a mut Context) -> Self { Self { - loader: ctx.loader.as_ref(), fonts: &mut ctx.fonts, images: &mut ctx.images, #[cfg(feature = "layout-cache")] diff --git a/src/layout/par.rs b/src/layout/par.rs index 72b3edfbf..8736452bf 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -47,7 +47,7 @@ impl Layout for ParNode { // Find out the BiDi embedding levels. let bidi = BidiInfo::new(&text, Level::from_dir(self.dir)); - // Prepare paragraph layout by bulding a representation on which we can + // Prepare paragraph layout by building a representation on which we can // do line breaking without layouting each and every line from scratch. let layouter = ParLayouter::new(self, ctx, regions, bidi); diff --git a/src/layout/tree.rs b/src/layout/tree.rs new file mode 100644 index 000000000..258f1cccc --- /dev/null +++ b/src/layout/tree.rs @@ -0,0 +1,158 @@ +use super::*; + +use std::any::Any; +use std::fmt::{self, Debug, Formatter}; + +#[cfg(feature = "layout-cache")] +use fxhash::FxHasher64; + +/// A tree of layout nodes. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct LayoutTree { + /// Runs of pages with the same properties. + pub runs: Vec, +} + +impl LayoutTree { + /// Layout the tree into a collection of frames. + pub fn layout(&self, ctx: &mut LayoutContext) -> Vec> { + self.runs.iter().flat_map(|run| run.layout(ctx)).collect() + } +} + +/// A run of pages that all have the same properties. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct PageRun { + /// The size of each page. + pub size: Size, + /// The layout node that produces the actual pages (typically a + /// [`StackNode`]). + pub child: LayoutNode, +} + +impl PageRun { + /// Layout the page run. + pub fn layout(&self, ctx: &mut LayoutContext) -> Vec> { + // When one of the lengths is infinite the page fits its content along + // that axis. + let Size { width, height } = self.size; + let expand = Spec::new(width.is_finite(), height.is_finite()); + let regions = Regions::repeat(self.size, expand); + self.child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect() + } +} + +/// A dynamic layouting node. +pub struct LayoutNode { + node: Box, + #[cfg(feature = "layout-cache")] + hash: u64, +} + +impl LayoutNode { + /// 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 + Eq + PartialEq + Hash + 'static, + { + 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 + Eq + PartialEq + 'static, + { + Self { node: Box::new(node) } + } +} + +impl Layout for LayoutNode { + fn layout( + &self, + ctx: &mut LayoutContext, + regions: &Regions, + ) -> Vec>> { + #[cfg(feature = "layout-cache")] + { + ctx.level += 1; + let frames = ctx.layouts.get(self.hash, regions.clone()).unwrap_or_else(|| { + let frames = self.node.layout(ctx, regions); + ctx.layouts.insert(self.hash, frames.clone(), ctx.level - 1); + frames + }); + ctx.level -= 1; + frames + } + + #[cfg(not(feature = "layout-cache"))] + self.node.layout(ctx, regions) + } +} + +impl Debug for LayoutNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.node.fmt(f) + } +} + +impl Clone for LayoutNode { + fn clone(&self) -> Self { + Self { + node: self.node.dyn_clone(), + #[cfg(feature = "layout-cache")] + hash: self.hash, + } + } +} + +impl Eq for LayoutNode {} + +impl PartialEq for LayoutNode { + fn eq(&self, other: &Self) -> bool { + self.node.dyn_eq(other.node.as_ref()) + } +} + +#[cfg(feature = "layout-cache")] +impl Hash for LayoutNode { + fn hash(&self, state: &mut H) { + state.write_u64(self.hash); + } +} + +trait Bounds: Layout + Debug + 'static { + fn as_any(&self) -> &dyn Any; + fn dyn_eq(&self, other: &dyn Bounds) -> bool; + fn dyn_clone(&self) -> Box; +} + +impl Bounds for T +where + T: Layout + Debug + Eq + PartialEq + Clone + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } + + fn dyn_eq(&self, other: &dyn Bounds) -> bool { + if let Some(other) = other.as_any().downcast_ref::() { + self == other + } else { + false + } + } + + fn dyn_clone(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 594b4a593..5c4772489 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,7 @@ pub mod util; use std::rc::Rc; use crate::diag::Pass; -use crate::eval::Scope; +use crate::eval::{ModuleCache, Scope}; use crate::exec::State; use crate::font::FontCache; use crate::image::ImageCache; @@ -68,6 +68,8 @@ pub struct Context { pub fonts: FontCache, /// Caches decoded images. pub images: ImageCache, + /// Caches evaluated modules. + pub modules: ModuleCache, /// Caches layouting artifacts. #[cfg(feature = "layout-cache")] pub layouts: LayoutCache, @@ -145,6 +147,7 @@ impl ContextBuilder { loader: Rc::clone(&loader), fonts: FontCache::new(Rc::clone(&loader)), images: ImageCache::new(loader), + modules: ModuleCache::new(), #[cfg(feature = "layout-cache")] layouts: LayoutCache::new(), std: self.std.unwrap_or(library::new()), diff --git a/tests/typeset.rs b/tests/typeset.rs index 5a071c759..08d812fb2 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -246,6 +246,9 @@ fn test_part( let (local_compare_ref, ref_diags) = parse_metadata(src, &map); let compare_ref = local_compare_ref.unwrap_or(compare_ref); + // Clear the module cache between tests. + ctx.modules.clear(); + let ast = parse(src); let module = eval(ctx, src_id, Rc::new(ast.output)); let tree = exec(ctx, &module.output.template); @@ -295,7 +298,7 @@ fn test_part( for level in 0 .. reference.levels() { ctx.layouts = reference.clone(); ctx.layouts.retain(|x| x == level); - if ctx.layouts.frames.is_empty() { + if ctx.layouts.is_empty() { continue; } @@ -304,10 +307,8 @@ fn test_part( let cached = layout(ctx, &tree.output); let misses = ctx .layouts - .frames - .iter() - .flat_map(|(_, e)| e) - .filter(|e| e.level == level && !e.hit() && e.age() == 2) + .entries() + .filter(|e| e.level() == level && !e.hit() && e.age() == 2) .count(); if misses > 0 {