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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ packages/cocos-cli-types/**/*

# static
static/tools/
static/web/scene-bundle.js
static/web/scene-bundle.js.map

# 构建文件
/dist
Expand Down
116 changes: 74 additions & 42 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,16 @@
"compiler": "FORCE_UPDATE=true npm i",
"compiler:engine": "node --max-old-space-size=8192 workflow/compiler-engine.js --force",
"build:cc-module": "node workflow/build-cc-module.js",
"build": "npm run build:clear && node workflow/prepare-dts.js && tsc -b && node workflow/generate-schema.js",
"compile": "npm run build:clear && node workflow/prepare-dts.js && tsc -b && node workflow/generate-schema.js",
"build": "npm run build:clear && node workflow/prepare-dts.js && tsc -b && node workflow/build-scene-bundle.js && node workflow/generate-schema.js",
"compile": "npm run build:clear && node workflow/prepare-dts.js && tsc -b && node workflow/build-scene-bundle.js && node workflow/generate-schema.js",
"build:clear": "node workflow/build-clear.js",
"build:watch": "tsc -b --watch",
"postinstall": "node workflow/postinstall.js",
"download-tools": "node workflow/download-tools.js",
"pack:script": "node workflow/pack-scripts.js",
"release": "npx --yes gulp release --gulpfile workflow/release.js",
"start:mcp-debug": "node ./dist/cli.js start-mcp-server --project=./tests/fixtures/projects/asset-operation",
"start:preview": "node ./dist/cli.js preview --project=./tests/fixtures/projects/asset-operation",
"start:mcp-inspector": "npx @modelcontextprotocol/inspector",
"cli": "node ./dist/cli.js",
"rebuild": "node workflow/electron-rebuild.js"
Expand Down Expand Up @@ -106,14 +107,14 @@
"@babel/core": "7.22.20",
"@cocos/asset-db": "^3.0.0-alpha.7",
"@cocos/build-polyfills": "2.0.0",
"@cocos/ccbuild": "2.3.17",
"@cocos/creator-programming-rollup-plugin-mod-lo": "1.4.4",
"@cocos/ccbuild": "2.3.18",
"@cocos/creator-programming-rollup-plugin-mod-lo": "1.4.6",
"@cocos/data-uri": "^1.0.6",
"@cocos/fbx-gltf-conv": "^1.0.0-alpha.49.editor.1",
"@cocos/fbx2gltf": "^1.0.8",
"@cocos/lib-programming": "3.8.13",
"@cocos/lib-programming": "3.8.15",
"@cocos/module-system": "^0.0.21",
"@cocos/quick-compiler": "^4.2.24",
"@cocos/quick-compiler": "^4.2.26",
"@ffprobe-installer/ffprobe": "^1.4.1",
"@microsoft/api-extractor": "^7.55.0",
"@modelcontextprotocol/sdk": "^1.18.0",
Expand Down
89 changes: 46 additions & 43 deletions packages/engine-compiler/src/core/compiler.ts

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions packages/engine-compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { EngineCompiler } from './core/compiler';
/**
* 根据路径编译引擎
* @param path
* @param outDirName
*/
export async function compileEngine(path: string) {
const compiler = EngineCompiler.create(path);
export async function compileEngine(enginePath: string, isWeb?: boolean) {
const compiler = EngineCompiler.create(enginePath, isWeb);
await compiler.clear();
await compiler.compileEngine(path, true);
await compiler.compileEngine(enginePath, true);
}
2 changes: 2 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ initSentry();
import { Command } from 'commander';
import { BuildCommand, McpServerCommand, CommandRegistry, CreateCommand, MakeCommand, RunCommand } from './commands';
import { config } from './display/config';
import { PreviewCommand } from './commands/preview';

const program = new Command();

Expand All @@ -27,6 +28,7 @@ commandRegistry.register(new BuildCommand(program));
commandRegistry.register(new McpServerCommand(program));
commandRegistry.register(new MakeCommand(program));
commandRegistry.register(new RunCommand(program));
commandRegistry.register(new PreviewCommand(program));

// 注册所有命令
commandRegistry.registerAll();
Expand Down
40 changes: 40 additions & 0 deletions src/commands/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import chalk from 'chalk';
import { BaseCommand } from './base';


/**
* Preview 命令类
*/
export class PreviewCommand extends BaseCommand {
register(): void {
this.program
.command('preview')
.description('Preview a Cocos project')
.requiredOption('-j, --project <path>', 'Path to the Cocos project (required)')
.option('-p, --port <number>', 'Port number for the preview server', '9527')
.action(async (options: any) => {
try {
const resolvedPath = this.validateProjectPath(options.project);
const port = parseInt(options.port, 10);

// 验证端口号
if (isNaN(port) || port < 1 || port > 65535) {
console.error(chalk.red('Error: Invalid port number. Port must be between 1 and 65535.'));
process.exit(1);
}

const { default: Launcher } = await import('../core/launcher');
const launcher = new Launcher(resolvedPath);
await launcher.startPreview(port);


// 保持进程运行
process.stdin.resume();
} catch (error) {
console.error(chalk.red('Failed to start MCP server'));
console.error(error);
process.exit(1);
}
});
}
}
51 changes: 51 additions & 0 deletions src/core/engine/compile-process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { join } from 'path';
import { fork } from 'child_process';

/**
* 在独立的子进程中运行引擎编译
* 这样可以避免繁重的 babel 转译阻塞主进程事件循环
*/
export function startCompileEngineProcess(force: boolean = false): Promise<void> {
return new Promise((resolve, reject) => {
// 根据运行环境决定是使用 ts-node 还是直接执行 js
const isTsNode = (process as any)[Symbol.for('ts-node.register.instance')] || process.env.TS_NODE_DEV;

let workerPath = join(__dirname, 'compile-worker.ts');
const execArgv = [...process.execArgv];

// 如果是编译后的环境
if (!__filename.endsWith('.ts')) {
workerPath = join(__dirname, 'compile-worker.js');
} else if (!isTsNode && __filename.endsWith('.ts')) {
// ts 环境但没有直接注册 ts-node(比如被某些 runner 调用)
execArgv.push('-r', 'ts-node/register');
}

console.log(`🚀 启动引擎编译子进程...`);
const worker = fork(workerPath, [], {
stdio: 'inherit',
execArgv,
});

worker.on('message', (message: any) => {
if (message.type === 'done') {
resolve();
} else if (message.type === 'error') {
reject(new Error(`[Worker Error] ${message.message}\n${message.stack}`));
}
});

worker.on('error', (err) => {
reject(err);
});

worker.on('exit', (code) => {
if (code !== 0 && code !== null) {
reject(new Error(`Engine compile worker exited with code ${code}`));
}
});

// 告诉 worker 开始编译
worker.send({ type: 'start', force });
});
}
34 changes: 34 additions & 0 deletions src/core/engine/compile-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { join } from 'path';
import { GlobalPaths } from '../../global';

// 监听来自主进程的消息
process.on('message', async (message: any) => {
if (message && message.type === 'start') {
try {
const engineCompilerPath = join(GlobalPaths.workspace, 'packages', 'engine-compiler', 'dist', 'index');
const { compileEngine } = require(engineCompilerPath);

const enginePath = GlobalPaths.enginePath;
//compile for editor
await compileEngine(enginePath);
//compile for web
await compileEngine(enginePath, true);

// 编译成功后给主进程发送完成消息
if (process.send) {
process.send({ type: 'done' });
}
process.exit(0);
} catch (error: any) {
console.error('Engine compile worker failed:', error);
if (process.send) {
process.send({
type: 'error',
message: error.message,
stack: error.stack
});
}
process.exit(1);
}
}
});
3 changes: 3 additions & 0 deletions src/core/engine/editor-extends/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export { walkProperties } from './missing-reporter/object-walker';

import utils from '../../base/utils';
import EventEmitter from 'events';
if (!EventEmitter.prototype.off) {
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
}
import ScriptManager from './manager/script';
import NodeManager from './manager/node';
import ComponentManager from './manager/component';
Expand Down
50 changes: 50 additions & 0 deletions src/core/engine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,56 @@ class EngineManager implements IEngine {
return this;
}

async getGameConfig(serverURL: string, importBase: string, nativeBase: string, isPreview?: boolean) {
const { physicsConfig, macroConfig, customLayers, sortingLayers, highQuality } = this.getConfig();
const bundles = assetManager.queryAssets({ isBundle: true }).map((item: any) => item.meta?.userData?.bundleName ?? item.name);
const builtinAssets = serverURL && await this.queryInternalAssetList(this.getInfo().typescript.path);
return {
debugMode: cc.debug.DebugMode.WARN,
overrideSettings: {
engine: {
builtinAssets: builtinAssets || [],
macros: macroConfig,
sortingLayers,
customLayers: customLayers.map((layer: any) => {
const index = layerMask.findIndex((num) => { return layer.value === num; });
return {
name: layer.name,
bit: index,
};
}),
},
profiling: {
showFPS: isPreview ? true : false,
},
screen: {
frameRate: 30,
exactFitScreen: true,
},
rendering: {
renderMode: 2,
highQualityMode: highQuality,
},
physics: {
...physicsConfig,
// 物理引擎如果没有明确设置,默认是开启的,因此需要明确定义为false
enabled: serverURL ? true : false,
},
assets: {
importBase: importBase,
nativeBase: nativeBase,
remoteBundles: ['internal', 'main'].concat(bundles),
server: serverURL,
}
},
exactFitScreen: true,
};
}

getModules(): string[] {
return this.getConfig().includeModules || [];
}

async queryInternalAssetList(enginePath: string) {
// 添加引擎依赖的预加载内置资源到主包内
const ccConfigJson = await fse.readJSON(join(enginePath, 'cc.config.json'));
Expand Down
34 changes: 33 additions & 1 deletion src/core/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import { join } from 'path';
import { IBuildCommandOption, Platform } from './builder/@types/protected';
import utils from './base/utils';
import { newConsole } from './base/console';
import { startServer } from '../server';
import { startServer, getServerUrl } from '../server';
import { GlobalConfig, GlobalPaths } from '../global';
import scripting from './scripting';
import { startupScene } from './scene';
import { spawn } from 'child_process';
import { middlewareService } from '../server/middleware';
import ScriptingMiddleware from './scene/scripting.middleware';
import SceneMiddleware from './scene/scene.middleware';
import { sceneConfigInstance } from './scene/scene-configs';


/**
Expand Down Expand Up @@ -60,6 +65,10 @@ export default class Launcher {
// 在导入资源之前,初始化 scripting 模块,才能正常导入编译脚本
const { Engine } = await import('./engine');
await scripting.initialize(this.projectPath, GlobalPaths.enginePath, Engine.getConfig().includeModules);

const { createProgrammingFacet } = await import('./scripting/programming/FacetInstance');
await createProgrammingFacet(Engine.getInfo().typescript.path, scripting.projectPath, Engine.getConfig().includeModules);

// 启动以及初始化资源数据库
const { initAssetDB, startAssetDB } = await import('./assets');
await initAssetDB();
Expand All @@ -75,10 +84,33 @@ export default class Launcher {
// 初始化构建
const { init: initBuilder } = await import('./builder');
await initBuilder();

// 启动场景进程,需要在 Builder 之后,因为服务器路由场景还没有做前缀约束匹配范围比较广
await startupScene(GlobalPaths.enginePath, this.projectPath);
}

async startPreview(port?: number) {
await this.import();
await startServer(port);
// 初始化构建
const { init: initBuilder } = await import('./builder');
await initBuilder();

middlewareService.register('scripting', ScriptingMiddleware);
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'
? 'open'
: 'xdg-open';
spawn(browserPath, [getServerUrl()], { stdio: 'ignore', detached: true });
}

/**
* 构建,主要是作为命令行构建的入口
* @param platform
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
Loading
Loading