summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-05-25 10:33:20 +0200
committerLaurenz <laurmaedje@gmail.com>2022-05-25 10:55:08 +0200
commit362a7f2a8ac76f944efa05eabcab0960817777c5 (patch)
tree07f1a177fbbecdbd11e69bba9baa6a5301b25382 /src
parent018860da9c72df846d80051a1408b3e632fbaaf6 (diff)
Thread-local query cache
Diffstat (limited to 'src')
-rw-r--r--src/export/render.rs15
-rw-r--r--src/lib.rs76
-rw-r--r--src/model/layout.rs13
-rw-r--r--src/query.rs121
4 files changed, 138 insertions, 87 deletions
diff --git a/src/export/render.rs b/src/export/render.rs
index 50257e1c..34fb4331 100644
--- a/src/export/render.rs
+++ b/src/export/render.rs
@@ -12,6 +12,7 @@ use crate::geom::{
self, Geometry, Length, Paint, PathElement, Shape, Size, Stroke, Transform,
};
use crate::image::{Image, RasterImage, Svg};
+use crate::query::query_ref;
use crate::Context;
/// Export a frame into a rendered image.
@@ -240,17 +241,15 @@ fn render_outline_glyph(
return Some(());
}
- // TODO(query)
+ // Rasterize the glyph with `pixglyph`.
// Try to retrieve a prepared glyph or prepare it from scratch if it
// doesn't exist, yet.
- let glyph = ctx
- .query((text.face_id, id), |ctx, (face_id, id)| {
- pixglyph::Glyph::load(ctx.fonts.get(face_id).ttf(), id)
- })
- .as_ref()?;
+ let bitmap = query_ref(
+ (&ctx.fonts, text.face_id, id),
+ |(fonts, face_id, id)| pixglyph::Glyph::load(fonts.get(face_id).ttf(), id),
+ |glyph| glyph.as_ref().map(|g| g.rasterize(ts.tx, ts.ty, ppem)),
+ )?;
- // Rasterize the glyph with `pixglyph`.
- let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem);
let cw = canvas.width() as i32;
let ch = canvas.height() as i32;
let mw = bitmap.width as i32;
diff --git a/src/lib.rs b/src/lib.rs
index 06324c11..5173b022 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -47,13 +47,11 @@ pub mod library;
pub mod loading;
pub mod model;
pub mod parse;
+pub mod query;
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::mem;
use std::path::PathBuf;
use std::sync::Arc;
@@ -68,7 +66,7 @@ use crate::model::StyleMap;
use crate::source::{SourceId, SourceStore};
use crate::util::PathExt;
-/// The core context which holds the loader, configuration and cached artifacts.
+/// The core context which holds the loader, stores, and configuration.
pub struct Context {
/// The loader the context was created with.
pub loader: Arc<dyn Loader>,
@@ -86,8 +84,6 @@ 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.
@@ -236,77 +232,9 @@ impl ContextBuilder {
std: self.std.clone().unwrap_or_else(|| Arc::new(library::new())),
styles: self.styles.clone().unwrap_or_default(),
modules: HashMap::new(),
- cache: HashMap::new(),
route: vec![],
deps: vec![],
flow: 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)
- }
-}
diff --git a/src/model/layout.rs b/src/model/layout.rs
index 3b82ddc2..1933fca1 100644
--- a/src/model/layout.rs
+++ b/src/model/layout.rs
@@ -14,6 +14,7 @@ use crate::geom::{
};
use crate::library::graphics::MoveNode;
use crate::library::layout::{AlignNode, PadNode};
+use crate::query::query;
use crate::util::Prehashed;
use crate::Context;
@@ -221,11 +222,13 @@ impl Layout for LayoutNode {
regions: &Regions,
styles: StyleChain,
) -> TypResult<Vec<Arc<Frame>>> {
- ctx.query((self, regions, styles), |ctx, (node, regions, styles)| {
- let entry = StyleEntry::Barrier(Barrier::new(node.id()));
- node.0.layout(ctx, regions, entry.chain(&styles))
- })
- .clone()
+ query(
+ (self, ctx, regions, styles),
+ |(node, ctx, regions, styles)| {
+ let entry = StyleEntry::Barrier(Barrier::new(node.id()));
+ node.0.layout(ctx, regions, entry.chain(&styles))
+ },
+ )
}
fn pack(self) -> LayoutNode {
diff --git a/src/query.rs b/src/query.rs
new file mode 100644
index 00000000..97af0139
--- /dev/null
+++ b/src/query.rs
@@ -0,0 +1,121 @@
+//! Query caching.
+
+use std::any::Any;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::fmt::{self, Display, Formatter};
+use std::hash::Hash;
+
+thread_local! {
+ /// The thread-local query cache.
+ static CACHE: RefCell<Cache> = RefCell::default();
+}
+
+/// A map from hashes to cache entries.
+type Cache = HashMap<u64, CacheEntry>;
+
+/// Access the cache.
+fn with<F, R>(f: F) -> R
+where
+ F: FnOnce(&mut Cache) -> R,
+{
+ CACHE.with(|cell| f(&mut cell.borrow_mut()))
+}
+
+/// 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,
+}
+
+/// Execute a query.
+///
+/// This hashes all inputs to the query and then either returns a cached version
+/// from the thread-local query cache or executes the query and saves a copy of
+/// the results in the cache.
+///
+/// Note that `f` must be a pure function.
+pub fn query<I, O>(input: I, f: fn(input: I) -> O) -> O
+where
+ I: Hash,
+ O: Clone + 'static,
+{
+ query_ref(input, f, Clone::clone)
+}
+
+/// Execute a query and then call a function with a reference to the result.
+///
+/// This hashes all inputs to the query and then either call `g` with a cached
+/// version from the thread-local query cache or executes the query, calls `g`
+/// with the fresh version and saves the result in the cache.
+///
+/// Note that `f` must be a pure function, while `g` does not need to be pure.
+pub fn query_ref<I, O, G, R>(input: I, f: fn(input: I) -> O, g: G) -> R
+where
+ I: Hash,
+ O: 'static,
+ G: Fn(&O) -> R,
+{
+ let hash = fxhash::hash64(&input);
+ let result = with(|cache| {
+ let entry = cache.get_mut(&hash)?;
+ entry.age = 0;
+ entry.data.downcast_ref().map(|output| g(output))
+ });
+
+ result.unwrap_or_else(|| {
+ let output = f(input);
+ let result = g(&output);
+ let entry = CacheEntry { data: Box::new(output), age: 0 };
+ with(|cache| cache.insert(hash, entry));
+ result
+ })
+}
+
+/// Garbage-collect the thread-local query cache.
+///
+/// This deletes elements which haven't been used in a while and returns details
+/// about the eviction.
+pub fn evict() -> Eviction {
+ with(|cache| {
+ const MAX_AGE: usize = 5;
+
+ let before = cache.len();
+ cache.retain(|_, entry| {
+ entry.age += 1;
+ entry.age <= MAX_AGE
+ });
+
+ Eviction { before, after: 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)
+ }
+}
+
+// These impls are temporary and incorrect.
+macro_rules! skip {
+ ($ty:ty) => {
+ impl Hash for $ty {
+ fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
+ }
+ };
+}
+
+skip!(crate::font::FontStore);
+skip!(crate::Context);