Move and refactor

This commit is contained in:
Laurenz 2021-07-21 20:35:02 +02:00
parent 927f1154fa
commit adb71ee040
7 changed files with 284 additions and 269 deletions

View File

@ -43,6 +43,9 @@ pub fn eval(ctx: &mut Context, file: FileId, ast: Rc<SyntaxTree>) -> Pass<Module
Pass::new(module, ctx.diags)
}
/// Caches evaluated modules.
pub type ModuleCache = HashMap<FileId, Module>;
/// 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<FileId>,
/// A map of loaded module.
pub modules: HashMap<FileId, Module>,
}
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<SyntaxTree> {
type Output = Template;

View File

@ -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<u64, Vec<FramesEntry>>,
frames: HashMap<u64, Vec<FramesEntry>>,
/// 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<F>(&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<Item = &FramesEntry> + '_ {
self.frames.values().flatten()
}
/// Fetch matching cached frames if there are any.
pub fn get(
&mut self,
hash: u64,
regions: Regions,
) -> Option<Vec<Constrained<Rc<Frame>>>> {
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<F>(&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<Constrained<Rc<Frame>>>,
frames: Vec<Constrained<Rc<Frame>>>,
/// 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<Vec<Constrained<Rc<Frame>>>> {
for (i, frame) in self.frames.iter().enumerate() {
if (i != 0 && !regions.next()) || !frame.constraints.check(&regions) {
@ -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<T> {
pub item: T,
pub constraints: Constraints,
}
impl<T> Deref for Constrained<T> {
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<T> {
pub item: T,
pub constraints: Constraints,
}
impl<T> Deref for Constrained<T> {
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 {

View File

@ -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<Rc<Frame>> {
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<PageRun>,
}
impl LayoutTree {
/// Layout the tree into a collection of frames.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
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<Rc<Frame>> {
// 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, &regions).into_iter().map(|c| c.item).collect()
}
}
/// A dynamic layouting node.
pub struct LayoutNode {
node: Box<dyn Bounds>,
#[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<T>(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<T>(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<Constrained<Rc<Frame>>> {
#[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<H: Hasher>(&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<dyn Bounds>;
}
impl<T> 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>() {
self == other
} else {
false
}
}
fn dyn_clone(&self) -> Box<dyn Bounds> {
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")]

View File

@ -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);

158
src/layout/tree.rs Normal file
View File

@ -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<PageRun>,
}
impl LayoutTree {
/// Layout the tree into a collection of frames.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
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<Rc<Frame>> {
// 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, &regions).into_iter().map(|c| c.item).collect()
}
}
/// A dynamic layouting node.
pub struct LayoutNode {
node: Box<dyn Bounds>,
#[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<T>(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<T>(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<Constrained<Rc<Frame>>> {
#[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<H: Hasher>(&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<dyn Bounds>;
}
impl<T> 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>() {
self == other
} else {
false
}
}
fn dyn_clone(&self) -> Box<dyn Bounds> {
Box::new(self.clone())
}
}

View File

@ -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()),

View File

@ -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 {