mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Pattern properties (#42)
Included in this package are: * Code review I: The unnamed review. * Code Review II: How I met your review. * Code Review III: Code, the final frontier. These are the voyages of the USS Review ...
This commit is contained in:
parent
c44ecbfbd2
commit
fdab7158c9
@ -22,8 +22,10 @@ opt-level = 2
|
|||||||
decorum = { version = "0.3.1", default-features = false, features = ["serialize-serde"] }
|
decorum = { version = "0.3.1", default-features = false, features = ["serialize-serde"] }
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
|
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
|
||||||
|
itertools = "0.10"
|
||||||
miniz_oxide = "0.4"
|
miniz_oxide = "0.4"
|
||||||
pdf-writer = "0.3"
|
pdf-writer = "0.3"
|
||||||
|
rand = "0.8"
|
||||||
rustybuzz = "0.4"
|
rustybuzz = "0.4"
|
||||||
serde = { version = "1", features = ["derive", "rc"] }
|
serde = { version = "1", features = ["derive", "rc"] }
|
||||||
ttf-parser = "0.12"
|
ttf-parser = "0.12"
|
||||||
|
@ -4,7 +4,7 @@ use iai::{black_box, main, Iai};
|
|||||||
|
|
||||||
use typst::eval::eval;
|
use typst::eval::eval;
|
||||||
use typst::layout::layout;
|
use typst::layout::layout;
|
||||||
use typst::loading::{MemLoader};
|
use typst::loading::MemLoader;
|
||||||
use typst::parse::{parse, Scanner, TokenMode, Tokens};
|
use typst::parse::{parse, Scanner, TokenMode, Tokens};
|
||||||
use typst::source::{SourceFile, SourceId};
|
use typst::source::{SourceFile, SourceId};
|
||||||
use typst::Context;
|
use typst::Context;
|
||||||
|
96
src/layout/constraints.rs
Normal file
96
src/layout/constraints.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use crate::util::OptionExt;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Carries an item that is only valid in certain regions and the constraints
|
||||||
|
/// that describe these regions.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct Constrained<T> {
|
||||||
|
/// The item that is only valid if the constraints are fullfilled.
|
||||||
|
pub item: T,
|
||||||
|
/// Constraints on regions in which the item is valid.
|
||||||
|
pub constraints: Constraints,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for Constrained<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describe regions that match them.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct Constraints {
|
||||||
|
/// The minimum available length in the region.
|
||||||
|
pub min: Spec<Option<Length>>,
|
||||||
|
/// The maximum available length in the region.
|
||||||
|
pub max: Spec<Option<Length>>,
|
||||||
|
/// The available length in the region.
|
||||||
|
pub exact: Spec<Option<Length>>,
|
||||||
|
/// The base length of the region used for relative length resolution.
|
||||||
|
pub base: Spec<Option<Length>>,
|
||||||
|
/// The expand settings of the region.
|
||||||
|
pub expand: Spec<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Constraints {
|
||||||
|
/// Create a new region constraint.
|
||||||
|
pub fn new(expand: Spec<bool>) -> Self {
|
||||||
|
Self {
|
||||||
|
min: Spec::default(),
|
||||||
|
max: Spec::default(),
|
||||||
|
exact: Spec::default(),
|
||||||
|
base: Spec::default(),
|
||||||
|
expand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the constraints are fullfilled in a region with the given
|
||||||
|
/// properties.
|
||||||
|
pub fn check(&self, current: Size, base: Size, expand: Spec<bool>) -> bool {
|
||||||
|
let current = current.to_spec();
|
||||||
|
let base = base.to_spec();
|
||||||
|
self.expand == expand
|
||||||
|
&& 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(
|
||||||
|
&mut self,
|
||||||
|
size: Spec<Option<Linear>>,
|
||||||
|
regions: &Regions,
|
||||||
|
) {
|
||||||
|
// The full sizes need to be equal if there is a relative component in the sizes.
|
||||||
|
if size.horizontal.map_or(false, |l| l.is_relative()) {
|
||||||
|
self.base.horizontal = Some(regions.base.width);
|
||||||
|
}
|
||||||
|
if size.vertical.map_or(false, |l| l.is_relative()) {
|
||||||
|
self.base.vertical = Some(regions.base.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes all constraints by adding the `size` to them if they are `Some`.
|
||||||
|
pub fn inflate(&mut self, size: Size, regions: &Regions) {
|
||||||
|
for spec in [&mut self.min, &mut self.max] {
|
||||||
|
if let Some(horizontal) = spec.horizontal.as_mut() {
|
||||||
|
*horizontal += size.width;
|
||||||
|
}
|
||||||
|
if let Some(vertical) = spec.vertical.as_mut() {
|
||||||
|
*vertical += size.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.exact.horizontal.and_set(Some(regions.current.width));
|
||||||
|
self.exact.vertical.and_set(Some(regions.current.height));
|
||||||
|
self.base.horizontal.and_set(Some(regions.base.width));
|
||||||
|
self.base.vertical.and_set(Some(regions.base.height));
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,18 @@
|
|||||||
#[cfg(feature = "layout-cache")]
|
use std::cmp::Reverse;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Deref;
|
|
||||||
|
use decorum::N32;
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
const CACHE_SIZE: usize = 20;
|
||||||
|
const TEMP_LEN: usize = 5;
|
||||||
|
const TEMP_LAST: usize = TEMP_LEN - 1;
|
||||||
|
|
||||||
/// Caches layouting artifacts.
|
/// Caches layouting artifacts.
|
||||||
///
|
///
|
||||||
/// _This is only available when the `layout-cache` feature is enabled._
|
/// _This is only available when the `layout-cache` feature is enabled._
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct LayoutCache {
|
pub struct LayoutCache {
|
||||||
/// Maps from node hashes to the resulting frames and regions in which the
|
/// Maps from node hashes to the resulting frames and regions in which the
|
||||||
@ -17,13 +22,18 @@ pub struct LayoutCache {
|
|||||||
frames: HashMap<u64, Vec<FramesEntry>>,
|
frames: HashMap<u64, Vec<FramesEntry>>,
|
||||||
/// In how many compilations this cache has been used.
|
/// In how many compilations this cache has been used.
|
||||||
age: usize,
|
age: usize,
|
||||||
|
/// What cache eviction policy should be used.
|
||||||
|
policy: EvictionStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
impl LayoutCache {
|
impl LayoutCache {
|
||||||
/// Create a new, empty layout cache.
|
/// Create a new, empty layout cache.
|
||||||
pub fn new() -> Self {
|
pub fn new(policy: EvictionStrategy) -> Self {
|
||||||
Self::default()
|
Self {
|
||||||
|
frames: HashMap::default(),
|
||||||
|
age: 0,
|
||||||
|
policy,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the cache is empty.
|
/// Whether the cache is empty.
|
||||||
@ -79,7 +89,7 @@ impl LayoutCache {
|
|||||||
self.frames.clear();
|
self.frames.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retain all elements for which the closure on the level returns `true`.
|
/// Retains all elements for which the closure on the level returns `true`.
|
||||||
pub fn retain<F>(&mut self, mut f: F)
|
pub fn retain<F>(&mut self, mut f: F)
|
||||||
where
|
where
|
||||||
F: FnMut(usize) -> bool,
|
F: FnMut(usize) -> bool,
|
||||||
@ -93,19 +103,112 @@ impl LayoutCache {
|
|||||||
pub fn turnaround(&mut self) {
|
pub fn turnaround(&mut self) {
|
||||||
self.age += 1;
|
self.age += 1;
|
||||||
for entry in self.frames.values_mut().flatten() {
|
for entry in self.frames.values_mut().flatten() {
|
||||||
for i in 0 .. (entry.temperature.len() - 1) {
|
if entry.temperature[0] > 0 {
|
||||||
entry.temperature[i + 1] = entry.temperature[i];
|
entry.used_cycles += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let last = entry.temperature[TEMP_LAST];
|
||||||
|
|
||||||
|
for i in (1 .. TEMP_LEN).rev() {
|
||||||
|
entry.temperature[i] = entry.temperature[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
entry.temperature[0] = 0;
|
entry.temperature[0] = 0;
|
||||||
|
entry.temperature[TEMP_LAST] += last;
|
||||||
|
|
||||||
entry.age += 1;
|
entry.age += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.evict();
|
||||||
|
|
||||||
|
self.frames.retain(|_, v| !v.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evict(&mut self) {
|
||||||
|
let len = self.len();
|
||||||
|
if len <= CACHE_SIZE {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.policy {
|
||||||
|
EvictionStrategy::LeastRecentlyUsed => {
|
||||||
|
// We find the element with the largest cooldown that cannot fit
|
||||||
|
// anymore.
|
||||||
|
let threshold = self
|
||||||
|
.frames
|
||||||
|
.values()
|
||||||
|
.flatten()
|
||||||
|
.map(|f| Reverse(f.cooldown()))
|
||||||
|
.k_smallest(len - CACHE_SIZE)
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
|
||||||
|
for entries in self.frames.values_mut() {
|
||||||
|
entries.retain(|e| e.cooldown() < threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EvictionStrategy::LeastFrequentlyUsed => {
|
||||||
|
let threshold = self
|
||||||
|
.frames
|
||||||
|
.values()
|
||||||
|
.flatten()
|
||||||
|
.map(|f| N32::from(f.hits() as f32 / f.age() as f32))
|
||||||
|
.k_smallest(len - CACHE_SIZE)
|
||||||
|
.last()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for entries in self.frames.values_mut() {
|
||||||
|
entries.retain(|f| {
|
||||||
|
f.hits() as f32 / f.age() as f32 > threshold.into_inner()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EvictionStrategy::Random => {
|
||||||
|
// Fraction of items that should be kept.
|
||||||
|
let threshold = CACHE_SIZE as f32 / len as f32;
|
||||||
|
for entries in self.frames.values_mut() {
|
||||||
|
entries.retain(|_| rand::random::<f32>() > threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EvictionStrategy::Patterns => {
|
||||||
|
let kept = self
|
||||||
|
.frames
|
||||||
|
.values()
|
||||||
|
.flatten()
|
||||||
|
.filter(|f| f.properties().must_keep())
|
||||||
|
.count();
|
||||||
|
|
||||||
|
let remaining_capacity = CACHE_SIZE - kept.min(CACHE_SIZE);
|
||||||
|
if len - kept <= remaining_capacity {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let threshold = self
|
||||||
|
.frames
|
||||||
|
.values()
|
||||||
|
.flatten()
|
||||||
|
.filter(|f| !f.properties().must_keep())
|
||||||
|
.map(|f| N32::from(f.hits() as f32 / f.age() as f32))
|
||||||
|
.k_smallest((len - kept) - remaining_capacity)
|
||||||
|
.last()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for (_, entries) in self.frames.iter_mut() {
|
||||||
|
entries.retain(|f| {
|
||||||
|
f.properties().must_keep()
|
||||||
|
|| f.hits() as f32 / f.age() as f32 > threshold.into_inner()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EvictionStrategy::None => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cached frames from past layouting.
|
/// Cached frames from past layouting.
|
||||||
///
|
///
|
||||||
/// _This is only available when the `layout-cache` feature is enabled._
|
/// _This is only available when the `layout-cache` feature is enabled._
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FramesEntry {
|
pub struct FramesEntry {
|
||||||
/// The cached frames for a node.
|
/// The cached frames for a node.
|
||||||
@ -115,11 +218,13 @@ pub struct FramesEntry {
|
|||||||
/// For how long the element already exists.
|
/// For how long the element already exists.
|
||||||
age: usize,
|
age: usize,
|
||||||
/// How much the element was accessed during the last five compilations, the
|
/// How much the element was accessed during the last five compilations, the
|
||||||
/// most recent one being the first element.
|
/// most recent one being the first element. The last element will collect
|
||||||
temperature: [usize; 5],
|
/// all usages that are farther in the past.
|
||||||
|
temperature: [usize; TEMP_LEN],
|
||||||
|
/// Amount of cycles in which the element has been used at all.
|
||||||
|
used_cycles: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
impl FramesEntry {
|
impl FramesEntry {
|
||||||
/// Construct a new instance.
|
/// Construct a new instance.
|
||||||
pub fn new(frames: Vec<Constrained<Rc<Frame>>>, level: usize) -> Self {
|
pub fn new(frames: Vec<Constrained<Rc<Frame>>>, level: usize) -> Self {
|
||||||
@ -127,7 +232,8 @@ impl FramesEntry {
|
|||||||
frames,
|
frames,
|
||||||
level,
|
level,
|
||||||
age: 1,
|
age: 1,
|
||||||
temperature: [0; 5],
|
temperature: [0; TEMP_LEN],
|
||||||
|
used_cycles: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +270,7 @@ impl FramesEntry {
|
|||||||
/// The amount of consecutive cycles in which this item has not been used.
|
/// The amount of consecutive cycles in which this item has not been used.
|
||||||
pub fn cooldown(&self) -> usize {
|
pub fn cooldown(&self) -> usize {
|
||||||
let mut cycle = 0;
|
let mut cycle = 0;
|
||||||
for &temp in &self.temperature[.. self.age] {
|
for &temp in &self.temperature[.. self.age.min(TEMP_LEN)] {
|
||||||
if temp > 0 {
|
if temp > 0 {
|
||||||
return cycle;
|
return cycle;
|
||||||
}
|
}
|
||||||
@ -172,103 +278,204 @@ impl FramesEntry {
|
|||||||
}
|
}
|
||||||
cycle
|
cycle
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Carries an item that is only valid in certain regions and the constraints
|
/// Get the total amount of hits over the lifetime of this item.
|
||||||
/// that describe these regions.
|
pub fn hits(&self) -> usize {
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
self.temperature.iter().sum()
|
||||||
pub struct Constrained<T> {
|
|
||||||
/// The item that is only valid if the constraints are fullfilled.
|
|
||||||
pub item: T,
|
|
||||||
/// Constraints on regions in which the item is valid.
|
|
||||||
pub constraints: Constraints,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for Constrained<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describe regions that match them.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
pub struct Constraints {
|
|
||||||
/// The minimum available length in the region.
|
|
||||||
pub min: Spec<Option<Length>>,
|
|
||||||
/// The maximum available length in the region.
|
|
||||||
pub max: Spec<Option<Length>>,
|
|
||||||
/// The available length in the region.
|
|
||||||
pub exact: Spec<Option<Length>>,
|
|
||||||
/// The base length of the region used for relative length resolution.
|
|
||||||
pub base: Spec<Option<Length>>,
|
|
||||||
/// The expand settings of the region.
|
|
||||||
pub expand: Spec<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Constraints {
|
|
||||||
/// Create a new region constraint.
|
|
||||||
pub fn new(expand: Spec<bool>) -> Self {
|
|
||||||
Self {
|
|
||||||
min: Spec::default(),
|
|
||||||
max: Spec::default(),
|
|
||||||
exact: Spec::default(),
|
|
||||||
base: Spec::default(),
|
|
||||||
expand,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether the constraints are fullfilled in a region with the given
|
pub fn properties(&self) -> PatternProperties {
|
||||||
/// properties.
|
let mut all_zeros = true;
|
||||||
pub fn check(&self, current: Size, base: Size, expand: Spec<bool>) -> bool {
|
let mut multi_use = false;
|
||||||
let current = current.to_spec();
|
let mut decreasing = true;
|
||||||
let base = base.to_spec();
|
let mut sparse = false;
|
||||||
self.expand == expand
|
let mut abandoned = false;
|
||||||
&& 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
|
let mut last = None;
|
||||||
/// metrics, respectively.
|
let mut all_same = true;
|
||||||
pub fn set_base_using_linears(
|
|
||||||
&mut self,
|
|
||||||
size: Spec<Option<Linear>>,
|
|
||||||
regions: &Regions,
|
|
||||||
) {
|
|
||||||
// The full sizes need to be equal if there is a relative component in the sizes.
|
|
||||||
if size.horizontal.map_or(false, |l| l.is_relative()) {
|
|
||||||
self.base.horizontal = Some(regions.base.width);
|
|
||||||
}
|
|
||||||
if size.vertical.map_or(false, |l| l.is_relative()) {
|
|
||||||
self.base.vertical = Some(regions.base.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Changes all constraints by adding the `size` to them if they are `Some`.
|
for (i, &temp) in self.temperature[.. TEMP_LAST].iter().enumerate() {
|
||||||
pub fn inflate(&mut self, size: Size, regions: &Regions) {
|
if temp == 0 && !all_zeros {
|
||||||
for spec in [
|
sparse = true;
|
||||||
&mut self.min,
|
|
||||||
&mut self.max,
|
|
||||||
&mut self.exact,
|
|
||||||
&mut self.base,
|
|
||||||
] {
|
|
||||||
if let Some(horizontal) = spec.horizontal.as_mut() {
|
|
||||||
*horizontal += size.width;
|
|
||||||
}
|
}
|
||||||
if let Some(vertical) = spec.vertical.as_mut() {
|
|
||||||
*vertical += size.height;
|
if temp != 0 {
|
||||||
|
all_zeros = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if all_zeros && i == 1 {
|
||||||
|
abandoned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if temp > 1 {
|
||||||
|
multi_use = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(prev) = last {
|
||||||
|
if prev > temp {
|
||||||
|
decreasing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if temp != prev {
|
||||||
|
all_same = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last = Some(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
let current = regions.current.to_spec();
|
if self.age >= TEMP_LEN && self.age - TEMP_LAST < self.temperature[TEMP_LAST] {
|
||||||
let base = regions.base.to_spec();
|
multi_use = true;
|
||||||
|
}
|
||||||
|
|
||||||
self.exact.horizontal.and_set(Some(current.horizontal));
|
if self.temperature[TEMP_LAST] > 0 {
|
||||||
self.exact.vertical.and_set(Some(current.vertical));
|
all_zeros = false;
|
||||||
self.base.horizontal.and_set(Some(base.horizontal));
|
}
|
||||||
self.base.vertical.and_set(Some(base.vertical));
|
|
||||||
|
decreasing = decreasing && !all_same;
|
||||||
|
|
||||||
|
PatternProperties {
|
||||||
|
mature: self.age >= TEMP_LEN,
|
||||||
|
hit: self.temperature[0] >= 1,
|
||||||
|
top_level: self.level == 0,
|
||||||
|
all_zeros,
|
||||||
|
multi_use,
|
||||||
|
decreasing,
|
||||||
|
sparse,
|
||||||
|
abandoned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cache eviction strategies.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum EvictionStrategy {
|
||||||
|
/// Evict the least recently used item.
|
||||||
|
LeastRecentlyUsed,
|
||||||
|
/// Evict the least frequently used item.
|
||||||
|
LeastFrequentlyUsed,
|
||||||
|
/// Evict randomly.
|
||||||
|
Random,
|
||||||
|
/// Use the pattern verdicts.
|
||||||
|
Patterns,
|
||||||
|
/// Do not evict.
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EvictionStrategy {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Patterns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes the properties that this entry's temperature array has.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct PatternProperties {
|
||||||
|
/// There only are zero values.
|
||||||
|
pub all_zeros: bool,
|
||||||
|
/// The entry exists for more or equal time as the temperature array is long.
|
||||||
|
pub mature: bool,
|
||||||
|
/// The entry was used more than one time in at least one compilation.
|
||||||
|
pub multi_use: bool,
|
||||||
|
/// The entry was used in the last compilation.
|
||||||
|
pub hit: bool,
|
||||||
|
/// The temperature is monotonously decreasing in non-terminal temperature fields.
|
||||||
|
pub decreasing: bool,
|
||||||
|
/// There are zero temperatures after non-zero temperatures.
|
||||||
|
pub sparse: bool,
|
||||||
|
/// There are multiple zero temperatures at the front of the temperature array.
|
||||||
|
pub abandoned: bool,
|
||||||
|
/// If the item is on the top level.
|
||||||
|
pub top_level: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatternProperties {
|
||||||
|
/// Check if it is vital to keep an entry based on its properties.
|
||||||
|
pub fn must_keep(&self) -> bool {
|
||||||
|
if self.top_level && !self.mature {
|
||||||
|
// Keep an undo stack.
|
||||||
|
true
|
||||||
|
} else if self.all_zeros && !self.mature {
|
||||||
|
// Keep the most recently created items, even if they have not yet
|
||||||
|
// been used.
|
||||||
|
true
|
||||||
|
} else if self.multi_use && !self.abandoned {
|
||||||
|
true
|
||||||
|
} else if self.hit {
|
||||||
|
true
|
||||||
|
} else if self.sparse {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn empty_frame() -> Vec<Constrained<Rc<Frame>>> {
|
||||||
|
vec![Constrained {
|
||||||
|
item: Rc::new(Frame::default()),
|
||||||
|
constraints: Constraints::new(Spec::splat(false)),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zero_region() -> Regions {
|
||||||
|
Regions::one(Size::zero(), Spec::splat(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_temperature() {
|
||||||
|
let mut cache = LayoutCache::new(EvictionStrategy::None);
|
||||||
|
let zero_region = zero_region();
|
||||||
|
cache.policy = EvictionStrategy::None;
|
||||||
|
cache.insert(0, empty_frame(), 0);
|
||||||
|
|
||||||
|
let entry = cache.frames.get(&0).unwrap().first().unwrap();
|
||||||
|
assert_eq!(entry.age(), 1);
|
||||||
|
assert_eq!(entry.temperature, [0, 0, 0, 0, 0]);
|
||||||
|
assert_eq!(entry.used_cycles, 0);
|
||||||
|
assert_eq!(entry.level, 0);
|
||||||
|
|
||||||
|
cache.get(0, &zero_region).unwrap();
|
||||||
|
let entry = cache.frames.get(&0).unwrap().first().unwrap();
|
||||||
|
assert_eq!(entry.age(), 1);
|
||||||
|
assert_eq!(entry.temperature, [1, 0, 0, 0, 0]);
|
||||||
|
|
||||||
|
cache.turnaround();
|
||||||
|
let entry = cache.frames.get(&0).unwrap().first().unwrap();
|
||||||
|
assert_eq!(entry.age(), 2);
|
||||||
|
assert_eq!(entry.temperature, [0, 1, 0, 0, 0]);
|
||||||
|
assert_eq!(entry.used_cycles, 1);
|
||||||
|
|
||||||
|
cache.get(0, &zero_region).unwrap();
|
||||||
|
for _ in 0 .. 4 {
|
||||||
|
cache.turnaround();
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = cache.frames.get(&0).unwrap().first().unwrap();
|
||||||
|
assert_eq!(entry.age(), 6);
|
||||||
|
assert_eq!(entry.temperature, [0, 0, 0, 0, 2]);
|
||||||
|
assert_eq!(entry.used_cycles, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_properties() {
|
||||||
|
let mut cache = LayoutCache::new(EvictionStrategy::None);
|
||||||
|
cache.policy = EvictionStrategy::None;
|
||||||
|
cache.insert(0, empty_frame(), 1);
|
||||||
|
|
||||||
|
let props = cache.frames.get(&0).unwrap().first().unwrap().properties();
|
||||||
|
assert_eq!(props.top_level, false);
|
||||||
|
assert_eq!(props.mature, false);
|
||||||
|
assert_eq!(props.multi_use, false);
|
||||||
|
assert_eq!(props.hit, false);
|
||||||
|
assert_eq!(props.decreasing, false);
|
||||||
|
assert_eq!(props.sparse, false);
|
||||||
|
assert_eq!(props.abandoned, true);
|
||||||
|
assert_eq!(props.all_zeros, true);
|
||||||
|
assert_eq!(props.must_keep(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
//! Layouting.
|
//! Layouting.
|
||||||
|
|
||||||
mod background;
|
mod background;
|
||||||
|
mod constraints;
|
||||||
mod fixed;
|
mod fixed;
|
||||||
mod frame;
|
mod frame;
|
||||||
mod grid;
|
mod grid;
|
||||||
mod image;
|
mod image;
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
mod incremental;
|
mod incremental;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod par;
|
mod par;
|
||||||
@ -14,9 +16,11 @@ mod tree;
|
|||||||
|
|
||||||
pub use self::image::*;
|
pub use self::image::*;
|
||||||
pub use background::*;
|
pub use background::*;
|
||||||
|
pub use constraints::*;
|
||||||
pub use fixed::*;
|
pub use fixed::*;
|
||||||
pub use frame::*;
|
pub use frame::*;
|
||||||
pub use grid::*;
|
pub use grid::*;
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
pub use incremental::*;
|
pub use incremental::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use par::*;
|
pub use par::*;
|
||||||
|
17
src/lib.rs
17
src/lib.rs
@ -49,15 +49,15 @@ pub mod util;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::{Scope, State, Module};
|
use crate::eval::{Module, Scope, State};
|
||||||
use crate::syntax::SyntaxTree;
|
|
||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
use crate::layout::LayoutCache;
|
use crate::layout::{EvictionStrategy, LayoutCache};
|
||||||
use crate::layout::{Frame, LayoutTree};
|
use crate::layout::{Frame, LayoutTree};
|
||||||
use crate::loading::Loader;
|
use crate::loading::Loader;
|
||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::{SourceId, SourceStore};
|
||||||
|
use crate::syntax::SyntaxTree;
|
||||||
|
|
||||||
/// The core context which holds the loader, configuration and cached artifacts.
|
/// The core context which holds the loader, configuration and cached artifacts.
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
@ -141,6 +141,8 @@ impl Context {
|
|||||||
pub struct ContextBuilder {
|
pub struct ContextBuilder {
|
||||||
std: Option<Scope>,
|
std: Option<Scope>,
|
||||||
state: Option<State>,
|
state: Option<State>,
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
policy: Option<EvictionStrategy>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextBuilder {
|
impl ContextBuilder {
|
||||||
@ -157,6 +159,13 @@ impl ContextBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The policy for eviction of the layout cache.
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
pub fn policy(mut self, policy: EvictionStrategy) -> Self {
|
||||||
|
self.policy = Some(policy);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Finish building the context by providing the `loader` used to load
|
/// Finish building the context by providing the `loader` used to load
|
||||||
/// fonts, images, source files and other resources.
|
/// fonts, images, source files and other resources.
|
||||||
pub fn build(self, loader: Rc<dyn Loader>) -> Context {
|
pub fn build(self, loader: Rc<dyn Loader>) -> Context {
|
||||||
@ -166,7 +175,7 @@ impl ContextBuilder {
|
|||||||
images: ImageStore::new(Rc::clone(&loader)),
|
images: ImageStore::new(Rc::clone(&loader)),
|
||||||
loader,
|
loader,
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
layouts: LayoutCache::new(),
|
layouts: LayoutCache::new(self.policy.unwrap_or_default()),
|
||||||
std: self.std.unwrap_or(library::new()),
|
std: self.std.unwrap_or(library::new()),
|
||||||
state: self.state.unwrap_or_default(),
|
state: self.state.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,9 @@ use typst::diag::Error;
|
|||||||
use typst::eval::{State, Value};
|
use typst::eval::{State, Value};
|
||||||
use typst::geom::{self, Length, PathElement, Point, Sides, Size};
|
use typst::geom::{self, Length, PathElement, Point, Sides, Size};
|
||||||
use typst::image::ImageId;
|
use typst::image::ImageId;
|
||||||
use typst::layout::{layout, Element, Frame, Geometry, LayoutTree, Paint, Text};
|
#[cfg(feature = "layout-cache")]
|
||||||
|
use typst::layout::LayoutTree;
|
||||||
|
use typst::layout::{layout, Element, Frame, Geometry, Paint, Text};
|
||||||
use typst::loading::FsLoader;
|
use typst::loading::FsLoader;
|
||||||
use typst::parse::Scanner;
|
use typst::parse::Scanner;
|
||||||
use typst::source::SourceFile;
|
use typst::source::SourceFile;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user