perf: retain precise name observations in property binding caches
This commit is contained in:
+15
-14
@@ -1,7 +1,7 @@
|
||||
//! Target-owned managed bindings. Dependency versions are borrowed identities;
|
||||
//! the host's weak Block registry never roots an otherwise unreachable graph.
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
collections::HashSet,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{block, event_dispatch::Subscription, ui_bridge::attach_desktop_type}
|
||||
/// It never owns a reference or dereferences a possibly reclaimed cached object.
|
||||
#[derive(Debug, Clone)]
|
||||
struct Cached {
|
||||
reads: BTreeMap<ObjectRef, u64>,
|
||||
reads: Vec<endor_core::ReadDependency>,
|
||||
output: Value,
|
||||
}
|
||||
|
||||
@@ -114,7 +114,11 @@ pub fn connect(
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
pub fn refresh(vm: &mut Vm, nodes: &[WeakObjectRef]) -> Result<()> {
|
||||
pub fn refresh(
|
||||
vm: &mut Vm,
|
||||
nodes: &[WeakObjectRef],
|
||||
host_desktops: &HashSet<ObjectRef>,
|
||||
) -> Result<usize> {
|
||||
let mut work = 0;
|
||||
loop {
|
||||
let mut evaluated = false;
|
||||
@@ -140,8 +144,8 @@ pub fn refresh(vm: &mut Vm, nodes: &[WeakObjectRef]) -> Result<()> {
|
||||
.map_err(|_| anyhow::anyhow!("binding cache poisoned"))?
|
||||
.clone();
|
||||
if cached.as_ref().is_some_and(|cached| {
|
||||
cached.reads.iter().all(|(object, version)| {
|
||||
vm.object_store().change_version(*object).ok() == Some(*version)
|
||||
cached.reads.iter().all(|read| {
|
||||
vm.object_store().read_dependency_valid(read)
|
||||
}) && vm
|
||||
.get_attr(target, &binding.field)
|
||||
.is_ok_and(|value| value == cached.output)
|
||||
@@ -154,7 +158,7 @@ pub fn refresh(vm: &mut Vm, nodes: &[WeakObjectRef]) -> Result<()> {
|
||||
}
|
||||
evaluated = true;
|
||||
let (value, reads) = vm
|
||||
.call_read_only_with_limits(
|
||||
.call_read_only_precise_with_limits(
|
||||
compute,
|
||||
&[],
|
||||
endor_core::ExecutionLimits {
|
||||
@@ -164,12 +168,9 @@ pub fn refresh(vm: &mut Vm, nodes: &[WeakObjectRef]) -> Result<()> {
|
||||
)
|
||||
.map_err(|error| crate::endor_host::runtime_error(vm, error))?;
|
||||
let applied: Result<()> = (|| {
|
||||
let mut versions = BTreeMap::new();
|
||||
for object in reads {
|
||||
if let Ok(version) = vm.object_store().change_version(object) {
|
||||
versions.insert(object, version);
|
||||
}
|
||||
}
|
||||
let reads = reads.dependencies.into_iter()
|
||||
.filter(|read| !host_desktops.contains(&read.object()))
|
||||
.collect();
|
||||
let (_, data) = block::resolve(vm.object_store(), target)?;
|
||||
block::apply_visual_property(&mut data.clone(), &binding.field, &value)?;
|
||||
if vm.get_attr(target, &binding.field)? != value {
|
||||
@@ -180,7 +181,7 @@ pub fn refresh(vm: &mut Vm, nodes: &[WeakObjectRef]) -> Result<()> {
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("binding cache poisoned"))? =
|
||||
Some(Cached {
|
||||
reads: versions,
|
||||
reads,
|
||||
output: value.clone(),
|
||||
});
|
||||
Ok(())
|
||||
@@ -194,7 +195,7 @@ pub fn refresh(vm: &mut Vm, nodes: &[WeakObjectRef]) -> Result<()> {
|
||||
result?;
|
||||
}
|
||||
if !evaluated {
|
||||
return Ok(());
|
||||
return Ok(work);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6175,6 +6175,37 @@ returned = button.dispatch(dispatched)"#,
|
||||
assert!(!session.is_faulted());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn property_bindings_use_precise_names_and_repair_direct_output_changes() {
|
||||
let (mut session, _) = ReplSession::load(
|
||||
r#"
|
||||
size = 80
|
||||
function readWidth(): Number { return size }
|
||||
rect = RectBlock(1,20)
|
||||
hidden = RectBlock(1,20)
|
||||
rect.bind("width",readWidth)
|
||||
hidden.bind("width",readWidth)
|
||||
page = Page()
|
||||
page.render(rect)
|
||||
"#, "app.main", 903, 1,
|
||||
).unwrap();
|
||||
let count = |session: &mut ReplSession| {
|
||||
session.execute("ui.stats().bindingEvaluations", "app.main").unwrap().value
|
||||
.parse::<usize>().unwrap()
|
||||
};
|
||||
let baseline = count(&mut session);
|
||||
assert_eq!(baseline, 2);
|
||||
assert!(session.execute("42", "app.main").unwrap().document.is_none());
|
||||
assert_eq!(count(&mut session), baseline);
|
||||
session.execute("size = 120", "app.main").unwrap();
|
||||
assert_eq!(count(&mut session), baseline + 2);
|
||||
assert_eq!(session.execute("hidden.width", "app.main").unwrap().value, "120");
|
||||
session.execute("rect.width = 200", "app.main").unwrap();
|
||||
assert_eq!(count(&mut session), baseline + 3);
|
||||
assert_eq!(session.execute("rect.width", "app.main").unwrap().value, "120");
|
||||
assert_eq!(count(&mut session), baseline + 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_render_caches_do_not_root_a_page_block_waiting_frame_cycle() {
|
||||
let (mut session, _) = ReplSession::load(
|
||||
|
||||
+13
-7
@@ -233,6 +233,7 @@ struct Session {
|
||||
layout_cancels: usize,
|
||||
paint_cancels: usize,
|
||||
paint_slices: usize,
|
||||
binding_evaluations: usize,
|
||||
exiting: bool,
|
||||
}
|
||||
|
||||
@@ -1176,6 +1177,7 @@ impl NativeCallable for Function {
|
||||
("layoutCancels", session.layout_cancels as i64),
|
||||
("paintCancels", session.paint_cancels as i64),
|
||||
("paintSlices", session.paint_slices as i64),
|
||||
("bindingEvaluations", session.binding_evaluations as i64),
|
||||
(
|
||||
"dependencyInvalidations",
|
||||
session.dependency_invalidations as i64,
|
||||
@@ -2471,13 +2473,16 @@ impl UiBridge {
|
||||
Ok(())
|
||||
}
|
||||
pub fn refresh_bindings(&self, vm: &mut Vm) -> Result<()> {
|
||||
let nodes = self
|
||||
.session
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("UI session poisoned"))?
|
||||
.nodes
|
||||
.clone();
|
||||
crate::binding::refresh(vm, &nodes)
|
||||
let (nodes, desktops) = {
|
||||
let state = self.session.lock()
|
||||
.map_err(|_| anyhow::anyhow!("UI session poisoned"))?;
|
||||
(state.nodes.clone(), state.host_desktops())
|
||||
};
|
||||
let evaluated = crate::binding::refresh(vm, &nodes, &desktops)?;
|
||||
let mut state = self.session.lock()
|
||||
.map_err(|_| anyhow::anyhow!("UI session poisoned"))?;
|
||||
state.binding_evaluations = state.binding_evaluations.saturating_add(evaluated);
|
||||
Ok(())
|
||||
}
|
||||
pub fn prepare_frame(&self, vm: &mut Vm) -> Result<Option<Option<Arc<UiNode>>>> {
|
||||
let changes = vm.take_changes();
|
||||
@@ -3018,6 +3023,7 @@ pub fn install(vm: &mut Vm, roots: &mut Vec<ObjectRef>) -> Result<UiBridge> {
|
||||
layout_cancels: 0,
|
||||
paint_cancels: 0,
|
||||
paint_slices: 0,
|
||||
binding_evaluations: 0,
|
||||
exiting: false,
|
||||
}));
|
||||
let mut public_functions = Vec::new();
|
||||
|
||||
Vendored
+1
-1
Submodule vendor/aster updated: ee68a0c54c...f3e1670c58
Reference in New Issue
Block a user