diff --git a/src/presentation.rs b/src/presentation.rs index d24f07c..a6b5ce2 100644 --- a/src/presentation.rs +++ b/src/presentation.rs @@ -21,6 +21,8 @@ pub(crate) struct Presentation { pub mounts: crate::mount::MountTable, pub published_version: u64, pub force_frame: bool, + pub paint_names: Vec, + pub layout_names: Vec, pub paint_reads: std::collections::BTreeMap, pub dependency_invalidations: usize, pub last_invalid_dependency: String, @@ -54,6 +56,25 @@ impl Presentation { vm.change_version(*object).ok() ); } + if let Some((phase, dependency)) = self + .layout_names + .iter() + .map(|dependency| ("layout", dependency)) + .chain( + self.paint_names + .iter() + .map(|dependency| ("paint", dependency)), + ) + .find(|(_, dependency)| !vm.object_store().read_dependency_valid(dependency)) + { + self.force_frame = true; + self.dependency_invalidations = self.dependency_invalidations.saturating_add(1); + let name = match dependency { + endor_core::ReadDependency::Name { name, .. } => name.as_str(), + _ => "", + }; + self.last_invalid_dependency = format!("{phase} name {}:{name}", dependency.object()); + } self.render_cache.invalidate(ChangeBatch { changes: changes.changes.clone(), rescan_required: changes.rescan_required, @@ -106,6 +127,8 @@ impl Presentation { mounts: crate::mount::MountTable::default(), published_version: 0, force_frame: false, + paint_names: Vec::new(), + layout_names: Vec::new(), paint_reads: std::collections::BTreeMap::new(), dependency_invalidations: 0, last_invalid_dependency: String::new(), @@ -174,3 +197,22 @@ mod tests { assert!(vm.upgrade(weak).unwrap().is_none()); } } + +pub(crate) fn split_reads( + reads: Vec, +) -> ( + std::collections::BTreeMap, + Vec, +) { + let mut objects = std::collections::BTreeMap::new(); + let mut names = Vec::new(); + for dependency in reads { + match dependency { + endor_core::ReadDependency::Object { object, version } => { + objects.insert(object, version); + } + dependency => names.push(dependency), + } + } + (objects, names) +} diff --git a/src/source_layout.rs b/src/source_layout.rs index ec725a7..f7c181d 100644 --- a/src/source_layout.rs +++ b/src/source_layout.rs @@ -14,7 +14,7 @@ use endor_core::{ VmError, WeakObjectRef, }; use std::{ - collections::{BTreeMap, BTreeSet, HashMap, HashSet}, + collections::{BTreeSet, HashMap, HashSet}, sync::{Arc, Mutex}, }; @@ -38,7 +38,7 @@ struct Measurement { children: Vec, constraints: [f32; 4], size: Size, - reads: BTreeMap, + reads: Vec, } /// Borrowed identities and versions only: this cache adds no managed roots. #[derive(Debug, Default, Clone)] @@ -167,20 +167,20 @@ impl NativeCallable for Step { if matches!(self.phase, Phase::Measure) { let key = cx.store().downgrade(self.mount)?; let constraints = plan.constraints[self.index]; - let cached = plan - .cache - .entries - .get(&key) - .filter(|entry| { - entry.owner == mount.block - && entry.method == method - && entry.children == children - && entry.constraints == constraints - && entry.reads.iter().all(|(object, version)| { - cx.store().change_version(*object).ok() == Some(*version) - }) - }) - .map(|entry| entry.size); + let cached = + plan.cache + .entries + .get(&key) + .filter(|entry| { + entry.owner == mount.block + && entry.method == method + && entry.children == children + && entry.constraints == constraints + && entry.reads.iter().all(|dependency| { + cx.store().read_dependency_valid(dependency) + }) + }) + .map(|entry| entry.size); if let Some(size) = cached { plan.cache.hits += 1; Some(size) @@ -359,15 +359,14 @@ impl NativeCallable for Collect { let tracked = pending .map(|pending| { cx.store() - .end_read_tracking_details(pending.token) + .end_read_tracking_precise(pending.token) .map(|reads| (pending, reads)) }) .transpose()?; if let Some((pending, reads)) = tracked { - let mut versions = BTreeMap::new(); let mut volatile = false; let registry = desktop_type_registry(cx, self.desktop)?; - for read in reads { + for read in &reads.accesses { let object = read.object; if object == self.desktop || object == registry { continue; @@ -379,7 +378,6 @@ impl NativeCallable for Collect { volatile |= read.content_read; continue; } - versions.insert(object, cx.store().change_version(object)?); } if volatile { plan.cache.entries.remove(&pending.key); @@ -392,7 +390,18 @@ impl NativeCallable for Collect { children: pending.children, constraints: pending.constraints, size, - reads: versions, + reads: reads + .dependencies + .into_iter() + .filter(|dependency| { + let object = dependency.object(); + object != self.desktop + && object != registry + && cx.store().object(object).is_ok_and(|value| { + !value.payload().as_any().is::() + }) + }) + .collect(), }, ); } @@ -719,7 +728,7 @@ pub struct Draft { pub frame: Frame, pub runtime: LayoutRuntime, pub cache: Cache, - pub reads: BTreeSet, + pub reads: Vec, } #[must_use = "pending layout work must be resumed or cancelled with its owning VM"] @@ -760,9 +769,13 @@ impl Work { ) -> Result { match progress { Ok(endor_core::ExecutionProgress::Pending { .. }) => Ok(Progress::Pending(self)), - Ok(endor_core::ExecutionProgress::Complete { value, reads, .. }) => { + Ok(endor_core::ExecutionProgress::Complete { + value, + dependencies, + .. + }) => { vm.release(self.prepare)?; - finish(vm, self, value, reads) + finish(vm, self, value, dependencies) } Err(error) => { // An invalid quantum can fail without discarding the VM's @@ -911,7 +924,12 @@ pub fn start( work.accept(vm, progress) } -fn finish(vm: &mut Vm, mut work: Work, value: Value, reads: Vec) -> Result { +fn finish( + vm: &mut Vm, + mut work: Work, + value: Value, + reads: Vec, +) -> Result { vm.release_value(value)?; let mut plan = work .plan @@ -946,11 +964,12 @@ fn finish(vm: &mut Vm, mut work: Work, value: Value, reads: Vec) -> R }; work.state.reads = reads .into_iter() - .filter(|object| { - *object != work.desktop - && *object != registry + .filter(|dependency| { + let object = dependency.object(); + object != work.desktop + && object != registry && vm - .object(*object) + .object(object) .is_ok_and(|object| !object.payload().as_any().is::()) }) .collect(); diff --git a/src/source_paint.rs b/src/source_paint.rs index 8efd484..b13ca3f 100644 --- a/src/source_paint.rs +++ b/src/source_paint.rs @@ -13,7 +13,7 @@ use endor_core::{ Value, Vm, VmError, WeakObjectRef, }; use std::{ - collections::{BTreeMap, BTreeSet, HashMap}, + collections::{BTreeMap, HashMap}, sync::{Arc, Mutex}, }; @@ -289,7 +289,7 @@ impl NativeCallable for Advance { pub struct Draft { pub frame: Frame, - pub reads: BTreeSet, + pub reads: Vec, pub slices: usize, } @@ -333,10 +333,14 @@ impl Work { Ok(endor_core::ExecutionProgress::Pending { .. }) => { Ok(Progress::Pending(Box::new(self))) } - Ok(endor_core::ExecutionProgress::Complete { value, reads, .. }) => { + Ok(endor_core::ExecutionProgress::Complete { + value, + dependencies, + .. + }) => { vm.release(self.prepare)?; vm.release_value(value)?; - finish(vm, self, reads) + finish(vm, self, dependencies) } Err(error) => { let diagnostic = crate::endor_host::runtime_error(vm, error); @@ -390,7 +394,7 @@ pub fn start( work.accept(vm, result) } -fn finish(vm: &mut Vm, mut work: Work, reads: Vec) -> Result { +fn finish(vm: &mut Vm, mut work: Work, reads: Vec) -> Result { let output = work .output .lock() @@ -398,7 +402,7 @@ fn finish(vm: &mut Vm, mut work: Work, reads: Vec) -> Result) -> Result()) }) .collect(); diff --git a/src/ui_bridge.rs b/src/ui_bridge.rs index 95045ab..a3a74d1 100644 --- a/src/ui_bridge.rs +++ b/src/ui_bridge.rs @@ -2596,17 +2596,13 @@ impl UiBridge { progress: crate::source_paint::Progress, ) -> Result { if let crate::source_paint::Progress::Complete(draft) = &progress { - let reads = draft - .reads - .iter() - .copied() - .map(|object| vm.change_version(object).map(|version| (object, version))) - .collect::, _>>()?; + let (reads, names) = crate::presentation::split_reads(draft.reads.clone()); let mut state = self .session .lock() .map_err(|_| anyhow::anyhow!("UI session poisoned"))?; state.paint_reads = reads; + state.paint_names = names; state.paint_slices = draft.slices; } Ok(progress) @@ -2650,6 +2646,11 @@ impl UiBridge { .map_err(|_| anyhow::anyhow!("UI session poisoned"))? .layout_reads .clear(); + self.session + .lock() + .map_err(|_| anyhow::anyhow!("UI session poisoned"))? + .layout_names + .clear(); let frame = { let mut state = self .session @@ -2893,10 +2894,7 @@ impl UiBridge { frame.tabs = tabs; frame.window = window; // Capture versions before lifecycle hooks can change a dependency. - let reads = reads - .into_iter() - .map(|object| vm.change_version(object).map(|version| (object, version))) - .collect::, _>>()?; + let (reads, names) = crate::presentation::split_reads(reads); frame.focus = match vm.get_attr(desktop, "focus") { Ok(Value::Number(id)) => id.as_i64().and_then(|n| u64::try_from(n).ok()), _ => None, @@ -2913,6 +2911,7 @@ impl UiBridge { state.layout = layout; state.measure_cache = cache; state.layout_reads = reads; + state.layout_names = names; } self.commit_frame(vm, Some(root), &frame)?; if let Some(hooks) = hooks { diff --git a/vendor/aster b/vendor/aster index 89b8174..42c27d0 160000 --- a/vendor/aster +++ b/vendor/aster @@ -1 +1 @@ -Subproject commit 89b817447e89e73c443304af8c207e1fc4502399 +Subproject commit 42c27d083d924e51578789680208324ad7672667