docs: explain Tessera modules and execution flows in Chinese
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
//! API 集成回归。通过实际本地 HTTP 请求检查队列、候选替换、状态码、版本和完整文档发布。
|
||||
//! 测试服务使用独立监听地址和串行 worker;应结合断言理解成功输入、失败候选、过期事件和关闭的不同保留语义。
|
||||
//!
|
||||
use super::*;
|
||||
use std::{
|
||||
io::Write,
|
||||
|
||||
@@ -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(¤t);
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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![]),
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! 运行时集成回归与示例验收。测试经真实 Aster 前端、编译器、VM 和展示协议执行,不是手工伪造布局结果。
|
||||
//! 覆盖共享对象/独立 Mount、源覆盖、绑定、窗口、任务、取消、二维/三维投影及像素。局部辅助函数负责取统计值、收敛刷新和构造可重复输入。
|
||||
//! 阅读时先看每个测试提交的 Endor 程序,再看后续变更与最终断言;编译成功、回调成功、文档发布成功是不同检查点。
|
||||
//!
|
||||
use super::*;
|
||||
use crate::document::{NamespaceState, RenderCommand};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user