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
+34
View File
@@ -1,3 +1,7 @@
//! 本机 HTTP API 入口:路由请求、限制输入大小、观察活动,并将执行工作送入串行 worker。
//! HTTP 线程不操作 VM;共享 NamespaceState 只保存最近完整发布文档和资源命令。提交序号用来拒绝已被较新 PUT/DELETE 取代的结果。
//! 关闭流程先请求运行时有界清理,再通过完成通知让原生事件循环退出。
//!
mod guide;
mod worker;
@@ -35,18 +39,21 @@ const MAX_CODE_SIZE: u64 = endor_host::MAX_SOURCE_BYTES as u64;
type Notify = Arc<dyn Fn() + Send + Sync>;
#[derive(Clone, Debug)]
/// 运行时清理的摘要与可选错误,供最终原生退出结果报告。
pub struct ShutdownOutcome {
pub summary: String,
pub error: Option<String>,
}
type ShutdownCompletion = Arc<(Mutex<Option<ShutdownOutcome>>, Condvar)>;
#[derive(Clone)]
/// 跨线程的退出控制句柄,使用有界通知和完成条件变量,不携带 VM。
pub struct RuntimeControl {
sender: SyncSender<()>,
requested: Arc<AtomicBool>,
completion: ShutdownCompletion,
}
impl RuntimeControl {
/// 只发出一次退出请求,唤醒 worker 执行清理,并通过完成句柄通知原生循环。
pub fn request_shutdown(&self) -> bool {
if self.requested.swap(true, Ordering::AcqRel) {
return true;
@@ -58,9 +65,11 @@ impl RuntimeControl {
false
}
}
/// 读取退出标志,使窗口事件和新输入尽早停止进入运行时。
pub fn is_exiting(&self) -> bool {
self.requested.load(Ordering::Acquire)
}
/// 在明确超时内等待 worker 的清理结果,不能无限阻塞退出。
pub fn wait_shutdown(&self, timeout: std::time::Duration) -> Option<ShutdownOutcome> {
let (lock, ready) = &*self.completion;
let result = lock.lock().ok()?;
@@ -70,6 +79,7 @@ impl RuntimeControl {
result.clone()
}
}
/// worker 持有的关闭接收端、控制状态和 GUI 完成通知代理。
struct Shutdown {
receiver: Receiver<()>,
control: RuntimeControl,
@@ -77,6 +87,7 @@ struct Shutdown {
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// 送入执行队列的请求类别,区分完整源码、持久输入、事件与重置。
enum RequestKind {
Event,
Code,
@@ -85,6 +96,7 @@ enum RequestKind {
ResetRepl,
}
/// 保留原 HTTP 请求与提交身份的执行工作,完成或失败后由 worker 回应。
struct BuildRequest {
request: ObservedRequest,
kind: RequestKind,
@@ -93,6 +105,7 @@ struct BuildRequest {
submission: u64,
}
/// 建立本模块的执行/服务上下文,并把后续推进交给返回的句柄或驱动。
pub fn start(
namespace: Arc<RwLock<NamespaceState>>,
proxy: EventLoopProxy<AppEvent>,
@@ -127,6 +140,7 @@ pub fn start(
}
#[cfg(test)]
/// 为本地监听服务建立请求处理线程,并把执行请求转交有界队列。
fn start_server(
server: Arc<Server>,
namespace: Arc<RwLock<NamespaceState>>,
@@ -142,6 +156,7 @@ fn start_server(
)
}
/// 启动带活动观察的 HTTP 服务,将请求状态变化通知管理界面。
fn start_server_observed(
server: Arc<Server>,
namespace: Arc<RwLock<NamespaceState>>,
@@ -283,6 +298,7 @@ fn start_server_observed(
}
// Capture only allowlisted headers: credentials and cookies never enter the inspector.
/// HTTP 请求的观察包装,在读取正文和响应时维护 Network 工具记录。
struct ObservedRequest {
inner: Request,
activity: Activity,
@@ -290,6 +306,7 @@ struct ObservedRequest {
notify: Notify,
}
impl ObservedRequest {
/// 构造本类型并初始化其状态;资源建立和默认字段以函数体为准。
fn new(inner: Request, activity: Activity, notify: Notify) -> Self {
let headers = inner
.headers()
@@ -322,10 +339,12 @@ impl ObservedRequest {
notify,
}
}
/// 将受限请求正文记录到活动预览,不在观察器中执行源码。
fn record_body(&self, body: &str) {
self.activity.lock().unwrap().body(self.id, body);
(self.notify)();
}
/// 发送最终 HTTP 响应,并同步结束该条活动记录。
fn respond(self, response: Response<std::io::Cursor<Vec<u8>>>) -> std::io::Result<()> {
let status = response.status_code();
let headers = response.headers().to_vec();
@@ -348,16 +367,19 @@ impl ObservedRequest {
}
impl Deref for ObservedRequest {
type Target = Request;
/// 将包装类型的只读访问转交给内部当前上下文。
fn deref(&self) -> &Request {
&self.inner
}
}
impl DerefMut for ObservedRequest {
/// 将包装类型的可变访问转交给内部当前上下文。
fn deref_mut(&mut self) -> &mut Request {
&mut self.inner
}
}
/// 解析 HTTP 方法和路径;只读路由读取发布值,执行路由交给 worker。
fn handle(
request: Request,
namespace: &Arc<RwLock<NamespaceState>>,
@@ -410,6 +432,7 @@ fn handle(
let _ = request.respond(response);
}
/// 处理源码资源的 GET/DELETE 等请求,返回对应版本头或撤销运行时所有权。
fn code_request(
method: Method,
addressed_namespace: &str,
@@ -510,6 +533,7 @@ fn code_request(
}
}
/// 读取并检查请求体,分配提交身份,再把工作送进有界执行队列。
fn submit(
mut request: ObservedRequest,
addressed: &str,
@@ -576,6 +600,7 @@ fn submit(
}
}
/// 生成较旧请求已被新提交取代的响应,拒绝晚到结果覆盖当前文档。
fn superseded(submission: u64) -> Response<std::io::Cursor<Vec<u8>>> {
json(
StatusCode(409),
@@ -584,15 +609,18 @@ fn superseded(submission: u64) -> Response<std::io::Cursor<Vec<u8>>> {
)
}
/// 从固定路由格式提取命名空间,避免把不匹配的路径当作代码资源。
fn namespace_from_route<'a>(path: &'a str, suffix: &str) -> Option<&'a str> {
path.strip_prefix("/v1/namespaces/")?
.strip_suffix(suffix)
.filter(|namespace| !namespace.is_empty() && !namespace.contains('/'))
}
/// 从固定路由格式提取命名空间,避免把不匹配的路径当作代码资源。
fn namespace_from_path(path: &str) -> Option<&str> {
namespace_from_route(path, "/code")
}
/// 按源码字节上限读取 UTF-8 请求体,读取失败或超限时返回诊断。
fn read_code(request: &mut Request) -> Result<String> {
if request
.body_length()
@@ -612,6 +640,7 @@ fn read_code(request: &mut Request) -> Result<String> {
Ok(code)
}
/// 将前端/编译/执行诊断编码为 API 错误响应,保留用户可理解的位置和消息。
fn compile_error(error: anyhow::Error) -> Response<std::io::Cursor<Vec<u8>>> {
let mut diagnostic =
serde_json::json!({ "severity": "error", "message": format!("{error:#}") });
@@ -630,6 +659,7 @@ fn compile_error(error: anyhow::Error) -> Response<std::io::Cursor<Vec<u8>>> {
)
}
/// 生成统一 JSON 错误响应,使调用方能区分协议错误与成功结果。
fn error_response(
status: StatusCode,
message: impl Into<String>,
@@ -637,6 +667,7 @@ fn error_response(
json(status, serde_json::json!({ "error": message.into() }))
}
/// 以明确的内容类型和状态码构造响应,并应用本机 API 的公共响应头。
fn json(status: StatusCode, value: serde_json::Value) -> Response<std::io::Cursor<Vec<u8>>> {
text(
status,
@@ -645,10 +676,12 @@ fn json(status: StatusCode, value: serde_json::Value) -> Response<std::io::Curso
)
}
/// 以明确的内容类型和状态码构造响应,并应用本机 API 的公共响应头。
fn markdown(status: StatusCode, value: String) -> Response<std::io::Cursor<Vec<u8>>> {
text(status, &value, "text/markdown; charset=utf-8")
}
/// 以明确的内容类型和状态码构造响应,并应用本机 API 的公共响应头。
fn text(status: StatusCode, value: &str, content_type: &str) -> Response<std::io::Cursor<Vec<u8>>> {
let mut response = Response::from_string(value).with_status_code(status);
add_header(&mut response, "Content-Type", content_type);
@@ -670,6 +703,7 @@ fn text(status: StatusCode, value: &str, content_type: &str) -> Response<std::io
response
}
/// 向响应追加经过 HTTP 库检查的头字段,不直接拼接未验证的原始报文。
fn add_header(response: &mut Response<std::io::Cursor<Vec<u8>>>, name: &str, value: &str) {
if let Ok(header) = Header::from_bytes(name, value) {
response.add_header(header);
+3
View File
@@ -1,6 +1,9 @@
//! 从当前命名空间生成 /agent 使用指南。返回的示例、地址和版本描述来自运行时上下文;这里生成文档,不执行用户源码。
//!
//! Live agent-facing guide generated from the current namespace.
use super::*;
/// 用当前命名空间、地址和版本填充在线指南,示例来自项目内置 Endor 源码。
pub(super) fn agent_guide(namespace: &NamespaceState) -> String {
format!(
r##"# Tessera Agent API
+3
View File
@@ -1,3 +1,6 @@
//! API 集成回归。通过实际本地 HTTP 请求检查队列、候选替换、状态码、版本和完整文档发布。
//! 测试服务使用独立监听地址和串行 worker;应结合断言理解成功输入、失败候选、过期事件和关闭的不同保留语义。
//!
use super::*;
use std::{
io::Write,
+24
View File
@@ -1,3 +1,7 @@
//! 唯一串行执行 VM 的 API worker。保留活跃运行时,另建 PUT 候选,并在候选完整成功后切换发布。
//! 输入、视口刷新与候选布局可以跨 worker 轮次挂起;中途只公布完整文档,不能让新输入进入未结束的只读调用。
//! 事件身份同时校验窗口、session、layout_version 和 Mount 能力。失败、替换与关闭分别处理,避免旧回调或旧命令泄漏到新 VM。
//!
//! Serial VM ownership, incremental requests, publication and shutdown.
use super::*;
@@ -13,6 +17,7 @@ type CandidateOutcome = std::thread::Result<
)>,
>;
/// 已执行业务部分但仍在等待展示刷新的输入元数据,避免恢复时重放回调。
pub(super) struct PendingInput {
epoch: u64,
id: u64,
@@ -20,17 +25,20 @@ pub(super) struct PendingInput {
result: endor_host::ReplResult,
}
/// 挂起 HTTP 请求的阶段:等旧刷新、继续输入或继续隔离候选。
pub(super) enum PendingRequest {
BeforeInput,
Input(Box<PendingInput>),
Candidate(Box<endor_host::ReplSession>),
}
/// 一次 worker 推进的结果:继续保留请求或交付最终响应。
pub(super) enum RequestProgress {
Pending(PendingRequest),
Complete(HttpResponse),
}
/// 只提取仍属于当前 session 的窗口尺寸,避免旧原生窗口给新候选提供错误约束。
fn current_viewports(
current: &NamespaceState,
) -> std::collections::BTreeMap<u64, crate::layout::Size> {
@@ -55,11 +63,13 @@ fn current_viewports(
}
#[derive(Default)]
/// 唯一活跃 VM 的串行拥有者,另行管理候选和下一 session 身份。
pub(super) struct Worker {
pub(super) repl: Option<(u64, endor_host::ReplSession)>,
pub(super) next_session: u64,
}
impl Worker {
/// 撤销活跃运行时并有界清理窗口、Mount 和任务;故障域不再执行新的业务回调。
pub(super) fn shutdown_runtime(&mut self) -> ShutdownOutcome {
let Some((_, mut runtime)) = self.repl.take() else {
return ShutdownOutcome {
@@ -90,6 +100,7 @@ impl Worker {
}
}
/// 处理当前无法继续正常交互的窗口关闭,撤销原生资源与相应运行时记录。
pub(super) fn close_inactive_window(
&mut self,
input: &crate::events::Input,
@@ -162,6 +173,7 @@ impl Worker {
)
}
/// 在没有前台输入的 worker 轮次推进尺寸和任务引起的展示刷新。
pub(super) fn refresh_viewport(
&mut self,
namespace: &Arc<RwLock<NamespaceState>>,
@@ -170,12 +182,14 @@ impl Worker {
self.refresh_viewport_mode(namespace, notify, false);
}
/// 在输入之间推进视口/任务刷新;提交序号或 session 已变时拒绝旧结果发布。
pub(super) fn refresh_viewport_mode(
&mut self,
namespace: &Arc<RwLock<NamespaceState>>,
notify: &Notify,
before_input: bool,
) {
// 在读锁内取得一个一致的提交身份和尺寸快照;耗时 VM 工作不占用共享状态写锁。
let (epoch, viewports) = match namespace.read() {
Ok(current) => {
let viewports = current_viewports(&current);
@@ -216,6 +230,7 @@ impl Worker {
}
Ok((completed.or(resized), runtime.take_window_commands()?))
}));
// 计算结束后重新核对归属:期间若发生 PUT/DELETE,不能把旧域草稿发布出去。
let mut current = match namespace.write() {
Ok(current) => current,
Err(_) => return,
@@ -267,6 +282,7 @@ impl Worker {
}
}
/// 检查请求种类和归属,必要时先等已有刷新结束,再建立输入或候选任务。
pub(super) fn begin_request(
&mut self,
job: &BuildRequest,
@@ -591,6 +607,7 @@ impl Worker {
))
}
/// 恢复已挂起的请求,保持原 HTTP 响应和运行时身份直到最终完成。
pub(super) fn resume_request(
&mut self,
job: &BuildRequest,
@@ -607,6 +624,7 @@ impl Worker {
}
}
/// 继续同一 Console/事件输入的展示刷新,不再次执行其业务回调。
fn resume_input(
&mut self,
job: &BuildRequest,
@@ -671,6 +689,7 @@ impl Worker {
}
#[allow(clippy::too_many_arguments)]
/// 校验输入仍属于当前域,再一次性发布结果、元数据和原生窗口命令。
fn finish_input(
&mut self,
job: &BuildRequest,
@@ -768,6 +787,7 @@ impl Worker {
/// An accepted PUT changes the submission, but does not revoke the old
/// runtime. DELETE clears runtime_session and does revoke it.
/// 接受较新提交不等于立即销毁旧 VM;候选失败时仍需保留当前可用运行时。
pub(super) fn preserve_live_owner(&mut self, current: &NamespaceState) {
if let Some((epoch, runtime)) = &mut self.repl {
if current.runtime_session == runtime.id {
@@ -778,6 +798,7 @@ impl Worker {
}
}
/// 为完整 PUT 建立隔离候选,保留旧运行时直到新候选完整成功。
fn begin_program_request(
&mut self,
job: &BuildRequest,
@@ -830,6 +851,7 @@ impl Worker {
RequestProgress::Complete(self.finish_candidate(job, namespace, notify, id, result))
}
/// 推进候选的布局与视口收敛,检查其是否已被新提交撤销。
fn resume_candidate(
&mut self,
job: &BuildRequest,
@@ -877,6 +899,7 @@ impl Worker {
RequestProgress::Complete(self.finish_candidate(job, namespace, notify, id, result))
}
/// 候选成功才切换活跃 VM 和文档;失败保留先前已发布程序。
fn finish_candidate(
&mut self,
job: &BuildRequest,
@@ -925,6 +948,7 @@ impl Worker {
}
}
/// 处理隔离表达式或一次性文档编译;编译前后核对 submission,只发布仍有效的完整快照。
fn build(
job: &BuildRequest,
namespace: &Arc<RwLock<NamespaceState>>,
+78
View File
@@ -1,3 +1,7 @@
//! 把 Aster 编译/执行与 Tessera 展示生命周期连接起来。Candidate 拥有 VM 与宿主根,ReplSession 保留实际运行中的全局环境。
//! 完整源码 PUT 使用隔离候选;Console 输入则在已有 VM 上执行,先前发生的写入不等于可以回放或事务回滚。
//! 刷新状态机遍历窗口并逐步推进布局/绘制。完成所有必要轮次才形成文档;取消会释放挂起调用并恢复未提交的 Mount。
//!
//! Compile complete source in a fresh VM or retain a worker-owned Console VM.
//! Console retains mounted native references and publishes immutable frames after successful inputs.
use anyhow::{Result, bail};
@@ -22,6 +26,7 @@ const LIMITS: ExecutionLimits = ExecutionLimits {
const MAX_AST_NODES: usize = 10_000;
const MAX_AST_DEPTH: usize = 64;
/// 把源输入编译并执行为一次独立结果,最终将可展示对象转换为检查后的文档。
pub fn compile(source: &str, namespace: &str, submission: u64) -> Result<RenderDocument> {
if source.len() > MAX_SOURCE_BYTES {
bail!("source exceeds the 1 MiB limit");
@@ -106,6 +111,7 @@ pub fn compile(source: &str, namespace: &str, submission: u64) -> Result<RenderD
}
/// Evaluate exactly one expression in a disposable VM; never touch the published page.
/// 在隔离执行域求一个表达式结果;不会把结果作为持久 Console 环境发布。
pub fn evaluate(source: &str) -> Result<String> {
if source.len() > MAX_SOURCE_BYTES {
bail!("expression exceeds the 1 MiB limit");
@@ -143,6 +149,7 @@ pub fn evaluate(source: &str) -> Result<String> {
}
}
/// 按 Script/Module 计划编译各根节点,为后续执行准备可加载模板。
fn compile_plan(source: &SourceUnit, plan: &SourcePlan) -> Result<Vec<compiler::CompiledCode>> {
compile_plan_with_globals(
source,
@@ -155,6 +162,7 @@ fn compile_plan(source: &SourceUnit, plan: &SourcePlan) -> Result<Vec<compiler::
)
}
/// 结合已有全局信息编译本轮输入,保留持久 Console 的名称解析上下文。
fn compile_plan_with_globals(
source: &SourceUnit,
plan: &SourcePlan,
@@ -200,31 +208,39 @@ fn compile_plan_with_globals(
/// One real VM, owned only by the existing execution worker. No history replay.
/// The UI bridge shares the same managed object heap and publishes atomic frames.
/// 指定窗口尚未完成的绘制工作,文档与源续体必须保持配对。
struct PendingPaint {
window: u64,
document: RenderDocument,
work: Box<crate::source_paint::Work>,
}
/// 跨 worker 轮次的全窗口刷新状态,保存已完成草稿和当前布局/绘制,直到稳定再发布。
struct RefreshWork {
// 已完成窗口的草稿先留在本轮;所有窗口收敛后才合成一次完整发布。
documents: std::collections::BTreeMap<u64, RenderDocument>,
windows: std::collections::VecDeque<u64>,
current: Option<u64>,
root: Option<std::sync::Arc<crate::ui_bridge::UiNode>>,
// 续体必须保留其 owning VM;取消时显式关闭,不能靠丢弃 Rust 值。
pending: Option<Box<crate::ui_bridge::PendingLayout>>,
paint: Option<Box<PendingPaint>>,
// pass_changed 要求再跑一轮确认稳定,changed 记录整次刷新是否有可发布变化。
pass_start: bool,
pass_changed: bool,
changed: bool,
work: usize,
// 记录本轮尺寸目标,下一次 worker 轮次据此判断是否需要重定向。
viewports: Option<std::collections::BTreeMap<u64, crate::layout::Size>>,
}
/// 刷新仍在进行或已完成;完成也可能没有新文档需要发布。
enum RefreshProgress {
Pending,
Complete(Option<Box<RenderDocument>>),
}
/// 持久源运行时与其桥接会话,保存实际全局对象、代码投影、全窗口文档和挂起刷新。
pub struct ReplSession {
candidate: Candidate,
bridge: ui_bridge::UiBridge,
@@ -238,6 +254,7 @@ pub struct ReplSession {
documents: std::collections::BTreeMap<u64, RenderDocument>,
refresh: Option<RefreshWork>,
}
/// 一次 Console/事件操作的标量显示结果与可选完整文档,避免直接跨线程暴露托管返回值。
pub struct ReplResult {
pub value: String,
pub document: Option<RenderDocument>,
@@ -248,6 +265,7 @@ impl ReplSession {
/// Compile once into an isolated candidate, then keep its actual heap and
/// callback environments alive after the first successful publication.
#[cfg(test)]
/// 测试用同步装载入口:建立完整候选并排空初始刷新,返回真实运行时与完成文档。
pub fn load(
source: &str,
namespace: &str,
@@ -258,6 +276,7 @@ impl ReplSession {
}
#[cfg(test)]
/// 测试用同步装载入口:建立完整候选并排空初始刷新,返回真实运行时与完成文档。
pub fn load_with_viewport(
source: &str,
namespace: &str,
@@ -275,6 +294,7 @@ impl ReplSession {
Ok((runtime, document))
}
/// 建立可由 worker 分轮推进的完整源码候选,初始布局不必在一次调用里排空。
pub fn load_incremental(
source: &str,
namespace: &str,
@@ -297,6 +317,7 @@ impl ReplSession {
Ok((runtime, document))
}
/// 建立隔离候选、安装宿主根并执行完整源码;候选尚未完成布局时不替换活跃程序。
fn prepare_program(
source: &str,
namespace: &str,
@@ -368,6 +389,7 @@ impl ReplSession {
Ok(runtime)
}
/// 建立空的持久 Console 运行时,并安装当前逻辑视口输入。
pub fn new_with_viewport(id: u64, viewport: Option<crate::layout::Size>) -> Result<Self> {
let mut runtime = Self::new(id)?;
runtime
@@ -376,6 +398,7 @@ impl ReplSession {
Ok(runtime)
}
/// 构造本类型并初始化其状态;资源建立和默认字段以函数体为准。
pub fn new(id: u64) -> Result<Self> {
let mut candidate = Candidate::new()?;
let globals = candidate.vm.new_dict()?;
@@ -398,10 +421,12 @@ impl ReplSession {
})
}
#[cfg(test)]
/// 同步执行一个 Console 输入并排空刷新,返回标量结果及可选新文档。
pub fn execute(&mut self, source: &str, namespace: &str) -> Result<ReplResult> {
self.execute_mode(source, namespace, None)
}
/// 执行一次输入的业务部分,再让展示刷新以可恢复工作跨 worker 轮次推进。
pub fn execute_incremental(
&mut self,
source: &str,
@@ -411,6 +436,7 @@ impl ReplSession {
self.execute_mode(source, namespace, Some(viewports))
}
/// 在当前真实 VM 中编译并执行新输入,再按同步或分片模式刷新展示;已发生的业务写入不会被源重放回滚。
fn execute_mode(
&mut self,
source: &str,
@@ -555,14 +581,17 @@ impl ReplSession {
result
}
/// 查询调度器是否仍有需要主动推进的源协程工作。
pub fn has_cooperative_tasks(&self) -> bool {
self.bridge.has_cooperative_tasks()
}
/// 检查是否存在尚未结束的视觉刷新工作,决定能否接收新的业务变更。
pub fn has_pending_refresh(&self) -> bool {
self.refresh.is_some()
}
/// 拒绝在未完成的只读刷新内部再次进行业务变更,防止同一 VM 重入。
fn require_idle_refresh(&self) -> Result<()> {
anyhow::ensure!(
!self.has_pending_refresh(),
@@ -571,6 +600,7 @@ impl ReplSession {
Ok(())
}
/// 在业务变化后开始或继续展示刷新,保证后续输入不会进入未结束的只读视觉调用。
fn refresh_after_mutation(
&mut self,
viewports: Option<&std::collections::BTreeMap<u64, crate::layout::Size>>,
@@ -588,6 +618,7 @@ impl ReplSession {
})
}
/// 同步排空当前刷新状态机,直到全窗口收敛或出现错误。
fn refresh_document(&mut self) -> Result<Option<RenderDocument>> {
loop {
match self.poll_refresh(false)? {
@@ -599,6 +630,7 @@ impl ReplSession {
}
}
/// 推进一次刷新状态机,保存挂起的 Work;只有全窗口收敛时才生成可发布文档。
fn poll_refresh(&mut self, record_yield: bool) -> Result<RefreshProgress> {
let mut work = self.refresh.take().unwrap_or_else(|| RefreshWork {
documents: self.documents.clone(),
@@ -654,7 +686,9 @@ impl ReplSession {
}
}
/// 执行一个刷新步骤:刷新绑定、选择窗口、准备快照、推进布局或绘制,并决定是否需要下一轮。
fn step_refresh(&mut self, work: &mut RefreshWork) -> Result<bool> {
// 阶段一:新一轮先刷新绑定,再取得稳定窗口遍历顺序。
if work.pass_start {
self.bridge.refresh_bindings(&mut self.candidate.vm)?;
work.windows = self.bridge.window_ids()?.into();
@@ -663,6 +697,7 @@ impl ReplSession {
work.pass_start = false;
return Ok(false);
}
// 阶段二:逐个选择窗口;一轮发生变化时再扫描一轮,直到依赖不再引起新帧。
let Some(id) = work.current else {
if let Some(id) = work.windows.pop_front() {
work.current = Some(id);
@@ -675,6 +710,7 @@ impl ReplSession {
return Ok(true);
};
self.bridge.select_window(id)?;
// 已进入 paint 的窗口直接恢复原绘制续体,不重放业务回调或布局。
if let Some(paint) = work.paint.take() {
let PendingPaint {
window,
@@ -686,6 +722,7 @@ impl ReplSession {
.resume_paint_frame(&mut self.candidate.vm, *paint_work)?;
return Self::accept_window_paint(work, window, document, progress);
}
// 阶段三:恢复旧布局,或在快照/依赖确实变化时建立新布局。
let progress = if let Some(pending) = work.pending.take() {
self.bridge
.resume_layout_frame(&mut self.candidate.vm, *pending)?
@@ -704,6 +741,7 @@ impl ReplSession {
}
crate::ui_bridge::LayoutProgress::Complete(frame) => *frame,
};
// 阶段四:几何完成后建立候选文档,再运行 paint;这里尚未向 GUI 发布。
let mut document = RenderDocument {
windows: Default::default(),
source: self.source.clone(),
@@ -732,6 +770,7 @@ impl ReplSession {
Self::accept_window_paint(work, id, document, progress)
}
/// 接收指定窗口的绘制进度,将挂起 Work 保留或把完整文档存入本轮结果。
fn accept_window_paint(
work: &mut RefreshWork,
id: u64,
@@ -757,6 +796,7 @@ impl ReplSession {
Ok(false)
}
/// 收敛后组合所有窗口文档,保留本轮完整结果;没有变化时不额外制造发布。
fn finish_refresh(&mut self, mut work: RefreshWork) -> Result<Option<RenderDocument>> {
self.bridge.finish_closed(&mut self.candidate.vm)?;
let ids = self.bridge.window_ids()?;
@@ -796,6 +836,7 @@ impl ReplSession {
}
#[cfg(test)]
/// 同步更新多个窗口尺寸,复用同一运行时并完成必要刷新。
pub fn resize_windows(
&mut self,
viewports: &std::collections::BTreeMap<u64, crate::layout::Size>,
@@ -803,6 +844,7 @@ impl ReplSession {
self.resize_windows_incremental(viewports, true)
}
/// 用最新视口替换旧目标,显式取消未完成布局,同时保留已完成窗口结果和已发生的业务效果。
fn retarget_refresh(
&mut self,
viewports: &std::collections::BTreeMap<u64, crate::layout::Size>,
@@ -846,6 +888,7 @@ impl ReplSession {
Ok(())
}
/// 把各窗口最新尺寸送入同一运行时;视口更新可跨轮次挂起,也可由调用方同步排空。
pub fn resize_windows_incremental(
&mut self,
viewports: &std::collections::BTreeMap<u64, crate::layout::Size>,
@@ -900,6 +943,7 @@ impl ReplSession {
}
}
/// 在空闲轮次推进一个刷新步骤,完成时执行统一结果收尾。
fn advance_idle_refresh(&mut self) -> Result<Option<RenderDocument>> {
let result = self.poll_refresh(true).map(|progress| match progress {
RefreshProgress::Pending => None,
@@ -908,6 +952,7 @@ impl ReplSession {
self.finish_refresh_update(result)
}
/// 统一处理刷新失败、未发布状态清理和运行时引用回收。
fn finish_refresh_update(
&mut self,
result: Result<Option<RenderDocument>>,
@@ -925,11 +970,13 @@ impl ReplSession {
/// Deliver ready work on the owning VM worker, never from the GUI or a
/// provider thread. A failed callback stops the same shared fault domain.
/// 同步推进可交付任务和必要刷新,供不使用分片 worker 的调用方使用。
pub fn poll_tasks(&mut self) -> Result<Option<RenderDocument>> {
self.poll_tasks_mode(None)
}
/// Idle delivery starts at most one callback, then parks its layout work.
/// 按有界任务交付量推进,再以可挂起刷新处理变化。
pub fn poll_tasks_incremental(
&mut self,
viewports: &std::collections::BTreeMap<u64, crate::layout::Size>,
@@ -937,6 +984,7 @@ impl ReplSession {
self.poll_tasks_mode(Some(viewports))
}
/// 按本轮预算交付已完成任务或推进协程,再对其业务变化执行相同的展示刷新流程。
fn poll_tasks_mode(
&mut self,
viewports: Option<&std::collections::BTreeMap<u64, crate::layout::Size>>,
@@ -973,12 +1021,14 @@ impl ReplSession {
result
}
/// 取得本轮已稳定运行时的窗口命令,挂起刷新时禁止提前取走。
pub fn take_window_commands(&self) -> Result<Vec<crate::window::Command>> {
self.require_idle_refresh()?;
self.bridge.take_window_commands(&self.candidate.vm)
}
#[cfg(test)]
/// 测试用单窗口尺寸更新入口,遵循与原生尺寸发布相同的校验和刷新规则。
pub fn resize(
&mut self,
viewport: Option<crate::layout::Size>,
@@ -1000,6 +1050,7 @@ impl ReplSession {
result
}
/// 关闭尚未完成的刷新任务,释放续体并恢复未提交 Mount;不能只丢弃 Work 结构。
fn cancel_pending_refresh(&mut self) -> Result<()> {
if let Some(mut work) = self.refresh.take() {
if let Some(paint) = work.paint.take() {
@@ -1017,6 +1068,7 @@ impl ReplSession {
Ok(())
}
/// 有界关闭任务、挂载和窗口,释放运行时根,并报告清理结果。
pub fn shutdown(&mut self) -> Result<(usize, usize, usize)> {
self.cancel_pending_refresh()?;
let callbacks = !self.faulted;
@@ -1024,6 +1076,7 @@ impl ReplSession {
self.bridge.shutdown(&mut self.candidate.vm, callbacks)
}
/// 撤销指定窗口的运行时展示资源,防止后续刷新重新发布它。
pub fn retire_window(&mut self, id: u64) -> Result<()> {
self.require_idle_refresh()?;
// This path is exclusively host cleanup after the domain has stopped.
@@ -1036,23 +1089,28 @@ impl ReplSession {
.reclaim(&mut self.candidate.vm, &mut self.candidate.roots)
}
/// 查询当前运行时是否仍接受对应的窗口或拖放目标。
pub fn accepts_tab_drop(&self, input: &crate::events::Input) -> bool {
self.bridge.accepts_tab_drop(&self.candidate.vm, input)
}
/// 查询当前运行时是否仍接受对应的窗口或拖放目标。
pub fn accepts_window(&self, id: u64) -> bool {
self.bridge.accepts_window(id)
}
/// 报告该运行时是否已经停止正常业务执行,供 worker 选择故障清理路径。
pub fn is_faulted(&self) -> bool {
self.faulted
}
#[cfg(test)]
/// 同步分发一次已校验输入并完成展示刷新。
pub fn dispatch(&mut self, input: &crate::events::Input) -> Result<ReplResult> {
self.dispatch_mode(input, None)
}
/// 分发一次输入回调,再把视觉刷新交给 worker 分片推进。
pub fn dispatch_incremental(
&mut self,
input: &crate::events::Input,
@@ -1061,6 +1119,7 @@ impl ReplSession {
self.dispatch_mode(input, Some(viewports))
}
/// 在正确窗口上下文执行输入回调,再刷新展示;故障时停止受影响域并清理未完成工作。
fn dispatch_mode(
&mut self,
input: &crate::events::Input,
@@ -1107,11 +1166,13 @@ impl ReplSession {
}
impl Drop for ReplSession {
/// 在 Rust 所有者离开作用域时执行本类型的配套清理;不能用它代替需要报告错误的显式关闭。
fn drop(&mut self) {
let _ = self.cancel_pending_refresh();
}
}
/// 递归修正代码投影的源行号,使拼接输入仍可定位到原始文本。
fn shift_node_lines(node: &mut CodeNode, offset: usize) {
node.start_line += offset;
node.end_line += offset;
@@ -1119,6 +1180,7 @@ fn shift_node_lines(node: &mut CodeNode, offset: usize) {
shift_node_lines(child, offset);
}
}
/// 把 VM 返回值转换为 Console 的可显示表示,不让借用的托管引用跨线程。
fn value_repr(vm: &Vm, value: &Value) -> Result<String> {
Ok(match value {
Value::Nil => "nil".into(),
@@ -1129,6 +1191,7 @@ fn value_repr(vm: &Vm, value: &Value) -> Result<String> {
})
}
/// 拥有 VM、全局字典与宿主保留根;关闭时按顺序释放,失败也必须收尾。
struct Candidate {
vm: Vm,
roots: Vec<ObjectRef>,
@@ -1136,6 +1199,7 @@ struct Candidate {
}
impl Candidate {
/// 创建独立 VM,先不保留任何宿主根或全局字典;后续安装失败也由该候选负责清理。
fn new() -> Result<Self> {
Ok(Self {
vm: Vm::new()?,
@@ -1144,6 +1208,7 @@ impl Candidate {
})
}
/// 释放候选拥有的宿主根并清理 VM;通过显式关闭保证错误也能被报告。
fn close(&mut self) -> Result<()> {
if let Some(globals) = self.globals.take() {
self.vm.dict_clear(globals)?;
@@ -1160,6 +1225,7 @@ impl Candidate {
}
impl Drop for Candidate {
/// 在 Rust 所有者离开作用域时执行本类型的配套清理;不能用它代替需要报告错误的显式关闭。
fn drop(&mut self) {
// Explicit close reports accounting errors; dropping the fresh VM is
// also the final cleanup boundary on every early return or unwind.
@@ -1167,6 +1233,7 @@ impl Drop for Candidate {
}
}
/// 把 VM 错误与当前源位置/调用栈组合成用户可读的诊断。
pub(crate) fn runtime_error(vm: &Vm, error: VmError) -> anyhow::Error {
let locations = vm
.last_traceback()
@@ -1187,6 +1254,7 @@ pub(crate) fn runtime_error(vm: &Vm, error: VmError) -> anyhow::Error {
}
#[derive(Default)]
/// 编译前的语法节点数量/嵌套预算,限制输入结构复杂度。
struct AstBudget {
depth: usize,
nodes: usize,
@@ -1194,6 +1262,7 @@ struct AstBudget {
impl Visitor for AstBudget {
type Error = anyhow::Error;
/// 更新源结构遍历的深度/数量预算,拒绝超出允许范围的语法树。
fn enter_node(&mut self, _span: Span) -> Result<()> {
self.depth += 1;
self.nodes += 1;
@@ -1202,11 +1271,13 @@ impl Visitor for AstBudget {
}
Ok(())
}
/// 离开当前语法节点时归还嵌套深度;累计节点数保留,不能通过退出子树绕过总量预算。
fn leave_node(&mut self) {
self.depth -= 1;
}
}
/// 把输入语法结构转换为 Sources 工具使用的 CodeNode 层次,保存原始源码位置。
fn architecture(
source: &SourceUnit,
plan: &SourcePlan,
@@ -1246,6 +1317,7 @@ fn architecture(
tree.node(namespace.to_owned(), kind, span, children)
}
/// 建立源文本每行起点表,供字节 span 转换为行列位置。
fn line_starts(source: &str) -> Vec<usize> {
let bytes = source.as_bytes();
let mut starts = vec![0];
@@ -1257,6 +1329,7 @@ fn line_starts(source: &str) -> Vec<usize> {
starts
}
/// 构造 Sources 语法投影所需的源码与定位辅助状态。
struct Architecture {
line_starts: Vec<usize>,
next_id: usize,
@@ -1264,6 +1337,7 @@ struct Architecture {
}
impl Architecture {
/// 将相应语法结构投影为带源码范围的 CodeNode,供 Sources 展示和定位。
fn node(&mut self, name: String, kind: &str, span: Span, children: Vec<CodeNode>) -> CodeNode {
let id = self.next_id;
self.next_id += 1;
@@ -1278,6 +1352,7 @@ impl Architecture {
}
}
/// 将相应语法结构投影为带源码范围的 CodeNode,供 Sources 展示和定位。
fn declaration(&mut self, declaration: &Decl) -> CodeNode {
match &declaration.kind {
DeclKind::Function(function) => self.function(function),
@@ -1293,6 +1368,7 @@ impl Architecture {
}
}
/// 将相应语法结构投影为带源码范围的 CodeNode,供 Sources 展示和定位。
fn function(&mut self, function: &frontend::ast::Function) -> CodeNode {
let children = function
.body
@@ -1308,6 +1384,7 @@ impl Architecture {
)
}
/// 将相应语法结构投影为带源码范围的 CodeNode,供 Sources 展示和定位。
fn statement(&mut self, statement: &Stmt) -> CodeNode {
match &statement.kind {
StmtKind::MemberAssignment(assignment) => {
@@ -1351,6 +1428,7 @@ impl Architecture {
}
}
/// 将相应语法结构投影为带源码范围的 CodeNode,供 Sources 展示和定位。
fn expression(&mut self, expr: &Expr) -> CodeNode {
let (name, kind, children) = match &expr.kind {
ExprKind::Name(name) => (name.clone(), "name", vec![]),
+4
View File
@@ -1,3 +1,7 @@
//! 运行时集成回归与示例验收。测试经真实 Aster 前端、编译器、VM 和展示协议执行,不是手工伪造布局结果。
//! 覆盖共享对象/独立 Mount、源覆盖、绑定、窗口、任务、取消、二维/三维投影及像素。局部辅助函数负责取统计值、收敛刷新和构造可重复输入。
//! 阅读时先看每个测试提交的 Endor 程序,再看后续变更与最终断言;编译成功、回调成功、文档发布成功是不同检查点。
//!
use super::*;
use crate::document::{NamespaceState, RenderCommand};
+12
View File
@@ -1,3 +1,6 @@
//! 区分完整 Module 与顶层 Script,并保留输入源码位置。
//! 解析包装或片段拼接产生的 span 偏移要递归修正,保证诊断和 Sources 视图仍指向用户原始行列。
//!
//! Feed a source snapshot to Aster's existing module or interactive entry points.
//! Statement syntax, tokens and ASTs all remain Aster-owned. The host only batches
//! complete interactive inputs, just as a REPL feeds consecutive inputs to a VM.
@@ -7,12 +10,14 @@ use frontend::{
ast::{AstRoot, Decl, DeclKind, Expr, ExprKind, InteractiveInput, Stmt, StmtKind},
};
/// 待编译输入是完整模块还是顶层脚本;两者保留不同根结构但使用统一后续接口。
pub enum SourcePlan {
Module(AstRoot),
Script(Vec<AstRoot>),
}
impl SourcePlan {
/// 按输入计划返回需要编译的语法根,隐藏 Script 与 Module 组织差异。
pub fn roots(&self) -> &[AstRoot] {
match self {
Self::Module(root) => std::slice::from_ref(root),
@@ -21,6 +26,7 @@ impl SourcePlan {
}
}
/// 把外部源码或属性表示转换为内部枚举/结构,并反馈不支持的输入。
pub fn parse(source: &SourceUnit) -> Result<SourcePlan> {
if let Ok(module) = frontend::parse_source_ast(source, ParseMode::Module) {
return Ok(SourcePlan::Module(module));
@@ -78,11 +84,13 @@ pub fn parse(source: &SourceUnit) -> Result<SourcePlan> {
Ok(SourcePlan::Script(roots))
}
/// 递归调整对应语法节点的源码范围,保持片段解析后的错误位置仍指向原输入。
fn shift_span(span: &mut Span, offset: usize) {
span.start += offset;
span.end += offset;
}
/// 递归调整对应语法节点的源码范围,保持片段解析后的错误位置仍指向原输入。
fn shift_input(input: &mut InteractiveInput, offset: usize) {
shift_span(&mut input.span, offset);
for statement in &mut input.statements {
@@ -90,6 +98,7 @@ fn shift_input(input: &mut InteractiveInput, offset: usize) {
}
}
/// 递归调整对应语法节点的源码范围,保持片段解析后的错误位置仍指向原输入。
fn shift_statement(statement: &mut Stmt, offset: usize) {
shift_span(&mut statement.span, offset);
match &mut statement.kind {
@@ -120,6 +129,7 @@ fn shift_statement(statement: &mut Stmt, offset: usize) {
}
}
/// 递归调整对应语法节点的源码范围,保持片段解析后的错误位置仍指向原输入。
fn shift_declaration(declaration: &mut Decl, offset: usize) {
shift_span(&mut declaration.span, offset);
match &mut declaration.kind {
@@ -142,6 +152,7 @@ fn shift_declaration(declaration: &mut Decl, offset: usize) {
}
}
/// 递归调整对应语法节点的源码范围,保持片段解析后的错误位置仍指向原输入。
fn shift_function(function: &mut frontend::ast::Function, offset: usize) {
shift_span(&mut function.span, offset);
shift_span(&mut function.name.span, offset);
@@ -157,6 +168,7 @@ fn shift_function(function: &mut frontend::ast::Function, offset: usize) {
}
}
/// 递归调整对应语法节点的源码范围,保持片段解析后的错误位置仍指向原输入。
fn shift_expression(expression: &mut Expr, offset: usize) {
shift_span(&mut expression.span, offset);
match &mut expression.kind {
+39
View File
@@ -1,3 +1,6 @@
//! 托管事件与订阅的传播实现。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};
@@ -7,10 +10,12 @@ use endor_core::{
};
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,
@@ -30,6 +35,7 @@ pub fn validate_handler(
}
#[derive(Debug, Clone, Default)]
/// 本次传播的托管事件状态,包含目标、当前阶段和阻止默认/停止传播控制。
pub struct Event {
dispatch: Option<WeakObjectRef>,
group: usize,
@@ -38,36 +44,45 @@ pub struct Event {
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);
@@ -77,22 +92,27 @@ impl ObjectPayload for Connection {
/// 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,
@@ -102,10 +122,12 @@ enum Method {
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"));
@@ -179,17 +201,21 @@ impl NativeCallable for Method {
}
#[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"));
@@ -212,6 +238,7 @@ impl NativeCallable for Constructor {
}
}
/// 建立本次分发拥有的托管 Event,保存目标、输入与传播控制状态。
pub fn new_event(
cx: &mut NativeContext<'_>,
definition: ObjectRef,
@@ -231,19 +258,23 @@ pub fn new_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>,
@@ -292,12 +323,14 @@ pub fn install(
}
#[derive(Debug)]
/// 本次递送选择的对象方法或显式订阅处理器。
enum Handler {
Token(ObjectRef),
Legacy(ObjectRef),
ObjectMethod,
}
#[derive(Debug)]
/// 事件传播过程中将要调用的一项处理器及其阶段/目标上下文。
struct Delivery {
event: ObjectRef,
owner: ObjectRef,
@@ -307,10 +340,12 @@ struct Delivery {
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) {
@@ -363,12 +398,15 @@ impl NativeCallable for Delivery {
}
}
#[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;
@@ -383,6 +421,7 @@ impl NativeCallable for Finish {
/// 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,
+13
View File
@@ -1,3 +1,7 @@
//! 原生窗口输入到执行 worker 的纯数据传输。Input 只含窗口/会话/版本/Mount 身份及有限坐标。
//! 连续相机和滚动输入出队时可更新帧版本,但不改变捕获目标和所属 VM;离散点击仍保留严格的过期帧语义。
//! 队列有界且当前逐项经本地 HTTP 发送;这不意味着输入已经按显示帧合并,也不是无损无限队列。
//!
//! Native input transport. Frame/mount identities and optional logical pointer positions cross to the VM worker.
use serde::{Deserialize, Serialize};
use std::{
@@ -14,6 +18,7 @@ use winit::event_loop::EventLoopProxy;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
/// 拖放目标窗口、目标布局版本和插入位置,源标签身份由外层 Input 保存。
pub struct TabDrop {
pub window: u64,
pub layout_version: u64,
@@ -22,6 +27,7 @@ pub struct TabDrop {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
/// 原生或 HTTP 输入的纯数据报文;身份与坐标必须同时通过 worker 和能力校验。
pub struct Input {
#[serde(default = "default_window")]
pub window: u64,
@@ -41,11 +47,13 @@ pub struct Input {
pub screen_position: Option<[i32; 2]>,
}
/// 返回默认主窗口身份,供省略窗口字段的兼容输入使用。
fn default_window() -> u64 {
crate::window::MAIN_WINDOW
}
impl Input {
/// 检查当前输入或发布值的不变量,调用方据校验结果决定是否继续。
pub fn validate(&self) -> Result<(), &'static str> {
if self.window == 0 {
return Err("window identity must be nonzero");
@@ -154,8 +162,10 @@ impl Input {
}
#[derive(Clone)]
/// 原生输入的有界发送端,后台线程串行发往本地 API;所有窗口共享权威发布状态。
pub struct Client(SyncSender<Input>);
impl Client {
/// 构造本类型并初始化其状态;资源建立和默认字段以函数体为准。
pub fn new(
proxy: EventLoopProxy<crate::app::AppEvent>,
namespace: Arc<RwLock<crate::document::NamespaceState>>,
@@ -181,6 +191,7 @@ impl Client {
})?;
Ok(Self(sender))
}
/// 通过有界原生输入客户端或本地 HTTP 发送纯数据事件;过期版本按既定协议处理。
pub fn send(&self, input: Input) -> bool {
self.0.try_send(input).is_ok()
}
@@ -189,6 +200,7 @@ impl Client {
// Native gestures retain the captured Mount across publications. Refresh only
// the version at dequeue, never session, window or target. HTTP callers and
// discrete actions still receive strict stale-frame rejection.
/// 连续手势出队时只更新有效目标的帧版本;保留原 session、窗口和 Mount,离散事件不自动重试。
fn refresh_continuous_input(input: &mut Input, current: &crate::document::NamespaceState) -> bool {
if !matches!(
input.kind.as_str(),
@@ -228,6 +240,7 @@ fn refresh_continuous_input(input: &mut Input, current: &crate::document::Namesp
true
}
/// 通过有界原生输入客户端或本地 HTTP 发送纯数据事件;过期版本按既定协议处理。
fn send(input: &Input) -> anyhow::Result<()> {
let body = serde_json::to_string(input)?;
let mut stream =
+21
View File
@@ -1,3 +1,7 @@
//! 后台文件/网络 provider。线程池只携带纯数据、取消控制与弱 Task 身份,不持有 VM。
//! Lease 由托管任务拥有,释放会撤销交付资格并取消可取消的后台工作;已经完成的结果还要由 VM worker 检查归属后才能交付。
//! 并发、队列、读取字节数、超时和 URL 协议均有边界,避免 provider 绕过运行时资源限制。
//!
//! Background providers carry plain data and weak task identities, never a VM.
use endor_core::{VmError, WeakObjectRef};
use std::{
@@ -21,20 +25,24 @@ const QUEUE: usize = 64;
type Completion = Result<String, String>;
#[derive(Debug, Default)]
/// provider 共享的就绪结果与并发计数;内容只有纯数据和弱任务身份。
struct Shared {
ready: Mutex<HashMap<WeakObjectRef, Completion>>,
active: AtomicUsize,
}
#[derive(Clone, Debug, Default)]
/// 某个运行时拥有的后台 provider 能力,负责提交和取走完成结果。
pub struct Provider {
shared: Arc<Shared>,
}
#[derive(Debug, Default)]
/// 后台工作取消标记与可取消子进程,不拥有托管 VM。
struct Control {
cancelled: AtomicBool,
process: Mutex<Option<Child>>,
}
impl Control {
/// 撤销当前操作的后续交付资格,并执行对应取消清理。
fn cancel(&self) {
self.cancelled.store(true, Ordering::Release);
if let Ok(mut process) = self.process.lock()
@@ -46,19 +54,23 @@ impl Control {
}
}
#[derive(Debug)]
/// 后台工作种类及其纯文本参数,源调用的类型检查在提交前完成。
pub enum Request {
ReadText(String),
FetchText(String),
}
/// Only Task payloads own this lease. Provider threads hold plain control data.
#[derive(Debug)]
/// 仅托管 Task 拥有的交付租约;释放它会取消后台资格并清理晚到结果。
pub struct Lease {
shared: Arc<Shared>,
task: WeakObjectRef,
control: Arc<Control>,
}
impl Drop for Lease {
/// 在 Rust 所有者离开作用域时执行本类型的配套清理;不能用它代替需要报告错误的显式关闭。
fn drop(&mut self) {
// 租约释放立即取消交付资格;后台 OS 读取即使稍后结束,也不能再次唤醒旧 Task。
self.control.cancel();
if let Ok(mut ready) = self.shared.ready.lock() {
ready.remove(&self.task);
@@ -66,6 +78,7 @@ impl Drop for Lease {
self.shared.active.fetch_sub(1, Ordering::AcqRel);
}
}
/// 线程池队列中的纯数据工作,关联弱任务身份、控制和共享完成表。
struct Job {
request: Request,
task: WeakObjectRef,
@@ -73,6 +86,7 @@ struct Job {
shared: Arc<Shared>,
}
impl Provider {
/// 检查并发额度并取得任务 Lease,再把纯数据工作放入有界线程池队列。
pub fn submit(
&self,
task: WeakObjectRef,
@@ -101,6 +115,7 @@ impl Provider {
.map_err(|_| VmError::ExecutionLimit("I/O provider queue full or unavailable"))?;
Ok(lease)
}
/// 取走指定弱任务身份的 provider 完成结果;实际托管写入仍由 VM worker 执行。
pub fn take_ready(&self) -> Result<Vec<(WeakObjectRef, Completion)>, VmError> {
Ok(self
.shared
@@ -111,6 +126,7 @@ impl Provider {
.collect())
}
}
/// 懒初始化共享工作线程和有界任务队列;初始化失败反馈给提交方。
fn pool() -> Result<&'static SyncSender<Job>, &'static str> {
static POOL: OnceLock<Result<SyncSender<Job>, String>> = OnceLock::new();
POOL.get_or_init(|| {
@@ -150,6 +166,7 @@ fn pool() -> Result<&'static SyncSender<Job>, &'static str> {
.as_ref()
.map_err(|_| "I/O provider unavailable")
}
/// 读取受字节上限限制的 UTF-8 文件内容,错误作为任务结果交付。
fn read_text(path: &str) -> Completion {
let metadata = std::fs::metadata(path).map_err(|error| error.to_string())?;
if !metadata.is_file() {
@@ -178,6 +195,7 @@ fn read_text(path: &str) -> Completion {
/// No shell interpretation, curlrc, URL globbing, or non-HTTP redirects.
/// Authentication/custom headers are deliberately outside this text GET API.
/// 检查网络请求的 URL 协议、长度与凭据等约束,拒绝不受支持的输入。
pub fn valid_url(url: &str) -> bool {
if url.len() > 8192 || url.chars().any(char::is_control) {
return false;
@@ -193,8 +211,10 @@ pub fn valid_url(url: &str) -> bool {
}
/// Created only on an I/O worker. All early exits kill and reap the child.
/// 子进程作用域收尾保护,避免取消/失败路径遗留未回收进程。
struct Reaper<'a>(&'a Control);
impl Drop for Reaper<'_> {
/// 在 Rust 所有者离开作用域时执行本类型的配套清理;不能用它代替需要报告错误的显式关闭。
fn drop(&mut self) {
let child = self.0.process.lock().ok().and_then(|mut slot| slot.take());
if let Some(mut child) = child {
@@ -203,6 +223,7 @@ impl Drop for Reaper<'_> {
}
}
}
/// 在取消、超时、协议和大小限制下取得 HTTP 文本;不把网络执行放在 VM 线程。
fn fetch_text(url: &str, control: &Control) -> Completion {
if !valid_url(url) {
return Err("invalid HTTP(S) URL".into());
+2
View File
@@ -1,3 +1,5 @@
//! 运行时模块入口。VM 生命周期、API、源程序组织、事件与异步任务在这里组合。
//!
// Module ownership follows the directory; crate-root aliases keep internal call sites stable.
pub(crate) mod api;
pub(crate) mod endor_host;
+40
View File
@@ -1,3 +1,7 @@
//! owner/key 管理的异步任务与协作式源函数调度。弱索引用于查找,回调、等待对象和协程帧是托管图中的真实边。
//! 任务可能处于等待 I/O、定时器、可执行协程或等待另一任务的状态;取消 owner/key 不能让晚到结果再次执行。
//! 调度使用有界量子和公平推进;await 挂起后保留操作数与帧,不重放此前副作用。失败和依赖环有明确处理路径。
//!
//! Owner-managed asynchronous work. Only weak identities enter the scheduler;
//! callbacks and scopes are ordinary traced edges in the Aster object graph.
use std::{
@@ -24,6 +28,7 @@ const MAX_READY: usize = 64;
type Index = Arc<Mutex<Vec<WeakObjectRef>>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// 任务对源代码可见的生命周期状态,完成/取消/失败各有不同 await 语义。
enum State {
Pending,
Running,
@@ -32,6 +37,7 @@ enum State {
Failed,
}
impl State {
/// 返回内部枚举或对象对应的稳定源名称。
fn name(self) -> &'static str {
match self {
Self::Pending => "pending",
@@ -44,6 +50,7 @@ impl State {
}
#[derive(Debug, Clone)]
/// 托管异步对象,保存 owner/key、结果与真正拥有的回调/协程/等待引用。
struct Task {
owner: WeakObjectRef,
key: String,
@@ -60,15 +67,19 @@ struct Task {
result: Option<Value>,
}
impl ObjectPayload for Task {
/// 提供受检查的 Any 下转型入口,让宿主按具体 payload 类型读取数据。
fn as_any(&self) -> &dyn std::any::Any {
self
}
/// 返回运行时诊断/类型检查使用的稳定 payload 名称。
fn kind_name(&self) -> &'static str {
"tessera-task"
}
/// 声明此 payload 的赋值遵循引用身份语义,避免把内部宿主状态当作可复制值。
fn reference_assignment_only(&self) -> bool {
true
}
/// 把 Task 状态投影成 await 的就绪、等待、取消或失败结果,不在等待期间主动轮询执行。
fn poll_await(&self) -> Result<endor_core::AwaitPoll, VmError> {
match self.state {
State::Pending | State::Running => Ok(endor_core::AwaitPoll::Pending),
@@ -86,6 +97,7 @@ impl ObjectPayload for Task {
)),
}
}
/// 枚举本对象真正拥有的托管强引用,供 Aster 引用追踪和循环回收使用。
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
if let Some(callback) = self.callback {
visit(callback);
@@ -101,15 +113,18 @@ impl ObjectPayload for Task {
}
}
}
/// 统一构造本模块的参数错误,让调用方停止处理无效输入。
fn invalid(message: &'static str) -> VmError {
VmError::InvalidArgument(message)
}
/// 从源值中提取并检查托管对象身份,错误类型沿 VM 错误返回。
fn object(value: &Value) -> Result<ObjectRef, VmError> {
match value {
Value::Object(object) => Ok(*object),
_ => Err(invalid("task owner and callback must be managed objects")),
}
}
/// 取得 owner 关联的托管任务作用域,供 key 替换和取消使用。
fn scope(store: &ObjectStore, owner: ObjectRef) -> Result<Option<ObjectRef>, VmError> {
let payload = store.object(owner)?.payload();
if let Some(value) = payload.as_any().downcast_ref::<Block>() {
@@ -132,6 +147,7 @@ fn scope(store: &ObjectStore, owner: ObjectRef) -> Result<Option<ObjectRef>, VmE
Err(error) => Err(error),
}
}
/// 为可拥有任务的对象安装作用域,真实引用仍由托管图追踪。
fn install_scope(
cx: &mut NativeContext<'_>,
owner: ObjectRef,
@@ -155,6 +171,7 @@ fn install_scope(
}
cx.set_attr(owner, SCOPE, Value::Object(scope))
}
/// 检查任务的 owner 仍有效且任务具备交付条件,避免晚到结果复活已关闭页面。
fn eligible(store: &ObjectStore, owner: ObjectRef, desktop: ObjectRef) -> Result<bool, VmError> {
let payload = store.object(owner)?.payload();
if let Some(mount) = payload.as_any().downcast_ref::<Mount>() {
@@ -194,6 +211,7 @@ fn eligible(store: &ObjectStore, owner: ObjectRef, desktop: ObjectRef) -> Result
}
Ok(true)
}
/// 检查弱索引对应的任务是否仍是 owner/key 当前登记的那一项。
fn current(
store: &ObjectStore,
owner: ObjectRef,
@@ -210,6 +228,7 @@ fn current(
/// Called at the actual close/detach boundary, including close-then-reopen in
/// one language input. A later appearance never revives these identities.
/// 取消某个 owner 的全部任务,防止页面/窗口生命周期结束后继续执行回调。
pub fn cancel_owner(cx: &mut NativeContext<'_>, owner: ObjectRef) -> Result<(), VmError> {
let Some(scope) = scope(cx.store(), owner)? else {
return Ok(());
@@ -233,6 +252,7 @@ pub fn cancel_owner(cx: &mut NativeContext<'_>, owner: ObjectRef) -> Result<(),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// 当前宿主 API 的操作分类,分派代码负责参数校验与状态更新。
enum Operation {
Timer,
Sleep,
@@ -241,6 +261,7 @@ enum Operation {
Spawn,
}
#[derive(Debug)]
/// 创建定时/I/O/协作任务时的原生调用适配器。
struct After {
operation: Operation,
provider: crate::io_tasks::Provider,
@@ -251,13 +272,16 @@ struct After {
desktop: ObjectRef,
}
impl NativeCallable for After {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<endor_core::CallSignature> {
Some(self.signature.clone())
}
/// 声明宿主可调用对象接受的参数个数,供 VM 在执行前检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(self.signature.arity)
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
if self.quiescing.load(Ordering::Acquire) {
return Err(invalid("runtime is exiting; new tasks are forbidden"));
@@ -459,6 +483,7 @@ impl NativeCallable for After {
}
#[derive(Debug, Clone, Copy)]
/// 该原生能力的查询/操作分类,方法包装用它选择具体实现。
enum Query {
Cancel,
Status,
@@ -467,19 +492,23 @@ enum Query {
Error,
}
#[derive(Debug)]
/// 保存 owner、签名或操作枚举的原生方法适配器,统一由 VM 调用。
struct Method {
signature: endor_core::CallSignature,
query: Query,
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(1))
}
/// VM 调入本宿主能力的入口;按所属操作解码参数并返回托管值或后续调用。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
let [value] = args else {
return Err(invalid("Task methods accept no arguments"));
@@ -561,6 +590,7 @@ impl NativeCallable for Method {
}
}
/// 弱任务索引、公平调度位置和 provider,索引不会单独把任务保活。
pub struct Scheduler {
index: Index,
runnable: Arc<AtomicBool>,
@@ -569,15 +599,18 @@ pub struct Scheduler {
provider: crate::io_tasks::Provider,
}
impl Scheduler {
/// 停止接收该域的新任务工作,为关闭/替换运行时做准备。
pub fn quiesce(&self, vm: &mut Vm) -> Result<usize> {
self.quiescing.store(true, Ordering::Release);
self.cancel_all(vm)
}
/// 检查是否有可推进的协程,而不是仅仅存在正在等待外部结果的任务。
pub fn has_cooperative_work(&self) -> bool {
self.runnable.load(Ordering::Acquire)
}
/// Mutation boundaries collect unreachable owner/callback cycles before
/// this weak lookup. Idle timer polling does not rescan the entire heap.
/// 在当前 VM 中有界交付就绪任务、执行回调或推进协程;开始布局后由外层刷新流程继续。
pub fn poll(&self, vm: &mut Vm, limit: usize) -> Result<usize> {
anyhow::ensure!((1..=MAX_READY).contains(&limit), "invalid task poll limit");
// Provider threads only fill the mailbox. Only this VM worker updates
@@ -731,6 +764,7 @@ impl Scheduler {
self.prune(vm)?;
Ok(work)
}
/// 取消当前域全部任务,切断回调、协程和等待交付资格。
pub fn cancel_all(&self, vm: &mut Vm) -> Result<usize> {
let snapshot = self
.index
@@ -753,6 +787,7 @@ impl Scheduler {
self.runnable.store(false, Ordering::Release);
Ok(count)
}
/// 移除已释放的弱任务索引,索引本身不让任务或其 owner 存活。
fn prune(&self, vm: &mut Vm) -> Result<()> {
let mut index = self
.index
@@ -789,6 +824,7 @@ impl Scheduler {
Ok(())
}
}
/// 沿等待链检查任务依赖环,防止 await 永远无法被满足。
fn cyclic_wait(vm: &Vm, source: ObjectRef, mut target: ObjectRef) -> Result<bool> {
let mut seen = std::collections::HashSet::new();
loop {
@@ -805,6 +841,7 @@ fn cyclic_wait(vm: &Vm, source: ObjectRef, mut target: ObjectRef) -> Result<bool
}
}
/// 恢复已保存的源函数帧,处理完成、软量子挂起或 await 等待,保留已发生的副作用。
fn advance_coroutine(vm: &mut Vm, reference: ObjectRef) -> Result<()> {
let mut task = vm.payload::<Task>(reference, "Task")?.clone();
let coroutine = if let Some(coroutine) = task.coroutine {
@@ -897,6 +934,7 @@ fn advance_coroutine(vm: &mut Vm, reference: ObjectRef) -> Result<()> {
}
}
/// 记录回调执行失败并沿运行时故障规则停止后续不安全的交付。
fn fail_callback(vm: &mut Vm, reference: ObjectRef, message: &str) -> Result<()> {
let mut task = vm.payload::<Task>(reference, "Task")?.clone();
task.error = Some(message.chars().take(1024).collect::<String>().into());
@@ -904,6 +942,7 @@ fn fail_callback(vm: &mut Vm, reference: ObjectRef, message: &str) -> Result<()>
finish(vm, reference, State::Failed)
}
/// 将任务转入终态并释放回调、provider 租约、协程和等待引用;取消还会清除保存的结果。
fn finish(vm: &mut Vm, reference: ObjectRef, state: State) -> Result<()> {
let mut task = vm.payload::<Task>(reference, "Task")?.clone();
if !matches!(task.state, State::Pending | State::Running) {
@@ -934,6 +973,7 @@ fn finish(vm: &mut Vm, reference: ObjectRef, state: State) -> Result<()> {
Ok(())
}
/// 把本模块的类型、方法或能力注册到所属运行时上下文。
pub fn install(
vm: &mut Vm,
roots: &mut Vec<ObjectRef>,
+8
View File
@@ -1,9 +1,13 @@
//! 构造共享 VM 调用序列与同帧循环驱动的字节码。
//! 用托管函数串联步骤,避免为每个布局节点反复创建独立解释器调用;重复驱动会保留同一个执行帧和预算域。
//!
//! Validated call sequences share the current VM budget and explicit frames.
use endor_core::{
CodeLayout, EndorCode, EndorCodeObject, Instruction, NativeContext, ObjectRef, Opcode, Value,
VmError,
};
/// 根据当前输入构造本模块的派生结果,具体的验证与资源边界见函数内步骤。
pub fn build(
cx: &mut NativeContext<'_>,
calls: Vec<ObjectRef>,
@@ -36,6 +40,7 @@ pub fn build(
/// A fixed-size driver. Each callback returns whether another occurrence is
/// needed; delegated source calls and all iterations share this VM budget.
/// 构造重复调用同一托管函数的驱动,保持共享参数与引用生命周期。
pub fn repeat_call(
cx: &mut NativeContext<'_>,
callback: ObjectRef,
@@ -63,6 +68,7 @@ pub fn repeat_call(
/// Prepare only the current occurrence, collect its result, then continue the
/// outer driver. The helper frame and its managed captures die before the next.
/// 将一步调用结果收集到驱动状态,再安排下一步调用。
pub fn collect_and_continue(
cx: &mut NativeContext<'_>,
collector: ObjectRef,
@@ -94,6 +100,7 @@ pub fn collect_and_continue(
driver(cx, instructions, constants, name, desktop)
}
/// 建立承载整个步骤序列的托管函数,使续体可以在同一个 VM 帧中恢复。
fn driver(
cx: &mut NativeContext<'_>,
instructions: Vec<Instruction>,
@@ -112,6 +119,7 @@ fn driver(
/// Consume the condition on the stack: true repeats this same frame from zero,
/// false falls through. Operand widths include EXTENDED_ARG code units.
/// 构造由继续条件决定是否再执行一轮的同帧循环。
pub fn repeat_while(instructions: &mut Vec<Instruction>) -> Result<(), VmError> {
let body = Instruction::encode_all(instructions)?.len() / 2;
let mut back = Instruction::new(Opcode::JumpBackward, 0);