-
Notifications
You must be signed in to change notification settings - Fork 105
修改rpc, 使其兼容fetch和process模式 #484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| /** | ||
| * 基于 fetch 的 RPC 客户端 | ||
| * 替代 ProcessRPC,将 module.method(args) 调用转为 HTTP POST 请求 | ||
| */ | ||
| export class FetchRPC<TModules extends Record<string, any>> { | ||
| private baseURL: string; | ||
| private defaultTimeout: number; | ||
| private disposed = false; | ||
|
|
||
| constructor(baseURL: string, options?: { timeout?: number }) { | ||
| // 确保 baseURL 不以 / 结尾 | ||
| this.baseURL = baseURL.replace(/\/+$/, ''); | ||
| this.defaultTimeout = options?.timeout ?? 30000; | ||
| } | ||
|
|
||
| /** | ||
| * 远程方法调用(请求-响应) | ||
| * 签名与 ProcessRPC.request 保持一致 | ||
| */ | ||
| async request<K extends keyof TModules, M extends keyof TModules[K]>( | ||
| module: K, | ||
| method: M, | ||
| args?: any[], | ||
| options?: { timeout?: number } | ||
| ): Promise<any> { | ||
| if (this.disposed) { | ||
| throw new Error('[FetchRPC] Instance has been disposed'); | ||
| } | ||
|
|
||
| const timeout = options?.timeout ?? this.defaultTimeout; | ||
| const controller = new AbortController(); | ||
| const timer = setTimeout(() => controller.abort(), timeout); | ||
|
|
||
| try { | ||
| const response = await fetch(`${this.baseURL}/scene/rpc`, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ | ||
| module: module as string, | ||
| method: method as string, | ||
| args: args || [], | ||
| }), | ||
| signal: controller.signal, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error( | ||
| `[FetchRPC] HTTP ${response.status}: ${module as string}.${method as string}` | ||
| ); | ||
| } | ||
|
|
||
| const data = await response.json(); | ||
|
|
||
| if (data.error) { | ||
| throw new Error(data.error); | ||
| } | ||
|
|
||
| return data.result; | ||
| } catch (err: any) { | ||
| if (err.name === 'AbortError') { | ||
| throw new Error( | ||
| `[FetchRPC] Request timeout: ${module as string}.${method as string}` | ||
| ); | ||
| } | ||
| throw err; | ||
| } finally { | ||
| clearTimeout(timer); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 是否已连接(fetch 模式下始终返回 true,除非 disposed) | ||
| */ | ||
| isConnect(): boolean { | ||
| return !this.disposed; | ||
| } | ||
|
|
||
| /** | ||
| * 清理 | ||
| */ | ||
| dispose(): void { | ||
| this.disposed = true; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,71 @@ | ||
| import { EventEmitter } from 'events'; | ||
| import { SceneProcessEventTag } from '../../../common'; | ||
| /** | ||
| * 纯 JS 实现的轻量 EventEmitter,替代 Node.js 的 EventEmitter | ||
| * 在浏览器/Worker 环境中无需依赖 Node 'events' 模块 | ||
| */ | ||
| class SimpleEventEmitter { | ||
| private _listeners = new Map<string, Set<(...args: any[]) => void>>(); | ||
|
|
||
| on(event: string, listener: (...args: any[]) => void): void { | ||
| if (!this._listeners.has(event)) { | ||
| this._listeners.set(event, new Set()); | ||
| } | ||
| this._listeners.get(event)!.add(listener); | ||
| } | ||
|
|
||
| once(event: string, listener: (...args: any[]) => void): void { | ||
| const wrapper = (...args: any[]) => { | ||
| this.off(event, wrapper); | ||
| listener(...args); | ||
| }; | ||
| (wrapper as any)._original = listener; | ||
| this.on(event, wrapper); | ||
| } | ||
|
|
||
| off(event: string, listener: (...args: any[]) => void): void { | ||
| const set = this._listeners.get(event); | ||
| if (!set) return; | ||
| // 直接移除 | ||
| if (set.delete(listener)) return; | ||
| // 尝试移除 once 包装 | ||
| for (const fn of set) { | ||
| if ((fn as any)._original === listener) { | ||
| set.delete(fn); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| emit(event: string, ...args: any[]): void { | ||
| const set = this._listeners.get(event); | ||
| if (!set) return; | ||
| for (const listener of set) { | ||
| listener(...args); | ||
| } | ||
| } | ||
|
|
||
| removeAllListeners(event?: string): void { | ||
| if (event) { | ||
| this._listeners.delete(event); | ||
| } else { | ||
| this._listeners.clear(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 全局共享的 EventEmitter 实例(内部使用,不对外暴露) | ||
| const globalEventEmitter = new EventEmitter(); | ||
| const globalEventEmitter = new SimpleEventEmitter(); | ||
|
|
||
| /** | ||
| * 全局事件管理器 | ||
| * 统一管理所有服务的事件监听,支持类型安全的事件订阅 | ||
| * | ||
| * Fetch 方案改造: | ||
| * - EventEmitter 替换为纯 JS 实现的 SimpleEventEmitter | ||
| * - 移除 IMessageTransport 跨线程广播 | ||
| * - broadcast 退化为与 emit 相同的本地事件触发 | ||
| */ | ||
| class GlobalEventManager { | ||
|
|
||
| /** | ||
| * 监听指定类型的事件(类型安全版本) | ||
| * @param event 事件名称 | ||
|
|
@@ -92,25 +149,22 @@ class GlobalEventManager { | |
| } | ||
|
|
||
| /** | ||
| * 跨进程广播,传的参数需要能被序列化 | ||
| * @param event 事件名称 | ||
| * @param args 事件参数 | ||
| * 广播事件 | ||
| * | ||
| * Fetch 方案改造: | ||
| * - 移除 process.send()(原实现:发送到 Node 父进程) | ||
| * - 移除 IMessageTransport(Worker 方案的产物,Fetch 方案不需要) | ||
| * - broadcast 退化为与 emit 相同的本地事件触发 | ||
| * | ||
| * 如需向服务端推送事件,应由调用方主动 fetch 接口 | ||
| */ | ||
| broadcast<TEvents extends Record<string, any>>( | ||
| event: keyof TEvents, | ||
| ...args: TEvents[keyof TEvents] | ||
| ): void; | ||
| broadcast(event: string, ...args: any[]): void; | ||
| broadcast(event: any, ...args: any[]): void { | ||
| const message = { | ||
| type: SceneProcessEventTag, | ||
| event: event as string, | ||
| args: [...args] | ||
| }; | ||
| globalEventEmitter.emit(event, ...args); | ||
| if ('connected' in process) { | ||
| process.send?.(message); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这个修改破坏 mcp模式的兼容性
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 是的, 还需要做下兼容。昨天只是先把做的web先提交了。今天还需要处理。 |
||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -166,9 +166,14 @@ | |
| importExceptionHandler: (...args) => this._handleImportException(...args), | ||
| cceModuleMap, | ||
| }); | ||
| // eslint-disable-next-line no-undef | ||
| globalThis.self = window; | ||
| this._executor.addPolyfillFile(require.resolve('@cocos/build-polyfills/prebuilt/editor/bundle')); | ||
| // Web 改造: | ||
| // - 移除 globalThis.self = window(Web 环境中 self 已自动指向 window 或 WorkerGlobalScope) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 移除self = window. |
||
| // - require.resolve 替换为构建时注入的常量或动态路径 | ||
| // 若使用 bundler(如 vite/webpack),可通过 define 插件注入 __POLYFILL_BUNDLE_URL__ | ||
| const polyfillUrl = typeof __POLYFILL_BUNDLE_URL__ !== 'undefined' | ||
|
Check failure on line 173 in src/core/scene/scene-process/service/script.ts
|
||
| ? __POLYFILL_BUNDLE_URL__ | ||
|
Check failure on line 174 in src/core/scene/scene-process/service/script.ts
|
||
| : '@cocos/build-polyfills/prebuilt/editor/bundle'; | ||
| this._executor.addPolyfillFile(polyfillUrl); | ||
| // 同步插件脚本列表 | ||
| await this._syncPluginScripts.nextIteration(); | ||
| // 重载项目与插件脚本 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
所有rpc请求统一转换到 scene/rpc ? 路由不用修改?