feat: slice large intrinsic layout trees between node calls

This commit is contained in:
2026-10-23 01:50:17 +08:00
parent 8a13cd027a
commit 5a1cf9d702
3 changed files with 12 additions and 2 deletions
+3
View File
@@ -524,3 +524,6 @@ New viewport targets now coalesce pending refresh work. The runtime cancels only
Cancellation checkpoints own explicit VM roots until restored or released. Initial presentations may have no mount dictionary; cancellation restores that absence too. Saved metadata values retain object-valued attributes. A pending refresh can restart while keeping already completed window drafts; no onMount/onUnmount callback is replayed merely because the viewport target changed. Mount-owned tasks are cancelled immediately before an actual onUnmount callback, never during abandoned preparation.
Large intrinsic layout trees (more than 64 occurrences) now use the resumable per-occurrence driver too. Native measurement caches remain active, and a single native callback remains atomic; VM instruction boundaries between nodes allow the worker to yield and replace an obsolete viewport. Small intrinsic trees retain the allocation-free native fast path. Structural extraction, driver preparation, settling checks and final paint/projection still run atomically; this is not a wall-clock preemption guarantee.
+3
View File
@@ -179,3 +179,6 @@ New viewport targets now coalesce pending refresh work. The runtime cancels only
Cancellation checkpoints own explicit VM roots until restored or released. Initial presentations may have no mount dictionary; cancellation restores that absence too. Saved metadata values retain object-valued attributes. A pending refresh can restart while keeping already completed window drafts; no onMount/onUnmount callback is replayed merely because the viewport target changed. Mount-owned tasks are cancelled immediately before an actual onUnmount callback, never during abandoned preparation.
source_layout uses the same resumable node driver for trees over 64 occurrences even when measure/arrange are intrinsic. Small intrinsic trees retain LayoutRuntime::frame. The larger path keeps LayoutRuntime measurement caches and yields between node calls; structural extraction, bytecode preparation, a single node, settling and paint remain atomic.
+6 -2
View File
@@ -18,6 +18,10 @@ use std::{
sync::{Arc, Mutex},
};
// Small native trees keep the allocation-free fast path. Larger trees use the
// same per-occurrence VM driver so even intrinsic work can yield between nodes.
const NATIVE_FAST_PATH_MAX_NODES: usize = 64;
fn invalid(message: &'static str) -> VmError {
VmError::InvalidArgument(message)
}
@@ -769,7 +773,7 @@ pub fn start(
let live: HashSet<_> = mounts.iter().copied().collect();
cache.entries.retain(|mount, _| live.contains(mount));
// Preserve the existing native measurement cache and avoid VM/result
// allocation for trees without custom layout methods. Contexts already exist.
// allocation for small trees without custom layout methods. Contexts already exist.
let mut custom = false;
for weak in &mounts {
let mount = vm
@@ -803,7 +807,7 @@ pub fn start(
}
// Flex allocation can change text wrapping and ancestor measurements even
// when every protocol method is native. Use the same settling driver.
if !custom {
if !custom && nodes.len() <= NATIVE_FAST_PATH_MAX_NODES {
cache.clear();
let mut native = runtime.frame(&root)?;
// Only the structural pass consumes a published version.