docs: explain scroll projection and source hit selection
This commit is contained in:
@@ -11,6 +11,7 @@ use endor_core::{
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 验证 hitTest 返回的是 Bool,避免把任意真值隐式当成命中。
|
||||
// selector 把源 hitTest 返回值交给此适配器,之后字节码才根据 Bool 决定返回或继续查找。
|
||||
struct Boolean;
|
||||
impl NativeCallable for Boolean {
|
||||
/// 接收 hitTest 返回的唯一值,交给 invoke 严格检查 Bool。
|
||||
@@ -29,6 +30,7 @@ impl NativeCallable for Boolean {
|
||||
}
|
||||
#[derive(Debug)]
|
||||
/// 比较按下与释放的选择结果,只有同一目标才能继续点击分发。
|
||||
// Prepare 生成的驱动先执行两次 selector,最后调用此比较器;对象相等比较的是实际 Mount 身份。
|
||||
struct SameTarget;
|
||||
impl NativeCallable for SameTarget {
|
||||
/// 接收按下与释放两次命中选择的结果。
|
||||
@@ -49,6 +51,7 @@ impl NativeCallable for SameTarget {
|
||||
}
|
||||
}
|
||||
/// 构造只读命中选择驱动所需的托管代码与函数对象。
|
||||
// selector 和 Prepare 共用代码工厂;传入常量形成托管引用边,保持协议函数、实参和中间驱动存活。
|
||||
fn code(
|
||||
cx: &mut NativeContext<'_>,
|
||||
instructions: Vec<Instruction>,
|
||||
@@ -61,22 +64,27 @@ fn code(
|
||||
"<tessera>",
|
||||
1,
|
||||
);
|
||||
// 先把校验后的字节码分配到当前 NativeContext,输出函数经托管引用拥有它及其常量。
|
||||
let code = cx.allocate(EndorCodeObject::new(code))?;
|
||||
// 驱动只读取显式常量和局部槽,空 globals 使它不依赖或修改业务脚本全局变量。
|
||||
let globals = cx.new_dict()?;
|
||||
cx.new_function("pointer hitTest", code, globals)
|
||||
}
|
||||
/// 按逆绘制顺序构造候选 hitTest 调用,先进行几何与局部坐标筛选。
|
||||
// Prepare 分别为 press/release 构造选择器;准备阶段做原生粗筛,执行阶段才调用真实 hitTest 方法。
|
||||
fn selector(
|
||||
cx: &mut NativeContext<'_>,
|
||||
candidates: &[WeakObjectRef],
|
||||
point: [f32; 2],
|
||||
desktop: ObjectRef,
|
||||
) -> Result<ObjectRef, VmError> {
|
||||
// 所有候选共享一个 Bool 验证器,放常量槽 0,避免每个候选重复创建适配器。
|
||||
let validate = cx.allocate(ContextNativeFunction::new(
|
||||
"validate hitTest",
|
||||
Boolean,
|
||||
vec![],
|
||||
))?;
|
||||
// 可变指令和常量池按候选依次追加;前面候选返回 true 即提前 ReturnValue,剩余候选不会执行。
|
||||
let mut instructions = Vec::new();
|
||||
let mut constants = vec![Value::Object(validate)];
|
||||
// 候选按绘制顺序提供,逆序使最上层先参与测试;弱引用失效视为过期输入错误。
|
||||
@@ -84,6 +92,7 @@ fn selector(
|
||||
let mount = cx
|
||||
.upgrade(*weak)?
|
||||
.ok_or(VmError::InvalidArgument("pointer Mount expired"))?;
|
||||
// 克隆 Mount 描述以便后面分配源 Point;不持有跨 NativeContext 分配的 store 借用。
|
||||
let target = cx.store().payload::<Mount>(mount, "Mount")?.clone();
|
||||
// 先排除已卸载、非交互和几何不命中的出现,避免为明显无效候选构造源调用。
|
||||
if !target.mounted
|
||||
@@ -94,6 +103,7 @@ fn selector(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// 几何是本次出现的布局与投影快照,同一 Block 的其他 Mount 不能共享这里的局部坐标。
|
||||
let geometry = &target.geometry;
|
||||
let point = crate::geometry::Point::new(point[0], point[1]);
|
||||
// 把投影坐标反算到节点逻辑空间;不可逆/无效投影没有可用于 hitTest 的局部坐标。
|
||||
@@ -103,6 +113,7 @@ fn selector(
|
||||
// 源 Point 以节点左上为原点;这里再次验证范围,避免变换后的点落到节点盒子之外。
|
||||
let x = point.x - geometry.x;
|
||||
let y = point.y - geometry.y;
|
||||
// 经过逻辑逆变换后再按本地盒检查,源 hitTest 不接收明显超出该出现范围的点。
|
||||
if x < 0.0 || y < 0.0 || x > geometry.width || y > geometry.height {
|
||||
continue;
|
||||
}
|
||||
@@ -111,15 +122,18 @@ fn selector(
|
||||
.store()
|
||||
.type_of(target.block)?
|
||||
.ok_or(VmError::InvalidArgument("untyped hit target"))?;
|
||||
// 方法按实际名义类型查找,既可命中原生默认实现,也可执行源覆盖。
|
||||
let function = cx
|
||||
.store()
|
||||
.lookup_method(definition, "hitTest")?
|
||||
.ok_or(VmError::InvalidArgument("missing hitTest method"))?;
|
||||
// 为本次候选分配本桌面的 Point,先写 x/y 再加入常量池,辅助代码用它作为第三实参。
|
||||
let local = cx.new_object()?;
|
||||
attach_desktop_type(cx, local, "Point", desktop)?;
|
||||
cx.set_attr(local, "x", Value::Number((x as f64).into()))?;
|
||||
cx.set_attr(local, "y", Value::Number((y as f64).into()))?;
|
||||
// 每个候选占四个常量:协议函数、Block、Mount、局部 Point;索引供下面字节码复用。
|
||||
// 记录当前候选四个常量的基址;start+2 是最终返回的 Mount,不能误返回共享 Block。
|
||||
let start = constants.len() as u32;
|
||||
constants.extend([
|
||||
Value::Object(function),
|
||||
@@ -129,6 +143,7 @@ fn selector(
|
||||
]);
|
||||
// false 分支只跳过“加载本 Mount 并返回”两条指令。按编码宽度计算,
|
||||
// 常量索引大到需要扩展操作数时仍能跳到下一候选。
|
||||
// 跳转偏移以两字节指令单元计,因此编码字节长度除以 2;扩展操作数的长度也计入。
|
||||
let skip = (Instruction::encode_all(&[
|
||||
Instruction::new(Opcode::LoadConst, start + 2),
|
||||
Instruction::simple(Opcode::ReturnValue),
|
||||
@@ -153,6 +168,7 @@ fn selector(
|
||||
]);
|
||||
}
|
||||
// 所有候选均未命中时显式返回 Nil;不会把最后一个 false 当作选择结果。
|
||||
// 末尾追加独立 Nil 常量,零候选或全部返回 false 都走同一个无命中返回路径。
|
||||
let end = constants.len() as u32;
|
||||
constants.push(Value::Nil);
|
||||
instructions.extend([
|
||||
@@ -164,7 +180,9 @@ fn selector(
|
||||
#[derive(Debug)]
|
||||
/// 在一个只读调用中准备两次指针选择所需的候选与局部坐标。
|
||||
struct Prepare {
|
||||
// 候选来自已提交帧的交互出现,按绘制顺序排列,构造选择器时才逐项升级。
|
||||
candidates: Vec<WeakObjectRef>,
|
||||
// 按下与释放点都位于输入投影空间,各自独立做逆变换和协议命中,而非仅比较粗命中矩形。
|
||||
press: [f32; 2],
|
||||
release: [f32; 2],
|
||||
desktop: ObjectRef,
|
||||
@@ -179,7 +197,9 @@ impl NativeCallable for Prepare {
|
||||
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
|
||||
// 按下和释放分别构造选择器,但都在本次只读执行内调用,使用同一预算域。
|
||||
let down = selector(cx, &self.candidates, self.press, self.desktop)?;
|
||||
// 释放点重用同一候选序列;按下 selector 的最终结果只在执行期出现,准备时不提前猜测目标。
|
||||
let up = selector(cx, &self.candidates, self.release, self.desktop)?;
|
||||
// 分配比较器无须捕获业务根;真正待比较的 Mount 由驱动局部槽及实参保持生命周期。
|
||||
let compare = cx.allocate(ContextNativeFunction::new(
|
||||
"same pointer target",
|
||||
SameTarget,
|
||||
@@ -202,6 +222,7 @@ impl NativeCallable for Prepare {
|
||||
Instruction::new(Opcode::Call, 2),
|
||||
Instruction::simple(Opcode::ReturnValue),
|
||||
];
|
||||
// 为两个选择结果显式声明局部槽,常量顺序与上面的 LoadConst 0/1/2 对应。
|
||||
let driver = code(
|
||||
cx,
|
||||
instructions,
|
||||
@@ -224,6 +245,7 @@ impl NativeCallable for Prepare {
|
||||
}
|
||||
/// Returned Mount owns one root. No-hit and mismatched targets return None.
|
||||
/// 在一次有界只读调用中分别选择按下与释放目标;仅二者为同一 Mount 时返回其拥有根。
|
||||
// ui_bridge 的点击分发路径调用,成功的 Some 根由调用方使用后释放;本函数不直接派发业务 click。
|
||||
pub fn resolve(
|
||||
vm: &mut Vm,
|
||||
desktop: ObjectRef,
|
||||
@@ -242,6 +264,7 @@ pub fn resolve(
|
||||
},
|
||||
vec![desktop],
|
||||
))?;
|
||||
// 同步执行整个准备、两次命中及比较过程,共享总帧深/指令预算;不把未完成续体交给输入调用方。
|
||||
let result = vm.call_read_only_with_limits(
|
||||
prepare,
|
||||
&[],
|
||||
@@ -252,7 +275,9 @@ pub fn resolve(
|
||||
);
|
||||
// 先保存调用结果再释放入口根,因此 VM 返回错误时也能完成入口清理。
|
||||
vm.release(prepare)?;
|
||||
// 只取选择值,丢弃只读依赖集合;一次性输入命中不建立绘制或布局的持久失效依赖。
|
||||
let (value, _) = result.map_err(|error| crate::endor_host::runtime_error(vm, error))?;
|
||||
// 接受 Mount 或 Nil 两类驱动结果,其他值先释放其可能拥有根,再报告内部协议错误。
|
||||
match value {
|
||||
// 成功返回的 Mount 仍拥有一次根引用,交由调用方使用并释放;None 不转移任何对象根。
|
||||
Value::Object(mount) => Ok(Some(mount)),
|
||||
|
||||
Reference in New Issue
Block a user