65 lines
5.4 KiB
TypeScript
65 lines
5.4 KiB
TypeScript
/**
|
||
* 应用级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[] }) {
|
||
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;
|
||
// 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;
|
||
}
|