//! 让原生组件也通过 Endor 的 measure/arrange/paint 协议被调用。 //! 将 Constraints、Size、Bounds 与 PaintCommand 在托管值和 Rust 值之间转换;原生方法与源覆盖方法共用调用契约。 //! //! 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 { 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 { let Value::Number(value) = cx.store().get_attr(object, name)? else { return Err(invalid("visual field must be a Number")); }; let value = value .to_string() .parse::() .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 { // 先赋精确名义类型,再初始化字段;只读协议可创建结果对象,但不能借机写入旧业务图。 let object = cx.new_object()?; attach_desktop_type(cx, object, kind, desktop)?; 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 { // 无 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 { Measure, Arrange, Paint, HitTest, } #[derive(Debug)] /// 保存 owner、签名或操作枚举的原生方法适配器,统一由 VM 调用。 struct Method { query: Query, desktop: ObjectRef, 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 { // 快速路径仅认可实际 ContextNativeFunction 的 Method 实现,函数名相同或签名相似的源函数不能冒充。 let Some(function) = store .object(function)? .payload() .as_any() .downcast_ref::() else { return Ok(false); }; let Some(method) = function.implementation::() 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 { Some(self.signature.clone()) } /// 接收三项底层参数:Block 接收者、具体 Mount、Constraints/Bounds/Point 协议输入。 fn arity(&self) -> Option { Some(endor_core::CallArity::exact(3)) } /// 检查 Block/Mount 归属与有效性,再按 measure/arrange/paint/hitTest 执行原生视觉协议并返回受检查的托管值。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { // 三个实际参数含接收者 Block、具体 Mount 和协议输入;几何属于 Mount,不能仅凭 Block 身份查询。 let [owner, context, input] = args else { return Err(invalid("visual method expects a Mount and an input value")); }; let owner = object(owner)?; let mount = cx.store().payload::(object(context)?, "Mount")?; // 默认协议也校验上下文归属,防止调用者把另一个出现的几何套到当前组件。 if !mount.mounted || mount.block != owner { return Err(invalid( "visual context is detached or belongs to another Block", )); } let geometry = mount.geometry.clone(); // 布局阶段从临时 Context 取当前节点、孩子实测尺寸与首选尺寸;克隆派生数据后释放锁再进行计算。 let staged = mount .layout .as_ref() .map(|layout| { 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) { if staged.is_some() { return Err(invalid("hitTest is unavailable during layout")); } let x = number(cx, input, "x", true)?; 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)? }; let result = match self.query { Query::Measure => { let min_w = number(cx, input, "minWidth", false)?; let min_h = number(cx, input, "minHeight", false)?; let max_w = number(cx, input, "maxWidth", false)?; 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))), ], )?) } Query::Arrange => { let x = number(cx, input, "x", true)?; let y = number(cx, input, "y", true)?; let width = number(cx, input, "width", false)?; 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::, _>>()? }; // 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); let points = layout::arrange_native(&node, x, y, Size { width, height }, &children) .map_err(|_| invalid("native arrangement exceeds limits"))?; let mut result = Vec::new(); // 每个返回 Bounds 附稳定 slot,与 children 当前顺序分开;调用方可按 slot 校验组成身份。 for ((slot, size), (x, y)) in node.slots.iter().zip(children).zip(points) { 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))], )?) } Query::Paint => { let x = number(cx, input, "x", true)?; let y = number(cx, input, "y", true)?; let width = number(cx, input, "width", false)?; 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")); } 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 的场景对象交给后续绘制线程。 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, } => { 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")), }; let command = record(cx, self.desktop, "PaintCommand", fields)?; commands.push(command); } 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 { expected: ObjectRef, circle: bool, signature: CallSignature, } impl NativeCallable for PointMethod { /// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。 fn signature(&self) -> Option { Some(self.signature.clone()) } /// 圆形测试接收 Point 加圆心/半径共四项;矩形测试接收 Point 和 Bounds 共两项。 fn arity(&self) -> Option { Some(endor_core::CallArity::exact(if self.circle { 4 } else { 2 })) } /// 先验证 Point 名义类型与有限坐标,再分别计算圆内距离或 Bounds 包含关系,返回 Bool。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { 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")); } let x = number(cx, *point, "x", true)?; let y = number(cx, *point, "y", true)?; let inside = if self.circle { 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 { let value = value .to_string() .parse::() .map_err(|_| invalid("invalid circle number"))?; if !value.is_finite() || value.abs() > MAX_DIMENSION { return Err(invalid("circle number exceeds bounds")); } Ok(value) }; let dx = x as f64 - parse(cx_value)?; 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 { let [_, Value::Object(bounds)] = args else { return Err(invalid("inside requires 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, desktop: ObjectRef, block_type: ObjectRef, function_type: ObjectRef, ) -> Result<(), VmError> { let nominal = |name| { crate::ui_bridge::desktop_type(vm.object_store(), name, desktop) .map(AnnotationType::Nominal) }; let mount = nominal("Mount")?; let constraints = nominal("Constraints")?; let bounds = nominal("Bounds")?; let point = nominal("Point")?; let measure = nominal("MeasureResult")?; let arrangement = nominal("Arrangement")?; 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), ] { 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)?; } let registry = object(&vm.get_attr(desktop, "types")?)?; 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)] { let function = vm.allocate(ContextNativeFunction::new( name, PointMethod { expected: point_type, circle, signature: CallSignature::exact( 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(()) }