diff --git a/docs/code-comments.md b/docs/code-comments.md new file mode 100644 index 0000000..ebb996d --- /dev/null +++ b/docs/code-comments.md @@ -0,0 +1,116 @@ +# Tessera 中文源码注释与阅读索引 + +本轮只为 Tessera 主项目补充注释,Aster 子模块固定版本与内容均沿用主线。注释按模块职责、类型/函数入口、关键字段、执行阶段和失败/取消路径组织;没有改变接口、算法、常量或资源预算。 + +## 建议阅读顺序 + +1. [main](../src/main.rs):创建原生循环、共享文档与执行 worker。 +2. [原生应用](../src/shell/app.rs):理解窗口生命周期、可用视口、输入边界和最终 present。 +3. [API worker](../src/runtime/api/worker.rs) 与 [运行时](../src/runtime/endor_host.rs):理解候选/活跃 VM、输入串行化、分片刷新和发布。 +4. [桥接层](../src/bridge/ui_bridge.rs)、[Block](../src/model/block.rs) 与 [Mount](../src/model/mount.rs):区分共享业务对象、每次出现和展示事务。 +5. [快照](../src/model/snapshot.rs)、[源布局](../src/render/source_layout.rs) 和 [源绘制](../src/render/source_paint.rs):理解依赖缓存、只读执行及挂起工作。 +6. [二维视口](../src/render/viewport.rs)、[3D](../src/render/scene3d.rs) 和 [面板映射](../src/render/scene3d/panel.rs):理解布局/世界/视图的边界。 +7. [任务](../src/runtime/tasks.rs)、[provider](../src/runtime/io_tasks.rs) 和 [事件传播](../src/runtime/event_dispatch.rs):理解异步交付资格与生命周期。 + +## 阅读时需要保留的边界 + +- Block/Data 属于共享业务组成;Mount 属于一个展示位置,不能把一次出现的尺寸写回共享对象来代替排版。 +- ObjectRef/WeakObjectRef 的含义由真实托管拥有关系决定;派生缓存中的身份索引不能成为隐藏根。 +- 窗口线程消费完整发布值;VM、源回调和托管堆操作只在执行 worker 内推进。 +- submission 防止旧请求覆盖新输入;session 防止跨 VM 资源误用;layout_version 防止旧几何上的离散操作误触。 +- Pending Work 同时拥有续体与上下文;取消必须关闭原 VM 的只读执行并恢复未提交 Mount,不能只丢弃 Rust 值。 +- 场景世界不是无限尺寸的排版盒子;有限视口参加布局,摄像机负责投影,命中沿相同链逆变换。 +- 现有 CPU 绘制、缓存范围与输入队列限制仍按当前代码执行;说明性注释不代表性能优化已实现。 + +## Rust 逐文件说明 + +每行说明该文件本次新增注释解释的职责和目的。生产函数/方法均增加中文入口说明;复杂状态机另外补充阶段和不变量说明。集成测试文件增加整体阅读与检查点说明,测试程序文本和源码范围断言保持原样。 + +| 文件 | 注释解释的职责与目的 | +| --- | --- | +| [src/bridge/mod.rs](../src/bridge/mod.rs) | 语言桥接模块入口。集中导出 Endor 类型和宿主实现之间的适配层。 | +| [src/bridge/ui_bridge.rs](../src/bridge/ui_bridge.rs) | Endor 与 Tessera 的展示事务协调层:维护每个窗口的 Page、Mount、布局缓存和发布版本。 | +| [src/bridge/ui_bridge/bootstrap.rs](../src/bridge/ui_bridge/bootstrap.rs) | 安装一次 VM 域需要的宿主类型、对象和方法。类型注册表是源代码调用原生能力的入口。 | +| [src/bridge/ui_bridge/calls.rs](../src/bridge/ui_bridge/calls.rs) | 把 Endor 方法调用分派到对应宿主操作。这里处理构造、树修改、绑定、状态与页面能力。 | +| [src/bridge/ui_bridge/developer_pages.rs](../src/bridge/ui_bridge/developer_pages.rs) | 把内置开发工具和起始页接入托管 Page。工厂在所属 VM 中编译和执行,而不是在窗口线程另建业务状态。 | +| [src/bridge/ui_bridge/page_input.rs](../src/bridge/ui_bridge/page_input.rs) | 把页面、2D 视口和 3D 视口的相机输入转换成托管对象更新。 | +| [src/bridge/ui_bridge/signatures.rs](../src/bridge/ui_bridge/signatures.rs) | 声明宿主方法在 Endor 类型系统中的参数和返回契约。 | +| [src/bridge/ui_bridge/tree.rs](../src/bridge/ui_bridge/tree.rs) | 定义从托管对象提取的不可变视觉树。共享业务对象可以有多次展示,树节点与 Mount 出现的身份不是同一概念。 | +| [src/main.rs](../src/main.rs) | 应用组合入口。先创建原生事件循环和共享发布状态,再启动 API/VM worker,最后把窗口事件交给 TesseraApp。 | +| [src/model/binding.rs](../src/model/binding.rs) | 属性绑定:在只读计算中收集依赖,将结果写回绑定目标。 | +| [src/model/block.rs](../src/model/block.rs) | Block 的共享赋值、底层 Data 与视觉属性实现。Block 通过 Cell 间接指向 Data,使别名能观察到同一次替换。 | +| [src/model/document.rs](../src/model/document.rs) | 运行时与窗口线程之间的发布数据契约。NamespaceState 管理提交序号、版本和当前完整文档。 | +| [src/model/mod.rs](../src/model/mod.rs) | 模型层模块入口。包含托管业务对象、每次展示上下文、依赖缓存,以及交给窗口线程的发布值。 | +| [src/model/mount.rs](../src/model/mount.rs) | 每次展示的托管上下文:同一个 Block 出现在两个位置时,有两个独立 Mount。 | +| [src/model/object_list.rs](../src/model/object_list.rs) | 给源协议返回的对象列表提供受控封装。列表内容通过托管引用被追踪,读方法只暴露检查后的条目。 | +| [src/model/presentation.rs](../src/model/presentation.rs) | 一个原生窗口对应一份 Presentation:独立视口、Mount、测量缓存、焦点与依赖记录。 | +| [src/model/protocols.rs](../src/model/protocols.rs) | 安装 Visual、EventTarget、Children 和 Presentable 等能力契约。 | +| [src/model/scene3d.rs](../src/model/scene3d.rs) | 将 Endor 托管场景对象转换成可供渲染线程读取的 3D 数据。 | +| [src/model/snapshot.rs](../src/model/snapshot.rs) | 从可变托管对象图提取不可变 UiNode,并维护反向依赖与父子失效传播。 | +| [src/model/window.rs](../src/model/window.rs) | 托管 Window 的页面所有权、原生命令与关闭生命周期。 | +| [src/render/box_model.rs](../src/render/box_model.rs) | 有限布局盒子的尺寸策略。区分 auto、固定值和 fill,并处理 min/max 对约束与最终尺寸的限制。 | +| [src/render/display_list.rs](../src/render/display_list.rs) | 面向设备像素的绘制指令集合。它承载矩形、文字、裁剪与 3D 表面等原语,不直接操作 VM。 | +| [src/render/geometry.rs](../src/render/geometry.rs) | 共享的二维点和矩形值。命中判断在调用方完成坐标系转换后使用,矩形本身不隐式处理相机。 | +| [src/render/layout.rs](../src/render/layout.rs) | 原生布局求解器:自底向上测量期望尺寸,再自顶向下分配位置和实际尺寸。 | +| [src/render/mod.rs](../src/render/mod.rs) | 渲染模块入口。排版、源视觉协议、相机投影和最终光栅化各自独立,通过不可变帧衔接。 | +| [src/render/native_visual.rs](../src/render/native_visual.rs) | 让原生组件也通过 Endor 的 measure/arrange/paint 协议被调用。 | +| [src/render/page.rs](../src/render/page.rs) | 旧 Page.mode 的兼容空间策略,以及页面级的正逆相机变换。 | +| [src/render/scene.rs](../src/render/scene.rs) | 合成整个原生窗口的显示列表:页面画面、宿主侧栏和开发工具绘制。 | +| [src/render/scene3d.rs](../src/render/scene3d.rs) | 有界 CPU 3D 渲染器。使用相同摄像机与世界变换进行视锥裁剪、深度测试、纹理采样和射线拾取。 | +| [src/render/scene3d/math.rs](../src/render/scene3d/math.rs) | 3D 数学基础:向量、仿射矩阵、摄像机基向量、投影射线和三角形相交。 | +| [src/render/scene3d/panel.rs](../src/render/scene3d/panel.rs) | 连接 3D 平面与普通 UI 的坐标和绘制桥梁。 | +| [src/render/scene3d/spatial.rs](../src/render/scene3d/spatial.rs) | 不可变世界空间 BVH。用网格变换后的包围盒快速筛选视锥与射线候选。 | +| [src/render/scroll.rs](../src/render/scroll.rs) | 逐 Mount 的滚动状态、裁剪和输入目标选择。 | +| [src/render/software_renderer.rs](../src/render/software_renderer.rs) | 把 DisplayList 写入 CPU 像素缓冲。裁剪栈限定矩形、文字和 3D 表面的实际写入区域。 | +| [src/render/source_hit.rs](../src/render/source_hit.rs) | 通过真正的源/原生 hitTest 协议确定指针目标。 | +| [src/render/source_layout.rs](../src/render/source_layout.rs) | 源 measure/arrange 协议的分片布局驱动器。自底向上的测量与自顶向下的安排共享一个只读 VM 调用域。 | +| [src/render/source_paint.rs](../src/render/source_paint.rs) | 执行源 paint 覆盖并解码受检查的 PaintCommand。 | +| [src/render/text.rs](../src/render/text.rs) | 统一字体发现、文字整形、测量与抗锯齿字形绘制。 | +| [src/render/viewport.rs](../src/render/viewport.rs) | 独立 2D 相机与可逆投影。视口尺寸属于布局;平移、缩放属于视图,不改变世界对象的逻辑位置。 | +| [src/render/viewport/spatial.rs](../src/render/viewport/spatial.rs) | 有界屏幕网格,用来减少普通二维命中和相机目标查询的候选。 | +| [src/runtime/api.rs](../src/runtime/api.rs) | 本机 HTTP API 入口:路由请求、限制输入大小、观察活动,并将执行工作送入串行 worker。 | +| [src/runtime/api/guide.rs](../src/runtime/api/guide.rs) | 从当前命名空间生成 /agent 使用指南。返回的示例、地址和版本描述来自运行时上下文;这里生成文档,不执行用户源码。 | +| [src/runtime/api/tests.rs](../src/runtime/api/tests.rs) | API 集成回归。通过实际本地 HTTP 请求检查队列、候选替换、状态码、版本和完整文档发布。 | +| [src/runtime/api/worker.rs](../src/runtime/api/worker.rs) | 唯一串行执行 VM 的 API worker。保留活跃运行时,另建 PUT 候选,并在候选完整成功后切换发布。 | +| [src/runtime/endor_host.rs](../src/runtime/endor_host.rs) | 把 Aster 编译/执行与 Tessera 展示生命周期连接起来。Candidate 拥有 VM 与宿主根,ReplSession 保留实际运行中的全局环境。 | +| [src/runtime/endor_host/tests.rs](../src/runtime/endor_host/tests.rs) | 运行时集成回归与示例验收。测试经真实 Aster 前端、编译器、VM 和展示协议执行,不是手工伪造布局结果。 | +| [src/runtime/endor_script.rs](../src/runtime/endor_script.rs) | 区分完整 Module 与顶层 Script,并保留输入源码位置。 | +| [src/runtime/event_dispatch.rs](../src/runtime/event_dispatch.rs) | 托管事件与订阅的传播实现。Connection 持有回调引用,Event 保存目标、阶段与停止传播状态。 | +| [src/runtime/events.rs](../src/runtime/events.rs) | 原生窗口输入到执行 worker 的纯数据传输。Input 只含窗口/会话/版本/Mount 身份及有限坐标。 | +| [src/runtime/io_tasks.rs](../src/runtime/io_tasks.rs) | 后台文件/网络 provider。线程池只携带纯数据、取消控制与弱 Task 身份,不持有 VM。 | +| [src/runtime/mod.rs](../src/runtime/mod.rs) | 运行时模块入口。VM 生命周期、API、源程序组织、事件与异步任务在这里组合。 | +| [src/runtime/tasks.rs](../src/runtime/tasks.rs) | owner/key 管理的异步任务与协作式源函数调度。弱索引用于查找,回调、等待对象和协程帧是托管图中的真实边。 | +| [src/runtime/vm_sequence.rs](../src/runtime/vm_sequence.rs) | 构造共享 VM 调用序列与同帧循环驱动的字节码。 | +| [src/shell/activity.rs](../src/shell/activity.rs) | 有界 HTTP 活动记录。保存请求/响应的可显示预览供 Network 工具读取,不负责执行请求。 | +| [src/shell/app.rs](../src/shell/app.rs) | winit 原生应用生命周期和窗口事件入口。这里创建窗口、发布逻辑视口、转发输入,并把不可变文档画到原生表面。 | +| [src/shell/clipboard.rs](../src/shell/clipboard.rs) | 把 UTF-8 文本读写适配到当前平台的系统剪贴板命令。 | +| [src/shell/developer_tools.rs](../src/shell/developer_tools.rs) | Console/Sources/Network/Settings 的宿主交互协调与绘制状态。 | +| [src/shell/developer_tools/console.rs](../src/shell/developer_tools/console.rs) | 绘制 Console 输入、结果历史和对应命中区域。 | +| [src/shell/developer_tools/network.rs](../src/shell/developer_tools/network.rs) | 绘制已观察到的 HTTP 活动、请求列表与详情。 | +| [src/shell/developer_tools/painter.rs](../src/shell/developer_tools/painter.rs) | 开发工具画面的绘制辅助:文字行、按钮、分隔线和滚动条。 | +| [src/shell/developer_tools/selection.rs](../src/shell/developer_tools/selection.rs) | 多行文本选择和光标导航。选择端点映射到合法字符边界,避免把 UTF-8 字节中间当成一个字符。 | +| [src/shell/developer_tools/settings.rs](../src/shell/developer_tools/settings.rs) | 绘制宿主设置页面和主题选择区域。 | +| [src/shell/developer_tools/sources.rs](../src/shell/developer_tools/sources.rs) | 绘制 AST、当前源码快照和云文件视图。 | +| [src/shell/mod.rs](../src/shell/mod.rs) | 原生宿主模块入口。窗口、管理控件、系统适配和开发工具状态与用户业务界面分开维护。 | +| [src/shell/native_chrome.rs](../src/shell/native_chrome.rs) | macOS 原生标题栏适配。定位系统窗口按钮并沿用用户的标题栏双击设置。 | +| [src/shell/sidebar.rs](../src/shell/sidebar.rs) | 宿主管理工具和页面列表的唯一侧栏。展开时占据页面左侧空间,折叠后只保留展开按钮区域。 | +| [src/shell/sources.rs](../src/shell/sources.rs) | 云文件浏览的 provider 抽象与工作区状态。默认 provider 未连接,不假装已有外部文件。 | +| [src/shell/system_ui/interaction.rs](../src/shell/system_ui/interaction.rs) | 即时绘制控件的跨帧交互状态:稳定 WidgetId、悬停、按下捕获、键盘焦点和释放激活。 | +| [src/shell/system_ui/mod.rs](../src/shell/system_ui/mod.rs) | 宿主管理控件的共享组件入口。主题、绘制原语和跨帧交互状态分离,用户 Endor 页面不依赖这里的控件树。 | +| [src/shell/system_ui/theme.rs](../src/shell/system_ui/theme.rs) | 宿主控件的颜色与状态调色。根据背景、强调色和交互状态生成可读颜色,不改变业务状态。 | +| [src/shell/system_ui/widgets.rs](../src/shell/system_ui/widgets.rs) | 用统一主题绘制宿主管理控件,并同步注册交互目标。 | +| [src/shell/tabs.rs](../src/shell/tabs.rs) | 发布页面标签的数据结构。保留稳定页面身份、标题、选中状态和开发工具类别;页面的托管所有权由模型层管理。 | + +## 其他文件 + +| 文件 | 本次改动及目的 | +| --- | --- | +| [src/bridge/ui_bridge/start_page.endor](../src/bridge/ui_bridge/start_page.endor) | 解释卡片源测量/背景、页面工厂、纵横布局与滚动组合、共享计数和场景位置,便于从真实 Endor 页面理解宿主边界。 | +| [scripts/build-macos-app.sh](../scripts/build-macos-app.sh) | 说明本地构建前提、锁定依赖、临时文件替换和 bundle 元数据,帮助理解每个打包步骤。 | +| [scripts/render-uml.mjs](../scripts/render-uml.mjs) | 说明独立文档依赖、DOM/Canvas 适配、错误图检查与 SVG/PNG 产物生成,避免混淆运行依赖与文档工具。 | +| [src/README.md](../src/README.md) | 增加本注释索引入口,便于从源码目录开始阅读。 | +| [docs/code-comments.md](code-comments.md) | 本逐文件说明与阅读顺序,覆盖本轮提交的文件及共同边界。 | + +## 核对方式 + +检查差异仅新增独立注释行和注释相关空白,并与主线去除注释后的文本对比;同时运行格式、完整 GUI 回归、Clippy 和包含私有项的 rustdoc。脚本使用语法检查,不执行文档渲染或应用打包。具体检查结果在 PR 中记录。 diff --git a/scripts/build-macos-app.sh b/scripts/build-macos-app.sh index e07bca2..49040fd 100755 --- a/scripts/build-macos-app.sh +++ b/scripts/build-macos-app.sh @@ -1,18 +1,22 @@ #!/bin/sh # Native macOS build and app bundle. No Docker, mirror configuration, or tests. set -eu +# 此入口只能在 macOS 使用;构建授权由调用方确认,脚本不自动执行测试或部署。 if [ "$(uname -s)" != Darwin ]; then echo "Build this app bundle on macOS." >&2 exit 1 fi cd "$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +# 锁定依赖并生成 release 可执行文件;附加参数原样交给 Cargo。 cargo build --release --locked "$@" version=$(awk -F '"' '$1 ~ /^version[[:space:]]*=/ { print $2; exit }' Cargo.toml) test -n "$version" bundle="$(pwd)/target/release/Tessera.app" mkdir -p "$bundle/Contents/MacOS" +# 先写临时文件再重命名,避免复制途中将半个二进制留作 bundle 的主程序。 cp target/release/tessera "$bundle/Contents/MacOS/tessera.new" mv -f "$bundle/Contents/MacOS/tessera.new" "$bundle/Contents/MacOS/tessera" +# 生成最小 macOS bundle 元数据;应用名、入口与高分辨率标志供系统启动/显示使用。 cat > "$bundle/Contents/Info.plist.new" < diff --git a/scripts/render-uml.mjs b/scripts/render-uml.mjs index 72cdfc9..a1257d8 100644 --- a/scripts/render-uml.mjs +++ b/scripts/render-uml.mjs @@ -7,6 +7,7 @@ import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; const tools = process.env.UML_RENDER_TOOLS; +// 工具依赖放在独立目录,避免给应用运行依赖增加文档渲染包。 if (!tools) throw new Error('Set UML_RENDER_TOOLS to the documentation tool directory; see docs/architecture/uml/README.md'); const require = createRequire(path.join(path.resolve(tools), 'package.json')); const { createCanvas } = require('@napi-rs/canvas'); @@ -23,6 +24,7 @@ const document = new DOMImplementation().createDocument(null, 'root', null); const element = document.createElement.bind(document); const elementNS = document.createElementNS.bind(document); function measurementCanvas() { + // PlantUML 的文本布局需要真实字体宽高,统一用本机 Canvas 测量而非固定字符宽度。 const canvas = createCanvas(1, 1); const context = canvas.getContext('2d'); const measure = context.measureText.bind(context); @@ -41,6 +43,7 @@ function measurementCanvas() { } document.createElement = tag => tag === 'canvas' ? measurementCanvas() : element(tag); document.createElementNS = (namespace, tag) => { + // 为无浏览器 DOM 补齐样式与 getBBox,满足官方渲染引擎的 SVG 文本布局需求。 const node = elementNS(namespace, tag); node.style = {}; node.getBBox = () => { @@ -65,6 +68,7 @@ const source = path.join(root, 'docs/architecture/uml'); const output = path.join(source, 'rendered'); await mkdir(output, { recursive: true }); for (const filename of (await readdir(source)).filter(name => name.endsWith('.puml')).sort()) { + // 每张图独立渲染并检查错误文本;错误图不能被当作正常产物覆盖已有预览。 const text = await readFile(path.join(source, filename), 'utf8'); const rendered = await new Promise((resolve, reject) => renderToString(text.split('\n'), resolve, reject)); const xml = new DOMParser().parseFromString(rendered, 'image/svg+xml'); @@ -77,6 +81,7 @@ for (const filename of (await readdir(source)).filter(name => name.endsWith('.pu for (const [key, value] of Object.entries({ x: '0', y: '0', width: '100%', height: '100%', fill: '#FFFFFF' })) paper.setAttribute(key, value); xml.documentElement.insertBefore(paper, xml.documentElement.firstChild); const svg = new XMLSerializer().serializeToString(xml); + // 同时保存可缩放 SVG 和通用 PNG;写完读取 PNG 元数据确认生成尺寸。 const stem = filename.slice(0, -5); await writeFile(path.join(output, `${stem}.svg`), svg); // High-resolution PNG is the universally visible Markdown preview; SVG keeps zoomable text. diff --git a/src/README.md b/src/README.md index 586271b..7a50acd 100644 --- a/src/README.md +++ b/src/README.md @@ -1,5 +1,7 @@ # Tessera 源码导航 +本体代码已补充中文模块、类型、函数与关键流程注释;建议按[注释阅读索引和逐文件说明](../docs/code-comments.md)从启动、运行时发布和视觉协议依次阅读。 + 整体设计和当前进展从 [架构导览](../docs/architecture/README.md) 开始;本轮每个文件的迁移对应见 [逐文件清单](../docs/architecture/file-map.md)。 `main.rs` 只负责组合启动,并保留 crate 私有模块路径别名。五组目录仍在同一个 binary crate 中,不是严格单向分层。 diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index 00773de..f642b14 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -1,2 +1,4 @@ +//! 语言桥接模块入口。集中导出 Endor 类型和宿主实现之间的适配层。 +//! // Module ownership follows the directory; crate-root aliases keep internal call sites stable. pub(crate) mod ui_bridge; diff --git a/src/bridge/ui_bridge.rs b/src/bridge/ui_bridge.rs index 5cba3ff..3752eae 100644 --- a/src/bridge/ui_bridge.rs +++ b/src/bridge/ui_bridge.rs @@ -1,3 +1,7 @@ +//! Endor 与 Tessera 的展示事务协调层:维护每个窗口的 Page、Mount、布局缓存和发布版本。 +//! Session 保存托管对象的展示上下文;跨线程发布的是不可变 UiNode/Frame,而不是可以在窗口线程执行的 VM。 +//! 刷新按 prepare → layout → paint → commit 推进。挂起工作必须恢复或显式取消;失败不能把未完成的几何和生命周期状态留作已发布结果。 +//! //! Tessera's native visual protocols over Aster's managed object graph. mod bootstrap; mod calls; @@ -90,6 +94,7 @@ pub const QUERY_NAMES: &[&str] = &[ pub use tree::{UiKind, UiNode}; #[derive(Debug)] +/// 整个 VM 的桥接会话。节点索引跨窗口共享,presentations 按窗口隔离派生状态;current 决定方法当前访问哪份展示上下文。 struct Session { // Weak visual-object registry belongs to the VM, not a particular window. nodes: Vec, @@ -109,6 +114,7 @@ struct Session { } impl Session { + /// 收集所有窗口的私有 desktop 身份,用于排除宿主记账读取造成的无关布局失效。 fn host_desktops(&self) -> std::collections::HashSet { self.presentations .values() @@ -119,6 +125,7 @@ impl Session { impl std::ops::Deref for Session { type Target = crate::presentation::Presentation; + /// 将包装类型的只读访问转交给内部当前上下文。 fn deref(&self) -> &Self::Target { self.presentations .get(&self.current) @@ -126,6 +133,7 @@ impl std::ops::Deref for Session { } } impl std::ops::DerefMut for Session { + /// 将包装类型的可变访问转交给内部当前上下文。 fn deref_mut(&mut self) -> &mut Self::Target { self.presentations .get_mut(&self.current) @@ -135,6 +143,7 @@ impl std::ops::DerefMut for Session { type Shared = Arc>; #[derive(Debug, Clone, Copy)] +/// ui/组件宿主入口的操作枚举;统一由调用适配器分派,不直接代表已经发生的业务变更。 enum Operation { IsInstance, Implement, @@ -202,6 +211,7 @@ enum Operation { } impl Operation { + /// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。 fn arity(self) -> endor_core::CallArity { use Operation::*; let (minimum, maximum) = match self { @@ -238,6 +248,7 @@ impl Operation { } #[derive(Debug)] +/// 将一个宿主 Operation 及会话环境封装成 VM 可调用对象。 struct Function { signature: Option, operation: Operation, @@ -249,6 +260,7 @@ struct Function { /// One shared implementation on the nominal type. The VM binds receivers; /// individual Blocks no longer allocate owner-capturing method closures. #[derive(Debug)] +/// 类型查询/能力方法的原生包装,保存调用所需的声明与会话。 struct TypeMethod { signature: CallSignature, operation: Operation, @@ -256,6 +268,7 @@ struct TypeMethod { session: Shared, } +/// 判断方法是否仍为宿主提供的原生事件实现,区分它与用户源覆盖。 pub(crate) fn is_intrinsic_event( store: &endor_core::ObjectStore, function: ObjectRef, @@ -274,9 +287,11 @@ pub(crate) fn is_intrinsic_event( } impl NativeCallable for TypeMethod { + /// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。 fn signature(&self) -> Option { Some(self.signature.clone()) } + /// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。 fn arity(&self) -> Option { let arity = self.operation.arity(); Some(endor_core::CallArity::range( @@ -285,6 +300,7 @@ impl NativeCallable for TypeMethod { )) } + /// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { let owner = object(args.first())?; if !cx.store().is_instance(owner, self.expected)? { @@ -304,6 +320,7 @@ impl NativeCallable for TypeMethod { /// The Type dispatcher passes the requested subclass explicitly. Native /// constructor options precede the subclass's inherited/own field arguments. #[derive(Debug)] +/// 为源代码调用原生类型构造器提供受检查的分配入口。 struct NativeFactory { signature: Option, options: Option, @@ -312,9 +329,11 @@ struct NativeFactory { } impl NativeCallable for NativeFactory { + /// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。 fn signature(&self) -> Option { self.signature.clone() } + /// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。 fn arity(&self) -> Option { Some(endor_core::CallArity::range( self.operation.arity().required + 1, @@ -322,6 +341,7 @@ impl NativeCallable for NativeFactory { )) } + /// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { let requested = object(args.first())?; let fields = cx @@ -367,21 +387,25 @@ impl NativeCallable for NativeFactory { } } +/// 统一构造本模块的参数错误,让调用方停止处理无效输入。 fn invalid(message: &'static str) -> VmError { VmError::InvalidArgument(message) } +/// 从源值中提取并检查托管对象身份,错误类型沿 VM 错误返回。 fn object(value: Option<&Value>) -> Result { match value { Some(Value::Object(value)) => Ok(*value), _ => Err(invalid("expected an object")), } } +/// 把当前源值或文本选择解码成此调用需要的文本表示。 fn text(value: Option<&Value>) -> Result { match value { Some(Value::Text(value)) => Ok(value.clone()), _ => Err(invalid("expected text")), } } +/// 读取整数参数,拒绝非整数或无法在目标范围表示的值。 fn integer(value: Option<&Value>) -> Result { match value { Some(Value::Number(value)) => value @@ -391,6 +415,7 @@ fn integer(value: Option<&Value>) -> Result { _ => Err(invalid("expected a nonnegative integer")), } } +/// 解析颜色值并检查其满足当前原生绘制约定。 fn color(value: Option<&Value>, default: &str) -> Result { let value = value .map(|v| text(Some(v))) @@ -399,6 +424,7 @@ fn color(value: Option<&Value>, default: &str) -> Result { parse_color(&value).map_err(|_| invalid("color must use #RRGGBB or #RRGGBBAA"))?; Ok(value) } +/// 读取布局尺寸参数,检查非负、有限和最大尺寸限制。 fn dimension(value: Option<&Value>) -> Result { let Some(Value::Number(number)) = value else { return Err(invalid("expected a finite nonnegative dimension")); @@ -414,6 +440,7 @@ fn dimension(value: Option<&Value>) -> Result { Ok(value as f32) } +/// 为一个托管对象安装绑定到当前会话的原生方法,声明调用名与具体 Operation。 fn bind( cx: &mut NativeContext<'_>, owner: ObjectRef, @@ -440,6 +467,7 @@ fn bind( cx.set_attr(owner, name, Value::Object(function)) } +/// 使用默认原生类型创建 Block;需要源子类身份的构造走 new_typed_block。 fn new_block( cx: &mut NativeContext<'_>, data: Data, @@ -448,6 +476,7 @@ fn new_block( new_typed_block(cx, data, session, None) } +/// 分配有名义类型和默认位置/缩放的独立相机,供新视口拥有自己的导航状态。 fn new_camera2d( cx: &mut NativeContext<'_>, session: &Shared, @@ -473,6 +502,7 @@ fn new_camera2d( Ok(camera) } +/// 创建原生 Block 并附加请求的 Endor 类型;先验证继承关系,再登记到会话节点索引。 fn new_typed_block( cx: &mut NativeContext<'_>, data: Data, @@ -528,6 +558,7 @@ fn new_typed_block( Ok(block) } +/// 在当前 VM 中建立窗口、desktop 和独立 Presentation,实际 OS 窗口等命令发布后再创建。 fn create_window( cx: &mut NativeContext<'_>, state: &mut Session, @@ -557,6 +588,7 @@ fn create_window( Ok(window) } +/// 从页面登记信息取得所属窗口,保持 Page 的归属与原生资源身份一致。 fn page_window( store: &endor_core::ObjectStore, state: &Session, @@ -570,6 +602,7 @@ fn page_window( } Ok(None) } +/// 撤销页面在展示会话中的登记,配合模型层处理活动页面与生命周期。 fn remove_page( cx: &mut NativeContext<'_>, view: &mut crate::presentation::Presentation, @@ -588,6 +621,7 @@ fn remove_page( } Ok(()) } +/// 在指定窗口安排页面展示,并维护目标 Presentation 的活动/待发布状态。 fn show_in( cx: &mut NativeContext<'_>, page: ObjectRef, @@ -638,20 +672,24 @@ fn show_in( view.pending = Some(Some(page)); Ok(()) } +/// 在当前窗口安排页面展示,实际几何和画面由后续刷新提交。 fn show(cx: &mut NativeContext<'_>, page: ObjectRef, session: &Shared) -> Result<(), VmError> { show_in(cx, page, session, None) } #[derive(Debug)] +/// 接收源 present 结果并将其转换成宿主可展示内容的原生续接步骤。 struct PresentResult { open: bool, session: Shared, } impl NativeCallable for PresentResult { + /// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。 fn arity(&self) -> Option { Some(endor_core::CallArity::exact(1)) } + /// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { let [Value::Object(root)] = args else { return Err(invalid("Presentable.present must return a Block")); @@ -676,6 +714,7 @@ impl NativeCallable for PresentResult { } } +/// 根据 Presentable 能力把任意源值转换成可展示内容,避免只允许某个原生 Block 类型。 fn present_value( cx: &mut NativeContext<'_>, value: &Value, @@ -718,11 +757,13 @@ fn present_value( } #[must_use = "pending layout must be resumed on its owning VM"] +/// 桥接布局的挂起/完成结果;PendingLayout 必须与其窗口上下文一起继续处理。 pub enum LayoutProgress { Pending(Box), Complete(Box), } +/// 一次未提交布局的上下文:目标窗口、根、布局/缓存、延后钩子以及用于失败恢复的检查点。 struct LayoutContext { surface: crate::page::Surface, root: Arc, @@ -734,17 +775,20 @@ struct LayoutContext { checkpoint: crate::mount::Checkpoint, } +/// 挂起布局的拥有者。续体与上下文必须一起保留并最终恢复或取消。 pub struct PendingLayout { work: crate::source_layout::Work, context: LayoutContext, } +/// 运行时持有的桥接能力句柄,通过共享 Session 协调托管对象与每窗口展示。 pub struct UiBridge { pub ui: ObjectRef, session: Shared, tasks: crate::tasks::Scheduler, } +/// 解析对象的方法并在同一 VM 中调用,使源覆盖与原生成员共用方法查找规则。 fn invoke_member( vm: &mut Vm, owner: ObjectRef, @@ -770,6 +814,7 @@ fn invoke_member( result } +/// 将托管页面注册表投影成稳定标签 ID、标题与活动状态,不把托管引用交给原生侧栏。 fn project_tabs( vm: &Vm, state: &mut crate::presentation::Presentation, @@ -816,6 +861,7 @@ fn project_tabs( impl UiBridge { /// Detach all occurrences without running layout/paint. All windows are /// already closed before source unmount hooks run, so they cannot remount. + /// 按窗口和展示生命周期执行有界退出清理,允许调用方根据故障状态决定是否执行回调。 pub fn shutdown(&self, vm: &mut Vm, callbacks: bool) -> Result<(usize, usize, usize)> { let tasks = self.tasks.quiesce(vm)?; let mut drivers = Vec::new(); @@ -885,16 +931,20 @@ impl UiBridge { result } + /// 查询任务系统是否仍有可推进的协作工作,供 worker 选择等待策略。 pub fn has_cooperative_tasks(&self) -> bool { self.tasks.has_cooperative_work() } + /// 把当前 VM 交给任务调度器推进,限制一轮交付数量。 pub fn poll_tasks(&self, vm: &mut Vm, limit: usize) -> Result { self.tasks.poll(vm, limit) } + /// 取消当前域中的任务,撤销关闭/失败后不应再发生的交付。 pub fn cancel_tasks(&self, vm: &mut Vm) -> Result { self.tasks.cancel_all(vm) } + /// 返回当前仍有效的展示窗口身份,用于稳定遍历一次全窗口刷新。 pub fn window_ids(&self) -> Result> { let state = self .session @@ -917,6 +967,7 @@ impl UiBridge { }); Ok(ids) } + /// 判断窗口是否仍登记且没有进入关闭状态。 pub fn accepts_window(&self, id: u64) -> bool { self.session.lock().is_ok_and(|state| { state @@ -926,6 +977,7 @@ impl UiBridge { }) } + /// 切换当前 Presentation 上下文;不会创建新窗口或复制 VM。 pub fn select_window(&self, id: u64) -> Result<()> { let mut state = self .session @@ -937,6 +989,7 @@ impl UiBridge { state.current = id; Ok(()) } + /// 从托管展示域撤销窗口,退役 Mount 并清理该窗口的私有对象关系。 pub fn retire_window(&self, vm: &mut Vm, id: u64) -> Result<()> { if id == crate::window::MAIN_WINDOW { bail!("the primary shell is closed by the OS window control"); @@ -964,6 +1017,7 @@ impl UiBridge { Ok(()) } + /// 收尾已经关闭的展示资源,避免它们参与下一次窗口遍历。 pub fn finish_closed(&self, vm: &mut Vm) -> Result<()> { let mut state = self .session @@ -988,6 +1042,7 @@ impl UiBridge { state.current = crate::window::MAIN_WINDOW; Ok(()) } + /// 从各托管窗口收集待发布命令;原生执行时还需校验 session。 pub fn take_window_commands(&self, vm: &Vm) -> Result> { let mut state = self .session @@ -1002,6 +1057,7 @@ impl UiBridge { Ok(commands) } + /// 校验并更新所属窗口的逻辑尺寸输入;尺寸不变时不制造新布局,改变时标记需要刷新。 pub fn set_viewport(&self, vm: &mut Vm, viewport: Option) -> Result { if let Some(size) = viewport && (!size.width.is_finite() @@ -1037,6 +1093,7 @@ impl UiBridge { Ok(true) } + /// 将 ui 入口与兼容名称安装到本次程序的全局字典,保持所有调用处在同一个 VM 域。 pub fn install_globals(&self, vm: &mut Vm, globals: ObjectRef) -> Result<()> { vm.dict_set(globals, Value::Text("ui".into()), Value::Object(self.ui))?; // Compatibility spelling used by the original module examples. @@ -1051,6 +1108,7 @@ impl UiBridge { } Ok(()) } + /// 检查当前会话是否已经安排或拥有可展示页面。 pub fn has_page(&self) -> Result { Ok(self .session @@ -1060,6 +1118,7 @@ impl UiBridge { .values() .any(|view| view.pending.flatten().is_some() || view.active.is_some())) } + /// 验证标签拖放仍指向有效页面和窗口关系,避免旧拖动操作跨域生效。 pub fn accepts_tab_drop(&self, vm: &Vm, input: &crate::events::Input) -> bool { let Some(target) = &input.tab_drop else { return true; @@ -1097,12 +1156,14 @@ impl UiBridge { } true } + /// 通过展示协议打开源值,必要时创建或选择承载它的页面。 pub fn open_value(&self, vm: &mut Vm, value: &Value) -> Result<()> { let function = object(Some(&vm.get_attr(self.ui, "open")?))?; let page = vm.call(function, std::slice::from_ref(value))?; vm.release_value(page)?; Ok(()) } + /// 处理页面激活、关闭、移动和拆分等标签操作,保持窗口所有权与展示状态一致。 fn dispatch_tab(&self, vm: &mut Vm, input: &crate::events::Input) -> Result<()> { let page = { let state = self @@ -1214,6 +1275,7 @@ impl UiBridge { Ok(()) } + /// 在所属窗口的展示上下文中处理输入,区分页面管理、滚动、焦点、相机与普通事件。 pub fn dispatch(&self, vm: &mut Vm, input: &crate::events::Input) -> Result<()> { input .validate() @@ -1419,6 +1481,7 @@ impl UiBridge { vm.release(mount)?; result } + /// 整理不再有效的弱索引与展示根,并配合 VM 回收,避免派生缓存延长业务对象生命周期。 pub fn reclaim(&self, vm: &mut Vm, _roots: &mut Vec) -> Result<()> { vm.collect_cycles(); let mut state = self @@ -1438,6 +1501,7 @@ impl UiBridge { } Ok(()) } + /// 丢弃未成功发布的展示请求,恢复后续输入可以使用的会话状态。 pub fn discard_pending(&self, vm: &mut Vm) -> Result<()> { let mut state = self .session @@ -1488,6 +1552,7 @@ impl UiBridge { } Ok(()) } + /// 重新评估已失效的属性绑定,并把结果计入会话统计。 pub fn refresh_bindings(&self, vm: &mut Vm) -> Result<()> { let (nodes, desktops) = { let state = self @@ -1504,6 +1569,7 @@ impl UiBridge { state.binding_evaluations = state.binding_evaluations.saturating_add(evaluated); Ok(()) } + /// 排空一次 VM 变化记录并通知各窗口,提取活动页面快照,判断本次是否需要布局与发布。 pub fn prepare_frame(&self, vm: &mut Vm) -> Result>>> { let changes = vm.take_changes(); let mut state = self @@ -1548,6 +1614,7 @@ impl UiBridge { } Ok(changed.then_some(root)) } + /// 提交已经完成的出现几何与快照,并处理展示生命周期;不把挂起或失败的布局当成已发布帧。 pub fn commit_frame( &self, vm: &mut Vm, @@ -1603,6 +1670,7 @@ impl UiBridge { result } + /// 在已完成的几何上启动绘制协议,保留绘制任务所需的宿主上下文。 pub fn begin_paint_frame( &self, vm: &mut Vm, @@ -1633,6 +1701,7 @@ impl UiBridge { self.accept_paint_progress(progress) } + /// 恢复同一次源绘制调用,把完成的 Draft 交回发布流程。 pub fn resume_paint_frame( &self, vm: &mut Vm, @@ -1642,6 +1711,7 @@ impl UiBridge { self.accept_paint_progress(progress) } + /// 区分挂起与完成的绘制进度;完成时保存真实读取依赖并接回发布流程。 fn accept_paint_progress( &self, progress: crate::source_paint::Progress, @@ -1666,6 +1736,7 @@ impl UiBridge { Ok(progress) } + /// 取消尚未完成的源绘制,释放其续体和未发布上下文。 pub fn cancel_paint_frame( &self, vm: &mut Vm, @@ -1684,6 +1755,7 @@ impl UiBridge { invalidated } + /// 为目标快照建立 Mount、检查点和布局上下文,再启动可挂起的只读布局求解。 pub fn begin_layout_frame( &self, vm: &mut Vm, @@ -1806,6 +1878,7 @@ impl UiBridge { self.accept_layout_progress(vm, context, progress) } + /// 记录本次刷新主动让出 worker 的次数,用于诊断分片执行。 pub fn note_refresh_yield(&self) -> Result<()> { let mut state = self .session @@ -1815,6 +1888,7 @@ impl UiBridge { Ok(()) } + /// 记录视口目标替换导致的刷新重启,便于区分正常挂起与取消重试。 pub fn note_refresh_restart( &self, cancelled_layout: bool, @@ -1834,6 +1908,7 @@ impl UiBridge { Ok(()) } + /// 撤销未完成布局所保留的窗口、挂载与生命周期上下文。 fn discard_layout_context(&self, vm: &mut Vm, context: LayoutContext) -> Result<()> { let LayoutContext { surface: _, @@ -1866,6 +1941,7 @@ impl UiBridge { Ok(()) } + /// 取消源布局续体并清理对应上下文,保持已发布帧可用。 pub fn cancel_layout_frame(&self, vm: &mut Vm, pending: PendingLayout) -> Result<()> { let PendingLayout { work, context } = pending; let cancelled = work.cancel(vm); @@ -1874,10 +1950,12 @@ impl UiBridge { restored } + /// 关闭失败布局的挂起工作并恢复检查点;供错误或候选撤销路径使用。 pub fn abort_layout_frame(&self, vm: &mut Vm, pending: PendingLayout) -> Result<()> { self.cancel_layout_frame(vm, pending) } + /// 继续同一次尚未发布的布局任务;完成时沿原上下文收尾,不能新建一轮来重放副作用。 pub fn resume_layout_frame( &self, vm: &mut Vm, @@ -1895,6 +1973,7 @@ impl UiBridge { self.accept_layout_progress(vm, context, progress) } + /// 接收布局的 Pending/Complete 结果;保留上下文或执行最终投影和提交。 fn accept_layout_progress( &self, vm: &mut Vm, @@ -1918,6 +1997,7 @@ impl UiBridge { } } + /// 依次投影滚动/视口、补页面元数据、保存读取版本、提交 Mount,并处理延后的生命周期调用。 fn finish_layout_frame( &self, vm: &mut Vm, @@ -2018,6 +2098,7 @@ impl UiBridge { Ok(frame) } + /// 读取当前源程序已经选择的展示内容,为编译入口生成最终文档提供根。 pub fn rendered(&self, vm: &Vm) -> Result>> { let state = self .session @@ -2032,6 +2113,7 @@ impl UiBridge { } } +/// 从会话的类型注册表取得指定类型,再给原生分配的对象附加托管类型身份。 fn attach_named_type( cx: &mut NativeContext<'_>, object: ObjectRef, @@ -2045,6 +2127,7 @@ fn attach_named_type( attach_desktop_type(cx, object, name, desktop) } +/// 按给定 desktop 查找类型并附加,支持不同窗口使用自己的展示上下文。 pub fn attach_desktop_type( cx: &mut NativeContext<'_>, instance: ObjectRef, @@ -2055,6 +2138,7 @@ pub fn attach_desktop_type( cx.attach_type(instance, definition) } +/// 读取 desktop 的托管类型注册表;缺失或类型错误时返回 VM 错误。 pub(crate) fn desktop_type( store: &endor_core::ObjectStore, name: &str, @@ -2064,6 +2148,7 @@ pub(crate) fn desktop_type( object(store.dict_get(types, &Value::Text(name.into()))?.as_ref()) } +/// 将当前托管或可变状态提取为后续阶段使用的派生表示。 pub fn snapshot(vm: &Vm, value: &Value) -> Result> { let Value::Object(root) = value else { bail!("main must return a Block"); diff --git a/src/bridge/ui_bridge/bootstrap.rs b/src/bridge/ui_bridge/bootstrap.rs index 415d4e3..e174df4 100644 --- a/src/bridge/ui_bridge/bootstrap.rs +++ b/src/bridge/ui_bridge/bootstrap.rs @@ -1,6 +1,10 @@ +//! 安装一次 VM 域需要的宿主类型、对象和方法。类型注册表是源代码调用原生能力的入口。 +//! 先建立类型与桌面基础设施,再绑定构造器、视觉协议和开发工具工厂;返回的根负责保持这些对象存活。 +//! //! Allocate and connect the managed desktop, type tables and host globals. use super::*; +/// 把本模块的类型、方法或能力注册到所属运行时上下文。 pub fn install(vm: &mut Vm, roots: &mut Vec) -> Result { let desktop = vm.new_object()?; roots.push(desktop); diff --git a/src/bridge/ui_bridge/calls.rs b/src/bridge/ui_bridge/calls.rs index 2e887e3..fd9f240 100644 --- a/src/bridge/ui_bridge/calls.rs +++ b/src/bridge/ui_bridge/calls.rs @@ -1,14 +1,20 @@ +//! 把 Endor 方法调用分派到对应宿主操作。这里处理构造、树修改、绑定、状态与页面能力。 +//! 先检查参数和只读阶段限制,再修改托管状态;对象引用的生命周期仍交给 NativeContext 和 Aster 管理。 +//! //! Native Block operations executed on the owning VM worker. use super::*; impl NativeCallable for Function { + /// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。 fn signature(&self) -> Option { self.signature.clone() } + /// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。 fn arity(&self) -> Option { Some(self.operation.arity()) } + /// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { use Operation::*; let arity = self.operation.arity(); diff --git a/src/bridge/ui_bridge/developer_pages.rs b/src/bridge/ui_bridge/developer_pages.rs index 1ba12c1..86fb423 100644 --- a/src/bridge/ui_bridge/developer_pages.rs +++ b/src/bridge/ui_bridge/developer_pages.rs @@ -1,15 +1,20 @@ +//! 把内置开发工具和起始页接入托管 Page。工厂在所属 VM 中编译和执行,而不是在窗口线程另建业务状态。 +//! //! Developer workspaces are managed Pages, using the same roots and placement API. use super::*; #[derive(Debug)] +/// 工具打开能力,保存所属会话和 ui 对象;调用时校验工具名,再在该 VM 中展示页面。 struct OpenTool { session: Shared, ui: ObjectRef, } impl NativeCallable for OpenTool { + /// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。 fn arity(&self) -> Option { Some(endor_core::CallArity::exact(1)) } + /// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { let [Value::Text(name)] = args else { return Err(invalid("developerTools requires a tool name")); @@ -63,6 +68,7 @@ impl NativeCallable for OpenTool { Ok(NativeResult::Return(Value::Object(page))) } } +/// 把本模块的类型、方法或能力注册到所属运行时上下文。 pub(super) fn install(vm: &mut Vm, ui: ObjectRef, session: &Shared) -> Result<()> { let function = vm.allocate(ContextNativeFunction::new( "developerTools", @@ -79,6 +85,7 @@ pub(super) fn install(vm: &mut Vm, ui: ObjectRef, session: &Shared) -> Result<() /// Compile the bundled Endor page once per VM, in a private module namespace. /// Its functions/classes and callbacks remain ordinary managed VM objects. +/// 编译并安装 VM 私有的起始页工厂,供新页面动作重复调用。 pub(super) fn install_start_page(vm: &mut Vm, bridge: &UiBridge) -> Result<()> { let source = frontend::EndorReader::from_text( "tessera:start-page.endor", diff --git a/src/bridge/ui_bridge/page_input.rs b/src/bridge/ui_bridge/page_input.rs index 01a49ec..b5b354f 100644 --- a/src/bridge/ui_bridge/page_input.rs +++ b/src/bridge/ui_bridge/page_input.rs @@ -1,7 +1,11 @@ +//! 把页面、2D 视口和 3D 视口的相机输入转换成托管对象更新。 +//! 输入携带具体出现的 Mount;嵌套世界 UI 先做逆投影,避免把屏幕位移直接当成场景坐标。 +//! //! Camera mutations run on the owning VM worker, just like other page input. use super::*; impl UiBridge { + /// 解码指定 3D 视口的旋转/平移/缩放输入,再调用其托管相机控制器。 pub(super) fn dispatch_scene_view( &self, vm: &mut Vm, @@ -108,6 +112,7 @@ impl UiBridge { result } + /// 把具体 CanvasViewport 的位移或缩放锚点映射到其显式 Camera2D。 pub(super) fn dispatch_viewport_view( &self, vm: &mut Vm, @@ -197,6 +202,7 @@ impl UiBridge { result } + /// 处理旧 Page 模式的兼容相机事件;独立视口使用各自的输入路径。 pub(super) fn dispatch_page_view( &self, vm: &mut Vm, diff --git a/src/bridge/ui_bridge/signatures.rs b/src/bridge/ui_bridge/signatures.rs index 513d1e2..aaa6744 100644 --- a/src/bridge/ui_bridge/signatures.rs +++ b/src/bridge/ui_bridge/signatures.rs @@ -1,6 +1,10 @@ +//! 声明宿主方法在 Endor 类型系统中的参数和返回契约。 +//! 签名用于构造/调用检查,应与 calls 和各 NativeCallable 实现保持一致;这里不执行实际业务操作。 +//! //! Canonical contracts for native Block methods and constructors. use super::*; +/// 根据宿主方法类别提供 Endor 方法的签名,供类型与调用检查使用。 pub(super) fn method_signature( store: &endor_core::ObjectStore, desktop: ObjectRef, @@ -86,6 +90,7 @@ pub(super) fn method_signature( Ok(signature) } +/// 将具体宿主 Operation 映射到参数/返回类型契约。 pub(super) fn operation_signature( store: &endor_core::ObjectStore, desktop: ObjectRef, diff --git a/src/bridge/ui_bridge/start_page.endor b/src/bridge/ui_bridge/start_page.endor index 2eeaa1b..4e2da9b 100644 --- a/src/bridge/ui_bridge/start_page.endor +++ b/src/bridge/ui_bridge/start_page.endor @@ -1,4 +1,6 @@ // The page, cards and actions belong to Endor. The host only owns its sidebar. +// 默认页由真实 Endor 对象组成:宿主负责窗口和侧栏,卡片布局、绘制和按钮回调在源代码中定义。 +// Note 复用 Column 的内容测量,但固定卡片宽度;paint 只提供背景,孩子仍沿正常树绘制。 class Note: Column { tint: String function measure(self: Note, mount: Mount, constraints: Constraints): MeasureResult { @@ -18,12 +20,15 @@ class Note: Column { } } class QuietButton: Button { + // 返回空绘制列表保留 Button 的交互能力,同时让这个入口只显示孩子文字。 function paint(self: QuietButton, mount: Mount, geometry: Bounds): DisplayList { return DisplayList(ObjectList()) } } class StartActions { + // 保存起始页面身份,子页面中的返回按钮激活同一 Page,而不是复制一份页面。 home: Page + // 窗口页面是普通有限 Column;可用区域来自宿主发布的逻辑视口。 function blank(self: StartActions, event: Event): Unit { page = Page() page.title = "窗口页面" @@ -31,6 +36,7 @@ class StartActions { back.onClick(self.home.activate) page.render(Column(56, 20, TextBlock("恰好一个窗口", 3), TextBlock("页面跟随可用区域调整。内容和工具栏由 Endor 自己组织。"), back)) } + // 延伸方向属于容器与滚动组合:Column 安排内容,有限 ScrollBlock 控制可见范围。 function vertical(self: StartActions, event: Event): Unit { page = Page() page.title = "纵向页面" @@ -45,6 +51,7 @@ class StartActions { page.crossAlign = "stretch" page.render(view) } + // Row 内可继续放 Column 卡片;横向滚动不要求改变 Page 的身份或生命周期。 function horizontal(self: StartActions, event: Event): Unit { page = Page() page.title = "横向页面" @@ -59,9 +66,11 @@ class StartActions { page.crossAlign = "stretch" page.render(view) } + // 重新调用工厂得到独立页面与相机,已有画布继续保留自己的视角。 function canvas(self: StartActions, event: Event): Unit { page = newTab() } + // 两个文字绑定观察同一个状态对象,示范共享业务状态与多个展示位置的区别。 function counter(self: StartActions, event: Event): Unit { count = ui.state(0) add = Button("增加计数") @@ -71,6 +80,7 @@ class StartActions { page.render(Column(48, 24, TextBlock("一个对象,两个视图", 2), Row(0, 32, ui.textOf(count, "value", 3), ui.textOf(count, "value", 3)), add)) } } +// 页面工厂:先建立业务对象/动作,再建立世界组成,最后用一个有限视口显示它。 function newTab(): Page { page = Page() page.title = "自由画布" @@ -86,6 +96,7 @@ function newTab(): Page { canvas.on("click", actions.canvas) shared.on("click", actions.counter) hero = Column(0, 16, TextBlock("给想法,一点空间。", 3, "#303941"), TextBlock("页面可以是一扇窗口,也可以一直延伸。", 1, "#7D858C"), Spacer(8), Row(0, 10, fixed, vertical), Row(0, 10, horizontal, canvas), shared) + // x/y 属于 Scene2D 子组件的世界位置;卡片内部依旧由普通 Row/Column 排版。 hero.x = 316 hero.y = 254 world = Scene2D(hero) @@ -109,6 +120,7 @@ function newTab(): Page { end.x = 716 end.y = 614 world.append(end) + // 视口参加有限布局并占用剩余空间;移动它的 Camera2D 不改写卡片世界位置。 view = CanvasViewport(1024, 800, world) view.flex = 1 page.crossAlign = "stretch" diff --git a/src/bridge/ui_bridge/tree.rs b/src/bridge/ui_bridge/tree.rs index de13fcb..89edc4d 100644 --- a/src/bridge/ui_bridge/tree.rs +++ b/src/bridge/ui_bridge/tree.rs @@ -1,8 +1,13 @@ +//! 定义从托管对象提取的不可变视觉树。共享业务对象可以有多次展示,树节点与 Mount 出现的身份不是同一概念。 +//! 节点保存排版、绘制和协议投影信息;浅比较用于决定是否复用已有快照。 +//! //! Owned immutable visual derivation; no managed VM handles. use std::sync::Arc; #[derive(Debug, Clone, PartialEq)] +/// 不可变视觉节点。block_id 关联业务身份,slots 保留子项出现的稳定组成;kind 和盒模型字段用于派生排版与绘制。 pub struct UiNode { + // 只有页面根带兼容 Page 相机;其余字段是普通有限盒子的布局策略。 pub surface: Option, pub flex: f32, pub flex_policy: crate::layout::FlexPolicy, @@ -15,14 +20,17 @@ pub struct UiNode { pub border: crate::layout::Insets, pub border_color: String, pub clip_children: bool, + // 这些字符串只是派生身份/协议版本键,不是可以操作托管堆的 ObjectRef。 pub block_id: String, pub protocol_key: String, + // slot 保持组成身份;数组次序确定当前绘制/布局顺序,二者不是同一概念。 pub slots: Vec, pub interactive: bool, pub kind: UiKind, } impl UiNode { + /// 按节点种类返回其视觉子项;叶节点使用空切片,避免为遍历另建组成。 pub fn children(&self) -> &[Arc] { match &self.kind { UiKind::Column { children, .. } @@ -38,6 +46,7 @@ impl UiNode { } } + /// 判断是否需要对子项执行弹性主轴分配。 pub fn has_flex(&self) -> bool { self.flex > 0.0 || self.flex_policy != crate::layout::FlexPolicy::default() @@ -45,6 +54,7 @@ impl UiNode { || self.sizing.height == crate::box_model::Dimension::Fill } + /// 判断当前节点是否会为子项指定实际分配范围,供源布局选择安排路径。 pub fn allocates_children(&self) -> bool { self.children().iter().any(|child| { child.has_flex() @@ -53,6 +63,7 @@ impl UiNode { }) } + /// 比较节点自身配置和直接子项身份,决定能否复用旧 Arc;不是深拷贝业务图。 pub fn shallow_eq(&self, other: &Self) -> bool { if self.surface != other.surface || self.flex_policy != other.flex_policy @@ -198,6 +209,7 @@ impl UiNode { } #[derive(Debug, Clone, PartialEq)] +/// 视觉内容的原生分类。叶节点保存绘制数据,容器保存子树,场景视口另外保存相机与世界投影。 pub enum UiKind { Viewport3D { width: f32, diff --git a/src/main.rs b/src/main.rs index aa071bf..f149124 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +//! 应用组合入口。先创建原生事件循环和共享发布状态,再启动 API/VM worker,最后把窗口事件交给 TesseraApp。 +//! VM 操作由运行时线程串行处理;窗口线程只消费发布文档与纯数据通知。crate 私有别名用于保持模块内部引用路径稳定。 +//! //! Application composition root. Directory modules own implementation; these //! crate-private aliases keep existing internal paths stable during reorganization. mod bridge; @@ -31,12 +34,16 @@ use app::{AppEvent, TesseraApp}; use document::NamespaceState; use winit::event_loop::{ControlFlow, EventLoop}; +/// 按顺序建立事件循环、共享文档、API/VM worker 和应用窗口;事件循环结束后返回原生退出清理结果。 fn main() -> Result<()> { + // 原生窗口由事件循环所属线程驱动;Wait 让空闲时休眠,而不是持续重绘。 let event_loop = EventLoop::::with_user_event().build()?; event_loop.set_control_flow(ControlFlow::Wait); + // 共享最近完整发布的文档。后台 worker 拥有 VM,GUI 不借用其托管堆。 let namespace = Arc::new(RwLock::new(NamespaceState::default())); let (activity, control) = api::start(namespace.clone(), event_loop.create_proxy())?; let mut app = TesseraApp::new(namespace, activity, event_loop.create_proxy(), control)?; + // run_app 接管线程直至退出;退出后仍需检查清理过程中记录的错误。 event_loop.run_app(&mut app)?; app.exit_result() } diff --git a/src/model/binding.rs b/src/model/binding.rs index 6d18c8e..c4689e9 100644 --- a/src/model/binding.rs +++ b/src/model/binding.rs @@ -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, output: Value, } #[derive(Debug, Clone)] +/// 托管属性连接,强持有计算/目标所需引用,并保留只读计算的版本缓存。 pub struct Binding { field: String, pub compute: Option, reads: Arc>>, } 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], diff --git a/src/model/block.rs b/src/model/block.rs index dc97e6e..5e72d44 100644 --- a/src/model/block.rs +++ b/src/model/block.rs @@ -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, 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, 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, @@ -155,6 +171,7 @@ pub struct Data { } impl Data { + /// 构造本类型并初始化其状态;资源建立和默认字段以函数体为准。 pub fn new(kind: Kind, children: Vec, 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 { 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