Files
endorphin-tessera-gui/src/model/object_list.rs
T
2026-10-25 21:25:43 +08:00

158 lines
5.1 KiB
Rust

//! 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)]
struct ObjectList(Vec<ObjectRef>);
impl ObjectPayload for ObjectList {
fn reference_assignment_only(&self) -> bool {
true
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn kind_name(&self) -> &'static str {
"tessera-object-list"
}
fn allows_attributes(&self) -> bool {
true
}
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
for mount in &self.0 {
visit(*mount);
}
}
}
#[derive(Debug)]
struct At(ObjectRef);
impl NativeCallable for At {
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> {
let [Value::Number(index)] = args else {
return Err(VmError::InvalidArgument("object list at expects an index"));
};
let index = index
.as_i64()
.and_then(|v| usize::try_from(v).ok())
.ok_or(VmError::InvalidArgument("invalid list index"))?;
let mount = *cx
.store()
.payload::<ObjectList>(self.0, "Object list")?
.0
.get(index)
.ok_or(VmError::InvalidArgument("list index out of range"))?;
Ok(NativeResult::Return(Value::Object(mount)))
}
}
pub fn collection(
cx: &mut NativeContext<'_>,
mounts: Vec<ObjectRef>,
desktop: ObjectRef,
) -> Result<ObjectRef, VmError> {
build(cx, mounts, desktop, None)
}
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",
));
}
let first = mounts
.first()
.copied()
.map(Value::Object)
.unwrap_or(Value::Nil);
let count = mounts.len();
let set = cx.allocate(ObjectList(mounts))?;
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()))?;
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))?;
Ok(set)
}
pub fn items(store: &endor_core::ObjectStore, list: ObjectRef) -> Result<&[ObjectRef], VmError> {
Ok(&store.payload::<ObjectList>(list, "ObjectList")?.0)
}
#[derive(Debug)]
pub struct Constructor {
pub signature: Option<endor_core::CallSignature>,
pub desktop: ObjectRef,
}
impl NativeCallable for Constructor {
fn signature(&self) -> Option<endor_core::CallSignature> {
self.signature.clone()
}
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> {
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"))
{
return Err(VmError::InvalidArgument("reserved ObjectList field"));
}
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))?;
for (field, value) in fields.iter().zip(&values[count..]) {
cx.set_attr(list, &field.name, value.clone())?;
}
Ok(NativeResult::Return(Value::Object(list)))
}
}