Query cache

This commit is contained in:
Laurenz 2022-02-23 16:46:44 +01:00
parent e1f29d6cb9
commit f2f473a81f
4 changed files with 95 additions and 13 deletions

View File

@ -221,8 +221,10 @@ impl Layout for LayoutNode {
regions: &Regions,
styles: StyleChain,
) -> TypResult<Vec<Arc<Frame>>> {
// TODO(query)
self.0.layout(ctx, regions, styles.barred(self.id()))
ctx.query((self, regions, styles), |ctx, (node, regions, styles)| {
node.0.layout(ctx, regions, styles.barred(node.id()))
})
.clone()
}
fn pack(self) -> LayoutNode {

View File

@ -43,7 +43,10 @@ impl ShowNode {
impl Show for ShowNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Template> {
self.0.show(ctx, styles)
ctx.query((self, styles), |ctx, (node, styles)| {
node.0.show(ctx, styles)
})
.clone()
}
fn pack(self) -> ShowNode {

View File

@ -7,7 +7,6 @@ use tiny_skia as sk;
use ttf_parser::{GlyphId, OutlineBuilder};
use usvg::FitTo;
use crate::font::Face;
use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
use crate::geom::{self, Length, Paint, PathElement, Size, Transform};
use crate::image::{Image, RasterImage, Svg};
@ -115,17 +114,15 @@ fn render_text(
ctx: &mut Context,
text: &Text,
) {
let face = ctx.fonts.get(text.face_id);
let mut x = 0.0;
for glyph in &text.glyphs {
let id = GlyphId(glyph.id);
let offset = x + glyph.x_offset.resolve(text.size).to_f32();
let ts = ts.pre_translate(offset, 0.0);
render_svg_glyph(canvas, ts, mask, text, face, id)
.or_else(|| render_bitmap_glyph(canvas, ts, mask, text, face, id))
.or_else(|| render_outline_glyph(canvas, ts, mask, text, face, id));
render_svg_glyph(canvas, ts, mask, ctx, text, id)
.or_else(|| render_bitmap_glyph(canvas, ts, mask, ctx, text, id))
.or_else(|| render_outline_glyph(canvas, ts, mask, ctx, text, id));
x += glyph.x_advance.resolve(text.size).to_f32();
}
@ -136,10 +133,11 @@ fn render_svg_glyph(
canvas: &mut sk::Pixmap,
ts: sk::Transform,
_: Option<&sk::ClipMask>,
ctx: &mut Context,
text: &Text,
face: &Face,
id: GlyphId,
) -> Option<()> {
let face = ctx.fonts.get(text.face_id);
let mut data = face.ttf().glyph_svg_image(id)?;
// Decompress SVGZ.
@ -186,12 +184,13 @@ fn render_bitmap_glyph(
canvas: &mut sk::Pixmap,
ts: sk::Transform,
mask: Option<&sk::ClipMask>,
ctx: &mut Context,
text: &Text,
face: &Face,
id: GlyphId,
) -> Option<()> {
let size = text.size.to_f32();
let ppem = size * ts.sy;
let face = ctx.fonts.get(text.face_id);
let raster = face.ttf().glyph_raster_image(id, ppem as u16)?;
let img = RasterImage::parse(&raster.data).ok()?;
@ -211,8 +210,8 @@ fn render_outline_glyph(
canvas: &mut sk::Pixmap,
ts: sk::Transform,
mask: Option<&sk::ClipMask>,
ctx: &mut Context,
text: &Text,
face: &Face,
id: GlyphId,
) -> Option<()> {
let ppem = text.size.to_f32() * ts.sy;
@ -221,6 +220,7 @@ fn render_outline_glyph(
// rasterization can't be used due to very large text size or weird
// scale/skewing transforms.
if ppem > 100.0 || ts.kx != 0.0 || ts.ky != 0.0 || ts.sx != ts.sy {
let face = ctx.fonts.get(text.face_id);
let path = {
let mut builder = WrappedPathBuilder(sk::PathBuilder::new());
face.ttf().outline_glyph(id, &mut builder)?;
@ -241,7 +241,11 @@ fn render_outline_glyph(
// TODO(query)
// Try to retrieve a prepared glyph or prepare it from scratch if it
// doesn't exist, yet.
let glyph = pixglyph::Glyph::load(face.ttf(), id)?;
let glyph = ctx
.query((text.face_id, id), |ctx, (face_id, id)| {
pixglyph::Glyph::load(ctx.fonts.get(face_id).ttf(), id)
})
.as_ref()?;
// Rasterize the glyph with `pixglyph`.
let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem);

View File

@ -48,7 +48,10 @@ pub mod parse;
pub mod source;
pub mod syntax;
use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use std::hash::Hash;
use std::path::PathBuf;
use std::sync::Arc;
@ -76,6 +79,8 @@ pub struct Context {
styles: Arc<StyleMap>,
/// Cached modules.
modules: HashMap<SourceId, Module>,
/// Cached queries.
cache: HashMap<u64, CacheEntry>,
/// The stack of imported files that led to evaluation of the current file.
route: Vec<SourceId>,
/// The dependencies of the current evaluation process.
@ -206,6 +211,7 @@ impl ContextBuilder {
std: self.std.unwrap_or_else(|| Arc::new(library::new())),
styles: self.styles.unwrap_or_default(),
modules: HashMap::new(),
cache: HashMap::new(),
route: vec![],
deps: vec![],
}
@ -217,3 +223,70 @@ impl Default for ContextBuilder {
Self { std: None, styles: None }
}
}
/// An entry in the query cache.
struct CacheEntry {
/// The query's results.
data: Box<dyn Any>,
/// How many evictions have passed since the entry has been last used.
age: usize,
}
impl Context {
/// Execute a query.
///
/// This hashes all inputs to the query and then either returns a cached
/// version or executes the query, saves the results in the cache and
/// returns a reference to them.
pub fn query<I, O>(
&mut self,
input: I,
query: fn(ctx: &mut Self, input: I) -> O,
) -> &O
where
I: Hash,
O: 'static,
{
let hash = fxhash::hash64(&input);
if !self.cache.contains_key(&hash) {
let output = query(self, input);
self.cache.insert(hash, CacheEntry { data: Box::new(output), age: 0 });
}
let entry = self.cache.get_mut(&hash).unwrap();
entry.age = 0;
entry.data.downcast_ref().expect("oh no, a hash collision")
}
/// Garbage-collect the query cache. This deletes elements which haven't
/// been used in a while.
///
/// Returns details about the eviction.
pub fn evict(&mut self) -> Eviction {
const MAX_AGE: usize = 5;
let before = self.cache.len();
self.cache.retain(|_, entry| {
entry.age += 1;
entry.age <= MAX_AGE
});
Eviction { before, after: self.cache.len() }
}
}
/// Details about a cache eviction.
pub struct Eviction {
/// The number of items in the cache before the eviction.
pub before: usize,
/// The number of items in the cache after the eviction.
pub after: usize,
}
impl Display for Eviction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "Before: {}", self.before)?;
writeln!(f, "Evicted: {}", self.before - self.after)?;
writeln!(f, "After: {}", self.after)
}
}