Files
endorphin-tessera-gui/src/model/object_list.rs
T

317 lines
15 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.
//! 给源协议返回的对象列表提供受控封装。列表内容通过托管引用被追踪,读方法只暴露检查后的条目。
//! 用于子节点组成、布局结果和几何输入等需要保留对象身份的场景;不是窗口线程共享的可变容器。
//!
//! 调用路径:桥接 bootstrap 注册 Constructor;Mount/视觉协议/绘图入口调用 collection 生成返回列表。
//! source-layout/graphics/三维等消费者用 items 借用成员;源代码通过 at/append/remove/extend/added 操作。
//! 列表只允许对象成员;子类普通字段另按名义类型检查,不能把标量字段值混进成员前缀。
//!
//! Managed indexed host results; values and the at method are traced edges.
use endor_core::{
ContextNativeFunction, NativeCallable, NativeContext, NativeResult, ObjectPayload, ObjectRef,
Value, VmError,
};
#[derive(Debug)]
/// 托管对象序列,trace 负责拥有其中的对象引用;源代码通过受检查方法读取。
struct ObjectList(Vec<ObjectRef>);
impl ObjectPayload for ObjectList {
/// 声明此 payload 的赋值遵循引用身份语义,避免把内部宿主状态当作可复制值。
fn reference_assignment_only(&self) -> bool {
true
}
/// 提供受检查的 Any 下转型入口,让宿主按具体 payload 类型读取数据。
fn as_any(&self) -> &dyn std::any::Any {
self
}
/// 返回运行时诊断/类型检查使用的稳定 payload 名称。
fn kind_name(&self) -> &'static str {
"tessera-object-list"
}
/// 声明此 payload 是否允许使用托管属性存储,具体字段仍由类型与调用检查约束。
fn allows_attributes(&self) -> bool {
true
}
/// 枚举本对象真正拥有的托管强引用,供 Aster 引用追踪和循环回收使用。
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
// Aster 引用追踪逐条遍历成员拥有边;变量名 mount 沿用最初用途,但列表也可持有其他对象。
for mount in &self.0 {
visit(*mount);
}
}
}
#[derive(Debug)]
/// 绑定到某个列表对象的下标访问适配器。
struct At(ObjectRef);
impl NativeCallable for At {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<endor_core::CallSignature> {
Some(endor_core::CallSignature::exact(
vec![endor_core::AnnotationType::Number],
endor_core::AnnotationType::Any,
endor_core::CallEffect::ReadOnly,
))
}
/// 绑定列表接收者已捕获,只接受一个显式索引。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(1))
}
/// 要求索引可精确转为非负平台整数,检查列表范围后返回对应托管成员,不复制成员对象。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
// VM 调用已绑定 at 时仍检查实参数形状,不能依赖调用方一定执行签名预检查。
let [Value::Number(index)] = args else {
return Err(VmError::InvalidArgument("object list at expects an index"));
};
// 先要求可精确表示为 i64 的整数,再转为 usize,拒绝小数、负数及目标平台越界值。
let index = index
.as_i64()
.and_then(|v| usize::try_from(v).ok())
.ok_or(VmError::InvalidArgument("invalid list index"))?;
// 根据已验证平台索引借用真实成员句柄;越界返回错误,不像 first 空列表那样返回 Nil。
let mount = *cx
.store()
.payload::<ObjectList>(self.0, "Object list")?
.0
.get(index)
.ok_or(VmError::InvalidArgument("list index out of range"))?;
// Return 交回原托管身份,由 VM 的 native 返回协议处理结果拥有关系,不复制成员对象。
Ok(NativeResult::Return(Value::Object(mount)))
}
}
#[derive(Debug)]
// append/remove/extend/added 共用的绑定适配器,操作差异由三个标志决定。
struct Edit {
// 当前列表接收者,方法 captures 强持有它,列表属性再指回方法形成可追踪托管环。
owner: ObjectRef,
// 新列表/函数的名义类型从该桌面解析,不误接到其他 VM 的类型注册表。
desktop: ObjectRef,
// true 时把唯一实参解释为删除索引;否则是追加成员(extend 优先)。
remove: bool,
// added 返回新列表而不改原接收者;原地操作则更新 payload/count/first。
persistent: bool,
// true 时唯一实参须为另一 ObjectList,将其成员按顺序追加。
extend: bool,
}
impl NativeCallable for Edit {
// 绑定接收者已经捕获,四种编辑操作都只接收一个显式参数。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(1))
}
// 由 VM 调用绑定编辑方法;先在 Vec 副本中验证/编辑,成功后才更新原列表或分配返回列表。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
// 复制借用成员句柄以结束原列表借用;改副本本身不会修改 VM 拥有边。
let mut members = items(cx.store(), self.owner)?.to_vec();
// extend 接收对象列表,不把整个列表作为一个成员追加;先检查合并后的总预算。
if self.extend {
// 要求实参为对象,再由 items 验证确为宿主 ObjectList payload。
let [Value::Object(other)] = args else {
return Err(VmError::InvalidArgument("extend expects ObjectList"));
};
// 借用另一列表当前成员;owner==other 时复制副本已建好,仍可安全按原序列扩展。
let incoming = items(cx.store(), *other)?;
// 合并后不能超过 10000,即使每个输入列表单独都合法也须重新检查。
if members.len() + incoming.len() > 10_000 {
return Err(VmError::InvalidArgument(
"object list exceeds 10000 members",
));
}
members.extend_from_slice(incoming);
} else if self.remove {
// 删除路径只修改指定索引,不按业务身份去重或删除所有相同成员。
// 索引类型必须为 Number,后面再拒绝小数、负数和越界值。
let [Value::Number(index)] = args else {
return Err(VmError::InvalidArgument("remove expects index"));
};
// 先精确转 i64/usize,再比较现有长度;无效索引不改变原 payload。
let index = index
.as_i64()
.and_then(|v| usize::try_from(v).ok())
.filter(|&i| i < members.len())
.ok_or(VmError::InvalidArgument("list index out of range"))?;
members.remove(index);
} else {
// 单项追加只接受对象句柄,不能装入 Nil、字符串或数字作为宿主成员。
let [Value::Object(item)] = args else {
return Err(VmError::InvalidArgument("append expects object"));
};
// 追加前已有 10000 项时拒绝,不能先写原列表再报预算错误。
if members.len() >= 10_000 {
return Err(VmError::InvalidArgument(
"object list exceeds 10000 members",
));
}
members.push(*item);
}
// added 的持久式结果使用默认 ObjectList 类型,原列表(含其子类字段)保持不变。
if self.persistent {
return Ok(NativeResult::Return(Value::Object(collection(
cx,
members,
self.desktop,
)?)));
}
// 原地路径重新计算元信息,payload 替换与 count/first 属性写入都经过 VM 写屏障。
let count = members.len();
// 空列表 first=Nil;非空保存第一个原成员身份,不构造副本。
let first = members
.first()
.copied()
.map(Value::Object)
.unwrap_or(Value::Nil);
// 替换 payload 让 VM 更新成员引用追踪并产生 Contents 变化,依赖消费者可观察编辑。
cx.replace_payload(self.owner, ObjectList(members))?;
cx.set_attr(self.owner, "count", Value::Number((count as i64).into()))?;
cx.set_attr(self.owner, "first", first)?;
Ok(NativeResult::Return(Value::Nil))
}
}
/// 将已有对象引用包装为新的 ObjectList,并安装默认名义类型及下标查询方法。
pub fn collection(
cx: &mut NativeContext<'_>,
mounts: Vec<ObjectRef>,
desktop: ObjectRef,
) -> Result<ObjectRef, VmError> {
// 宿主返回的集合不带源子类字段,统一使用桌面的默认 ObjectList 名义类型。
build(cx, mounts, desktop, None)
}
/// 建立列表 payload 和 first/count/at 属性;definition 指定子类时保留该名义类型。
/// 最多接受 10000 个成员;空列表的 first 为 Nil,at 越界仍报错而不返回 Nil。
fn build(
cx: &mut NativeContext<'_>,
mounts: Vec<ObjectRef>,
desktop: ObjectRef,
definition: Option<ObjectRef>,
) -> Result<ObjectRef, VmError> {
// 构造入口也检查预算,不能只在编辑入口检查而让宿主一次生成超大列表。
if mounts.len() > 10_000 {
return Err(VmError::InvalidArgument(
"object list exceeds 10000 members",
));
}
// 在移动 Vec 之前提取元信息;成员对象的拥有边由 ObjectList::trace 声明。
let first = mounts
.first()
.copied()
.map(Value::Object)
.unwrap_or(Value::Nil);
// 在成员 Vec 移入 payload 之前保存数量,供普通 count 属性使用。
let count = mounts.len();
// ObjectList payload 声明全部成员强拥有边,VM 分配负责登记引用。
let set = cx.allocate(ObjectList(mounts))?;
// 源子类构造保留请求的名义类型;普通 collection 返回值才用默认类型。
if let Some(definition) = definition {
cx.attach_type(set, definition)?;
} else {
crate::ui_bridge::attach_desktop_type(cx, set, "ObjectList", desktop)?;
}
cx.set_attr(set, "first", first)?;
cx.set_attr(set, "count", Value::Number((count as i64).into()))?;
// at 捕获列表,列表属性又指向 at,形成托管环;显式 captures 让 GC 能追踪和回收此环。
let at = cx.allocate(ContextNativeFunction::new("at", At(set), vec![set]))?;
crate::ui_bridge::attach_desktop_type(cx, at, "Function", desktop)?;
cx.set_attr(set, "at", Value::Object(at))?;
// 逐个安装四种绑定操作,标志表是方法语义的唯一分派来源。
for (name, remove, persistent, extend) in [
("append", false, false, false),
("remove", true, false, false),
("added", false, true, false),
("extend", false, false, true),
] {
// 显式 captures=[set,desktop] 保活接收者与类型环境,循环可由 Aster GC 看到。
let method = cx.allocate(ContextNativeFunction::new(
name,
Edit {
owner: set,
desktop,
remove,
persistent,
extend,
},
vec![set, desktop],
))?;
crate::ui_bridge::attach_desktop_type(cx, method, "Function", desktop)?;
cx.set_attr(set, name, Value::Object(method))?;
}
Ok(set)
}
/// 借用受检查列表的条目;返回值的有效期仍依赖原托管对象存活。
pub fn items(store: &endor_core::ObjectStore, list: ObjectRef) -> Result<&[ObjectRef], VmError> {
Ok(&store.payload::<ObjectList>(list, "ObjectList")?.0)
}
#[derive(Debug)]
/// ObjectList 原生类型的可调用构造器,负责检查传入子对象。
pub struct Constructor {
// bootstrap 提供的可选构造契约,子类构造仍在 invoke 按字段后缀切分参数。
pub signature: Option<endor_core::CallSignature>,
// 构造列表与绑定方法的所属桌面,captures 由注册构造器的 bootstrap 负责保活。
pub desktop: ObjectRef,
}
impl NativeCallable for Constructor {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<endor_core::CallSignature> {
self.signature.clone()
}
/// 至少包含请求 Type,之后为可变成员对象与源子类字段,具体分段在 invoke 校验。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::range(1, usize::MAX))
}
/// 按请求类型字段数切分成员/字段,禁止覆盖列表协议保留属性,验证后分配列表并初始化源字段。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
// VM 类型调用传入请求 Type 为首参,允许构造 ObjectList 名义子类。
let [Value::Object(definition), values @ ..] = args else {
return Err(VmError::InvalidArgument("ObjectList type argument"));
};
// 首参是要构造的名义类型,余下实参分成“成员对象前缀”和“子类字段后缀”。
let fields = cx
.store()
.payload::<endor_core::EndorTypeObject>(*definition, "Type")?
.fields()
.to_vec();
// 列表查询和修改方法均由原生协议保留,子类字段不得覆盖这些名称。
if fields.iter().any(|field| {
matches!(
field.name.as_str(),
"count" | "first" | "at" | "append" | "remove" | "added" | "extend"
)
}) {
return Err(VmError::InvalidArgument("reserved ObjectList field"));
}
// 字段按类型定义顺序占据最后 N 项;checked_sub 在实参不足时返回错误,避免下标下溢。
let count = values
.len()
.checked_sub(fields.len())
.ok_or(VmError::InvalidArgument(
"missing ObjectList subclass fields",
))?;
cx.store()
.check_class_field_arguments(*definition, &values[count..])?;
// 字段值先按声明检查;成员前缀另行要求全部是对象,禁止把标量装入宿主对象列表。
let members = values[..count]
.iter()
.map(|value| {
// 只检查成员前缀的每项是对象;源子类标量字段已经从后缀单独校验。
if let Value::Object(object) = value {
Ok(*object)
} else {
Err(VmError::InvalidArgument(
"ObjectList members must be objects",
))
}
})
.collect::<Result<Vec<_>, _>>()?;
// 所有字段/成员验证完成后才分配,避免坏字段参数先产生原生列表副作用。
let list = build(cx, members, self.desktop, Some(*definition))?;
// 按声明顺序初始化子类字段,保留契约检查后的原 Value 身份。
for (field, value) in fields.iter().zip(&values[count..]) {
cx.set_attr(list, &field.name, value.clone())?;
}
Ok(NativeResult::Return(Value::Object(list)))
}
}