Files
Endoleap/src/components/workspace/canvas-runtime-host.tsx
T

67 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 应用级Canvas运行时装配点。一次安装模型registry与React renderer目录,按账户/工作区创建runtime,
* 按项目root安装Foundry服务适配。组件只装配依赖,业务根、history、ledger和持久化仍由下层实例管理。
* 真实宿主卸载才关闭项目;单个画布页面切换只借还视图,不在这里每次重建engine。
*/
"use client";
import { createContext, useContext, useEffect, useRef, useState, type ReactNode } from "react";
import { defaultCanvasRenderers } from "@/components/canvas/canvas-renderers";
import { createReactRenderers, type CanvasRuntimeExtension } from "@/lib/canvas-bindings/react-flow";
import { notify } from "@/lib/notifications";
import { errorText } from "@/lib/api";
import { createRegistry, builtinComponents } from "@/lib/canvas-core/index";
import { createRuntimeHost, createWorkspaceRuntime, type RuntimeHost } from "@/lib/canvas-runtime/index";
import { createBrowserDigests, createBrowserImageDecoder, createBrowserStorage } from "@/lib/canvas-runtime/browser";
import { createFoundryAssets, createFoundryPersistence, createFoundryTransport, createFoundryRuns,createFoundryModelLoader } from "@/lib/canvas-service/index";
/** 应用共享RuntimeHost引用,未装配时为null;renderer Context另有内置显示目录默认值。 */
const Context = createContext<RuntimeHost | null>(null);
/** 模型定义与显示实现一起在宿主初始化捕获,子页面用此目录保持一致解释。 */
const Renderers=createContext(defaultCanvasRenderers);
/** 读取宿主安装的React renderer集合,不为每个节点重新构造类型表。 */
export function useCanvasRenderers(){return useContext(Renderers);}
/** extensions仅在useState惰性初始化时捕获;后续prop换引用不会热替换已打开项目的schema/renderer。 */
export function CanvasRuntimeHost({ children,extensions=[] }: { children: ReactNode;extensions?:readonly CanvasRuntimeExtension[] }) {
// 惰性初始化只装配对象/工厂,不在 render 直接打开项目;实际账户绑定和持久化租约由后续调用取得。
const [{host,renderers}] = useState(() => {
// 先组合内置与扩展模型,再按精确版本/结构验证renderer,冲突在打开项目之前失败。
const registry = createRegistry([...builtinComponents(),...extensions.flatMap(extension=>extension.components??[])]),renderers=createReactRenderers([...defaultCanvasRenderers.definitions,...extensions.flatMap(extension=>extension.renderers??[])],registry);
const host=createRuntimeHost({ registry,
/** 每身份作用域绑定transport,current护栏由host提供,流读取也要防换账户后的迟到结果。 */
modelLoader(accountId,workspaceId,_authEpoch,current){return createFoundryModelLoader({workspaceId,registry,transport:createFoundryTransport({accountId,isCurrentIdentity:current})});},
/** 工作区持有浏览器存储、摘要/解码池;root项目共享其身份范围,各自再装配业务端口。 */
workspace(accountId, workspaceId, authEpoch, isCurrentIdentity) {
return createWorkspaceRuntime({ accountId, workspaceId, authEpoch, isCurrentIdentity, registry, storage: createBrowserStorage(),digests:createBrowserDigests(),images:createBrowserImageDecoder(),
/** 一项目一组协议适配器;summary策略只压缩已协调回执,不改变网络请求或原始操作身份。 */
adapters(rootId, current) {
const transport = createFoundryTransport({ accountId, isCurrentIdentity: current });
return { runs: createFoundryRuns({ workspaceId, rootId, registry, transport }), persistence: createFoundryPersistence({ workspaceId, rootId, registry, transport, resolvedReceiptPolicy:'summary' }), assets: createFoundryAssets({ workspaceId, transport, project: process.env.NEXT_PUBLIC_FOUNDRY_PROJECT || "endoleap", origin: process.env.NEXT_PUBLIC_FOUNDRY_API_ORIGIN }) };
},
// 同工作区错误用稳定通知id更新,不创建另一份任务状态;错误恢复仍须runtime明确操作。
onError: error => notify({ id: `canvas-runtime:${accountId}:${workspaceId}`, title: "画布同步需要处理", description: errorText(error), level: "error" }),
});
},
onError: error => notify({ id: "canvas-host-close", title: "本地修改尚未保存成功", description: errorText(error), level: "error" }),
});
return {host,renderers};
});
// effect重放的代次令牌只保护宿主清理,不是账户authEpoch,也不进入持久模型。
const lifetime = useRef(0);
/** cleanup延到微任务检查代次:Strict Mode同步重设effect会递增代次,旧cleanup不关闭新挂载宿主。
* 真正卸载才发起host.close,保存失败由宿主错误回调/日志报告,不在此丢弃根或强行释放leader。 */
useEffect(() => {
const generation = ++lifetime.current;
// cleanup 不等待 close 完成,也不主动重试;异步失败经 host 的错误通道和此处 catch 报告。
// Strict Mode replays effects synchronously; only a real detach retires the host.
return () => { queueMicrotask(() => { if (lifetime.current === generation) void host.close().catch(error => console.error("[canvas-host]", error.message)); }); };
}, [host]);
return <Context.Provider value={host}><Renderers.Provider value={renderers}>{children}</Renderers.Provider></Context.Provider>;
}
/** 需要runtime的页面必须在根Provider内,缺失装配明确抛错而不偷建一个临时host。 */
export function useCanvasRuntimeHost() {
const host = useContext(Context);
if (!host) throw new Error("CanvasRuntimeHost is missing");
return host;
}