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

631 lines
32 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 让原生组件也通过 Endor 的 measure/arrange/paint 协议被调用。
//! 将 Constraints、Size、Bounds 与 PaintCommand 在托管值和 Rust 值之间转换;原生方法与源覆盖方法共用调用契约。
//!
//! 调用路径:bootstrap 调用 install 安装 Block 默认协议与 Point 辅助方法。
//! 源子类可显式调用基类方法;source_layout 在 staged Context 中复用已测孩子,独立调用则运行完整原生布局。
//! is_intrinsic 供协议驱动判断能否跳过 VM 调用;仅认可本实现及同一名义类型注册表,不能凭函数名判定。
//!
//! Native visual protocol entry points. Values are ordinary managed Endor
//! objects; geometry is scoped to a live occurrence, never stored on a Block.
use crate::{
block,
document::RenderCommand,
layout::{self, Size},
mount::Mount,
ui_bridge::{MAX_DIMENSION, attach_desktop_type},
};
use endor_core::{
AnnotationType, CallEffect, CallSignature, ContextNativeFunction, NativeCallable,
NativeContext, NativeResult, ObjectRef, ObjectStore, Value, Vm, VmError,
};
/// 统一构造本模块的参数错误,让调用方停止处理无效输入。
fn invalid(message: &'static str) -> VmError {
VmError::InvalidArgument(message)
}
/// 从源值中提取并检查托管对象身份,错误类型沿 VM 错误返回。
fn object(value: &Value) -> Result<ObjectRef, VmError> {
// 协议 receiver/输入必须为真实托管对象,不能接受仅可字符串化的标量。
if let Value::Object(object) = value {
Ok(*object)
} else {
Err(invalid("visual protocol requires objects"))
}
}
/// 在源 Value 与所需数值表示之间转换,并沿用本调用的有限值/范围检查。
pub(crate) fn number(
cx: &NativeContext<'_>,
object: ObjectRef,
name: &str,
signed: bool,
) -> Result<f32, VmError> {
// 所有几何字段从当前属性存储读取,类型必须为 Number,不在这里尝试源函数或转换运算。
let Value::Number(value) = cx.store().get_attr(object, name)? else {
return Err(invalid("visual field must be a Number"));
};
// 转到布局实际使用的 f32 后再检查有限/范围,防止大数溢出为无穷。
let value = value
.to_string()
.parse::<f32>()
.map_err(|_| invalid("invalid visual number"))?;
// 所有协议数值须有限且位于世界尺寸预算内;signed 区分坐标(可负)与长度/约束(非负)。
if !value.is_finite() || value.abs() > MAX_DIMENSION as f32 || (!signed && value < 0.0) {
return Err(invalid("visual number exceeds bounds"));
}
Ok(value)
}
/// 将原生布局计算结果封装为协议需要的托管 Value。
pub(crate) fn value(value: f32) -> Value {
// 原生 f32 提升到 f64 包装为 Endor Number;本函数不单独校验,调用路径必须已保证输入合法。
Value::Number((value as f64).into())
}
/// 新建指定名义类型的托管记录,并逐项设置字段,作为视觉协议的结果返回。
pub(crate) fn record(
cx: &mut NativeContext<'_>,
desktop: ObjectRef,
kind: &str,
fields: Vec<(&str, Value)>,
) -> Result<ObjectRef, VmError> {
// 先赋精确名义类型,再初始化字段;只读协议可创建结果对象,但不能借机写入旧业务图。
let object = cx.new_object()?;
attach_desktop_type(cx, object, kind, desktop)?;
// 新结果记录在本次调用内逐字段初始化,VM 可区分派生对象写入与旧业务图修改。
for (key, value) in fields {
cx.set_attr(object, key, value)?;
}
Ok(object)
}
/// 以最大宽高作视口执行原生布局并提取根尺寸;最小/最大最终钳制由 Measure 分支负责。
fn size(node: &crate::ui_bridge::UiNode, constraints: [f32; 4]) -> Result<Size, VmError> {
// 无 staged 子测量结果时,使用有限最大约束运行原生布局取得根尺寸;最终 min/max 钳制在调用方 Measure 分支。
let frame = layout::frame_with_viewport(
node,
Size {
width: constraints[2],
height: constraints[3],
},
)
.map_err(|_| invalid("native measurement exceeds layout limits"))?;
// 只取已求出的根出现尺寸,空布局不是合法测量返回,明确报错。
let root = frame
.mounts
.first()
.ok_or(invalid("empty native measurement"))?;
Ok(Size {
width: root.width,
height: root.height,
})
}
#[derive(Debug, Clone, Copy)]
/// 该原生能力的查询/操作分类,方法包装用它选择具体实现。
enum Query {
// 按 Constraints 返回本出现的自然/受限 MeasureResult。
Measure,
// 按 Bounds 和孩子测量返回带 slot 的 Arrangement。
Arrange,
// 把默认原生绘制编码为源可组合的 DisplayList。
Paint,
// 按本出现已提交的宽高检查局部 Point,返回 Bool。
HitTest,
}
#[derive(Debug)]
/// 保存 owner、签名或操作枚举的原生方法适配器,统一由 VM 调用。
struct Method {
// 操作分类决定输入/返回契约与分派实现。
query: Query,
// 名义类型注册环境,native captures 保活它;同 VM 窗口可共享 types。
desktop: ObjectRef,
// 含 Block receiver、Mount 和协议输入的三参只读契约。
signature: CallSignature,
}
/// Only this actual implementation can use the matching intrinsic fast path.
/// 判断方法是否为匹配协议的宿主原生实现,决定能否安全使用快速路径。
pub(crate) fn is_intrinsic(
store: &ObjectStore,
function: ObjectRef,
name: &str,
desktop: ObjectRef,
) -> Result<bool, VmError> {
// 快速路径仅认可实际 ContextNativeFunction 的 Method 实现,函数名相同或签名相似的源函数不能冒充。
let Some(function) = store
.object(function)?
.payload()
.as_any()
.downcast_ref::<ContextNativeFunction>()
else {
return Ok(false);
};
// 即使是 ContextNativeFunction,也必须确为本 Method 实现,其他 native payload 不享受该快速路径。
let Some(method) = function.implementation::<Method>() else {
return Ok(false);
};
// Per-window desktops share the same nominal registry and intrinsic bodies.
// 同一 VM 不同窗口 desktop 可共享 types 字典,所以以注册表身份允许复用原生方法体。
let same_registry = method.desktop == desktop
|| matches!(
(store.get_attr(method.desktop, "types")?, store.get_attr(desktop, "types")?),
(Value::Object(left), Value::Object(right)) if left == right
);
Ok(same_registry
&& matches!(
(name, method.query),
("measure", Query::Measure)
| ("arrange", Query::Arrange)
| ("paint", Query::Paint)
| ("hitTest", Query::HitTest)
))
}
impl NativeCallable for Method {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<CallSignature> {
Some(self.signature.clone())
}
/// 接收三项底层参数:Block 接收者、具体 Mount、Constraints/Bounds/Point 协议输入。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(3))
}
/// 检查 Block/Mount 归属与有效性,再按 measure/arrange/paint/hitTest 执行原生视觉协议并返回受检查的托管值。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
// 三个实际参数含接收者 Block、具体 Mount 和协议输入;几何属于 Mount,不能仅凭 Block 身份查询。
let [owner, context, input] = args else {
return Err(invalid("visual method expects a Mount and an input value"));
};
// receiver 必须为对象,后面的 Mount.block 归属检查使用同一原始业务身份。
let owner = object(owner)?;
// 从显式上下文对象取得真实 Mount,不凭 Bounds 形状推断出现关系。
let mount = cx.store().payload::<Mount>(object(context)?, "Mount")?;
// 默认协议也校验上下文归属,防止调用者把另一个出现的几何套到当前组件。
if !mount.mounted || mount.block != owner {
return Err(invalid(
"visual context is detached or belongs to another Block",
));
}
// 复制最后提交几何,只有非 staged 的命中/独立调用使用这份出现值。
let geometry = mount.geometry.clone();
// 布局阶段从临时 Context 取当前节点、孩子实测尺寸与首选尺寸;克隆派生数据后释放锁再进行计算。
let staged = mount
.layout
.as_ref()
.map(|layout| {
// 锁内只克隆当前节点和孩子尺寸,源/原生计算期间不持临时布局 Context 锁。
let layout = layout
.lock()
.map_err(|_| invalid("layout context poisoned"))?;
Ok::<_, VmError>((
layout.node.clone(),
layout.children.clone(),
layout.preferred.clone(),
))
})
.transpose()?;
// 第三参数须为对象,具体名义输入由安装的签名约束,字段数值在各分支继续检查。
let input = object(input)?;
// 默认 hitTest 检查局部点在已提交盒内,采用左上包含、右下排除;布局阶段几何不稳定,禁止调用。
// 先处理只依赖已提交局部盒的默认命中,成功后无需构建视觉树或结果记录。
if matches!(self.query, Query::HitTest) {
// 布局期间可能有临时尺寸,默认 hitTest 不能把它当作最终可交互几何。
if staged.is_some() {
return Err(invalid("hitTest is unavailable during layout"));
}
// 默认 hitTest 的 Point 为本出现局部坐标,允许带符号以自然排除盒外点。
let x = number(cx, input, "x", true)?;
// 纵坐标独立解析,与 x 一起采用右/下排除的半开盒范围。
let y = number(cx, input, "y", true)?;
return Ok(NativeResult::Return(Value::Bool(
x >= 0.0 && y >= 0.0 && x < geometry.width && y < geometry.height,
)));
}
// 存在布局 Context 但尚无当前节点,说明准备顺序不完整,不能回退读取旧快照伪装成当前数据。
if matches!(&staged, Some((None, _, _))) {
return Err(invalid(
"layout context is not prepared for this occurrence",
));
}
// 准备好则复用本轮不可变节点,非 staged 调用才从托管 Block 提取视觉快照。
let node = if let Some((Some(node), _, _)) = &staged {
node.clone()
} else {
block::snapshot(cx.store(), owner)?
};
// measure/arrange/paint 产生本域名义结果记录,几何从不写回共享 Block。
let result = match self.query {
// 读取并验证四项约束,staged 复用孩子测量,非 staged 走完整原生测量。
Query::Measure => {
// 横轴最小约束非负有限,与 maxWidth 配对检查顺序。
let min_w = number(cx, input, "minWidth", false)?;
// 纵轴最小约束非负有限,不能从横向约束推断。
let min_h = number(cx, input, "minHeight", false)?;
// 横轴最大可用宽影响文字换行与子项分配。
let max_w = number(cx, input, "maxWidth", false)?;
// 纵轴最大可用高与 minHeight 组合,非法区间拒绝而非交换两端。
let max_h = number(cx, input, "maxHeight", false)?;
// 最小值不可超过最大值,否则后面的 clamp 会无意义甚至失败;各数值已通过非负有限检查。
if min_w > max_w || min_h > max_h {
return Err(invalid("minimum exceeds maximum constraint"));
}
// 混合源/原生布局使用已经测好的子尺寸,避免重复执行子树;独立调用则走完整原生测量。
let size = if let Some((_, children, _)) = &staged {
layout::measure_native(&node, children.clone(), [min_w, min_h, max_w, max_h])
.map_err(|_| invalid("native measurement exceeds layout limits"))?
} else {
size(&node, [min_w, min_h, max_w, max_h])?
};
Value::Object(record(
cx,
self.desktop,
"MeasureResult",
vec![
// MeasureResult 最终明确落在传入 min/max 内,原生内容自然大小不能突破源协议约束。
("width", value(size.width.clamp(min_w, max_w))),
("height", value(size.height.clamp(min_h, max_h))),
],
)?)
}
// 排列输入 Bounds 使用绝对布局位置和实际分配大小,结果按稳定 slot 标识孩子。
Query::Arrange => {
// 父布局框横原点可负,需受有限世界范围约束。
let x = number(cx, input, "x", true)?;
// 父布局框纵原点可负,和高度共同验证下端点。
let y = number(cx, input, "y", true)?;
// 分配宽必须非负,不能把负宽当作镜像变换。
let width = number(cx, input, "width", false)?;
// 分配高必须非负,两轴不依赖旧 Mount 自然尺寸。
let height = number(cx, input, "height", false)?;
// 各分量合法后仍检查正向远端是否越出视觉预算;这里保持原协议的端点规则。
if x + width > MAX_DIMENSION as f32 || y + height > MAX_DIMENSION as f32 {
return Err(invalid("visual bounds exceed limits"));
}
// Arrange 优先使用本轮子测量;没有 staged 时按孩子的实际分配约束逐个测量后再安排。
let children = if let Some((_, children, _)) = &staged {
children.clone()
} else {
node.children()
.iter()
.enumerate()
.map(|(child_index, child)| {
size(
child,
layout::occurrence_constraints(
&node,
child,
layout::arranged_child_constraints(
&node,
[0.0, 0.0, width, height],
Size { width, height },
),
Size { width, height },
child_index,
),
)
})
.collect::<Result<Vec<_>, _>>()?
};
// preferred 保留弹性分配所需首选大小,与已受约束的实测 children 区分;无 staged 时两者相同。
let preferred = staged
.as_ref()
.map_or(children.as_slice(), |(_, _, preferred)| {
preferred.as_slice()
});
// 先依据父盒分配 flex 尺寸,再由 arrange_native 求位置,避免先定位再改变孩子大小导致重叠。
let children =
layout::flex_sizes(&node, Size { width, height }, &children, preferred);
// 尺寸求解完成后再排列位置,返回顺序对应 children/slots 三者的同一索引。
let points = layout::arrange_native(&node, x, y, Size { width, height }, &children)
.map_err(|_| invalid("native arrangement exceeds limits"))?;
// 收集每个孩子的新 Bounds 记录,最后包装为受控 ObjectList。
let mut result = Vec::new();
// 每个返回 Bounds 附稳定 slot,与 children 当前顺序分开;调用方可按 slot 校验组成身份。
for ((slot, size), (x, y)) in node.slots.iter().zip(children).zip(points) {
// 每条返回记录只描述当前孩子的分配,另附 slot 供源布局驱动验证归属。
let bounds = record(
cx,
self.desktop,
"Bounds",
vec![
("x", value(x)),
("y", value(y)),
("width", value(size.width)),
("height", value(size.height)),
],
)?;
cx.set_attr(bounds, "slot", Value::Number((*slot as i64).into()))?;
result.push(bounds);
}
// Arrangement 返回受控 ObjectList,不返回宿主 Vec 或任意形状的源对象集合。
let result = crate::object_list::collection(cx, result, self.desktop)?;
Value::Object(record(
cx,
self.desktop,
"Arrangement",
vec![("children", Value::Object(result))],
)?)
}
// 把默认绘制原生命令编码回源 PaintCommand,供源覆盖调用基类后追加或改新结果。
Query::Paint => {
// 本次 paint 使用给定布局 x,不把共享业务 x 当成最终出现位置。
let x = number(cx, input, "x", true)?;
// 本次 paint 使用给定布局 y,投影到面板/设备空间在后续显示阶段处理。
let y = number(cx, input, "y", true)?;
// 绘制宽是当前出现分配的非负宽度,与其他共享出现可以不同。
let width = number(cx, input, "width", false)?;
// 绘制高独立解析,结合 y 检查远端上界。
let height = number(cx, input, "height", false)?;
// 绘制输入也执行与 arrange 相同的正向端点预算检查。
if x + width > MAX_DIMENSION as f32 || y + height > MAX_DIMENSION as f32 {
return Err(invalid("visual bounds exceed limits"));
}
// 本次 paint 的派生结果集合,不能包含运行时可变 VM 场景对象。
let mut commands = Vec::new();
// 将 Rust 默认绘制命令反向编码成源 PaintCommand,允许源覆盖方法调用基类 paint 后继续组合。
for command in layout::paint_commands(&node, x, y, Size { width, height }) {
// 只编码明确支持的命令种类;其他变体报错而不是静默省略画面。
let fields = match command {
RenderCommand::Scene3D {
x,
y,
width,
height,
view,
} => {
// 3D 通过不可变 PaintView payload 暴露场景快照,不把运行中 VM 的场景对象交给后续绘制线程。
// PaintView payload 包装已拥有的原生 View,后续源解码无需从渲染线程读托管场景。
let snapshot = cx.allocate(crate::scene3d::PaintView(view))?;
vec![
("kind", Value::Text("scene3d".into())),
("x", value(x)),
("y", value(y)),
("width", value(width)),
("height", value(height)),
("scene", Value::Object(snapshot)),
]
}
RenderCommand::Text {
x,
y,
value: text,
size,
color,
wrap_bounds,
} => {
// 先写文字必需字段,只有存在 wrap_bounds 才成对添加 wrapWidth/wrapHeight。
let mut fields = vec![
("kind", Value::Text("text".into())),
("x", value(x)),
("y", value(y)),
("text", Value::Text(text)),
("size", Value::Number((size as i64).into())),
("color", Value::Text(color)),
];
// 可选文本换行边界成对写入;缺省时两个字段都不创建,与源 paint 解码契约保持一致。
if let Some([width, height]) = wrap_bounds {
fields.push(("wrapWidth", value(width)));
fields.push(("wrapHeight", value(height)));
}
fields
}
RenderCommand::RoundedRect {
x,
y,
width,
height,
radius,
color,
} => vec![
("kind", Value::Text("roundedRect".into())),
("x", value(x)),
("y", value(y)),
("width", value(width)),
("height", value(height)),
("radius", value(radius)),
("color", Value::Text(color)),
],
// 当前反向编码支持明确列出的命令;新增 RenderCommand 变体时须同步此转换,不能静默丢弃。
_ => return Err(invalid("unsupported native paint command")),
};
// 给每条命令附本域 PaintCommand 类型,字段数据仍只属于本次结果。
let command = record(cx, self.desktop, "PaintCommand", fields)?;
commands.push(command);
}
// 先生成命令对象列表,再放进名义 DisplayList 记录的 commands 字段。
let commands = crate::object_list::collection(cx, commands, self.desktop)?;
Value::Object(record(
cx,
self.desktop,
"DisplayList",
vec![("commands", Value::Object(commands))],
)?)
}
Query::HitTest => unreachable!("handled above"),
};
Ok(NativeResult::Return(result))
}
}
#[derive(Debug)]
/// Point 的矩形/圆形命中辅助方法,保存结果类型和具体几何判定种类。
struct PointMethod {
// Point 的当前桌面名义类型,用于检查 receiver,而非仅比较 x/y 字段。
expected: ObjectRef,
// true 选择圆内距离,否则选择 Bounds 半开矩形包含判断。
circle: bool,
// 两个辅助方法均为只读 Bool 返回契约,参数形状由 circle 决定。
signature: CallSignature,
}
impl NativeCallable for PointMethod {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<CallSignature> {
Some(self.signature.clone())
}
/// 圆形测试接收 Point 加圆心/半径共四项;矩形测试接收 Point 和 Bounds 共两项。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(if self.circle {
4
} else {
2
}))
}
/// 先验证 Point 名义类型与有限坐标,再分别计算圆内距离或 Bounds 包含关系,返回 Bool。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
// 取隐式 Point receiver,错误实参形状在字段读取之前拒绝。
let Some(Value::Object(point)) = args.first() else {
return Err(invalid("Point method requires a Point"));
};
// Point 辅助方法验证名义接收者,不能只要有 x/y 字段就接受普通对象。
if !cx.store().is_instance(*point, self.expected)? {
return Err(invalid("Point method requires a Point"));
}
// 先读并验证 receiver 的有限 x,绝对值不超过视觉世界范围。
let x = number(cx, *point, "x", true)?;
// y 使用同样规则,后续圆距离再提升到 f64 运算。
let y = number(cx, *point, "y", true)?;
// 按适配器分类检查圆心/半径或 Bounds,最终统一返回 Bool。
let inside = if self.circle {
// 圆分支要求四项实际参数,receiver 已在前面检查,另外三项必须为 Number。
let [
_,
Value::Number(cx_value),
Value::Number(cy_value),
Value::Number(radius),
] = args
else {
return Err(invalid("inCircle requires center x/y and radius"));
};
// 圆心和半径按 f64 解析及求平方距离,在有界数值内避免不必要的 f32 精度损失。
let parse = |value: &endor_core::EndorNumber| -> Result<f64, VmError> {
// 圆心/半径转 f64,避免距离平方过早损失到 f32 精度。
let value = value
.to_string()
.parse::<f64>()
.map_err(|_| invalid("invalid circle number"))?;
// 圆字段也须有限且不超过同一世界范围,半径非负随后另查。
if !value.is_finite() || value.abs() > MAX_DIMENSION {
return Err(invalid("circle number exceeds bounds"));
}
Ok(value)
};
// Point.x 到圆心横差,以 f64 求平方距离。
let dx = x as f64 - parse(cx_value)?;
// Point.y 到圆心纵差,不能在圆形测试中误用矩形裁剪规则。
let dy = y as f64 - parse(cy_value)?;
// 半径先按通用圆数值规则解析,再排除负数;零只包含圆心。
let radius = parse(radius)?;
// 半径不能负;零半径只包含圆心,边界使用 <= 因此圆周也命中。
if radius < 0.0 {
return Err(invalid("circle radius must be nonnegative"));
}
dx * dx + dy * dy <= radius * radius
} else {
// 矩形分支必须正好两参,Bounds 名义类型由安装签名检查,字段数值仍逐项验证。
let [_, Value::Object(bounds)] = args else {
return Err(invalid("inside requires Bounds"));
};
// 边框左原点可负,Point 坐标必须与 Bounds 在同一空间。
let left = number(cx, *bounds, "x", true)?;
// 边框顶原点独立读取,右/下边界由宽高相加决定。
let top = number(cx, *bounds, "y", true)?;
// 宽度为非负长度,不用签名检查代替具体值范围检查。
let width = number(cx, *bounds, "width", false)?;
// 高度也按非负长度规则,零面积半开矩形不包含任何点。
let height = number(cx, *bounds, "height", false)?;
// 矩形辅助同样使用半开右/下边界;这与 geometry::Rect 的闭区间 contains 不同,调用方应留意语义。
x >= left && y >= top && x < left + width && y < top + height
};
Ok(NativeResult::Return(Value::Bool(inside)))
}
}
/// 把本模块的类型、方法或能力注册到所属运行时上下文。
pub fn install(
vm: &mut Vm,
roots: &mut Vec<ObjectRef>,
desktop: ObjectRef,
block_type: ObjectRef,
function_type: ObjectRef,
) -> Result<(), VmError> {
// bootstrap 已创建视觉值类型,以下只取得当前桌面的名义身份来构造契约。
let nominal = |name| {
crate::ui_bridge::desktop_type(vm.object_store(), name, desktop)
.map(AnnotationType::Nominal)
};
// 第二参数固定为 Mount,协议不能只拿 Block 或任意 Bounds 冒充上下文。
let mount = nominal("Mount")?;
// measure 输入约束记录的名义类型。
let constraints = nominal("Constraints")?;
// arrange/paint 输入边框盒的名义类型,Point.inside 也使用它。
let bounds = nominal("Bounds")?;
// hitTest 输入局部 Point,后续 Point 辅助方法 receiver 也用该注解。
let point = nominal("Point")?;
// measure 返回 MeasureResult,源覆写可返回其更具体子类。
let measure = nominal("MeasureResult")?;
// arrange 返回 Arrangement,孩子 Bounds 经受控列表持有。
let arrangement = nominal("Arrangement")?;
// paint 返回源 DisplayList,区别于窗口线程消费的原生显示列表容器。
let display = nominal("DisplayList")?;
// 把四个默认方法安装到 Block,源子类可覆盖;所有视觉协议声明 ReadOnly,允许派生结果分配而不修改模型。
for (name, query, input, returns) in [
("measure", Query::Measure, constraints, measure),
("arrange", Query::Arrange, bounds, arrangement),
("paint", Query::Paint, bounds, display),
("hitTest", Query::HitTest, point, AnnotationType::Bool),
] {
// 每个默认协议函数 captures 保活桌面环境,方法表再持有该函数。
let function = vm.allocate(ContextNativeFunction::new(
name,
Method {
query,
desktop,
signature: CallSignature::exact(
vec![AnnotationType::Nominal(block_type), mount, input],
returns,
CallEffect::ReadOnly,
),
},
vec![desktop],
))?;
roots.push(function);
vm.attach_type(function, function_type)?;
vm.define_method(block_type, name, function)?;
}
// Point 辅助方法安装在同一桌面的真实类型注册表上。
let registry = object(&vm.get_attr(desktop, "types")?)?;
// 取得具体 Point Type 对象,用作 expected receiver 及 define_method 目标。
let point_type = object(
&vm.dict_get(registry, &Value::Text("Point".into()))?
.ok_or(invalid("missing Point type"))?,
)?;
// Point.inside 接收 Bounds,inCircle 接收圆心 x/y 和半径;签名都包含隐式 Point receiver。
for (name, circle) in [("inside", false), ("inCircle", true)] {
// 分别建立 inside/inCircle 的参数契约与适配器,captures 保活 Point 类型。
let function = vm.allocate(ContextNativeFunction::new(
name,
PointMethod {
expected: point_type,
circle,
signature: CallSignature::exact(
// 圆测试为 receiver+三数,矩形测试为 receiver+Bounds,参数元数据与 PointMethod.arity 一致。
if circle {
vec![
point,
AnnotationType::Number,
AnnotationType::Number,
AnnotationType::Number,
]
} else {
vec![point, bounds]
},
AnnotationType::Bool,
CallEffect::ReadOnly,
),
},
vec![point_type],
))?;
roots.push(function);
vm.attach_type(function, function_type)?;
vm.define_method(point_type, name, function)?;
}
Ok(())
}