From e2802efe0a6e346db693286d85e7be314f81453e Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sat, 24 Oct 2026 02:18:25 +0800 Subject: [PATCH] perf: retain precise name observations in property binding caches --- src/binding.rs | 29 +++++++++++++++-------------- src/endor_host.rs | 31 +++++++++++++++++++++++++++++++ src/ui_bridge.rs | 20 +++++++++++++------- vendor/aster | 2 +- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/binding.rs b/src/binding.rs index de96089..f01a56c 100644 --- a/src/binding.rs +++ b/src/binding.rs @@ -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, + reads: Vec, 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, +) -> Result { 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); } } } diff --git a/src/endor_host.rs b/src/endor_host.rs index 7f71180..0ab53ef 100644 --- a/src/endor_host.rs +++ b/src/endor_host.rs @@ -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::().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( diff --git a/src/ui_bridge.rs b/src/ui_bridge.rs index d9d1656..076f7a2 100644 --- a/src/ui_bridge.rs +++ b/src/ui_bridge.rs @@ -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>>> { let changes = vm.take_changes(); @@ -3018,6 +3023,7 @@ pub fn install(vm: &mut Vm, roots: &mut Vec) -> Result { layout_cancels: 0, paint_cancels: 0, paint_slices: 0, + binding_evaluations: 0, exiting: false, })); let mut public_functions = Vec::new(); diff --git a/vendor/aster b/vendor/aster index ee68a0c..f3e1670 160000 --- a/vendor/aster +++ b/vendor/aster @@ -1 +1 @@ -Subproject commit ee68a0c54c99f29cfc8aea80ba6374293f2d6c77 +Subproject commit f3e1670c580a6985262311847b16752df2129b21