Files
endorphin-tessera-gui/src/render/source_layout.rs
T

1084 lines
47 KiB
Rust

//! 源 measure/arrange 协议的分片布局驱动器。自底向上的测量与自顶向下的安排共享一个只读 VM 调用域。
//! Plan 只保存派生几何和求解过程;Measurement 缓存记录真实读取依赖。已有业务对象在只读阶段不能被修改。
//! 较大树可在节点/指令量子之间挂起。Work 拥有尚未发布的执行状态,必须 resume 或 cancel;完成后才把 Draft 交给桥接层提交。
//!
//! 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.
use crate::{
document::Frame,
layout::{self, LayoutRuntime, Size},
mount::Mount,
native_visual::{number, record, value},
object_list,
ui_bridge::{MAX_DIMENSION, UiNode, desktop_type},
};
use anyhow::Result;
use endor_core::{
ContextNativeFunction, NativeCallable, NativeContext, NativeResult, ObjectRef, Value, Vm,
VmError, WeakObjectRef,
};
use std::{
collections::{BTreeSet, HashMap, HashSet},
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)
}
/// 读取当前 desktop 的类型字典,为布局协议结果提供名义类型检查。
fn desktop_type_registry(cx: &NativeContext<'_>, desktop: ObjectRef) -> Result<ObjectRef, VmError> {
match cx.store().get_attr(desktop, "types")? {
Value::Object(object) => Ok(object),
_ => Err(invalid("missing type registry")),
}
}
#[derive(Debug, Clone)]
/// 一次源测量的缓存输入、输出和真实读取依赖;不同约束或依赖版本不能复用。
struct Measurement {
// 缓存记录借用的业务身份及方法身份,与子测量、约束和依赖版本共同决定复用资格,不增加托管根。
owner: ObjectRef,
method: ObjectRef,
children: Vec<Size>,
constraints: [f32; 4],
size: Size,
reads: Vec<endor_core::ReadDependency>,
}
/// Borrowed identities and versions only: this cache adds no managed roots.
#[derive(Debug, Default, Clone)]
/// 按 Mount 弱身份保存源测量结果,不为业务对象增加托管根。
pub struct Cache {
entries: HashMap<WeakObjectRef, Measurement>,
pub hits: usize,
pub misses: usize,
}
impl Cache {
/// 从测量缓存依赖中移除宿主私有 desktop 记账对象,防止内部同步造成自我失效。
pub(crate) fn exclude_host_desktops(&mut self, desktops: &HashSet<ObjectRef>) {
for entry in self.entries.values_mut() {
entry
.reads
.retain(|read| !desktops.contains(&read.object()));
}
}
/// 移除受影响 owner 的源测量缓存;其他出现继续按其读取版本检查。
pub fn invalidate(&mut self, owners: &HashSet<ObjectRef>) {
self.entries
.retain(|_, entry| !owners.contains(&entry.owner));
}
/// 清空本对象维护的当前内容或缓存,后续状态由调用方重新建立。
pub fn clear(&mut self) {
self.entries.clear();
self.hits = 0;
self.misses = 0;
}
}
#[derive(Debug)]
/// 一个等待源测量返回的收集项,记录本次方法、约束和子项输入。
struct Pending {
// 只读子调用的精确读取跟踪区间 token,由 Step 开始、Collect 在结果解码后结束。
token: usize,
owner: ObjectRef,
method: ObjectRef,
children: Vec<Size>,
constraints: [f32; 4],
key: WeakObjectRef,
}
#[derive(Debug)]
/// 未发布的布局求解工作区,包含出现树、期望尺寸、分配边界、收敛记录和缓存。
struct Plan {
// 使用出现索引保存树和几何,不为每个位置建立第二份业务对象。
nodes: Vec<Arc<UiNode>>,
children: Vec<Vec<usize>>,
sizes: Vec<Size>,
// preferred 是测量偏好,bounds 是父级实际分配;换行/flex 会使两者不同。
preferred: Vec<Option<Size>>,
bounds: Vec<Option<[f32; 4]>>,
runtime: LayoutRuntime,
custom: bool,
cache: Cache,
pending: Vec<Option<Pending>>,
constraints: Vec<[f32; 4]>,
// Source-arranged dimensions remain exact for this solve, not business state.
// 每个出现两个轴的显式分配,Some 形成精确 min=max 约束;不是可写业务尺寸属性。
allocations: Vec<[Option<f32>; 2]>,
custom_arrange: Vec<bool>,
native_arrange: Vec<bool>,
custom_measure: Vec<bool>,
measured_limits: Vec<[Option<f32>; 2]>,
// 保存几何状态用于检测收敛和重复状态,防止相互依赖布局无限循环。
previous: Option<Vec<u32>>,
seen: BTreeSet<Vec<u32>>,
}
type Shared = Arc<Mutex<Plan>>;
#[derive(Debug, Clone, Copy)]
/// 源视觉调用当前处于测量还是安排阶段,决定调用方法与结果解码。
enum Phase {
Measure,
Arrange,
}
impl Phase {
/// 把当前布局阶段映射为 measure 或 arrange 方法名称。
fn method(self) -> &'static str {
match self {
Self::Measure => "measure",
Self::Arrange => "arrange",
}
}
}
#[derive(Debug)]
/// 为某次出现准备一个 measure/arrange 调用,选择原生快速结果或真正的源覆盖。
struct Step {
plan: Shared,
index: usize,
phase: Phase,
mount: ObjectRef,
desktop: ObjectRef,
}
impl NativeCallable for Step {
/// 单个布局步骤所需 Mount、阶段和计划已保存,实际调用无参。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(0))
}
/// 解析当前实际 measure/arrange 实现,决定原生快速路径或源调用,准备本节点输入及依赖收集,避免跳过源覆盖。
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
let mount = cx.store().payload::<Mount>(self.mount, "Mount")?.clone();
let definition = cx
.store()
.type_of(mount.block)?
.ok_or(invalid("untyped layout target"))?;
let method = cx
.store()
.lookup_method(definition, self.phase.method())?
.ok_or(invalid("missing layout method"))?;
// 判断真实宿主实现而非方法名;源覆盖即使调用基类也不能整体绕过其用户逻辑。
let intrinsic = crate::native_visual::is_intrinsic(
cx.store(),
method,
self.phase.method(),
self.desktop,
)?;
let (node, children, preferred, bounds, native_size) = {
let mut plan = self
.plan
.lock()
.map_err(|_| invalid("layout plan poisoned"))?;
let node = plan.nodes[self.index].clone();
let children = plan.children[self.index]
.iter()
.map(|child| plan.sizes[*child])
.collect::<Vec<_>>();
// preferred 优先取本轮最初测量值,不能将上一轮 flex 分配当作新的自然基准,造成累计漂移。
let preferred = plan.children[self.index]
.iter()
.map(|child| plan.preferred[*child].unwrap_or(plan.sizes[*child]))
.collect::<Vec<_>>();
let bounds = if matches!(self.phase, Phase::Arrange) {
plan.bounds[self.index]
} else {
None
};
if matches!(self.phase, Phase::Measure) {
plan.custom_measure[self.index] = !intrinsic;
}
// 原生 measure 直接计算避免源 VM 调用;源 measure 尝试按 Mount、owner、方法、子尺寸、约束和读取版本复用。
let native_size = if matches!(self.phase, Phase::Measure) && intrinsic {
let constraints = plan.constraints[self.index];
Some(
plan.runtime
.measure_node(&node, children.clone(), constraints)
.map_err(|error| {
error.downcast_ref::<VmError>().cloned().unwrap_or_else(|| {
invalid("native measurement exceeds layout limits")
})
})?,
)
} else {
if !intrinsic {
plan.custom = true;
}
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(|dependency| {
cx.store().read_dependency_valid(dependency)
})
})
.map(|entry| entry.size);
// 命中无需再打开读取跟踪;未命中保存 Pending 元信息,等待真正源调用结果返回才建立缓存。
if let Some(size) = cached {
plan.cache.hits += 1;
Some(size)
} else {
plan.cache.misses += 1;
plan.runtime.measured += 1;
plan.pending[self.index] = Some(Pending {
token: 0,
owner: mount.block,
method,
children: children.clone(),
constraints,
key,
});
None
}
} else {
None
}
};
if matches!(self.phase, Phase::Arrange) {
plan.runtime.arranged += 1;
plan.native_arrange[self.index] = intrinsic;
// 原生 arrange 若需要分配 flex/stretch 也视作需显式记录子分配,参与后面的约束收敛。
plan.custom_arrange[self.index] = !intrinsic || node.allocates_children();
}
(node, children, preferred, bounds, native_size)
};
// 先安装本轮节点、子测量和临时 bounds,让源代码调用基类或 Mount.bounds 时观察当前阶段数据。
let context = mount.layout.ok_or(invalid("missing pre-layout context"))?;
{
let mut context = context
.lock()
.map_err(|_| invalid("layout context poisoned"))?;
context.node = Some(node);
context.children = children;
context.preferred = preferred;
context.bounds = bounds;
}
// 原生结果或缓存命中都包装为同一名义 MeasureResult,后续 Collect 无需区分来源。
if let Some(size) = native_size {
return Ok(NativeResult::Return(Value::Object(record(
cx,
self.desktop,
"MeasureResult",
vec![("width", value(size.width)), ("height", value(size.height))],
)?)));
}
let input = match self.phase {
Phase::Measure => {
let constraints = self
.plan
.lock()
.map_err(|_| invalid("layout plan poisoned"))?
.constraints[self.index];
record(
cx,
self.desktop,
"Constraints",
vec![
("minWidth", value(constraints[0])),
("minHeight", value(constraints[1])),
("maxWidth", value(constraints[2])),
("maxHeight", value(constraints[3])),
],
)?
}
Phase::Arrange => {
let [x, y, width, height] =
bounds.ok_or(invalid("arrangement has no allocated bounds"))?;
record(
cx,
self.desktop,
"Bounds",
vec![
("x", value(x)),
("y", value(y)),
("width", value(width)),
("height", value(height)),
],
)?
}
};
if matches!(self.phase, Phase::Measure) {
// 输入记录初始化完成后再开始子调用跟踪;Collect 解码返回值也在区间内,覆盖复用旧结果对象的字段读取。
let token = cx.store().begin_read_tracking()?;
self.plan
.lock()
.map_err(|_| invalid("layout plan poisoned"))?
.pending[self.index]
.as_mut()
.ok_or(invalid("missing measurement tracking state"))?
.token = token;
}
Ok(NativeResult::Invoke {
function: method,
arguments: vec![
Value::Object(mount.block),
Value::Object(self.mount),
Value::Object(input),
],
})
}
}
#[derive(Debug)]
/// 接收并检查源协议返回结果,将其合入 Plan,而不是直接写成发布帧。
struct Collect {
plan: Shared,
index: usize,
phase: Phase,
desktop: ObjectRef,
}
impl NativeCallable for Collect {
/// 接收一次 measure/arrange 返回的托管结果,按当前 phase 解码。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(1))
}
/// 结束当前测量读跟踪并解码结果,验证尺寸或完整子槽安排,再更新计划与可复用测量依赖。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
// 取走本节点待收集状态,确保每个跟踪区间只结束一次;arrange 不建立测量缓存项。
let pending = if matches!(self.phase, Phase::Measure) {
self.plan
.lock()
.map_err(|_| invalid("layout plan poisoned"))?
.pending[self.index]
.take()
} else {
None
};
let [Value::Object(result)] = args else {
return Err(invalid("layout must return a managed result"));
};
let expected = match self.phase {
Phase::Measure => "MeasureResult",
Phase::Arrange => "Arrangement",
};
// 先验证 MeasureResult/Arrangement 名义类型,再解码字段,拒绝只有同名字段的普通对象。
if !cx
.store()
.is_instance(*result, desktop_type(cx.store(), expected, self.desktop)?)?
{
return Err(invalid("layout returned the wrong result type"));
}
let mut plan = self
.plan
.lock()
.map_err(|_| invalid("layout plan poisoned"))?;
match self.phase {
Phase::Measure => {
// 源返回宽高先检查非负有限,再钳制到本出现约束,不允许结果绕过父级分配。
let size = layout::constrained(
Size {
width: number(cx, *result, "width", false)?,
height: number(cx, *result, "height", false)?,
},
plan.constraints[self.index],
);
// Native shrink-to-content containers keep their existing input
// policy. Only a source measurement that differs from the native
// result supplies an additional content constraint.
// 源容器测量若不同于默认原生结果,将相应轴记成额外内容上限;后续碰巧相等也不撤销本轮已选限制。
if plan.custom_measure[self.index] && !plan.children[self.index].is_empty() {
let children = plan.children[self.index]
.iter()
.map(|child| plan.sizes[*child])
.collect();
let native = layout::measure_native(
&plan.nodes[self.index],
children,
plan.constraints[self.index],
)
.ok()
.map(|native| layout::constrained(native, plan.constraints[self.index]));
// Equality after remeasurement does not retract a source
// limit selected earlier in this solve. New renders start fresh.
if !native.is_some_and(|native| native.width == size.width) {
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);
}
}
// Decoding is part of the subcall dependency region: a source
// method may return an existing result whose fields can change.
let tracked = pending
.map(|pending| {
cx.store()
.end_read_tracking_precise(pending.token)
.map(|reads| (pending, reads))
})
.transpose()?;
if let Some((pending, reads)) = tracked {
// 若观察了 Mount 内容/几何等不稳定宿主状态,无法证明跨轮可复用,保守地删除该测量缓存。
let mut volatile = false;
let registry = desktop_type_registry(cx, self.desktop)?;
for read in &reads.accesses {
let object = read.object;
if object == self.desktop || object == registry {
continue;
}
// A Mount's layoutVersion/geometry may be observed by
// source. Object-granular tracking cannot prove those
// accesses stable, so conservatively bypass its cache.
if cx.store().object(object)?.payload().as_any().is::<Mount>() {
volatile |= read.content_read;
continue;
}
}
if volatile {
plan.cache.entries.remove(&pending.key);
} else {
plan.cache.entries.insert(
pending.key,
Measurement {
owner: pending.owner,
method: pending.method,
children: pending.children,
constraints: pending.constraints,
size,
reads: reads
.dependencies
.into_iter()
.filter(|dependency| {
let object = dependency.object();
// 缓存只保留仍存在的业务依赖,去除 desktop、注册表与 Mount;避免宿主提交几何自己反过来使缓存失效。
object != self.desktop
&& object != registry
&& cx.store().object(object).is_ok_and(|value| {
!value.payload().as_any().is::<Mount>()
})
})
.collect(),
},
);
}
}
// 仅首次填写 preferred;sizes 每轮更新,根测量直接给本轮根 bounds,安排才能从根向下展开。
plan.preferred[self.index].get_or_insert(size);
plan.sizes[self.index] = size;
if self.index == 0 {
plan.bounds[0] = Some([0.0, 0.0, size.width, size.height]);
}
}
Phase::Arrange => {
let Value::Object(list) = cx.store().get_attr(*result, "children")? else {
return Err(invalid("Arrangement.children must be an ObjectList"));
};
let items = object_list::items(cx.store(), list)?;
let children = plan.children[self.index].clone();
// 安排结果必须覆盖全部孩子,不能省略零尺寸项;结合下面 slot 唯一检查保证逐一对应。
if items.len() != children.len() {
return Err(invalid(
"Arrangement must assign every child slot exactly once",
));
}
// 按稳定 slot 找实际孩子下标,不要求源返回列表与孩子原顺序一致;外来或重复 slot 都拒绝。
let slots: HashMap<_, _> = plan.nodes[self.index]
.slots
.iter()
.copied()
.enumerate()
.map(|(index, slot)| (slot, index))
.collect();
let mut assigned = BTreeSet::new();
let expected = desktop_type(cx.store(), "Bounds", self.desktop)?;
for item in items {
if !cx.store().is_instance(*item, expected)? {
return Err(invalid("Arrangement child must be Bounds"));
}
let Value::Number(slot) = cx.store().get_attr(*item, "slot")? else {
return Err(invalid("Arrangement child requires a slot"));
};
let slot = slot
.as_i64()
.and_then(|value| u64::try_from(value).ok())
.ok_or(invalid("invalid arrangement slot"))?;
let position = slots
.get(&slot)
.copied()
.ok_or(invalid("Arrangement references a foreign child slot"))?;
if !assigned.insert(slot) {
return Err(invalid("duplicate arrangement slot"));
}
let bounds = [
number(cx, *item, "x", true)?,
number(cx, *item, "y", true)?,
number(cx, *item, "width", false)?,
number(cx, *item, "height", false)?,
];
if bounds[0] + bounds[2] > MAX_DIMENSION as f32
|| bounds[1] + bounds[3] > MAX_DIMENSION as f32
{
return Err(invalid("arrangement bounds exceed limits"));
}
let child = children[position];
// 源安排或原生弹性分配的差异保留为显式轴分配,下一轮测量使用精确尺寸,确保文字按实际宽度重排。
if plan.custom_arrange[self.index] {
if plan.native_arrange[self.index]
&& let Some(main) = layout::main_axis(&plan.nodes[self.index])
{
// Keep an allocation explicit even if this pass's
// natural size happens to match it. It must survive
// the next parent-constraint propagation pass.
plan.allocations[child][main] = Some(bounds[main + 2]);
}
if bounds[2] != plan.sizes[child].width {
plan.allocations[child][0] = Some(bounds[2]);
}
if bounds[3] != plan.sizes[child].height {
plan.allocations[child][1] = Some(bounds[3]);
}
}
plan.bounds[child] = Some(bounds);
}
}
}
Ok(NativeResult::Return(Value::Nil))
}
}
#[derive(Debug)]
/// 检查本轮布局是否已收敛,必要时安排下一次测量/分配迭代。
struct Settle {
plan: Shared,
mounts: Arc<[WeakObjectRef]>,
}
impl NativeCallable for Settle {
/// 一轮测量/排列完成后无参进入收敛检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(0))
}
/// 由新 bounds 推导下一轮约束与自然尺寸,比较完整布局状态;稳定则结束,循环状态报错,否则继续同帧求解。
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
let repeat = {
let mut plan = self
.plan
.lock()
.map_err(|_| invalid("layout plan poisoned"))?;
plan.runtime.settle_passes += 1;
// 安排完成后按最终 bounds 从父到子推导下一轮约束,测量限制和源分配可反向影响换行高度。
let mut next = plan.constraints.clone();
for index in 0..plan.nodes.len() {
let children = plan.children[index].clone();
let [_, _, width, height] =
plan.bounds[index].ok_or(invalid("unarranged occurrence"))?;
let mut available = next[index];
if let Some(width) = plan.measured_limits[index][0] {
available[2] = width;
}
if let Some(height) = plan.measured_limits[index][1] {
available[3] = height;
}
if width != plan.sizes[index].width {
available[2] = width;
}
if height != plan.sizes[index].height {
available[3] = height;
}
let base = layout::arranged_child_constraints(
&plan.nodes[index],
available,
Size { width, height },
);
for (child_index, child) in children.into_iter().enumerate() {
let natural = layout::occurrence_constraints(
&plan.nodes[index],
&plan.nodes[child],
base,
Size { width, height },
child_index,
);
let mut constraints = natural;
// 显式轴分配同时设置最小与最大,区别于仅给孩子一个可自由缩小的最大可用宽度。
if let Some(width) = plan.allocations[child][0] {
constraints[0] = width;
constraints[2] = width;
}
if let Some(height) = plan.allocations[child][1] {
constraints[1] = height;
constraints[3] = height;
}
if let Some(main) = layout::main_axis(&plan.nodes[index]) {
let cross = 1 - main;
// 交叉轴变化可能改变换行及主轴自然高度,清 preferred 让其重新测量;原生派生主轴分配也需暂时撤销。
if constraints[cross] != plan.constraints[child][cross]
|| constraints[cross + 2] != plan.constraints[child][cross + 2]
{
// Wrapping or other cross-dependent measurement
// invalidates this occurrence's natural main size.
plan.preferred[child] = None;
if plan.native_arrange[index] {
// Derived flex sizes must not constrain their
// own replacement measurement. Source arrange
// allocations remain authoritative.
plan.allocations[child][main] = None;
constraints[main] = natural[main];
constraints[main + 2] = natural[main + 2];
}
}
}
next[child] = constraints;
}
}
let mut state = Vec::with_capacity(plan.nodes.len() * 14);
// 把约束、实测、bounds、分配和首选值编码成几何状态,统一 +0/-0,避免仅符号位差异造成假不收敛。
let bits = |value: f32| if value == 0.0 { 0 } else { value.to_bits() };
for (index, constraints) in next.iter().enumerate() {
state.extend(constraints.iter().copied().map(bits));
state.extend([
bits(plan.sizes[index].width),
bits(plan.sizes[index].height),
]);
state.extend(
plan.bounds[index]
.ok_or(invalid("unarranged occurrence"))?
.into_iter()
.map(bits),
);
for dimension in plan.allocations[index] {
state.push(u32::from(dimension.is_some()));
state.push(dimension.map_or(0, bits));
}
state.push(u32::from(plan.preferred[index].is_some()));
if let Some(size) = plan.preferred[index] {
state.extend([bits(size.width), bits(size.height)]);
} else {
state.extend([0, 0]);
}
}
// 相邻两轮状态和约束均相同才停止;非相邻重复则是振荡环,不能把循环误当作稳定布局。
if plan.previous.as_ref() == Some(&state) && plan.constraints == next {
false
} else {
if !plan.seen.insert(state.clone()) {
return Err(invalid("layout constraints did not converge (cycle)"));
}
plan.previous = Some(state);
plan.constraints = next;
// 需要重算时清全部 bounds;下一轮测量不能访问上轮安排产生的陈旧几何。
plan.bounds.fill(None);
true
}
};
if repeat {
// No stale geometry from a prior pass is observable during measurement.
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 {
context
.lock()
.map_err(|_| invalid("layout context poisoned"))?
.bounds = None;
}
}
}
Ok(NativeResult::Return(Value::Bool(repeat)))
}
}
#[derive(Debug)]
/// 准备当前求解步骤所需的约束和 Mount 临时上下文。
struct Prepare {
plan: Shared,
mounts: Vec<WeakObjectRef>,
desktop: ObjectRef,
}
impl NativeCallable for Prepare {
/// 布局驱动的零参入口,Mount 列表/desktop/计划已由调用方捕获。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(0))
}
/// 建立按出现位置推进的 Advance 和固定字节码循环,保持同一 VM 预算,避免首次让出前创建整树临时调用对象。
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
let advance = cx.allocate(ContextNativeFunction::new(
"next layout occurrence",
Advance {
plan: self.plan.clone(),
mounts: self.mounts.clone().into(),
desktop: self.desktop,
next: Mutex::new(0),
},
vec![self.desktop],
))?;
let function = crate::vm_sequence::repeat_call(cx, advance, "source layout", self.desktop)?;
Ok(NativeResult::Invoke {
function,
arguments: vec![],
})
}
}
#[derive(Debug)]
/// 在同一驱动帧中安排下一项布局工作,使大树能够分片让出。
struct Advance {
plan: Shared,
mounts: Arc<[WeakObjectRef]>,
desktop: ObjectRef,
next: Mutex<usize>,
}
impl NativeCallable for Advance {
/// 由共享布局循环无参调用,游标表示本轮测量/排列的位置。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(0))
}
/// 按游标安排自底向上 measure 与自顶向下 arrange,短锁结束后才创建/执行当前节点调用,末尾进入收敛检查。
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
let count = self.mounts.len();
// 游标取值/递增只在短锁内完成,不能持锁执行源协议,否则源调用基类会再次访问同一 Plan。
let position = {
let mut next = self
.next
.lock()
.map_err(|_| invalid("layout cursor poisoned"))?;
let position = *next;
if position < count * 2 {
*next += 1;
}
position
};
// 每轮恰有 N 个逆序 measure 和 N 个正序 arrange,之后调用 Settle;需要重算时游标归零。
if position == count * 2 {
let settled = Settle {
plan: self.plan.clone(),
mounts: self.mounts.clone(),
}
.invoke(cx, &[])?;
let NativeResult::Return(Value::Bool(repeat)) = settled else {
return Err(invalid("invalid settling result"));
};
if repeat {
*self
.next
.lock()
.map_err(|_| invalid("layout cursor poisoned"))? = 0;
}
return Ok(NativeResult::Return(Value::Bool(repeat)));
}
// nodes 是先序,逆序测量保证孩子先完成,正序安排保证父 bounds 已经分配到孩子。
let (phase, index) = if position < count {
(Phase::Measure, count - position - 1)
} else {
(Phase::Arrange, position - count)
};
let mount = cx
.upgrade(self.mounts[index])?
.ok_or(invalid("layout Mount expired"))?;
// 只为当前出现建立 Step 与 collector,显式 captures 保活本次 Mount/desktop,避免一次分配整棵调用树。
let step = cx.allocate(ContextNativeFunction::new(
"layout step",
Step {
plan: self.plan.clone(),
index,
phase,
mount,
desktop: self.desktop,
},
vec![mount, self.desktop],
))?;
let collector = cx.allocate(ContextNativeFunction::new(
"collect layout",
Collect {
plan: self.plan.clone(),
index,
phase,
desktop: self.desktop,
},
vec![self.desktop],
))?;
// 执行一步并立即收集,再给外层 repeat_call 返回 true;各轮迭代共享原 VM 帧和预算。
let function = crate::vm_sequence::collect_and_continue(
cx,
collector,
step,
vec![],
"layout occurrence",
self.desktop,
)?;
Ok(NativeResult::Invoke {
function,
arguments: vec![],
})
}
}
/// An unpublished layout frame and derived caches. These are merged only after
/// the driver completes; they are not another copy of application state.
/// 已经完成本阶段、但尚未由桥接层发布的派生结果,包含帧/缓存或真实读取依赖。
pub struct Draft {
pub frame: Frame,
pub runtime: LayoutRuntime,
pub cache: Cache,
pub reads: Vec<endor_core::ReadDependency>,
}
#[must_use = "pending layout work must be resumed or cancelled with its owning VM"]
/// 本次推进处于挂起还是完成;挂起工作必须继续恢复或显式取消。
pub enum Progress {
Pending(Work),
Complete(Draft),
}
/// Host-owned continuation. No borrowed frame/cache references cross a pause.
/// The prepare handle owns one root until completion or explicit cancellation.
/// 拥有未完成源调用及临时根的工作句柄;丢弃 Rust 值不能替代 VM 内的取消。
pub struct Work {
state: Draft,
plan: Shared,
prepare: ObjectRef,
desktop: ObjectRef,
slices: usize,
}
impl Work {
/// 恢复当前 Work 拥有的 VM 续体,继续同一轮求解而不是重放 measure/arrange。
pub fn resume(mut self, vm: &mut Vm, slice: usize) -> Result<Progress> {
self.slices += 1;
let progress = vm.resume_read_only_call(slice);
self.accept(vm, progress)
}
/// 显式结束布局续体和只读域,释放本次尚未提交的临时根。
pub fn cancel(self, vm: &mut Vm) -> Result<()> {
// 取消与释放入口根均先尝试,再依次传播错误;普通 Rust drop 无法替代 VM 只读域清理。
let cancelled = vm.cancel_read_only_call();
let released = vm.release(self.prepare);
cancelled?;
released?;
Ok(())
}
/// 接收一次续体进度,挂起时保留工作,完成时将派生结果交给收尾阶段。
fn accept(
self,
vm: &mut Vm,
progress: Result<endor_core::ExecutionProgress, VmError>,
) -> Result<Progress> {
match progress {
Ok(endor_core::ExecutionProgress::Pending { .. }) => Ok(Progress::Pending(self)),
Ok(endor_core::ExecutionProgress::Complete {
value,
dependencies,
..
}) => {
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.
let diagnostic = crate::endor_host::runtime_error(vm, error);
self.cancel(vm)?;
Err(diagnostic)
}
}
}
}
/// 建立只读布局 Plan 与托管驱动函数;小型原生树可走直接路径,大树保留分片推进能力。
pub fn start(
vm: &mut Vm,
desktop: ObjectRef,
mounts: Vec<WeakObjectRef>,
root: Arc<UiNode>,
mut state: Draft,
slice: usize,
) -> Result<Progress> {
anyhow::ensure!(slice > 0, "layout slice must be positive");
anyhow::ensure!(
!vm.has_suspended_call() && !vm.object_store().is_read_only(),
"cannot start layout while another read-only execution is active"
);
let frame = &mut state.frame;
let runtime = &mut state.runtime;
let cache = &mut state.cache;
cache.hits = 0;
cache.misses = 0;
let live: HashSet<_> = mounts.iter().copied().collect();
// 先删除不再出现在此结构中的测量项,防止已移除的弱 Mount 身份无限积累。
cache.entries.retain(|mount, _| live.contains(mount));
// Preserve the existing native measurement cache and avoid VM/result
// allocation for small trees without custom layout methods. Contexts already exist.
// 扫描每个出现的实际 measure/arrange,实现全部为原生还不够,下面 flex/拉伸也会要求收敛驱动。
let mut custom = false;
for weak in &mounts {
let mount = vm
.upgrade(*weak)?
.ok_or_else(|| anyhow::anyhow!("layout Mount expired"))?;
let result = (|| {
let block = vm.payload::<Mount>(mount, "Mount")?.block;
let definition = vm
.object_store()
.type_of(block)?
.ok_or(invalid("untyped layout target"))?;
for name in ["measure", "arrange"] {
let Some(method) = vm.object_store().lookup_method(definition, name)? else {
return Ok::<_, VmError>(true);
};
if !crate::native_visual::is_intrinsic(vm.object_store(), method, name, desktop)? {
return Ok(true);
}
}
Ok(false)
})();
vm.release(mount)?;
custom |= result?;
}
let mut nodes = Vec::new();
let mut work = vec![root.clone()];
while let Some(node) = work.pop() {
custom |= node.has_flex() || node.allocates_children();
work.extend(node.children().iter().rev().cloned());
nodes.push(node);
}
// Flex allocation can change text wrapping and ancestor measurements even
// when every protocol method is native. Use the same settling driver.
// 仅小于等于64节点且无自定义/弹性分配的树走直接路径;大原生树仍经同一驱动以便分片让出。
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.
// 结构准备已经分配本次发布版本,快速完整测量不能再多消耗一个可见布局版本。
native.layout_version = frame.layout_version;
runtime.version = frame.layout_version;
*frame = native;
state.reads.clear();
return Ok(Progress::Complete(state));
}
// 帧结构、弱 Mount 和视觉节点必须同序同数量,否则按出现下标执行协议会把上下文接错。
anyhow::ensure!(
nodes.len() == mounts.len() && nodes.len() == frame.mounts.len(),
"layout structure does not match Mount contexts"
);
let indices: HashMap<_, _> = frame
.mounts
.iter()
.enumerate()
.map(|(index, mount)| (mount.id, index))
.collect();
let mut children = vec![Vec::new(); nodes.len()];
for (index, mount) in frame.mounts.iter().enumerate() {
if let Some(parent) = mount.parent {
children[indices[&parent]].push(index);
}
}
// 从根向下初始化约束,之后每轮由 Settle 结合真实父分配细化;初始值不是最终精确几何。
let mut constraints = vec![runtime.root_constraints(); nodes.len()];
for index in 0..nodes.len() {
for (child_index, child) in children[index].iter().enumerate() {
constraints[*child] = layout::initial_child_constraints(
&nodes[index],
&nodes[*child],
constraints[index],
child_index,
);
}
}
let count = nodes.len();
// 把运行时/缓存克隆进未发布 Plan,失败或取消时上层可以丢弃派生求解结果而不提交半帧。
let plan = Arc::new(Mutex::new(Plan {
sizes: vec![Size::default(); nodes.len()],
preferred: vec![None; nodes.len()],
bounds: vec![None; nodes.len()],
nodes,
children,
runtime: runtime.clone(),
custom: false,
cache: cache.clone(),
pending: (0..count).map(|_| None).collect(),
constraints,
allocations: vec![[None, None]; count],
custom_arrange: vec![false; count],
native_arrange: vec![false; count],
custom_measure: vec![false; count],
measured_limits: vec![[None, None]; count],
previous: None,
seen: BTreeSet::new(),
}));
let prepare = vm.allocate(ContextNativeFunction::new(
"prepare source layout",
Prepare {
plan: plan.clone(),
mounts,
desktop,
},
vec![desktop],
))?;
let work = Work {
state,
plan,
prepare,
desktop,
slices: 1,
};
// 一次有界只读调用跨所有节点与收敛轮次;slice 只控制让出量,总指令预算仍共享一百万。
let progress = vm.start_read_only_call(
prepare,
&[],
endor_core::ExecutionLimits {
max_frames: 128,
max_instructions: 1_000_000,
},
slice,
);
work.accept(vm, progress)
}
/// 验证求解已收敛,收集最终几何、读取依赖和缓存,返回尚待桥接层提交的 Draft。
fn finish(
vm: &mut Vm,
mut work: Work,
value: Value,
reads: Vec<endor_core::ReadDependency>,
) -> Result<Progress> {
// 驱动返回值仅为控制流结果,先释放;真正结果保存在原生 Plan,而非返回值中的第二份业务图。
vm.release_value(value)?;
let mut plan = work
.plan
.lock()
.map_err(|_| anyhow::anyhow!("layout plan poisoned"))?;
plan.runtime.slices = work.slices;
let frame = &mut work.state.frame;
// 用最终收敛 bounds 更新 frame 的各出现;这里只生成默认内容命令,后续投影/源 paint 还会重建完整绘制表。
frame.commands.clear();
for (index, mount) in frame.mounts.iter_mut().enumerate() {
let [x, y, width, height] =
plan.bounds[index].ok_or_else(|| anyhow::anyhow!("unarranged occurrence"))?;
mount.x = x;
mount.y = y;
mount.width = width;
mount.height = height;
if let Some(command) =
layout::paint_native(&plan.nodes[index], x, y, Size { width, height })
{
command.validate()?;
frame.commands.push(command);
}
}
work.state.runtime = plan.runtime.clone();
work.state.cache = plan.cache.clone();
// 大原生树可能使用分片驱动但没有真正源读取,因此无需把宿主驱动读取当业务布局依赖发布。
if !plan.custom {
work.state.reads.clear();
return Ok(Progress::Complete(work.state));
}
let registry = match vm.get_attr(work.desktop, "types")? {
Value::Object(object) => object,
_ => anyhow::bail!("missing type registry"),
};
work.state.reads = reads
.into_iter()
.filter(|dependency| {
let object = dependency.object();
object != work.desktop
&& object != registry
&& vm
.object(object)
.is_ok_and(|object| !object.payload().as_any().is::<Mount>())
})
.collect();
Ok(Progress::Complete(work.state))
}