Files
endorphin-tessera-gui/src/runtime/event_dispatch.rs
T

569 lines
21 KiB
Rust

//! 托管事件与订阅的传播实现。Connection 持有回调引用,Event 保存目标、阶段与停止传播状态。
//! 分发沿出现关系执行 capture → target → bubble,并处理默认行为;只有同一 VM 中的回调可以接触托管对象。
//!
//! Managed subscriptions and occurrence-specific propagation. A validated VM
//! function sequences calls, so all handlers share one execution budget.
use crate::{block, mount::Mount, ui_bridge::attach_desktop_type};
use endor_core::{
ContextNativeFunction, NativeCallable, NativeContext, NativeResult, ObjectPayload, ObjectRef,
Value, Vm, VmError, WeakObjectRef,
};
const MAX_DELIVERIES: usize = 10_000;
/// 统一构造本模块的参数错误,让调用方停止处理无效输入。
fn invalid(message: &'static str) -> VmError {
VmError::InvalidArgument(message)
}
/// 检查订阅回调满足事件处理器调用契约,避免等到实际点击才暴露签名错误。
pub fn validate_handler(
store: &endor_core::ObjectStore,
function: ObjectRef,
) -> Result<(), VmError> {
if !store.object(function)?.payload().is_function() {
return Err(invalid("event handler must be a Function"));
}
let arity = store
.call_arity(function)?
.ok_or(invalid("event handler requires positional metadata"))?;
if !arity.accepts(0) && !arity.accepts(1) {
return Err(invalid(
"event handler must accept zero arguments or one Event",
));
}
Ok(())
}
#[derive(Debug, Clone, Default)]
/// 本次传播的托管事件状态,包含目标、当前阶段和阻止默认/停止传播控制。
pub struct Event {
dispatch: Option<WeakObjectRef>,
group: usize,
stopped_at: Option<usize>,
immediate: bool,
prevented: bool,
}
impl ObjectPayload for Event {
/// 声明此 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-event"
}
/// 声明此 payload 是否允许使用托管属性存储,具体字段仍由类型与调用检查约束。
fn allows_attributes(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
/// 实际拥有回调引用的托管连接,挂在目标的订阅关系中参与 GC。
pub struct Connection {
pub handler: Option<ObjectRef>,
pub kind: String,
pub capture: bool,
}
impl ObjectPayload for Connection {
/// 声明此 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-event-connection"
}
/// 枚举本对象真正拥有的托管强引用,供 Aster 引用追踪和循环回收使用。
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
if let Some(handler) = self.handler {
visit(handler);
}
}
}
/// A token never retains its target or callback. The Block owns the connection.
#[derive(Debug)]
/// 源代码可保留的订阅能力包装,用于取消对应连接。
pub struct Subscription {
pub connection: WeakObjectRef,
}
impl ObjectPayload for Subscription {
/// 声明此 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-subscription"
}
}
#[derive(Debug, Clone, Copy)]
/// 保存 owner、签名或操作枚举的原生方法适配器,统一由 VM 调用。
enum Method {
Stop,
StopImmediate,
Prevent,
Prevented,
Disconnect,
Connected,
}
impl NativeCallable for Method {
/// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(1))
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
let [Value::Object(owner)] = args else {
return Err(invalid("event method receiver"));
};
let result = match self {
Self::Disconnect | Self::Connected => {
let weak = cx
.store()
.payload::<Subscription>(*owner, "Subscription")?
.connection;
let connection = cx.upgrade(weak)?;
let connected = if let Some(connection) = connection {
if cx
.store()
.object(connection)?
.payload()
.as_any()
.is::<crate::binding::Binding>()
{
let mut data = cx
.store()
.payload::<crate::binding::Binding>(connection, "Binding")?
.clone();
let connected = data.compute.is_some();
if matches!(self, Self::Disconnect) {
data.compute = None;
cx.replace_payload(connection, data)?;
}
connected
} else {
let mut data = cx
.store()
.payload::<Connection>(connection, "Connection")?
.clone();
let connected = data.handler.is_some();
if matches!(self, Self::Disconnect) {
data.handler = None;
cx.replace_payload(connection, data)?;
}
connected
}
} else {
false
};
if matches!(self, Self::Disconnect) {
Value::Nil
} else {
Value::Bool(connected)
}
}
_ => {
let mut event = cx.store().payload::<Event>(*owner, "Event")?.clone();
if matches!(self, Self::Prevented) {
Value::Bool(event.prevented)
} else {
match self {
Self::Stop | Self::StopImmediate => {
event.stopped_at = Some(event.group);
event.immediate |= matches!(self, Self::StopImmediate);
}
Self::Prevent => event.prevented = true,
_ => unreachable!(),
}
cx.replace_payload(*owner, event)?;
Value::Nil
}
}
};
Ok(NativeResult::Return(result))
}
}
#[derive(Debug)]
/// 安装到托管类型上的原生构造调用适配器。
pub struct Constructor {
pub signature: Option<endor_core::CallSignature>,
}
impl NativeCallable for Constructor {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<endor_core::CallSignature> {
self.signature.clone()
}
/// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::range(2, usize::MAX))
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
let [Value::Object(definition), Value::Text(kind), values @ ..] = args else {
return Err(invalid("Event requires an event kind"));
};
let fields = cx
.store()
.payload::<endor_core::EndorTypeObject>(*definition, "Type")?
.fields()
.to_vec();
if fields.len() != values.len() {
return Err(invalid("incorrect Event field arguments"));
}
cx.store()
.check_class_field_arguments(*definition, values)?;
let event = new_event(cx, *definition, kind)?;
for (field, value) in fields.iter().zip(values) {
cx.set_attr(event, &field.name, value.clone())?;
}
Ok(NativeResult::Return(Value::Object(event)))
}
}
/// 建立本次分发拥有的托管 Event,保存目标、输入与传播控制状态。
pub fn new_event(
cx: &mut NativeContext<'_>,
definition: ObjectRef,
kind: &str,
) -> Result<ObjectRef, VmError> {
if kind.is_empty() || kind.len() > 128 {
return Err(invalid("event kind must contain 1..128 UTF-8 bytes"));
}
let event = cx.allocate(Event::default())?;
cx.attach_type(event, definition)?;
cx.set_attr(event, "kind", Value::Text(kind.into()))?;
for name in ["target", "mount", "currentTarget", "currentMount", "hit"] {
cx.set_attr(event, name, Value::Nil)?;
}
cx.set_attr(event, "phase", Value::Text("none".into()))?;
Ok(event)
}
#[derive(Debug)]
/// 带事件类型签名和 owner 的原生方法包装。
struct TypedMethod {
method: Method,
signature: endor_core::CallSignature,
}
impl NativeCallable for TypedMethod {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<endor_core::CallSignature> {
Some(self.signature.clone())
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
self.method.invoke(cx, args)
}
}
/// 把本模块的类型、方法或能力注册到所属运行时上下文。
pub fn install(
vm: &mut Vm,
roots: &mut Vec<ObjectRef>,
event_type: ObjectRef,
token_type: ObjectRef,
function_type: ObjectRef,
) -> Result<(), VmError> {
for (definition, name, method) in [
(event_type, "stopPropagation", Method::Stop),
(
event_type,
"stopImmediatePropagation",
Method::StopImmediate,
),
(event_type, "preventDefault", Method::Prevent),
(event_type, "isDefaultPrevented", Method::Prevented),
(token_type, "disconnect", Method::Disconnect),
(token_type, "isConnected", Method::Connected),
] {
let query = matches!(method, Method::Prevented | Method::Connected);
let function = vm.allocate(ContextNativeFunction::new(
name,
TypedMethod {
method,
signature: endor_core::CallSignature::exact(
vec![endor_core::AnnotationType::Nominal(definition)],
if query {
endor_core::AnnotationType::Bool
} else {
endor_core::AnnotationType::Unit
},
if query {
endor_core::CallEffect::ReadOnly
} else {
endor_core::CallEffect::Mutating
},
),
},
vec![],
))?;
roots.push(function);
vm.attach_type(function, function_type)?;
vm.define_method(definition, name, function)?;
}
Ok(())
}
#[derive(Debug)]
/// 本次递送选择的对象方法或显式订阅处理器。
enum Handler {
Token(ObjectRef),
Legacy(ObjectRef),
ObjectMethod,
}
#[derive(Debug)]
/// 事件传播过程中将要调用的一项处理器及其阶段/目标上下文。
struct Delivery {
event: ObjectRef,
owner: ObjectRef,
mount: Option<ObjectRef>,
group: usize,
phase: &'static str,
handler: Handler,
}
impl NativeCallable for Delivery {
/// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(0))
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
let mut event = cx.store().payload::<Event>(self.event, "Event")?.clone();
if event.immediate || event.stopped_at.is_some_and(|group| group != self.group) {
return Ok(NativeResult::Return(Value::Nil));
}
let (function, mut arguments) = match self.handler {
Handler::Token(token) => {
let Some(handler) = cx
.store()
.payload::<Connection>(token, "Connection")?
.handler
else {
return Ok(NativeResult::Return(Value::Nil));
};
(handler, vec![])
}
Handler::Legacy(function) => (function, vec![]),
Handler::ObjectMethod => {
let Some(definition) = cx.store().type_of(self.owner)? else {
return Err(invalid("untyped event target"));
};
let function = cx
.store()
.lookup_method(definition, "onEvent")?
.ok_or(invalid("Block has no onEvent method"))?;
(function, vec![Value::Object(self.owner)])
}
};
event.group = self.group;
cx.replace_payload(self.event, event)?;
cx.set_attr(self.event, "phase", Value::Text(self.phase.into()))?;
cx.set_attr(self.event, "currentTarget", Value::Object(self.owner))?;
cx.set_attr(
self.event,
"currentMount",
self.mount.map(Value::Object).unwrap_or(Value::Nil),
)?;
if matches!(self.handler, Handler::ObjectMethod)
|| cx
.store()
.call_arity(function)?
.is_some_and(|arity| arity.accepts(1))
{
arguments.push(Value::Object(self.event));
}
Ok(NativeResult::Invoke {
function,
arguments,
})
}
}
#[derive(Debug)]
/// 传播结束后的默认行为收尾步骤。
struct Finish(ObjectRef);
impl NativeCallable for Finish {
/// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(0))
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
let mut event = cx.store().payload::<Event>(self.0, "Event")?.clone();
event.dispatch = None;
cx.replace_payload(self.0, event)?;
cx.set_attr(self.0, "phase", Value::Text("none".into()))?;
cx.set_attr(self.0, "currentTarget", Value::Nil)?;
cx.set_attr(self.0, "currentMount", Value::Nil)?;
Ok(NativeResult::Return(Value::Object(self.0)))
}
}
/// Snapshot the selected occurrence's route and listener order. Disconnection
/// before a listener's turn skips it; newly registered listeners wait for the
/// next dispatch. A code event without a Mount visits only its target object.
/// 按 Mount 祖先路径执行捕获、目标与冒泡阶段,再处理对应默认行为。
pub fn dispatch(
cx: &mut NativeContext<'_>,
owner: ObjectRef,
event_ref: ObjectRef,
mount: Option<ObjectRef>,
desktop: ObjectRef,
) -> Result<NativeResult, VmError> {
let mut event = cx.store().payload::<Event>(event_ref, "Event")?.clone();
if let Some(weak) = event.dispatch
&& cx.upgrade(weak)?.is_some()
{
return Err(invalid("Event is already being dispatched"));
}
let Value::Text(kind) = cx.store().get_attr(event_ref, "kind")? else {
return Err(invalid("Event kind must be text"));
};
if kind.is_empty() || kind.len() > 128 {
return Err(invalid("invalid event kind"));
}
let mut route = vec![(owner, mount)];
if let Some(mount) = mount {
let target = cx.store().payload::<Mount>(mount, "Mount")?.clone();
if !target.mounted || target.block != owner {
return Err(invalid("event context does not belong to this Block"));
}
let mut parent = target.parent;
let mut seen = std::collections::HashSet::from([mount]);
while let Some(weak) = parent {
let ancestor = cx
.upgrade(weak)?
.ok_or(invalid("event route has expired"))?;
if !seen.insert(ancestor) {
return Err(invalid("cyclic event route"));
}
let data = cx.store().payload::<Mount>(ancestor, "Mount")?;
if !data.mounted {
return Err(invalid("event route is detached"));
}
route.push((data.block, Some(ancestor)));
parent = data.parent;
}
if let Some(weak) = target.page {
let page = cx.upgrade(weak)?.ok_or(invalid("event Page has expired"))?;
route.push((page, None));
}
}
let guard = cx.new_object()?;
let mut calls = Vec::new();
let mut group = 0;
// Each occurrence is its own propagation group, even for shared Blocks.
let traversal = route
.iter()
.enumerate()
.rev()
.map(|(index, entry)| (index, entry, true))
.chain(
route
.iter()
.enumerate()
.map(|(index, entry)| (index, entry, false)),
);
for (index, &(block, current_mount), capture) in traversal {
let (_, data) = block::resolve(cx.store(), block)?;
let mut handlers = Vec::new();
for token in &data.subscriptions {
let token_data = cx.store().payload::<Connection>(*token, "Connection")?;
if token_data.handler.is_some()
&& token_data.kind == kind
&& token_data.capture == capture
{
handlers.push(Handler::Token(*token));
}
}
if !capture {
handlers.insert(0, Handler::ObjectMethod);
if kind == "click"
&& let Some(handler) = data.click
{
handlers.push(Handler::Legacy(handler));
}
}
for handler in handlers {
if calls.len() >= MAX_DELIVERIES {
return Err(invalid("event delivery budget exceeded"));
}
let mut captures = vec![event_ref, guard, block];
if let Some(mount) = current_mount {
captures.push(mount);
}
match handler {
Handler::Token(token) | Handler::Legacy(token) => captures.push(token),
Handler::ObjectMethod => {}
}
let function = cx.allocate(ContextNativeFunction::new(
"event delivery",
Delivery {
event: event_ref,
owner: block,
mount: current_mount,
group,
phase: if index == 0 {
"target"
} else if capture {
"capture"
} else {
"bubble"
},
handler,
},
captures,
))?;
attach_desktop_type(cx, function, "Function", desktop)?;
calls.push(function);
}
// Capture and bubble at the target are one group: stopPropagation at
// target capture still permits other target listeners.
if index != 0 || !capture {
group += 1;
}
}
let finish = cx.allocate(ContextNativeFunction::new(
"event finish",
Finish(event_ref),
vec![event_ref, guard],
))?;
calls.push(finish);
let driver = crate::vm_sequence::build(cx, calls, "<event dispatch>", desktop)?;
event.dispatch = Some(cx.store().downgrade(guard)?);
event.group = 0;
event.stopped_at = None;
event.immediate = false;
event.prevented = false;
cx.replace_payload(event_ref, event)?;
cx.set_attr(event_ref, "target", Value::Object(owner))?;
cx.set_attr(
event_ref,
"mount",
mount.map(Value::Object).unwrap_or(Value::Nil),
)?;
Ok(NativeResult::Invoke {
function: driver,
arguments: vec![],
})
}