perf: validate precise source name dependencies before relayout and repaint
This commit is contained in:
@@ -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<endor_core::ReadDependency>,
|
||||
pub layout_names: Vec<endor_core::ReadDependency>,
|
||||
pub paint_reads: std::collections::BTreeMap<ObjectRef, u64>,
|
||||
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(),
|
||||
_ => "<dictionary kind>",
|
||||
};
|
||||
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<endor_core::ReadDependency>,
|
||||
) -> (
|
||||
std::collections::BTreeMap<ObjectRef, u64>,
|
||||
Vec<endor_core::ReadDependency>,
|
||||
) {
|
||||
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)
|
||||
}
|
||||
|
||||
+48
-29
@@ -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<Size>,
|
||||
constraints: [f32; 4],
|
||||
size: Size,
|
||||
reads: BTreeMap<ObjectRef, u64>,
|
||||
reads: Vec<endor_core::ReadDependency>,
|
||||
}
|
||||
/// 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::<Mount>()
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -719,7 +728,7 @@ pub struct Draft {
|
||||
pub frame: Frame,
|
||||
pub runtime: LayoutRuntime,
|
||||
pub cache: Cache,
|
||||
pub reads: BTreeSet<ObjectRef>,
|
||||
pub reads: Vec<endor_core::ReadDependency>,
|
||||
}
|
||||
|
||||
#[must_use = "pending layout work must be resumed or cancelled with its owning VM"]
|
||||
@@ -760,9 +769,13 @@ impl Work {
|
||||
) -> Result<Progress> {
|
||||
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<ObjectRef>) -> Result<Progress> {
|
||||
fn finish(
|
||||
vm: &mut Vm,
|
||||
mut work: Work,
|
||||
value: Value,
|
||||
reads: Vec<endor_core::ReadDependency>,
|
||||
) -> Result<Progress> {
|
||||
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<ObjectRef>) -> 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::<Mount>())
|
||||
})
|
||||
.collect();
|
||||
|
||||
+15
-10
@@ -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<ObjectRef>,
|
||||
pub reads: Vec<endor_core::ReadDependency>,
|
||||
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<ObjectRef>) -> Result<Progress> {
|
||||
fn finish(vm: &mut Vm, mut work: Work, reads: Vec<endor_core::ReadDependency>) -> Result<Progress> {
|
||||
let output = work
|
||||
.output
|
||||
.lock()
|
||||
@@ -398,7 +402,7 @@ fn finish(vm: &mut Vm, mut work: Work, reads: Vec<ObjectRef>) -> Result<Progress
|
||||
if output.commands.is_empty() {
|
||||
return Ok(Progress::Complete(Box::new(Draft {
|
||||
frame: work.frame,
|
||||
reads: BTreeSet::new(),
|
||||
reads: Vec::new(),
|
||||
slices: work.slices,
|
||||
})));
|
||||
}
|
||||
@@ -443,11 +447,12 @@ fn finish(vm: &mut Vm, mut work: Work, reads: Vec<ObjectRef>) -> Result<Progress
|
||||
let registry = object(vm.get_attr(desktop, "types")?)?;
|
||||
let reads = reads
|
||||
.into_iter()
|
||||
.filter(|object| {
|
||||
*object != desktop
|
||||
&& *object != registry
|
||||
.filter(|dependency| {
|
||||
let object = dependency.object();
|
||||
object != desktop
|
||||
&& object != registry
|
||||
&& vm
|
||||
.object(*object)
|
||||
.object(object)
|
||||
.is_ok_and(|object| !object.payload().as_any().is::<Mount>())
|
||||
})
|
||||
.collect();
|
||||
|
||||
+9
-10
@@ -2596,17 +2596,13 @@ impl UiBridge {
|
||||
progress: crate::source_paint::Progress,
|
||||
) -> Result<crate::source_paint::Progress> {
|
||||
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::<Result<std::collections::BTreeMap<_, _>, _>>()?;
|
||||
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::<Result<std::collections::BTreeMap<_, _>, _>>()?;
|
||||
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 {
|
||||
|
||||
Vendored
+1
-1
Submodule vendor/aster updated: 89b817447e...42c27d083d
Reference in New Issue
Block a user