From 97858e5992a52459dd8a34be7a6b4786952b491a Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 31 May 2022 09:13:31 +0200 Subject: Basic manual tracking --- src/model/layout.rs | 25 +++++++++-- src/model/locate.rs | 125 ++++++++++++++++++++++++++++++++++++---------------- src/model/styles.rs | 2 + 3 files changed, 111 insertions(+), 41 deletions(-) (limited to 'src/model') diff --git a/src/model/layout.rs b/src/model/layout.rs index 92d73977..b0247258 100644 --- a/src/model/layout.rs +++ b/src/model/layout.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::hash::Hash; use std::sync::Arc; -use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry}; +use super::{Barrier, NodeId, PinConstraint, Resolve, StyleChain, StyleEntry}; use crate::diag::TypResult; use crate::eval::{RawAlign, RawLength}; use crate::frame::{Element, Frame}; @@ -132,6 +132,8 @@ impl Regions { } } +impl_track_hash!(Regions); + /// A type-erased layouting node with a precomputed hash. #[derive(Clone, Hash)] pub struct LayoutNode(Arc>); @@ -221,19 +223,32 @@ impl Layout for LayoutNode { regions: &Regions, styles: StyleChain, ) -> TypResult>> { - let (result, at, pins) = crate::memo::memoized( + let prev = ctx.pins.dirty.replace(false); + + let (result, at, fresh, dirty) = crate::memo::memoized( (self, &mut *ctx, regions, styles), |(node, ctx, regions, styles)| { + let hash = fxhash::hash64(&ctx.pins); let at = ctx.pins.cursor(); + let entry = StyleEntry::Barrier(Barrier::new(node.id())); let result = node.0.layout(ctx, regions, entry.chain(&styles)); - (result, at, ctx.pins.from(at)) + + let fresh = ctx.pins.from(at); + let dirty = ctx.pins.dirty.get(); + let constraint = PinConstraint(dirty.then(|| hash)); + ((result, at, fresh, dirty), ((), constraint, (), ())) }, ); + ctx.pins.dirty.replace(prev || dirty); + // Replay the side effect in case of caching. This should currently be // more or less the only relevant side effect on the context. - ctx.pins.replay(at, pins); + if dirty { + ctx.pins.replay(at, fresh); + } + result } @@ -242,6 +257,8 @@ impl Layout for LayoutNode { } } +impl_track_hash!(LayoutNode); + impl Default for LayoutNode { fn default() -> Self { EmptyNode.pack() diff --git a/src/model/locate.rs b/src/model/locate.rs index 97c14034..495203aa 100644 --- a/src/model/locate.rs +++ b/src/model/locate.rs @@ -1,4 +1,6 @@ +use std::cell::Cell; use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; use std::sync::Arc; use super::Content; @@ -6,6 +8,7 @@ use crate::diag::TypResult; use crate::eval::{Args, Array, Dict, Func, Value}; use crate::frame::{Element, Frame, Location}; use crate::geom::{Point, Transform}; +use crate::memo::Track; use crate::syntax::Spanned; use crate::util::EcoString; use crate::Context; @@ -84,7 +87,7 @@ struct SingleNode(Spanned); impl SingleNode { fn realize(&self, ctx: &mut Context) -> TypResult { - let idx = ctx.pins.cursor(); + let idx = ctx.pins.cursor; let pin = ctx.pins.get_or_create(None, None); let dict = pin.encode(None); let args = Args::new(self.0.span, [Value::Dict(dict)]); @@ -105,7 +108,7 @@ struct EntryNode { impl EntryNode { fn realize(&self, ctx: &mut Context) -> TypResult { - let idx = ctx.pins.cursor(); + let idx = ctx.pins.cursor; let pin = ctx.pins.get_or_create(Some(self.group.clone()), self.value.clone()); // Determine the index among the peers. @@ -155,7 +158,7 @@ impl AllNode { } /// Manages document pins. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone)] pub struct PinBoard { /// All currently active pins. list: Vec, @@ -164,14 +167,63 @@ pub struct PinBoard { /// If larger than zero, the board is frozen and the cursor will not be /// advanced. This is used to disable pinning during measure-only layouting. frozen: usize, + /// Whether the board was accessed. + pub(super) dirty: Cell, } impl PinBoard { /// Create an empty pin board. pub fn new() -> Self { - Self { list: vec![], cursor: 0, frozen: 0 } + Self { + list: vec![], + cursor: 0, + frozen: 0, + dirty: Cell::new(false), + } + } +} + +/// Internal methods for implementation of locatable nodes. +impl PinBoard { + /// Access or create the next pin. + fn get_or_create(&mut self, group: Option, value: Option) -> Pin { + self.dirty.set(true); + if self.frozen() { + return Pin::default(); + } + + let cursor = self.cursor; + self.cursor += 1; + if self.cursor >= self.list.len() { + self.list.resize(self.cursor, Pin::default()); + } + + let pin = &mut self.list[cursor]; + pin.group = group; + pin.value = value; + pin.clone() } + /// Encode a group into a user-facing array. + fn encode_group(&self, group: &Group) -> Array { + self.dirty.set(true); + let mut all: Vec<_> = self.iter().filter(|pin| pin.is_in(group)).collect(); + all.sort_by_key(|pin| pin.flow); + all.iter() + .enumerate() + .map(|(index, member)| Value::Dict(member.encode(Some(index)))) + .collect() + } + + /// Iterate over all pins on the board. + fn iter(&self) -> std::slice::Iter { + self.dirty.set(true); + self.list.iter() + } +} + +/// Caching related methods. +impl PinBoard { /// The current cursor. pub fn cursor(&self) -> usize { self.cursor @@ -190,7 +242,10 @@ impl PinBoard { self.list.splice(at .. end, pins); } } +} +/// Control methods that are called during layout. +impl PinBoard { /// Freeze the board to prevent modifications. pub fn freeze(&mut self) { self.frozen += 1; @@ -205,11 +260,15 @@ impl PinBoard { pub fn frozen(&self) -> bool { self.frozen > 0 } +} +/// Methods that are called in between layout passes. +impl PinBoard { /// Reset the cursor and remove all unused pins. pub fn reset(&mut self) { self.list.truncate(self.cursor); self.cursor = 0; + self.dirty.set(false); } /// Locate all pins in the frames. @@ -230,39 +289,6 @@ impl PinBoard { pub fn unresolved(&self, prev: &Self) -> usize { self.list.len() - self.list.iter().zip(&prev.list).filter(|(a, b)| a == b).count() } - - /// Access or create the next pin. - fn get_or_create(&mut self, group: Option, value: Option) -> Pin { - if self.frozen() { - return Pin::default(); - } - - let cursor = self.cursor; - self.cursor += 1; - if self.cursor >= self.list.len() { - self.list.resize(self.cursor, Pin::default()); - } - - let pin = &mut self.list[cursor]; - pin.group = group; - pin.value = value; - pin.clone() - } - - /// Iterate over all pins on the board. - fn iter(&self) -> std::slice::Iter { - self.list.iter() - } - - /// Encode a group into a user-facing array. - fn encode_group(&self, group: &Group) -> Array { - let mut all: Vec<_> = self.iter().filter(|pin| pin.is_in(group)).collect(); - all.sort_by_key(|pin| pin.flow); - all.iter() - .enumerate() - .map(|(index, member)| Value::Dict(member.encode(Some(index)))) - .collect() - } } /// Locate all pins in a frame. @@ -295,6 +321,31 @@ fn locate_in_frame( } } +impl Hash for PinBoard { + fn hash(&self, state: &mut H) { + self.list.hash(state); + self.cursor.hash(state); + self.frozen.hash(state); + } +} + +/// Describes pin usage. +#[derive(Debug, Copy, Clone)] +pub struct PinConstraint(pub Option); + +impl Track for PinBoard { + type Constraint = PinConstraint; + + fn key(&self, _: &mut H) {} + + fn matches(&self, constraint: &Self::Constraint) -> bool { + match constraint.0 { + Some(hash) => fxhash::hash64(self) == hash, + None => true, + } + } +} + /// A document pin. #[derive(Debug, Clone, PartialEq, Hash)] pub struct Pin { diff --git a/src/model/styles.rs b/src/model/styles.rs index 82194792..9e723171 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -394,6 +394,8 @@ impl PartialEq for StyleChain<'_> { } } +impl_track_hash!(StyleChain<'_>); + /// An iterator over the values in a style chain. struct Values<'a, K> { entries: Entries<'a>, -- cgit v1.2.3