docs: explain Tessera modules and execution flows in Chinese

This commit is contained in:
2026-11-11 23:36:03 +08:00
parent 4c3fc8b5dd
commit 1fedd21d3c
77 changed files with 1418 additions and 0 deletions
+11
View File
@@ -1,3 +1,6 @@
//! 属性绑定:在只读计算中收集依赖,将结果写回绑定目标。
//! 缓存记录读取版本而不是复制业务模型;依赖变化才重新计算。托管连接对象追踪其拥有的引用。
//!
//! Target-owned managed bindings. Dependency versions are borrowed identities;
//! the host's weak Block registry never roots an otherwise unreachable graph.
use std::{
@@ -13,27 +16,33 @@ use crate::{block, event_dispatch::Subscription, ui_bridge::attach_desktop_type}
/// The output value is borrowed for equality only, including object handles.
/// It never owns a reference or dereferences a possibly reclaimed cached object.
#[derive(Debug, Clone)]
/// 上次绑定计算的读取观察,用于判断是否需要重新执行源计算。
struct Cached {
reads: Vec<endor_core::ReadDependency>,
output: Value,
}
#[derive(Debug, Clone)]
/// 托管属性连接,强持有计算/目标所需引用,并保留只读计算的版本缓存。
pub struct Binding {
field: String,
pub compute: Option<ObjectRef>,
reads: Arc<Mutex<Option<Cached>>>,
}
impl ObjectPayload for Binding {
/// 声明此 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-binding"
}
/// 枚举本对象真正拥有的托管强引用,供 Aster 引用追踪和循环回收使用。
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
if let Some(compute) = self.compute {
visit(compute);
@@ -41,6 +50,7 @@ impl ObjectPayload for Binding {
}
}
/// 检查绑定目标与可调用计算,建立托管连接及其引用关系。
pub fn connect(
cx: &mut NativeContext<'_>,
target: ObjectRef,
@@ -114,6 +124,7 @@ pub fn connect(
Ok(token)
}
/// 在只读调用中重算失效绑定,采集新的读取版本,再将成功结果应用到目标属性。
pub fn refresh(
vm: &mut Vm,
nodes: &[WeakObjectRef],
+33
View File
@@ -1,3 +1,7 @@
//! Block 的共享赋值、底层 Data 与视觉属性实现。Block 通过 Cell 间接指向 Data,使别名能观察到同一次替换。
//! Data 保存子组件 slot、布局配置、绑定和订阅。属性读写需要沿有效的类型/存储路径解析,不能把快照当成可变业务对象。
//! 提取视觉数据时将托管属性覆盖到原生描述;校验负责拒绝循环、非法尺寸和不符合类型的组成。
//!
//! Live visual objects. All semantic references are owned by Aster's heap;
//! native Arcs are reserved for immutable rendering snapshots.
use std::sync::Arc;
@@ -7,12 +11,14 @@ use endor_core::{NativeContext, ObjectPayload, ObjectRef, ObjectStore, Value, Vm
use crate::ui_bridge::UiNode;
#[derive(Debug, Clone)]
/// 稳定组成 slot 与子 Block 的配对;slot 不依赖孩子当前的屏幕位置。
pub struct Child {
pub slot: u64,
pub block: ObjectRef,
}
#[derive(Debug, Clone)]
/// 原生组件分类,只描述业务组件种类;每种展示几何由布局和 Mount 另行生成。
pub enum Kind {
Text {
value: String,
@@ -49,6 +55,7 @@ pub enum Kind {
/// The VM owns the Block -> Cell -> Data relationship. Shared assignment
/// changes the Cell to point at another Block, retaining the original identity.
#[derive(Debug, Clone)]
/// 对共享 Cell 的稳定包装。赋值可让所有别名一起改指向,而每次出现的 Mount 仍独立。
pub struct Block {
pub state: ObjectRef,
pub page: bool,
@@ -56,9 +63,11 @@ pub struct Block {
}
impl ObjectPayload for Block {
/// 声明此 payload 的赋值遵循引用身份语义,避免把内部宿主状态当作可复制值。
fn reference_assignment_only(&self) -> bool {
true
}
/// 沿 Block 的共享 Cell 定位属性存储,使别名替换后读取到同一份新 Data。
fn attribute_target(&self, store: &ObjectStore) -> Result<Option<ObjectRef>, VmError> {
let Value::Object(value) = store.cell_get(self.state)? else {
return Err(VmError::InvalidArgument("Block state is not an object"));
@@ -76,6 +85,7 @@ impl ObjectPayload for Block {
}
}
/// 从 Block 当前 Data 解析其类型转发目标,使共享替换后的类型查询仍一致。
fn type_target(&self, store: &ObjectStore) -> Result<Option<ObjectRef>, VmError> {
let Value::Object(value) = store.cell_get(self.state)? else {
return Err(VmError::InvalidArgument("Block state is not an object"));
@@ -88,21 +98,26 @@ impl ObjectPayload for Block {
}
}
/// 提供受检查的 Any 下转型入口,让宿主按具体 payload 类型读取数据。
fn as_any(&self) -> &dyn std::any::Any {
self
}
/// 返回运行时诊断/类型检查使用的稳定 payload 名称。
fn kind_name(&self) -> &'static str {
"tessera-block"
}
/// 声明此 payload 是否允许使用托管属性存储,具体字段仍由类型与调用检查约束。
fn allows_attributes(&self) -> bool {
true
}
/// 枚举本对象真正拥有的托管强引用,供 Aster 引用追踪和循环回收使用。
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
visit(self.state);
if let Some(tasks) = self.tasks {
visit(tasks);
}
}
/// 向 VM 暴露共享赋值单元;替换的是指向关系,不是逐项复制组件业务字段。
fn assignment_cell(
&self,
source: ObjectRef,
@@ -122,6 +137,7 @@ impl ObjectPayload for Block {
}
#[derive(Debug, Clone)]
/// 组件当前业务描述与组成。与一次求出的屏幕几何分开保存,替换或属性变化会触发派生快照失效。
pub struct Data {
pub kind: Kind,
pub children: Vec<Child>,
@@ -155,6 +171,7 @@ pub struct Data {
}
impl Data {
/// 构造本类型并初始化其状态;资源建立和默认字段以函数体为准。
pub fn new(kind: Kind, children: Vec<ObjectRef>, padding: f32, gap: f32) -> Self {
let next_slot = children.len() as u64;
Self {
@@ -196,6 +213,7 @@ impl Data {
properties: None,
}
}
/// 检查原生组件种类是否承载子组成,供树修改和布局选择使用。
pub fn is_container(&self) -> bool {
matches!(
self.kind,
@@ -212,15 +230,19 @@ impl Data {
}
impl ObjectPayload for Data {
/// 声明此 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-block-data"
}
/// 枚举本对象真正拥有的托管强引用,供 Aster 引用追踪和循环回收使用。
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
if let Some(scene) = self.scene3d {
visit(scene);
@@ -255,6 +277,7 @@ impl ObjectPayload for Data {
}
}
/// 分配 Data、共享 Cell 和 Block 包装,并让托管图拥有必要引用。
pub fn allocate(cx: &mut NativeContext<'_>, mut data: Data) -> Result<ObjectRef, VmError> {
let page = matches!(data.kind, Kind::Page);
for child in &data.children {
@@ -276,11 +299,13 @@ pub fn allocate(cx: &mut NativeContext<'_>, mut data: Data) -> Result<ObjectRef,
/// Borrowed final data; forwarding is part of the managed graph, never an Arc
/// ownership graph hidden from the VM's cycle collector.
/// 解析一个 Block 当前指向的 Data,返回底层身份和借用描述。
pub fn resolve(store: &ObjectStore, block: ObjectRef) -> Result<(ObjectRef, &Data), VmError> {
let (reference, data, _) = resolve_tracked(store, block)?;
Ok((reference, data))
}
/// 解析 Block 的完整引用路径,并返回快照失效所需的依赖身份。
pub fn resolve_tracked(
store: &ObjectStore,
block: ObjectRef,
@@ -311,11 +336,13 @@ pub fn resolve_tracked(
/// Validate the entire finite expansion before establishing a new composition
/// edge. The node count is an explicit rejected resource quota, not truncated UI.
/// 检查当前输入或发布值的不变量,调用方据校验结果决定是否继续。
pub fn validate(
store: &ObjectStore,
root: ObjectRef,
forbidden_state: Option<ObjectRef>,
) -> Result<(), VmError> {
// 显式进入/退出栈模拟深度优先遍历;退出时移除祖先链标记,允许共享子树。
enum Step {
Enter(ObjectRef),
Exit(ObjectRef),
@@ -361,13 +388,16 @@ pub fn validate(
Ok(())
}
/// 将当前托管或可变状态提取为后续阶段使用的派生表示。
pub fn snapshot(store: &ObjectStore, root: ObjectRef) -> Result<Arc<UiNode>, VmError> {
crate::snapshot::RenderCache::default().snapshot(store, root)
}
/// Native visual parameters are ordinary managed attributes too. Composition,
/// histories and connections remain structural payload edges.
/// 列出本类型可投影到托管属性的原生字段及默认值。
pub fn native_properties(data: &Data) -> Vec<(&'static str, Value)> {
/// 在源 Value 与所需数值表示之间转换,并沿用本调用的有限值/范围检查。
fn number(value: f32) -> Value {
Value::Number((value as f64).into())
}
@@ -486,6 +516,7 @@ pub fn native_properties(data: &Data) -> Vec<(&'static str, Value)> {
fields
}
/// 复制原生描述并用当前托管属性覆盖它;结果只用于展示,不反向修改业务对象。
pub fn visual_data(store: &ObjectStore, data: &Data) -> Result<Data, VmError> {
let mut data = data.clone();
let properties = data
@@ -544,6 +575,7 @@ pub fn visual_data(store: &ObjectStore, data: &Data) -> Result<Data, VmError> {
Ok(data)
}
/// 按属性名检查和解码源值,再更新对应视觉字段;非法尺寸、枚举或颜色被拒绝。
pub fn apply_visual_property(data: &mut Data, name: &str, value: &Value) -> Result<(), VmError> {
let bad = || VmError::InvalidArgument("invalid native visual property");
let number = || -> Result<f32, VmError> {
@@ -799,6 +831,7 @@ pub fn apply_visual_property(data: &mut Data, name: &str, value: &Value) -> Resu
Ok(())
}
/// 统一返回非法页面缩放错误,确保属性写入与输入路径使用相同限制。
fn invalid_zoom() -> VmError {
VmError::InvalidArgument("Page.zoom expects a number from 0.1 to 4")
}
+41
View File
@@ -1,3 +1,7 @@
//! 运行时与窗口线程之间的发布数据契约。NamespaceState 管理提交序号、版本和当前完整文档。
//! RenderDocument/Frame 包含绘制命令、Mount 几何、页面标签和相机投影。发布前统一检查数量、坐标、颜色及索引一致性。
//! 屏幕命中、逻辑坐标和世界 UI 的射线映射必须与绘制采用同一变换;这里的 ID 是数据身份,不是窗口线程可操作的托管引用。
//!
use std::collections::HashSet;
use std::sync::Arc;
@@ -13,18 +17,23 @@ use crate::{
pub const DEFAULT_NAMESPACE: &str = "app.main";
#[derive(Clone, Debug)]
/// HTTP/GUI 共享的最近完整发布状态;submission 决定结果归属,revision 表示实际发布次数。
pub struct NamespaceState {
pub name: String,
pub revision: u64,
// 较新完整 PUT/DELETE 会改变提交身份;旧输入即使晚完成也不得覆盖新结果。
pub submission: u64,
pub document: RenderDocument,
// viewport 是主窗口逻辑页面区域;window_viewports 同时记录仍存活的副窗口。
pub viewport: Option<crate::layout::Size>,
pub window_viewports: std::collections::BTreeMap<u64, crate::layout::Size>,
// 原生命令与运行时世代一起发布,防止窗口 ID 在跨 VM 时被误用。
pub runtime_session: u64,
pub window_commands: Vec<crate::window::Envelope>,
}
impl Default for NamespaceState {
/// 建立类型约定的初始值,供尚未发布或尚未交互的状态使用。
fn default() -> Self {
Self {
name: DEFAULT_NAMESPACE.to_owned(),
@@ -40,6 +49,7 @@ impl Default for NamespaceState {
}
impl NamespaceState {
/// 仅接收当前 session 的纯数据窗口命令,并按种类合并同资源的重复请求。
pub fn publish_window_commands(&mut self, session: u64, commands: Vec<crate::window::Command>) {
if self.runtime_session != session {
self.window_commands.clear();
@@ -63,13 +73,16 @@ impl NamespaceState {
.sort_by_key(|command| command.command.sequence);
}
/// 计算下一个完整源码提交身份;计数溢出时返回错误而不是复用旧身份。
pub fn next_submission(&self) -> Result<u64> {
self.submission
.checked_add(1)
.context("submission counter exhausted")
}
/// 仅当提交身份仍是最新时替换完整文档并递增版本;已被取代的结果不发布。
pub fn commit(&mut self, submission: u64, document: RenderDocument) -> Result<Option<u64>> {
// 先核对归属,再计算新版本;失败或过时的结果不会替换已显示文档。
if submission != self.submission {
return Ok(None);
}
@@ -82,6 +95,7 @@ impl NamespaceState {
Ok(Some(revision))
}
/// 清空本对象维护的当前内容或缓存,后续状态由调用方重新建立。
pub fn clear(&mut self) -> Result<u64> {
let submission = self.next_submission()?;
let revision = self
@@ -98,6 +112,7 @@ impl NamespaceState {
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
/// 可以离开 VM 线程的完整展示值,包含源码投影、根帧和其他窗口的文档。
pub struct RenderDocument {
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub windows: std::collections::BTreeMap<u64, RenderDocument>,
@@ -112,6 +127,7 @@ pub struct RenderDocument {
}
impl RenderDocument {
/// 检查当前输入或发布值的不变量,调用方据校验结果决定是否继续。
pub fn validate(&self) -> Result<()> {
let surface = &self.frame.surface;
if !surface.zoom.is_finite()
@@ -234,6 +250,7 @@ impl RenderDocument {
Ok(())
}
/// 校验文档后把逻辑绘制命令转换成给定设备比例的显示列表,应用变换与裁剪。
pub fn display_list(&self, scale: f32) -> Result<DisplayList> {
let frame = &self.frame;
let mut list = DisplayList::default();
@@ -321,12 +338,14 @@ impl RenderDocument {
Ok(list)
}
/// 从源码投影根按 ID 查找节点,供 Sources 查看与定位使用。
pub fn node(&self, id: &str) -> Option<&CodeNode> {
self.root.as_ref()?.find(id)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
/// Sources 使用的源码层次节点;ID、名称、种类和行列范围都属于代码投影而不是运行时对象句柄。
pub struct CodeNode {
pub id: String,
pub name: String,
@@ -340,6 +359,7 @@ pub struct CodeNode {
}
impl CodeNode {
/// 检查当前输入或发布值的不变量,调用方据校验结果决定是否继续。
fn validate<'a>(&'a self, depth: usize, ids: &mut HashSet<&'a str>) -> Result<()> {
if depth > 64 {
bail!("architecture tree exceeds 64 levels");
@@ -359,6 +379,7 @@ impl CodeNode {
Ok(())
}
/// 按请求的代码节点身份或路径查找对应的源码投影。
fn find(&self, id: &str) -> Option<&CodeNode> {
if self.id == id {
return Some(self);
@@ -368,7 +389,9 @@ impl CodeNode {
}
#[derive(Clone, Debug, Serialize, Deserialize)]
/// 一个窗口的一次完整视觉发布:有序命令及其裁剪/变换、出现几何、焦点和版本。平行索引表必须和命令数量一致。
pub struct Frame {
// 命中索引是可重建的本地优化,不作为序列化协议的一部分保存。
#[serde(skip)]
pub hit_index: Arc<crate::viewport::spatial::Index>,
#[serde(default)]
@@ -400,6 +423,7 @@ pub struct Frame {
}
impl Default for Frame {
/// 建立类型约定的初始值,供尚未发布或尚未交互的状态使用。
fn default() -> Self {
Self {
hit_index: Arc::default(),
@@ -422,6 +446,7 @@ impl Default for Frame {
/// A position in a published frame. Identifiers are opaque data, never VM handles.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
/// 某次出现的几何与投影元数据。逻辑矩形不被相机直接改写,view/panel 保存通往视图空间的映射。
pub struct MountGeometry {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub panel: Option<Arc<crate::scene3d::panel::Mapping>>,
@@ -452,15 +477,18 @@ pub struct MountGeometry {
}
impl MountGeometry {
/// 先沿世界 UI 面板逆投影指针,按需要执行可见性和裁剪检查。
pub fn view_point(&self, point: Point, clipped: bool) -> Option<Point> {
self.panel
.as_ref()
.map_or(Some(point), |panel| panel.texture_point(point, clipped))
}
/// 把页面指针转换为该出现的布局坐标,供 hitTest 和局部事件处理使用。
pub fn logical_point(&self, point: Point) -> Option<Point> {
let point = self.view_point(point, true)?;
Some(self.view.unwrap_or_default().inverse(point))
}
/// 把视图位移换算成目标出现的局部位移,处理缩放与世界 UI 投影差异。
pub fn logical_delta(&self, point: Option<[f32; 2]>, delta: [f32; 2]) -> Option<[f32; 2]> {
let zoom = self.view.unwrap_or_default().zoom;
if self.panel.is_none() {
@@ -475,6 +503,7 @@ impl MountGeometry {
((previous.y - current.y) / zoom).clamp(-1_000_000., 1_000_000.),
])
}
/// 判断给定点是否位于目标范围;需要相机转换的类型会先转换再判断。
pub fn contains(&self, point: Point) -> bool {
let Some(point) = self.view_point(point, true) else {
return false;
@@ -488,6 +517,7 @@ impl MountGeometry {
}
impl Frame {
/// 从顶层绘制顺序向后查找可控制的视口,命中与裁剪均成立才接收相机手势。
pub fn camera_at(&self, point: Point) -> Option<&MountGeometry> {
self.hit_index
.candidates(point, self.mounts.len())
@@ -503,6 +533,7 @@ impl Frame {
})
})
}
/// 在无故障帧中按逆绘制顺序选中可交互的出现,空间索引只缩小候选。
pub fn hit_test(&self, point: Point) -> Option<&MountGeometry> {
if self.fault.is_some() {
return None;
@@ -515,16 +546,19 @@ impl Frame {
}
}
/// 返回默认主窗口身份,供省略窗口字段的兼容输入使用。
fn default_window() -> u64 {
crate::window::MAIN_WINDOW
}
/// 提供空帧或未指定背景时的默认页面底色。
fn default_background() -> String {
"#F7F7F5".to_owned()
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
/// 可校验、可序列化的逻辑绘制命令;转换到设备像素在 display_list 阶段完成。
pub enum RenderCommand {
Scene3D {
x: f32,
@@ -566,6 +600,7 @@ pub enum RenderCommand {
}
impl RenderCommand {
/// 检查当前输入或发布值的不变量,调用方据校验结果决定是否继续。
pub(crate) fn validate(&self) -> Result<()> {
match self {
Self::Scene3D {
@@ -638,6 +673,7 @@ impl RenderCommand {
Ok(())
}
/// 把一条已验证的发布命令变成原生绘制原语,缩放几何和文字样式。
fn to_draw_command(&self, scale: f32) -> Result<DrawCommand> {
Ok(match self {
Self::Scene3D {
@@ -701,14 +737,17 @@ impl RenderCommand {
}
}
/// 提供未指定描边宽度时的兼容默认值。
fn default_stroke_width() -> f32 {
1.0
}
/// 提供未指定字号时的协议默认值。
fn default_text_size() -> u32 {
1
}
/// 统一检查有限矩形坐标、尺寸与可选圆角,阻止异常值进入光栅循环。
fn validate_rect(x: f32, y: f32, width: f32, height: f32, radius: f32) -> Result<()> {
if !x.is_finite() || !y.is_finite() || !width.is_finite() || !height.is_finite() {
bail!("rectangle values must be finite");
@@ -719,10 +758,12 @@ fn validate_rect(x: f32, y: f32, width: f32, height: f32, radius: f32) -> Result
Ok(())
}
/// 将逻辑矩形转换为设备比例下的矩形,保持位置与尺寸同比缩放。
fn scaled_rect(x: f32, y: f32, width: f32, height: f32, scale: f32) -> Rect {
Rect::new(x * scale, y * scale, width * scale, height * scale)
}
/// 解析支持的十六进制 RGB/RGBA 文本,拒绝错误长度或非法字符。
pub fn parse_color(value: &str) -> Result<Color> {
let hex = value
.strip_prefix('#')
+2
View File
@@ -1,3 +1,5 @@
//! 模型层模块入口。包含托管业务对象、每次展示上下文、依赖缓存,以及交给窗口线程的发布值。
//!
// Module ownership follows the directory; crate-root aliases keep internal call sites stable.
pub(crate) mod binding;
pub(crate) mod block;
+34
View File
@@ -1,3 +1,7 @@
//! 每次展示的托管上下文:同一个 Block 出现在两个位置时,有两个独立 Mount。
//! Mount 保存父子关系、出现几何和布局期间的查询上下文;MountTable 用弱索引定位对象,不靠索引强行保活业务树。
//! 同步会安排 onMount/onUnmount。布局取消时使用 Checkpoint 恢复未发布状态,避免半完成的出现关系影响下一次刷新。
//!
//! Managed presentation contexts. The active desktop owns the mount dictionary;
//! reverse links and the host registry are weak, so shared content cannot retain a Page.
use crate::document::{Frame, MountGeometry};
@@ -11,16 +15,20 @@ use std::{
};
#[derive(Debug, Clone)]
/// 一个组件在一个展示位置上的上下文。强持有 Block,反向页面/父子链接使用弱身份,避免共享组件把已关闭页面保活。
pub struct Mount {
// 组件是强引用;页面、父节点和兄弟链接是弱引用,防止反向链接形成保活环。
pub block: ObjectRef,
pub page: Option<WeakObjectRef>,
pub parent: Option<WeakObjectRef>,
pub first_child: Option<WeakObjectRef>,
pub next_sibling: Option<WeakObjectRef>,
// 已发布几何与每次出现的滚动位置分开保存,同一 Block 的多个位置互不覆盖。
pub geometry: MountGeometry,
pub scroll_offset: [f32; 2],
pub version: u64,
pub mounted: bool,
// Mount 可以拥有任务;退役后任务交付还要检查 owner 是否仍有效。
pub tasks: Option<ObjectRef>,
// Private derived scratch, installed before the read-only layout call and
// cleared by geometry commit. It contains no managed references or roots.
@@ -28,18 +36,23 @@ pub struct Mount {
}
impl ObjectPayload for Mount {
/// 声明此 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-mount"
}
/// 声明此 payload 是否允许使用托管属性存储,具体字段仍由类型与调用检查约束。
fn allows_attributes(&self) -> bool {
true
}
/// 枚举本对象真正拥有的托管强引用,供 Aster 引用追踪和循环回收使用。
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
visit(self.block);
if let Some(tasks) = self.tasks {
@@ -49,6 +62,7 @@ impl ObjectPayload for Mount {
}
#[derive(Debug, Clone, Copy)]
/// 该原生能力的查询/操作分类,方法包装用它选择具体实现。
enum Query {
Bounds,
ScrollTo,
@@ -68,6 +82,7 @@ enum Query {
}
#[derive(Debug)]
/// 保存 owner、签名或操作枚举的原生方法适配器,统一由 VM 调用。
struct Method {
signature: endor_core::CallSignature,
owner: ObjectRef,
@@ -76,9 +91,11 @@ struct Method {
}
impl NativeCallable for Method {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<endor_core::CallSignature> {
Some(self.signature.clone())
}
/// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(
if matches!(
@@ -97,6 +114,7 @@ impl NativeCallable for Method {
))
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
let mount = cx.store().payload::<Mount>(self.owner, "Mount")?.clone();
if !mount.mounted
@@ -324,6 +342,7 @@ impl NativeCallable for Method {
}
}
/// 声明 Mount 查询方法的源类型契约,区分几何、层次与坐标操作。
fn mount_signature(
store: &endor_core::ObjectStore,
desktop: ObjectRef,
@@ -370,12 +389,14 @@ fn mount_signature(
}
#[derive(Debug, Default, Clone)]
/// 活动出现的弱索引。真实托管拥有关系在 desktop 的挂载字典中,索引不能充当隐藏 GC 根。
pub struct MountTable {
pub active: HashMap<u64, WeakObjectRef>,
order: Vec<u64>,
}
#[derive(Debug)]
/// 同步出现关系时的共享临时工作状态,保存本轮重用、建立和退役安排。
struct Sync {
desktop: ObjectRef,
page: Option<ObjectRef>,
@@ -388,6 +409,7 @@ struct Sync {
}
#[derive(Debug)]
/// 将一个生命周期回调及参数接入同一 VM 调用序列。
struct LifecycleCall {
function: ObjectRef,
block: ObjectRef,
@@ -395,10 +417,12 @@ struct LifecycleCall {
unmount: bool,
}
impl NativeCallable for LifecycleCall {
/// 声明宿主可调用对象接受的参数个数,供 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> {
if self.unmount {
crate::tasks::cancel_owner(cx, self.mount)?;
@@ -410,6 +434,7 @@ impl NativeCallable for LifecycleCall {
}
}
/// 构造生命周期调用所需的托管函数与参数,使回调遵循同一 VM 的引用和执行约束。
fn lifecycle_call(
cx: &mut NativeContext<'_>,
mount: ObjectRef,
@@ -437,10 +462,12 @@ fn lifecycle_call(
}
impl NativeCallable for Sync {
/// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(0))
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(&self, cx: &mut NativeContext<'_>, _args: &[Value]) -> Result<NativeResult, VmError> {
let mut blocks = HashMap::new();
for weak in &self.nodes {
@@ -630,6 +657,7 @@ impl NativeCallable for Sync {
/// Explicitly owned roots protect the old presentation while preparation
/// temporarily replaces its desktop dictionary and Mount payloads.
/// 布局准备前的挂载快照与临时保留,用于取消恢复;成功提交后必须释放其保留引用。
pub struct Checkpoint {
table: MountTable,
dictionary: Option<ObjectRef>,
@@ -638,6 +666,7 @@ pub struct Checkpoint {
}
impl Checkpoint {
/// 释放检查点为回滚临时保留的托管根,成功提交后不再需要这些引用。
pub fn release(self, vm: &mut Vm) -> Result<(), VmError> {
let mut result = Ok(());
for root in self.roots.into_iter().rev() {
@@ -648,6 +677,7 @@ impl Checkpoint {
result
}
/// 恢复检查点中尚未发布的出现关系与布局上下文,并释放临时保留。
pub fn restore(
self,
vm: &mut Vm,
@@ -707,6 +737,7 @@ impl Checkpoint {
}
impl MountTable {
/// 保留布局准备前的 Mount 状态,供失败或取消路径恢复。
pub fn checkpoint(&self, vm: &mut Vm, desktop: ObjectRef) -> Result<Checkpoint, VmError> {
let dictionary = match vm.get_attr(desktop, "mounts") {
Ok(Value::Object(dictionary)) => Some(dictionary),
@@ -762,6 +793,7 @@ impl MountTable {
/// A faulted domain cannot run user onUnmount callbacks. Still detach every
/// context and release its owning dictionary through the normal heap barrier.
/// 退役当前出现表,撤销挂载关系并准备不再展示的生命周期状态。
pub fn retire(&mut self, vm: &mut Vm, desktop: ObjectRef) -> Result<(), VmError> {
for weak in self.ordered().into_iter().rev() {
if let Some(object) = vm.upgrade(weak)? {
@@ -788,6 +820,7 @@ impl MountTable {
Ok(())
}
/// 按原始展示顺序返回弱 Mount 身份,供事件和协议驱动器保持稳定顺序。
pub fn ordered(&self) -> Vec<WeakObjectRef> {
self.order
.iter()
@@ -795,6 +828,7 @@ impl MountTable {
.collect()
}
/// 根据新结构复用或建立每次出现的 Mount,连接父子关系并准备生命周期调用。
pub fn synchronize(
&mut self,
vm: &mut Vm,
+20
View File
@@ -1,23 +1,32 @@
//! 给源协议返回的对象列表提供受控封装。列表内容通过托管引用被追踪,读方法只暴露检查后的条目。
//! 用于儿童组成、布局结果和几何输入等需要保留对象身份的场景;不是窗口线程共享的可变容器。
//!
//! 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)) {
for mount in &self.0 {
visit(*mount);
@@ -26,8 +35,10 @@ impl ObjectPayload for ObjectList {
}
#[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],
@@ -35,10 +46,12 @@ impl NativeCallable for At {
endor_core::CallEffect::ReadOnly,
))
}
/// 声明宿主可调用对象接受的参数个数,供 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::Number(index)] = args else {
return Err(VmError::InvalidArgument("object list at expects an index"));
@@ -57,6 +70,7 @@ impl NativeCallable for At {
}
}
/// 将已有对象引用包装为新的 ObjectList,并安装默认名义类型及下标查询方法。
pub fn collection(
cx: &mut NativeContext<'_>,
mounts: Vec<ObjectRef>,
@@ -65,6 +79,7 @@ pub fn collection(
build(cx, mounts, desktop, None)
}
/// 根据当前输入构造本模块的派生结果,具体的验证与资源边界见函数内步骤。
fn build(
cx: &mut NativeContext<'_>,
mounts: Vec<ObjectRef>,
@@ -96,23 +111,28 @@ fn build(
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 {
pub signature: Option<endor_core::CallSignature>,
pub desktop: ObjectRef,
}
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(1, usize::MAX))
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
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"));
+8
View File
@@ -1,3 +1,6 @@
//! 一个原生窗口对应一份 Presentation:独立视口、Mount、测量缓存、焦点与依赖记录。
//! 多个窗口仍使用同一个 VM 和共享业务对象。读取版本失效时只标记相关展示需要刷新,不能因此复制一份业务状态。
//!
//! Per-window rendering state. Business objects and code remain in one VM.
//! Registries and caches borrow checked identities; the managed desktop owns
//! presentation roots through its pending/mount dictionaries.
@@ -6,6 +9,7 @@ use endor_core::{ChangeBatch, NativeContext, ObjectRef, Vm, VmError, WeakObjectR
use std::sync::Arc;
#[derive(Debug)]
/// 一个窗口独立的展示状态:活动页面、待提交请求、Mount、布局与源读取缓存;共享 VM 的业务状态不放在这里复制。
pub(crate) struct Presentation {
pub desktop: ObjectRef,
pub closing: bool,
@@ -34,6 +38,7 @@ pub(crate) struct Presentation {
}
impl Presentation {
/// 比较布局/绘制读取版本和名称依赖,标记需要刷新并通知快照缓存。
pub fn observe_changes(&mut self, vm: &Vm, changes: &ChangeBatch) {
if let Some((phase, object, version)) = self
.layout_reads
@@ -82,6 +87,7 @@ impl Presentation {
});
}
/// 按尺寸、内容或绘制失效原因清理对应缓存,避免把所有变化都当成相同重建。
pub fn invalidate(
&mut self,
cx: &mut NativeContext<'_>,
@@ -112,6 +118,7 @@ impl Presentation {
Ok(())
}
/// 构造本类型并初始化其状态;资源建立和默认字段以函数体为准。
pub fn new(desktop: ObjectRef, tabs: ObjectRef) -> Self {
Self {
desktop,
@@ -142,6 +149,7 @@ impl Presentation {
}
}
/// 把读取记录拆成对象版本和精确名称依赖,便于按实际观察内容判断失效。
pub(crate) fn split_reads(
reads: Vec<endor_core::ReadDependency>,
) -> (
+4
View File
@@ -1,9 +1,13 @@
//! 安装 Visual、EventTarget、Children 和 Presentable 等能力契约。
//! 契约描述对象能响应什么源方法;具体测量、绘制、事件执行仍在各自协议驱动器中完成。
//!
//! Host capability definitions over Aster's explicit managed trait relations.
use endor_core::{
AnnotationType, CallArity, CallEffect, CallSignature, ObjectRef, TypedMethodRequirement, Value,
Vm, VmError,
};
/// 把本模块的类型、方法或能力注册到所属运行时上下文。
pub fn install(
vm: &mut Vm,
roots: &mut Vec<ObjectRef>,
+43
View File
@@ -1,3 +1,7 @@
//! 将 Endor 托管场景对象转换成可供渲染线程读取的 3D 数据。
//! 构造器和字段仍属于 VM;快照复制必要的数值、不可变网格和依赖,渲染器不读取托管堆。
//! 场景遍历组合局部变换、检查环和资源限制,并为 Panel3D 提取独立 UI 根。控制器更新相机;拾取结果再映射回托管节点。
//!
//! Traced 3D objects and checked immutable snapshots. No managed handle enters
//! the renderer; cameras and node transforms remain editable Endor attributes.
use crate::{
@@ -11,6 +15,7 @@ use endor_core::{NativeContext, ObjectPayload, ObjectRef, ObjectStore, Value, Vm
use std::{collections::HashSet, sync::Arc};
#[derive(Debug, Clone, Copy)]
/// 源 3D 类型的构造元数据入口,统一管理名称、参数、数量和可变子项。
pub enum Constructor {
Vec3,
Triangle,
@@ -26,6 +31,7 @@ pub enum Constructor {
Panel,
}
impl Constructor {
/// 返回内部枚举或对象对应的稳定源名称。
pub fn name(self) -> &'static str {
match self {
Self::Vec3 => "Vec3",
@@ -42,6 +48,7 @@ impl Constructor {
Self::Panel => "Panel3D",
}
}
/// 取得本构造器对应的源工厂函数名称。
pub fn function(self) -> &'static str {
match self {
Self::Vec3 => "vec3",
@@ -58,6 +65,7 @@ impl Constructor {
Self::Panel => "panel3D",
}
}
/// 反查源构造器名称对应的原生 3D 种类,不认识的名称返回空值。
pub fn from_function(name: &str) -> Option<Self> {
[
Self::Vec3,
@@ -76,6 +84,7 @@ impl Constructor {
.into_iter()
.find(|kind| kind.function() == name)
}
/// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。
pub fn arity(self) -> (usize, usize) {
match self {
Self::Vec3 | Self::Triangle => (3, 3),
@@ -87,6 +96,7 @@ impl Constructor {
_ => (0, 0),
}
}
/// 列出本构造器的参数类型名称,供签名安装与源调用检查使用。
pub fn parameters(self) -> &'static [&'static str] {
match self {
Self::Vec3 | Self::Triangle => &["Number", "Number", "Number"],
@@ -98,12 +108,14 @@ impl Constructor {
_ => &[],
}
}
/// 标记构造器是否接收数量可变的子对象。
pub fn variadic(self) -> bool {
matches!(self, Self::Group | Self::Scene)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// 托管 3D 节点的名义种类,防止仅有同名字段的任意对象冒充场景节点。
pub enum NodeKind {
Vector,
Triangle,
@@ -117,40 +129,52 @@ pub enum NodeKind {
Panel,
}
#[derive(Debug)]
/// 允许普通属性的 3D 标记 payload;数值字段仍由托管属性系统拥有。
pub struct Node(pub NodeKind);
impl ObjectPayload for Node {
/// 提供受检查的 Any 下转型入口,让宿主按具体 payload 类型读取数据。
fn as_any(&self) -> &dyn std::any::Any {
self
}
/// 返回运行时诊断/类型检查使用的稳定 payload 名称。
fn kind_name(&self) -> &'static str {
"scene-3d-object"
}
/// 声明此 payload 的赋值遵循引用身份语义,避免把内部宿主状态当作可复制值。
fn reference_assignment_only(&self) -> bool {
true
}
/// 声明此 payload 是否允许使用托管属性存储,具体字段仍由类型与调用检查约束。
fn allows_attributes(&self) -> bool {
true
}
}
#[derive(Debug)]
/// 托管不可变网格包装,顶点和三角形数值已经复制为原生 Arc 数据。
pub struct Geometry(pub Arc<scene3d::Geometry>);
impl ObjectPayload for Geometry {
/// 提供受检查的 Any 下转型入口,让宿主按具体 payload 类型读取数据。
fn as_any(&self) -> &dyn std::any::Any {
self
}
/// 返回运行时诊断/类型检查使用的稳定 payload 名称。
fn kind_name(&self) -> &'static str {
"geometry-3d"
}
/// 声明此 payload 的赋值遵循引用身份语义,避免把内部宿主状态当作可复制值。
fn reference_assignment_only(&self) -> bool {
true
}
/// 声明此 payload 是否允许使用托管属性存储,具体字段仍由类型与调用检查约束。
fn allows_attributes(&self) -> bool {
true
}
}
/// 统一构造本模块的参数错误,让调用方停止处理无效输入。
fn invalid(message: &'static str) -> VmError {
VmError::InvalidArgument(message)
}
/// 把源 Value 转换为受检查的托管对象引用,拒绝非对象输入。
fn object_ref(value: &Value) -> Result<ObjectRef, VmError> {
if let Value::Object(object) = value {
Ok(*object)
@@ -158,9 +182,11 @@ fn object_ref(value: &Value) -> Result<ObjectRef, VmError> {
Err(invalid("3D field requires an object"))
}
}
/// 在源 Value 与所需数值表示之间转换,并沿用本调用的有限值/范围检查。
fn number(value: &Value) -> Result<f32, VmError> {
crate::scroll::coordinate(value)
}
/// 把当前源值或文本选择解码成此调用需要的文本表示。
fn text(value: &Value) -> Result<String, VmError> {
if let Value::Text(text) = value {
Ok(text.clone())
@@ -168,6 +194,7 @@ fn text(value: &Value) -> Result<String, VmError> {
Err(invalid("3D field requires text"))
}
}
/// 把经过转换的数值写回指定托管字段,仍通过 VM/NativeContext 的检查入口。
fn set_number(
cx: &mut NativeContext<'_>,
object: ObjectRef,
@@ -176,6 +203,7 @@ fn set_number(
) -> Result<(), VmError> {
cx.set_attr(object, name, crate::native_visual::value(value))
}
/// 解析颜色值并检查其满足当前原生绘制约定。
fn color(value: &str) -> Result<[f32; 3], VmError> {
let color = crate::document::parse_color(value).map_err(|_| invalid("invalid 3D color"))?;
if color.alpha != 255 {
@@ -188,6 +216,7 @@ fn color(value: &str) -> Result<[f32; 3], VmError> {
])
}
/// 按构造器建立对应托管 3D 类型,检查几何输入并安装默认属性。
pub fn allocate(
cx: &mut NativeContext<'_>,
desktop: ObjectRef,
@@ -368,18 +397,22 @@ pub fn allocate(
Ok(instance)
}
/// 快照字段读取器,同时记录实际观察到的对象/属性依赖。
struct Reader<'a> {
store: &'a ObjectStore,
reads: HashSet<(ObjectRef, String)>,
}
impl Reader<'_> {
/// 读取字段并登记依赖,使后续字段变更能够使 3D 快照失效。
fn value(&mut self, object: ObjectRef, name: &str) -> Result<Value, VmError> {
self.reads.insert((object, name.into()));
self.store.get_attr(object, name)
}
/// 在源 Value 与所需数值表示之间转换,并沿用本调用的有限值/范围检查。
fn number(&mut self, object: ObjectRef, name: &str) -> Result<f32, VmError> {
number(&self.value(object, name)?)
}
/// 连续读取三个数值字段,组成位置、旋转或缩放向量。
fn vector(&mut self, object: ObjectRef, names: [&str; 3]) -> Result<Vec3, VmError> {
Ok(Vec3::new(
self.number(object, names[0])?,
@@ -387,6 +420,7 @@ impl Reader<'_> {
self.number(object, names[2])?,
))
}
/// 读取并校验透视或正交摄像机字段,生成不持有 VM 引用的投影描述。
fn camera(&mut self, object: ObjectRef) -> Result<Camera, VmError> {
let projection = match self.store.payload::<Node>(object, "3D camera")?.0 {
NodeKind::Perspective => Projection::Perspective {
@@ -416,6 +450,7 @@ impl Reader<'_> {
}
}
/// 从场景中提取的有限 UI 根,携带稳定面板 key 和逻辑排版尺寸。
pub struct PanelMount {
pub key: u64,
pub content: ObjectRef,
@@ -426,6 +461,7 @@ pub type Snapshot = (
Vec<(ObjectRef, String)>,
Vec<PanelMount>,
);
/// 将当前托管或可变状态提取为后续阶段使用的派生表示。
pub fn snapshot(
store: &ObjectStore,
scene: ObjectRef,
@@ -433,6 +469,7 @@ pub fn snapshot(
) -> Result<Snapshot, VmError> {
snapshot_reusing(store, scene, camera, None)
}
/// 遍历场景并记录字段依赖;如果世界数据未变则复用不可变场景和 BVH,只替换摄像机视图。
pub fn snapshot_reusing(
store: &ObjectStore,
scene: ObjectRef,
@@ -615,6 +652,7 @@ pub fn snapshot_reusing(
Ok((view, reader.reads.into_iter().collect(), panels))
}
/// 把视口中的指针射线结果转换成 RayHit3D,关联实际托管网格和交点数据。
pub fn pick(
cx: &mut NativeContext<'_>,
desktop: ObjectRef,
@@ -706,6 +744,7 @@ pub fn pick(
}
#[derive(Debug)]
/// 指针拾取的中间结果,保留交点与所属实例信息供 Event/RayHit3D 使用。
pub struct PointerPick {
pub mount: ObjectRef,
pub desktop: ObjectRef,
@@ -713,6 +752,7 @@ pub struct PointerPick {
}
/// The default controller is replaceable; camera data has no dependency on it.
/// 为指定出现解码相机手势并调用控制器;嵌套面板输入先转换到所属视口坐标。
pub fn control(
cx: &mut NativeContext<'_>,
controller: ObjectRef,
@@ -809,6 +849,7 @@ pub fn control(
Ok(())
}
/// 执行托管 3D 节点的原生平移/旋转操作,供尚不支持普通数值算术表达式的源程序调用。
pub fn transform(
cx: &mut NativeContext<'_>,
owner: ObjectRef,
@@ -863,9 +904,11 @@ pub fn transform(
Ok(())
}
impl endor_core::NativeCallable for PointerPick {
/// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(0))
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(
&self,
cx: &mut NativeContext<'_>,
+14
View File
@@ -1,3 +1,7 @@
//! 从可变托管对象图提取不可变 UiNode,并维护反向依赖与父子失效传播。
//! 缓存只借用可检查的对象身份,不增加托管根。对象释放后必须清理缓存索引;快照仍可持有独立的原生数值数据。
//! 干净子树直接复用 Arc;脏节点重新检查属性、方法、绑定和子项,循环与展开数量在发布前拒绝。
//!
//! Object-to-visual dependencies. Cached results never own VM references.
use crate::{
block::{self, Kind},
@@ -10,6 +14,7 @@ use std::{
};
#[derive(Debug)]
/// 某个业务组件的视觉快照,以及使它失效的读取依赖和子项身份。
struct Cached {
node: Arc<UiNode>,
dependencies: Vec<Dependency>,
@@ -17,12 +22,14 @@ struct Cached {
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// 反向依赖键:区分整个对象内容和精确属性名称,减少无关字段变化引起的失效。
struct Dependency {
object: ObjectRef,
property: Option<String>,
}
#[derive(Debug, Default)]
/// 视觉快照缓存与反向依赖图。对象身份是借用索引,原生 Arc 数据不拥有 VM 根。
pub struct RenderCache {
scenes3d: HashMap<ObjectRef, Arc<crate::scene3d::Scene>>,
entries: HashMap<ObjectRef, Cached>,
@@ -35,6 +42,7 @@ pub struct RenderCache {
}
impl RenderCache {
/// 把 VM 变化映射到读取过这些属性/对象的视觉节点,再沿父链向上传播脏标记。
pub fn invalidate(&mut self, batch: ChangeBatch) {
let mut queue = VecDeque::new();
if batch.rescan_required {
@@ -58,6 +66,7 @@ impl RenderCache {
self.invalidate_objects(queue);
}
/// 迭代传播显式失效,使用集合避免共享父链重复遍历或循环入队。
pub fn invalidate_objects(&mut self, objects: impl IntoIterator<Item = ObjectRef>) {
let mut queue: VecDeque<_> = objects.into_iter().collect();
while let Some(block) = queue.pop_front() {
@@ -69,6 +78,7 @@ impl RenderCache {
}
}
/// 撤销旧缓存的反向读取边和父子边,为重新登记该节点做准备。
fn remove(&mut self, block: ObjectRef) -> Option<Cached> {
let old = self.entries.remove(&block)?;
for source in &old.dependencies {
@@ -90,6 +100,7 @@ impl RenderCache {
Some(old)
}
/// 删除托管堆中已不存在的身份,缓存本身不负责保持业务对象存活。
pub fn prune(&mut self, store: &ObjectStore) {
self.scenes3d
.retain(|object, _| store.object(*object).is_ok());
@@ -105,6 +116,7 @@ impl RenderCache {
}
}
/// 按显式栈遍历托管组成;复用干净子树,重建脏节点,并在完成前检查循环和资源上限。
pub fn snapshot(
&mut self,
store: &ObjectStore,
@@ -116,6 +128,7 @@ impl RenderCache {
let mut path = HashSet::new();
let mut values = Vec::<Arc<UiNode>>::new();
while let Some((block, finish)) = work.pop() {
// 入栈时先尝试复用;退出标记表示孩子已形成快照,可以组装父节点。
if !finish {
if !self.dirty.contains(&block)
&& let Some(cached) = self.entries.get(&block)
@@ -198,6 +211,7 @@ impl RenderCache {
} else {
None
};
// path 只追踪当前祖先链,因此允许同一业务组件在不同分支多次出现。
if !finish {
if !path.insert(data_ref) {
return Err(VmError::InvalidArgument("cyclic Block composition"));
+33
View File
@@ -1,3 +1,7 @@
//! 托管 Window 的页面所有权、原生命令与关闭生命周期。
//! 源码 resize/focus/setTitle 先产生纯数据命令;只有所属输入成功发布后,GUI 线程才应用它们。
//! 窗口 ID 和运行时 session 共同限定资源归属;切换 VM 或关闭窗口时不能让旧命令作用到新资源。
//!
//! Managed window resources and versioned, coalesced native projections.
//! The VM owns Page roots; only scalar commands cross to the GUI thread.
use crate::{native_visual, object_list, ui_bridge::desktop_type};
@@ -10,6 +14,7 @@ use std::sync::{Arc, Mutex};
pub const MAIN_WINDOW: u64 = 1;
pub const MAX_WINDOW_DIMENSION: f64 = 4096.0;
#[derive(Debug, Clone, PartialEq)]
/// 可投影到原生窗口的动作类别,如尺寸、标题、焦点与关闭。
pub enum Action {
Resize { width: f64, height: f64 },
SetTitle(String),
@@ -18,23 +23,27 @@ pub enum Action {
Close,
}
#[derive(Debug, Clone, PartialEq)]
/// 成功发布后由 GUI 执行的一条原生窗口请求,含资源身份和动作顺序。
pub struct Command {
pub window: u64,
pub sequence: u64,
pub action: Action,
}
impl Command {
/// 判断两条命令是否针对同一资源的同类动作,用于替换尚未执行的旧请求。
pub fn same_kind(&self, other: &Self) -> bool {
self.window == other.window
&& std::mem::discriminant(&self.action) == std::mem::discriminant(&other.action)
}
}
#[derive(Debug, Clone)]
/// 附带运行时 session 的命令,阻止旧域请求在新 VM 上生效。
pub struct Envelope {
pub session: u64,
pub command: Command,
}
#[derive(Debug, Clone)]
/// 托管窗口资源状态与页面拥有关系;本对象存在不等于 OS 窗口已创建。
pub struct ManagedWindow {
pub id: u64,
pub desktop: ObjectRef,
@@ -48,18 +57,23 @@ pub struct ManagedWindow {
outbox: Arc<Mutex<Vec<Command>>>,
}
impl ObjectPayload for ManagedWindow {
/// 提供受检查的 Any 下转型入口,让宿主按具体 payload 类型读取数据。
fn as_any(&self) -> &dyn std::any::Any {
self
}
/// 返回运行时诊断/类型检查使用的稳定 payload 名称。
fn kind_name(&self) -> &'static str {
"tessera-window"
}
/// 声明此 payload 是否允许使用托管属性存储,具体字段仍由类型与调用检查约束。
fn allows_attributes(&self) -> bool {
true
}
/// 声明此 payload 的赋值遵循引用身份语义,避免把内部宿主状态当作可复制值。
fn reference_assignment_only(&self) -> bool {
true
}
/// 枚举本对象真正拥有的托管强引用,供 Aster 引用追踪和循环回收使用。
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
visit(self.desktop);
if let Some(tasks) = self.tasks {
@@ -71,9 +85,11 @@ impl ObjectPayload for ManagedWindow {
}
}
}
/// 统一构造本模块的参数错误,让调用方停止处理无效输入。
fn invalid(message: &'static str) -> VmError {
VmError::InvalidArgument(message)
}
/// 把源数值转换为有限屏幕坐标,拒绝越界或不可表示的值。
fn screen_coordinate(value: &Value) -> Result<i32, VmError> {
let Value::Number(value) = value else {
return Err(invalid("screen position requires integer coordinates"));
@@ -84,6 +100,7 @@ fn screen_coordinate(value: &Value) -> Result<i32, VmError> {
.and_then(|number| i32::try_from(number).ok())
.ok_or(invalid("screen position exceeds limits"))
}
/// 从源 Value 取得窗口操作需要的数值,保留错误类型诊断。
fn scalar(value: &Value) -> Result<f64, VmError> {
let Value::Number(value) = value else {
return Err(invalid("window size requires numbers"));
@@ -98,6 +115,7 @@ fn scalar(value: &Value) -> Result<f64, VmError> {
Ok(number)
}
#[derive(Debug, Clone, Copy)]
/// 当前宿主 API 的操作分类,分派代码负责参数校验与状态更新。
enum Operation {
Id,
Viewport,
@@ -108,15 +126,18 @@ enum Operation {
Focus,
}
#[derive(Debug)]
/// 保存 owner、签名或操作枚举的原生方法适配器,统一由 VM 调用。
struct Method {
signature: endor_core::CallSignature,
operation: Operation,
desktop: ObjectRef,
}
impl NativeCallable for Method {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<endor_core::CallSignature> {
Some(self.signature.clone())
}
/// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(match self.operation {
Operation::Resize | Operation::MoveTo => 3,
@@ -125,6 +146,7 @@ impl NativeCallable for Method {
}))
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
let Some(Value::Object(owner)) = args.first() else {
return Err(invalid("Window method requires a Window"));
@@ -196,6 +218,7 @@ impl NativeCallable for Method {
Ok(NativeResult::Return(result))
}
}
/// 把合法窗口操作追加到所属窗口的待发布命令,不能直接在 VM 线程操作 OS。
pub fn enqueue(
cx: &mut NativeContext<'_>,
owner: ObjectRef,
@@ -225,6 +248,7 @@ pub fn enqueue(
Ok(())
}
/// 将页面登记到其窗口,维护托管所有权与页面顺序。
pub fn register_page(
cx: &mut NativeContext<'_>,
window: ObjectRef,
@@ -253,6 +277,7 @@ pub fn register_page(
}
Ok(())
}
/// 从窗口的页面集合撤销指定页面,保持后续展示索引一致。
pub fn unregister_page(
cx: &mut NativeContext<'_>,
window: ObjectRef,
@@ -276,6 +301,7 @@ pub fn unregister_page(
Ok(())
}
/// Discard an unpublished Page registration without invoking guest callbacks.
/// 宿主收尾路径撤销页面登记,用于已经由外层决定的关闭/退役流程。
pub fn unregister_page_host(
vm: &mut Vm,
window: ObjectRef,
@@ -290,6 +316,7 @@ pub fn unregister_page_host(
Ok(())
}
/// 在窗口之间移动页面的所有权和登记顺序,保留同一业务页面身份。
pub fn move_page(
cx: &mut NativeContext<'_>,
window: ObjectRef,
@@ -323,9 +350,11 @@ pub fn move_page(
}
Ok(())
}
/// 借用一个有效托管窗口的已登记页面集合。
pub fn pages(store: &ObjectStore, window: ObjectRef) -> Result<&[ObjectRef], VmError> {
Ok(&store.payload::<ManagedWindow>(window, "Window")?.pages)
}
/// 取走当前窗口累积的命令,后续由成功发布结果携带到 GUI。
pub fn take_commands(vm: &Vm, window: ObjectRef) -> Result<Vec<Command>, VmError> {
let data = vm.payload::<ManagedWindow>(window, "Window")?;
let mut queue = data
@@ -336,6 +365,7 @@ pub fn take_commands(vm: &Vm, window: ObjectRef) -> Result<Vec<Command>, VmError
commands.sort_by_key(|command| command.sequence);
Ok(commands)
}
/// 把本模块的类型、方法或能力注册到所属运行时上下文。
pub fn install(
vm: &mut Vm,
roots: &mut Vec<ObjectRef>,
@@ -413,6 +443,7 @@ pub fn install(
/// Create a managed native-window resource. The GUI creates the OS resource only
/// after the owning candidate/input publishes its immutable projection.
/// 建立托管窗口、desktop 与页面容器;原生窗口通过命令发布后出现。
pub fn create(
cx: &mut NativeContext<'_>,
master: ObjectRef,
@@ -449,6 +480,7 @@ pub fn create(
Ok((window, desktop, tabs))
}
/// 发起窗口关闭和相应生命周期清理,拒绝重复或不再有效的操作。
pub fn close(cx: &mut NativeContext<'_>, window: ObjectRef) -> Result<(), VmError> {
if cx.store().is_read_only() {
return Err(invalid(
@@ -477,6 +509,7 @@ pub fn close(cx: &mut NativeContext<'_>, window: ObjectRef) -> Result<(), VmErro
}
/// Host teardown for a stopped VM: no binding, dispatch or source execution.
/// 撤销已失效窗口的托管资源关系,不重新执行源关闭请求。
pub fn retire(vm: &mut Vm, window: ObjectRef) -> Result<(), VmError> {
let mut data = vm.payload::<ManagedWindow>(window, "Window")?.clone();
vm.dict_clear(data.tabs)?;