docs: clarify layout continuation and runtime refresh cleanup
This commit is contained in:
@@ -4,6 +4,11 @@
|
||||
//!
|
||||
//! Bottom-up measurement and top-down arrangement share one read-only VM call.
|
||||
//! The host scratch plan contains derived data only, never a second business heap.
|
||||
//!
|
||||
//! ui_bridge::begin_layout_frame 先建立临时 Mount 上下文和检查点,再调用 start;本模块只拥有求解草稿与 VM 续体。
|
||||
//! Prepare 建立循环,Advance 依次安排 Step → 源方法/原生结果 → Collect,整轮结束由 Settle 决定继续还是停止。
|
||||
//! Progress::Complete 只证明本轮布局驱动完成,桥接层仍需投影、提交几何及生命周期,运行时还需绘制并验证全窗口稳定。
|
||||
//! Work::cancel 结束只读调用并释放入口根;恢复 Mount 检查点属于桥接层,不能把本模块取消误认为业务状态回滚。
|
||||
use crate::{
|
||||
document::Frame,
|
||||
layout::{self, LayoutRuntime, Size},
|
||||
@@ -289,6 +294,7 @@ impl NativeCallable for Step {
|
||||
// 先安装本轮节点、子测量和临时 bounds,让源代码调用基类或 Mount.bounds 时观察当前阶段数据。
|
||||
let context = mount.layout.ok_or(invalid("missing pre-layout context"))?;
|
||||
{
|
||||
// 只更新当前出现的临时协议输入;不写 Plan 的兄弟位置,也不修改共享 Block 的业务属性。
|
||||
let mut context = context
|
||||
.lock()
|
||||
.map_err(|_| invalid("layout context poisoned"))?;
|
||||
@@ -448,6 +454,7 @@ impl NativeCallable for Collect {
|
||||
plan.measured_limits[self.index][0] = Some(size.width);
|
||||
}
|
||||
if !native.is_some_and(|native| native.height == size.height) {
|
||||
// 高度独立比较,源方法只改高时也要保留纵向内容上限,不能只根据宽度是否不同决定两轴。
|
||||
plan.measured_limits[self.index][1] = Some(size.height);
|
||||
}
|
||||
}
|
||||
@@ -465,9 +472,11 @@ impl NativeCallable for Collect {
|
||||
if let Some((pending, reads)) = tracked {
|
||||
// 若观察了 Mount 内容/几何等不稳定宿主状态,无法证明跨轮可复用,保守地删除该测量缓存。
|
||||
let mut volatile = false;
|
||||
// 类型字典用于协议名义检查,不是源业务状态;它和 desktop 都要从易变性判断与缓存依赖中排除。
|
||||
let registry = desktop_type_registry(cx, self.desktop)?;
|
||||
// accesses 提供内容读取细节,dependencies 则用于后面保留可校验依赖,两者用途不同。
|
||||
for read in &reads.accesses {
|
||||
// access 记录哪个对象被怎样读取,后面仅对真实 Mount 内容读取禁用缓存,不因普通身份比较一概禁用。
|
||||
let object = read.object;
|
||||
// 当前桌面和类型注册表属于宿主内部检查,跳过这类访问而不把它们认成易变业务输入。
|
||||
if object == self.desktop || object == registry {
|
||||
@@ -477,6 +486,7 @@ impl NativeCallable for Collect {
|
||||
// source. Object-granular tracking cannot prove those
|
||||
// accesses stable, so conservatively bypass its cache.
|
||||
if cx.store().object(object)?.payload().as_any().is::<Mount>() {
|
||||
// Mount 身份可用于查父关系,但内容读取可能涉及本轮派生几何;content_read 才决定此次测量是否易变。
|
||||
volatile |= read.content_read;
|
||||
continue;
|
||||
}
|
||||
@@ -524,6 +534,7 @@ impl NativeCallable for Collect {
|
||||
return Err(invalid("Arrangement.children must be an ObjectList"));
|
||||
};
|
||||
let items = object_list::items(cx.store(), list)?;
|
||||
// 固定当前父出现的直接孩子列表,源结果按 slot 查回这个列表,再转换为全局出现下标。
|
||||
let children = plan.children[self.index].clone();
|
||||
// 安排结果必须覆盖全部孩子,不能省略零尺寸项;结合下面 slot 唯一检查保证逐一对应。
|
||||
if items.len() != children.len() {
|
||||
@@ -541,6 +552,7 @@ impl NativeCallable for Collect {
|
||||
.collect();
|
||||
// 记录已安排 slot 防止重复,项数相等加逐项唯一性共同保证每个孩子恰好分配一次。
|
||||
let mut assigned = BTreeSet::new();
|
||||
// 列表每项都需满足 Bounds 名义类型,不能只有 x/y/width/height 同名字段就当作合法安排。
|
||||
let expected = desktop_type(cx.store(), "Bounds", self.desktop)?;
|
||||
// 源返回列表可以重新排序,实际孩子用 slot 查表确定,不按列表位置直接对应。
|
||||
for item in items {
|
||||
@@ -549,6 +561,7 @@ impl NativeCallable for Collect {
|
||||
return Err(invalid("Arrangement child must be Bounds"));
|
||||
}
|
||||
let Value::Number(slot) = cx.store().get_attr(*item, "slot")? else {
|
||||
// Bounds 名义类型合法仍不足以接受任意 slot 值,缺失或非 Number 要在建立孩子映射之前拒绝。
|
||||
return Err(invalid("Arrangement child requires a slot"));
|
||||
};
|
||||
// slot 先要求可转为 i64 整数,再拒绝负值转为 u64;超出 i64 范围的值也不能作为此处身份。
|
||||
@@ -596,6 +609,7 @@ impl NativeCallable for Collect {
|
||||
plan.allocations[child][0] = Some(bounds[2]);
|
||||
}
|
||||
if bounds[3] != plan.sizes[child].height {
|
||||
// 高度差异单独形成精确纵向分配,和上面的宽度约束可同时存在,也可只约束其中一轴。
|
||||
plan.allocations[child][1] = Some(bounds[3]);
|
||||
}
|
||||
}
|
||||
@@ -631,6 +645,7 @@ impl NativeCallable for Settle {
|
||||
let mut next = plan.constraints.clone();
|
||||
// 按父先子后传播下一轮约束,next[parent] 先确定后才推导直接孩子。
|
||||
for index in 0..plan.nodes.len() {
|
||||
// 克隆直接孩子索引以结束对 plan.children 的借用,随后要在同一 Plan 内更新约束与显式分配。
|
||||
let children = plan.children[index].clone();
|
||||
let [_, _, width, height] =
|
||||
plan.bounds[index].ok_or(invalid("unarranged occurrence"))?;
|
||||
@@ -641,6 +656,7 @@ impl NativeCallable for Settle {
|
||||
available[2] = width;
|
||||
}
|
||||
if let Some(height) = plan.measured_limits[index][1] {
|
||||
// 源测量给出的纵向内容限制只覆盖最大高,最小约束仍从 next 继承,再由孩子约束策略统一处理。
|
||||
available[3] = height;
|
||||
}
|
||||
// 父级实际分配与测量不同,孩子的可用宽度应跟随分配,否则换行仍按旧宽度计算。
|
||||
@@ -648,6 +664,7 @@ impl NativeCallable for Settle {
|
||||
available[2] = width;
|
||||
}
|
||||
if height != plan.sizes[index].height {
|
||||
// 最终分配高度与测高不同,则孩子预算跟随实际分配;不把此值写回业务对象 height。
|
||||
available[3] = height;
|
||||
}
|
||||
// 根据父盒模型与实际宽高计算孩子公共约束基础,随后再考虑各个出现的独立尺寸规则。
|
||||
@@ -666,6 +683,7 @@ impl NativeCallable for Settle {
|
||||
Size { width, height },
|
||||
child_index,
|
||||
);
|
||||
// 从自然约束起步,再逐轴套入显式精确分配;natural 留作原生 flex 分配失效时恢复基线。
|
||||
let mut constraints = natural;
|
||||
// 显式轴分配同时设置最小与最大,区别于仅给孩子一个可自由缩小的最大可用宽度。
|
||||
if let Some(width) = plan.allocations[child][0] {
|
||||
@@ -673,11 +691,13 @@ impl NativeCallable for Settle {
|
||||
constraints[2] = width;
|
||||
}
|
||||
if let Some(height) = plan.allocations[child][1] {
|
||||
// 显式高同时成为 minHeight/maxHeight;这比仅给上限更强,使下一轮按安排分配重新测量。
|
||||
constraints[1] = height;
|
||||
constraints[3] = height;
|
||||
}
|
||||
// 线性布局才有主/交叉轴;交叉轴范围变化可触发文字重排,进而改变主轴自然尺寸。
|
||||
if let Some(main) = layout::main_axis(&plan.nodes[index]) {
|
||||
// 轴索引只有 0/1;Row 主轴横向、Column 主轴纵向,所以用 1-main 得到另一轴而非孩子顺序。
|
||||
let cross = 1 - main;
|
||||
// 交叉轴变化可能改变换行及主轴自然高度,清 preferred 让其重新测量;原生派生主轴分配也需暂时撤销。
|
||||
if constraints[cross] != plan.constraints[child][cross]
|
||||
@@ -752,6 +772,7 @@ impl NativeCallable for Settle {
|
||||
for weak in self.mounts.iter() {
|
||||
let mount = cx.upgrade(*weak)?.ok_or(invalid("layout Mount expired"))?;
|
||||
if let Some(context) = &cx.store().payload::<Mount>(mount, "Mount")?.layout {
|
||||
// 仍有临时上下文才清 bounds;其余输入留给下一轮 Step 更新,不在这里调用源 measure。
|
||||
context
|
||||
.lock()
|
||||
.map_err(|_| invalid("layout context poisoned"))?
|
||||
@@ -788,6 +809,7 @@ impl NativeCallable for Prepare {
|
||||
},
|
||||
vec![self.desktop],
|
||||
))?;
|
||||
// 固定循环以 Advance 返回的 Bool 决定继续,不为每个节点展开另一份巨大的驱动字节码。
|
||||
let function = crate::vm_sequence::repeat_call(cx, advance, "source layout", self.desktop)?;
|
||||
Ok(NativeResult::Invoke {
|
||||
function,
|
||||
@@ -836,6 +858,7 @@ impl NativeCallable for Advance {
|
||||
}
|
||||
.invoke(cx, &[])?;
|
||||
let NativeResult::Return(Value::Bool(repeat)) = settled else {
|
||||
// Settle 是宿主内部零参计算,必须同步返回 Bool;其他结果不能误作“已经稳定”而提前交付。
|
||||
return Err(invalid("invalid settling result"));
|
||||
};
|
||||
// 需要继续时重置同一个游标,后续仍回到原 repeat_call 帧执行整轮;false 则结束布局。
|
||||
@@ -933,6 +956,7 @@ impl Work {
|
||||
/// 恢复当前 Work 拥有的 VM 续体,继续同一轮求解而不是重放 measure/arrange。
|
||||
// ui_bridge::resume_layout_frame 调用,沿原只读续体继续;slices 统计包含 start 的第一片。
|
||||
pub fn resume(mut self, vm: &mut Vm, slice: usize) -> Result<Progress> {
|
||||
// resume 消费原 Work,计数包含最初 start;非法量子/恢复错误同样交 accept 取消原只读续体,不重建求解。
|
||||
self.slices += 1;
|
||||
let progress = vm.resume_read_only_call(slice);
|
||||
self.accept(vm, progress)
|
||||
@@ -963,12 +987,14 @@ impl Work {
|
||||
dependencies,
|
||||
..
|
||||
}) => {
|
||||
// VM 调用域已经完成,先释放其入口根;finish 消费同一个 Work,并只读取最终 Plan 汇总布局草稿。
|
||||
vm.release(self.prepare)?;
|
||||
finish(vm, self, value, dependencies)
|
||||
}
|
||||
Err(error) => {
|
||||
// An invalid quantum can fail without discarding the VM's
|
||||
// continuation. No failed job may strand a read-only scope.
|
||||
// 在取消调用可能清理 VM 执行上下文之前提取原始 traceback,避免最后只报告清理阶段位置。
|
||||
let diagnostic = crate::endor_host::runtime_error(vm, error);
|
||||
self.cancel(vm)?;
|
||||
Err(diagnostic)
|
||||
@@ -996,6 +1022,7 @@ pub fn start(
|
||||
// 分字段借用草稿以准备本轮,cache 命中统计重置但条目先保留供下方筛选。
|
||||
let frame = &mut state.frame;
|
||||
let runtime = &mut state.runtime;
|
||||
// 与 frame/runtime 借用同一 Draft 的独立字段,重置的是本轮源测量统计,不是清掉全部仍可复用的条目。
|
||||
let cache = &mut state.cache;
|
||||
cache.hits = 0;
|
||||
cache.misses = 0;
|
||||
@@ -1008,12 +1035,14 @@ pub fn start(
|
||||
// 扫描每个出现的实际 measure/arrange,实现全部为原生还不够,下面 flex/拉伸也会要求收敛驱动。
|
||||
let mut custom = false;
|
||||
for weak in &mounts {
|
||||
// 扫描只为选择驱动路径,临时升级保证方法身份检查期间 Mount 不被回收;下面无论检查结果如何都 release。
|
||||
let mount = vm
|
||||
.upgrade(*weak)?
|
||||
.ok_or_else(|| anyhow::anyhow!("layout Mount expired"))?;
|
||||
// 方法检查错误先保存为 Result,下面总会尝试 release 临时升级的 Mount,再传播检查结果。
|
||||
let result = (|| {
|
||||
let block = vm.payload::<Mount>(mount, "Mount")?.block;
|
||||
// 从真实业务对象取得类型,不能使用 UiNode 种类推断源覆盖是否存在。
|
||||
let definition = vm
|
||||
.object_store()
|
||||
.type_of(block)?
|
||||
@@ -1024,6 +1053,7 @@ pub fn start(
|
||||
return Ok::<_, VmError>(true);
|
||||
};
|
||||
if !crate::native_visual::is_intrinsic(vm.object_store(), method, name, desktop)? {
|
||||
// 任一协议为源覆盖就需要 VM 驱动,不能因另一协议仍是原生而整体走直接布局。
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user