docs: clarify API request phases and native input ordering

This commit is contained in:
2026-11-12 20:05:19 +08:00
parent a1c1781b00
commit f1e413c8c5
3 changed files with 26 additions and 0 deletions
+7
View File
@@ -5,6 +5,9 @@
//! main::main 调用 start,shell 持有 RuntimeControl;HTTP 请求经 handle/submit 入队,原生输入经 dispatch_native 入同一队列。
//! worker 子模块拥有实际 ReplSession;本文件的线程循环只决定继续哪个请求、何时等待、如何响应和退出。
//! 本地端点、观察器和 EventLoopProxy 都是 Tessera 宿主适配,不能把整个文件直接作为无 GUI 的 Nexus 核心。
//!
//! 成功入队仅表示接受执行请求,最终回执由 worker 完成或明确拒绝后发出;它与发布 revision 并非同一事件。
//! 普通请求队列和退出通道相互独立,pending 保存原请求与继续状态;退出优先于下一量子,但不由 HTTP 线程抢先操作 VM。
mod guide;
mod worker;
@@ -298,6 +301,7 @@ fn start_server_observed(
current.document.frame.session = 0;
current.document.frame.focus = None;
for view in current.document.windows.values_mut() {
// 共享 VM 已清理,每个副窗保留画面都必须撤销交互会话和焦点,避免退出期间仍向旧目标投递输入。
view.frame.session = 0;
view.frame.focus = None;
}
@@ -376,6 +380,7 @@ fn start_server_observed(
worker.repl = None;
}
if wait.is_zero() {
// 活跃续体/协程采用零接收超时;此处仅让出线程调度机会,下一循环仍先检查退出并推进原 VM。
thread::yield_now();
}
}
@@ -748,6 +753,7 @@ fn submit(
}
// 仅完整 Code 请求分配新 submission,事件/REPL/表达式继承当前身份,保留共享域输入顺序。
let submission = match if kind == RequestKind::Code {
// 先计算候选接受号而不改 current.submission;只有下面 try_send 成功才让旧提交失去发布资格。
current.next_submission()
} else {
Ok(current.submission)
@@ -831,6 +837,7 @@ fn read_code(request: &mut Request) -> Result<String> {
.read_to_string(&mut code)
.context("read code")?;
if code.len() as u64 > MAX_CODE_SIZE {
// code.len() 是 UTF-8 字节数,流读取上限多留一个字节用于探测,不能以字符数替代 1 MiB 源码预算。
anyhow::bail!("code exceeds the 1 MiB limit");
}
Ok(code)
+12
View File
@@ -5,6 +5,11 @@
//! api::start_server_observed 的唯一执行线程创建 Worker,每次循环调用 begin_request/resume_request 或 refresh_viewport。
//! HTTP/原生请求仍由外层持有,PendingRequest 只记录继续哪一步;本文件不重新读取请求流或回放已执行输入。
//! NamespaceState 是对外权威值,repl 是实际 VM 所有者;submission 是接受顺序,runtime_session 才表示画面仍属哪个 VM。
//!
//! 请求阶段不能互换:BeforeInput 尚未执行业务,可重新进入 begin_request;Input 已执行,只能恢复视觉工作;
//! Candidate 持有独立候选,成功前不替换 self.repl。外层 api 循环保留原 job,因此这里不会创建第二份回应端。
//! 接受 PUT 先改变 submission,候选成功提交后才切换 runtime_session;preserve_live_owner 在两者之间保留仍权威的旧域。
//! 源业务与视觉方法在 Namespace 锁外执行,文档/命令发布回到写锁内复核身份;错误响应和故障画面发布是两项不同结果。
use super::*;
// 响应已编码成内存字节,外层 RequestReply 可发 HTTP 或转换成原生回执,不需要读取 VM 返回值。
@@ -376,6 +381,7 @@ impl Worker {
current.document.frame.session = 0;
current.document.frame.focus = None;
for document in current.document.windows.values_mut() {
// 重置的是共享堆,所有副窗旧身份都失效;保留的是静态绘制文档,不是仍可接收事件的窗口业务对象。
document.frame.session = 0;
document.frame.focus = None;
}
@@ -388,6 +394,7 @@ impl Worker {
serde_json::json!({"ok":true,"execution":"repl-reset","persistent":true,"session_reset":true,"message":"Console memory cleared; displayed page preserved"}),
));
}
// 旧布局/绘制仍挂起时只保存等待阶段,不能在它的只读 VM 调用中执行新的 REPL 或事件副作用。
if self
.repl
.as_ref()
@@ -414,6 +421,7 @@ impl Worker {
}
}
};
// 先按输入种类筛选再查资源能力:合法 JSON 不代表有权触发任意名字的宿主操作。
if !input.kind.starts_with("developer-open:")
&& !matches!(
input.kind.as_str(),
@@ -638,6 +646,7 @@ impl Worker {
// Console 不是事件,不制造一个空 Input;下面按 source 执行同一持久会话的交互语句。
None
};
// 只有当前 epoch 没有实际会话才惰性创建空 Console;正常输入沿用已有 VM,不为每次 POST 复制 globals。
if self
.repl
.as_ref()
@@ -935,6 +944,7 @@ impl Worker {
/// 新提交/候选过期或失败时调用,若旧 VM 仍拥有发布画面则只跟进 epoch;DELETE 已撤权威则丢弃它。
pub(super) fn preserve_live_owner(&mut self, current: &NamespaceState) {
// 调用方已持发布状态锁,本函数只对照身份修改 worker 本地拥有关系,不执行回调或生成新文档。
if let Some((epoch, runtime)) = &mut self.repl {
if current.runtime_session == runtime.id {
// 旧程序还是真正交互域,接受次序更新但其实际对象和 session 身份都不改变。
@@ -1071,10 +1081,12 @@ impl Worker {
let Ok(mut current) = namespace.write() else {
return error_response(StatusCode(500), "namespace lock poisoned");
};
// 候选计算结束不代表仍被接受;先处理过期并保留权威旧域,再考虑把候选结果交给 commit。
if current.submission != job.submission {
self.preserve_live_owner(&current);
return superseded(job.submission);
}
// 失败分支遵循同一原则:候选未被完整接受就不把其 VM 装进 self.repl,不用半成品替换旧程序。
match result {
Ok(Ok((runtime, document, commands))) => match current.commit(job.submission, document)
{
+7
View File
@@ -5,6 +5,10 @@
//! shell::app 建立 Client 并发送捕获到的 Input;Client 后台 relay 经 api::RuntimeControl::dispatch_native 交给 worker。
//! HTTP 路由也反序列化同一 Input 并调用 validate,但不会走原生 relay 的连续输入版本刷新。
//! 这里只合并同目标相邻 pointermove 的绝对采样;滚动/相机的增量逐条保留,不能用最后一个覆盖累计移动。
//!
//! 队列合并和版本追随是两个独立规则:仅 pointermove 能覆盖队尾采样;原始 pointerdown/up/leave/cancel 也可追随版本,
//! 但仍逐条保持顺序。高层 click 不追随,必须用捕获时的按下/释放与几何版本重新确认目标。
//! 版本追随只确认原 window/session/Mount 及其能力仍有效,不做重新命中,也不把旧目标换成当前光标下的新对象。
use serde::{Deserialize, Serialize};
use std::{
collections::VecDeque,
@@ -277,6 +281,7 @@ impl Inbox {
// wait 原子放锁并等待;GUI 生产者可入队,通知后重新取得同一状态锁。
q = self.ready.wait(q).ok()?;
}
// 关闭优先于残留项;正常唤醒后只取队首一个事件,relay 完成这条 worker 回执后才会再次 receive。
if q.closed { None } else { q.items.pop_front() }
}
}
@@ -304,6 +309,7 @@ impl Client {
while let Some(mut input) = receiver.receive() {
// 每条输入出队时再看最新权威发布帧,连续采样不沿用排队前已经过时的布局版本。
if let Ok(current) = namespace.read() {
// 这里只更新允许类别的 layout_version;下面 worker 接纳输入时还会再次读取权威帧,覆盖 relay 排队期间的新变化。
if !refresh_continuous_input(&mut input, &current) {
// 目标消失/换会话时丢弃,不能把旧手势重定向到同位置的新出现。
continue;
@@ -329,6 +335,7 @@ impl Client {
return false;
};
if q.closed {
// 最后一个生产者已关闭队列,不能因新的 send 再唤醒 relay 并恢复过期输入流。
return false;
}
// enqueue 的覆盖合并也算接收成功,relay 会读取最终最新样本。