Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/core/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ export default class Launcher {
middlewareService.register('Scene', SceneMiddleware);
await sceneConfigInstance.init();

const { Rpc } = await import('./scene/main-process/rpc');
await Rpc.startup();

const browserPath = process.platform === 'win32'
? 'start'
: process.platform === 'darwin'
Expand Down
8 changes: 5 additions & 3 deletions src/core/scene/main-process/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,19 @@ export class RpcProxy {
return this.rpcInstance?.isConnect();
}

async startup(prc: ChildProcess | NodeJS.Process) {
async startup(prc?: ChildProcess | NodeJS.Process) {
// 在创建新实例前,先清理旧实例,防止内存泄漏
this.dispose();
this.rpcInstance = new ProcessRPC<IPublicServiceManager>();
this.rpcInstance.attach(prc);
if (prc) {
this.rpcInstance.attach(prc);
}
this.rpcInstance.register({
assetManager: assetManager,
programming: scriptManager,
sceneConfigInstance: sceneConfigInstance,
});
console.log('[Node] Scene Process RPC ready');
console.log(`[Node] Scene Process RPC ready ${prc ? '(Attached)' : '(Detached - Web Mode)'}`);
}

/**
Expand Down
53 changes: 52 additions & 1 deletion src/core/scene/process-rpc/process-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,29 @@ export class ProcessRPC<TModules extends Record<string, any>> {
private msgId = 0;
private process: NodeJS.Process | ChildProcess | undefined;
private onMessageBind = this.onMessage.bind(this);
private serverUrl: string | undefined;
private isWebMode = false;

/**
* @param proc - NodeJS.Process 或 ChildProcess 实例
*/
attach(proc: NodeJS.Process | ChildProcess) {
this.dispose();
this.process = proc;
this.isWebMode = false;
this.listen();
}

/**
* 设置 Web 传输模式(浏览器环境使用)
* @param baseUrl - 服务器基础连接
*/
setWebTransport(baseUrl: string) {
this.dispose();
this.serverUrl = baseUrl;
this.isWebMode = true;
}

/**
* 注册模块,只支持对象或者类实例
* @param handler - 注册模块列表
Expand All @@ -110,6 +123,8 @@ export class ProcessRPC<TModules extends Record<string, any>> {
this.callbacks.clear();
this.process?.off('message', this.onMessageBind);
this.process = undefined;
this.serverUrl = undefined;
this.isWebMode = false;
}

/**
Expand Down Expand Up @@ -194,6 +209,27 @@ export class ProcessRPC<TModules extends Record<string, any>> {
: [args: Parameters<TModules[K][M]>, options?: RequestOptions]
): Promise<Awaited<ReturnType<TModules[K][M]>>> {
const [args, options] = rest;

if (this.isWebMode && this.serverUrl) {
const url = `${this.serverUrl}/rpc/${String(module)}/${String(method)}`;
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(args || [])
}).then(async (res) => {
if (res.ok) {
const rpcRes = (await res.json()) as RpcResponse;
if (rpcRes.error) {
throw new Error(rpcRes.error);
}
return rpcRes.result;
}
throw new Error(`RPC request failed with status: ${res.status}`);
});
}

return new Promise((resolve, reject) => {
const id = ++this.msgId;

Expand All @@ -219,12 +255,27 @@ export class ProcessRPC<TModules extends Record<string, any>> {
});

if (!this.process) {
throw new Error('未挂载进程');
throw new Error('RPC 尚未挂载进程且未开启 Web 模式');
}
this.process.send?.(req);
});
}

/**
* 直接执行本地注册的模块方法(用于服务器接收到基于 HTTP 的 RPC 请求时直接处理)
*/
async executeLocal<K extends keyof TModules, M extends keyof TModules[K]>(
module: K,
method: M,
args: any[]
): Promise<Awaited<ReturnType<TModules[K][M]>>> {
const target = this.handlers[module as string];
if (!target || typeof target[method as string] !== 'function') {
throw new Error(`Method not found: ${String(module)}.${String(method)}`);
}
return await target[method as string](...(args || []));
}

/**
* 发送单向消息(无返回值)
*/
Expand Down
2 changes: 2 additions & 0 deletions src/core/scene/scene-process/engine-bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as EditorExtends from '../../engine/editor-extends';
import { Rpc } from './rpc';
import { serviceManager } from './service/service-manager';
import { Service as DecoratorService } from './service/core/decorator';
import './service';
Expand Down Expand Up @@ -79,6 +80,7 @@ export async function startup(options: {
if (EditorExtends.init) {
await EditorExtends.init();
}
await Rpc.startup({ serverURL });

cc.physics.selector.runInEditor = true;
await cc.game.init(config);
Expand Down
15 changes: 10 additions & 5 deletions src/core/scene/scene-process/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@ export class RpcProxy {
return this.rpcInstance;
}

async startup() {
async startup(options?: { serverURL: string }) {
// 在创建新实例前,先清理旧实例,防止内存泄漏
this.dispose();
this.rpcInstance = new ProcessRPC<IMainModule>();
this.rpcInstance.attach(process);
const { Service } = await import('./service/core/decorator');
this.rpcInstance.register(Service);
console.log('[Scene] Scene Process RPC ready');
if (options?.serverURL) {
this.rpcInstance.setWebTransport(options.serverURL);
console.log('[Scene] Scene Process Web RPC ready');
} else {
this.rpcInstance.attach(process);
const { Service } = await import('./service/core/decorator');
this.rpcInstance.register(Service);
console.log('[Scene] Scene Process RPC ready');
}
}

/**
Expand Down
26 changes: 6 additions & 20 deletions src/core/scene/scene-process/service/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import {
} from '../../common';
import { PrefabEditor, SceneEditor } from './editors';
import { IAssetInfo } from '../../../assets/@types/public';
import { parseCommandLineArgs } from '../utils';
import { serviceManager } from './service-manager';
import { Rpc } from '../rpc';

/**
* EditorAsset - 统一的编辑器管理入口
Expand Down Expand Up @@ -56,19 +55,6 @@ export class EditorService extends BaseService<IEditorEvents> implements IEditor
}
}

private async queryAssetInfo(urlOrUUID: string): Promise<IAssetInfo | null> {
const serverURL = serviceManager.getServerUrl();
try {
const res = await fetch(`${serverURL}/query-asset-info/${urlOrUUID}`);
if (res.ok) {
return await res.json() as IAssetInfo;
}
} catch (error) {
console.warn(`[Scene] queryAssetInfo failed:`, error);
}
return null;
}

async waitLocks() {
if (this.lockPromise) {
await this.lockPromise;
Expand Down Expand Up @@ -125,7 +111,7 @@ export class EditorService extends BaseService<IEditorEvents> implements IEditor
async open(params: IOpenOptions): Promise<IScene | INode> {
const { urlOrUUID, simpleNode = true } = params;

const assetInfo = await this.queryAssetInfo(urlOrUUID);
const assetInfo = await Rpc.getInstance().request('assetManager', 'queryAssetInfo', [urlOrUUID]);
if (!assetInfo) {
throw new Error(`通过 ${urlOrUUID} 无法打开,查询不到该资源信息`);
}
Expand All @@ -135,7 +121,7 @@ export class EditorService extends BaseService<IEditorEvents> implements IEditor
if (currentEditor) {
try {
// 关闭当前场景
const assetInfo = await this.queryAssetInfo(this.currentEditorUuid);
const assetInfo = await Rpc.getInstance().request('assetManager', 'queryAssetInfo', [this.currentEditorUuid]);
if (assetInfo) {
await currentEditor.close();
}
Expand Down Expand Up @@ -190,7 +176,7 @@ export class EditorService extends BaseService<IEditorEvents> implements IEditor
try {
if (!urlOrUUID) return true;

const assetInfo = await this.queryAssetInfo(urlOrUUID);
const assetInfo = await Rpc.getInstance().request('assetManager', 'queryAssetInfo', [urlOrUUID]);
if (!assetInfo) {
throw new Error(`通过 ${urlOrUUID} 请求资源失败`);
}
Expand Down Expand Up @@ -226,7 +212,7 @@ export class EditorService extends BaseService<IEditorEvents> implements IEditor
throw new Error('当前没有打开任何编辑器');
}

const assetInfo = await this.queryAssetInfo(urlOrUUID);
const assetInfo = await Rpc.getInstance().request('assetManager', 'queryAssetInfo', [urlOrUUID]);
if (!assetInfo) {
throw new Error(`通过 ${urlOrUUID} 请求资源失败`);
}
Expand Down Expand Up @@ -264,7 +250,7 @@ export class EditorService extends BaseService<IEditorEvents> implements IEditor
return ReloadResult.NO_EDITOR;
}

const assetInfo = await this.queryAssetInfo(urlOrUUID);
const assetInfo = await Rpc.getInstance().request('assetManager', 'queryAssetInfo', [urlOrUUID]);
if (!assetInfo) {
console.warn(`通过 ${urlOrUUID} 请求资源失败`);
this._isReloading = false;
Expand Down
27 changes: 3 additions & 24 deletions src/core/scene/scene-process/service/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,14 @@ export class ScriptService extends BaseService<IScriptEvents> implements IScript
classConstructor, 'i18n:menu.custom_script/' + className, -1);
}
});
const serverUrl = serviceManager.getServerUrl();
const serialPromise = await fetch(`${serverUrl}/programming/getPackerDriverLoaderContext/editor`);
const serializedPackLoaderContext = await serialPromise.json();
const serializedPackLoaderContext = await Rpc.getInstance().request('programming', 'getPackerDriverLoaderContext', ['editor']);
if (!serializedPackLoaderContext) {
throw new Error('packer-driver/get-loader-context is not defined');
}
const quickPackLoaderContext = QuickPackLoaderContext.deserialize(serializedPackLoaderContext);

const { loadDynamic } = await import('cc/preload');
const moduleMapPromise = await fetch(`${serverUrl}/programming/queryCCEModuleMap`);
const cceModuleMap = await moduleMapPromise.json();
const cceModuleMap = await Rpc.getInstance().request('programming', 'queryCCEModuleMap');
this._executor = await Executor.create({
// @ts-ignore
importEngineMod: async (id) => {
Expand Down Expand Up @@ -278,29 +275,11 @@ export class ScriptService extends BaseService<IScriptEvents> implements IScript
* @private
*/
private async _syncPluginScriptList() {
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
if (isBrowser) {
try {
const serverUrl = serviceManager.getServerUrl();
const res = await fetch(`${serverUrl}/assetManager/querySortedPlugins`);
if (res.ok) {
const pluginScripts = await res.json();
this._executor.setPluginScripts(pluginScripts || []);
} else {
this._executor.setPluginScripts([]);
}
} catch (err) {
console.error('Failed to fetch plugin scripts', err);
this._executor.setPluginScripts([]);
}
return;
}

return Promise.resolve(Rpc.getInstance().request('assetManager', 'querySortedPlugins', [{
loadPluginInEditor: true,
}]))
.then((pluginScripts) => {
this._executor.setPluginScripts(pluginScripts);
this._executor.setPluginScripts(pluginScripts || []);
})
.catch((reason) => {
console.error(reason);
Expand Down
Loading
Loading