diff --git a/.api/public.d.ts b/.api/public.d.ts index 6aef176..afb8008 100644 --- a/.api/public.d.ts +++ b/.api/public.d.ts @@ -27,6 +27,22 @@ declare module "@cocos/ccbuild" { */ function enumerateAllDependents(meta: buildEngine.Result, featureUnits: string[]): string[]; export type ModuleFormat = "esm" | "cjs" | "system" | "iife"; + export type HasModuleSideEffects = (id: string, external: boolean) => boolean; + export type ModuleSideEffectsOption = boolean | "no-external" | string[] | HasModuleSideEffects; + export type TreeshakingPreset = "smallest" | "safest" | "recommended"; + export interface NormalizedTreeshakingOptions { + annotations: boolean; + correctVarValueBeforeDeclaration: boolean; + manualPureFunctions: readonly string[]; + moduleSideEffects: HasModuleSideEffects; + propertyReadSideEffects: boolean | "always"; + tryCatchDeoptimization: boolean; + unknownGlobalSideEffects: boolean; + } + export interface TreeshakingOptions extends Partial> { + moduleSideEffects?: ModuleSideEffectsOption; + preset?: TreeshakingPreset; + } export interface Options { /** * 引擎仓库目录。 @@ -162,6 +178,7 @@ declare module "@cocos/ccbuild" { * @note It's only avaiable when options.moduleFormat is 'system'. */ enableNamedRegisterForSystemJSModuleFormat?: boolean; + treeshake?: TreeshakingOptions; } export interface Result { /** diff --git a/modules/build-engine/src/engine-js/index.ts b/modules/build-engine/src/engine-js/index.ts index 9112627..8b5e551 100644 --- a/modules/build-engine/src/engine-js/index.ts +++ b/modules/build-engine/src/engine-js/index.ts @@ -402,6 +402,10 @@ export async function buildJsEngine(options: Required): Pro onwarn: rollupWarningHandler, }; + if (options.treeshake) { + rollupOptions.treeshake = options.treeshake; + } + const perf = true; if (perf) { diff --git a/modules/build-engine/src/index.ts b/modules/build-engine/src/index.ts index 8c6b144..67e901d 100644 --- a/modules/build-engine/src/index.ts +++ b/modules/build-engine/src/index.ts @@ -69,6 +69,27 @@ export async function buildEngine (options: buildEngine.Options): Promise boolean; + export type ModuleSideEffectsOption = boolean | 'no-external' | string[] | HasModuleSideEffects; + + export type TreeshakingPreset = 'smallest' | 'safest' | 'recommended'; + + export interface NormalizedTreeshakingOptions { + annotations: boolean; + correctVarValueBeforeDeclaration: boolean; + manualPureFunctions: readonly string[]; + moduleSideEffects: HasModuleSideEffects; + propertyReadSideEffects: boolean | 'always'; + tryCatchDeoptimization: boolean; + unknownGlobalSideEffects: boolean; + } + + // See https://rollupjs.org/configuration-options/#treeshake for more details. + export interface TreeshakingOptions extends Partial> { + moduleSideEffects?: ModuleSideEffectsOption; + preset?: TreeshakingPreset; + } export interface Options { /** @@ -237,6 +258,8 @@ export namespace buildEngine { * @note It's only avaiable when options.moduleFormat is 'system'. */ enableNamedRegisterForSystemJSModuleFormat?: boolean; + + treeshake?: TreeshakingOptions; } export interface Result { diff --git a/package-lock.json b/package-lock.json index 69564bb..59b4a48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cocos/ccbuild", - "version": "2.2.12", + "version": "2.2.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cocos/ccbuild", - "version": "2.2.12", + "version": "2.2.13", "hasInstallScript": true, "license": "MIT", "workspaces": [ diff --git a/package.json b/package.json index 8fe5dac..aa7d7c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cocos/ccbuild", - "version": "2.2.12", + "version": "2.2.13", "description": "The next generation of build tool for Cocos engine.", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/scripts/test-build-cocos.js b/scripts/test-build-cocos.js index 9d1e3ca..d89b7f2 100644 --- a/scripts/test-build-cocos.js +++ b/scripts/test-build-cocos.js @@ -15,6 +15,11 @@ const argv = process.argv; const outDir = ps.join(__dirname, '..', 'build-cc-out'); + const noSideEffectFiles = [ + 'instantiate-jit.ts', + 'splash-screen.ts', + ]; + const options = { "engine": enginePath, "out": outDir, @@ -27,18 +32,31 @@ const argv = process.argv; "noDeprecatedFeatures": true, "sourceMap": false, // "features": ["2d", "3d", "animation", "audio", "base", "debug-renderer", "dragon-bones", "geometry-renderer", "gfx-webgl", "intersection-2d", "legacy-pipeline", "light-probe", "particle", "particle-2d", "physics-2d-builtin", "physics-cannon", "primitive", "profiler", "skeletal-animation", "spine", "terrain", "tiled-map", "tween", "ui", "video", "websocket", "webview"], - "features":["gfx-webgl2"], + // "features":["gfx-webgl2"], + "features": ["2d","audio","base","gfx-webgl2","legacy-pipeline","ui"], //,"animation","spine","tween" + // "features":["base", "audio", "2d", "ui", "gfx-webgl2"], "loose": true, "mode": "BUILD", "flags": { "DEBUG": false, + ONLY_2D: true, "WEBGPU": false }, // "metaFile": ps.join(outDir, "meta.json"), // "incremental": ps.join(outDir, "watch-files.json"), - "wasmCompressionMode": false, + "wasmCompressionMode": 'brotli', "visualize": true, "inlineEnum": true, + treeshake: { + moduleSideEffects: (id, isExternal) => { + const fileName = ps.basename(id); + if (noSideEffectFiles.indexOf(fileName) >= 0) { + console.info(`--> Found fileName: ${fileName}`); + return false; + } + return true; + } + }, }; await ensureDir(outDir); diff --git a/test/build-engine/__snapshots__/engine-js.test.ts.snap b/test/build-engine/__snapshots__/engine-js.test.ts.snap index 986d570..58f7172 100644 --- a/test/build-engine/__snapshots__/engine-js.test.ts.snap +++ b/test/build-engine/__snapshots__/engine-js.test.ts.snap @@ -1,5 +1,470 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`engine-js HTML5 with treeshake option 1`] = ` +[ + "cc.js", +] +`; + +exports[`engine-js HTML5 with treeshake option 2`] = ` +"System.register([], (function (exports) { + 'use strict'; + return { + execute: (function () { + + function tryDefineGlobal (name, value) { + const _global = typeof window === 'undefined' ? global : window; + if (typeof _global[name] === 'undefined') { + return (_global[name] = value); + } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return _global[name]; + } + } + tryDefineGlobal('CC_WECHAT', false); + tryDefineGlobal('CC_BAIDU', false); + tryDefineGlobal('CC_XIAOMI', false); + tryDefineGlobal('CC_ALIPAY', false); + tryDefineGlobal('CC_BYTEDANCE', false); + tryDefineGlobal('CC_OPPO', false); + tryDefineGlobal('CC_VIVO', false); + tryDefineGlobal('CC_HUAWEI', false); + tryDefineGlobal('CC_COCOSPLAY', false); + tryDefineGlobal('CC_QTT', false); + tryDefineGlobal('CC_LINKSURE', false); + tryDefineGlobal('CC_EDITOR', false); + tryDefineGlobal('CC_PREVIEW', false); + tryDefineGlobal('CC_BUILD', true); + tryDefineGlobal('CC_TEST', false); + tryDefineGlobal('CC_DEBUG', false); + tryDefineGlobal('CC_DEV', false); + tryDefineGlobal('CC_MINIGAME', false); + tryDefineGlobal('CC_RUNTIME_BASED', false); + tryDefineGlobal('CC_SUPPORT_JIT', true); + tryDefineGlobal('CC_JSB', false); + + var Pool = function () { + var _proto = Pool.prototype; + _proto.get = function get() { + return this._get(); + }; + function Pool(_0, _1) { + this.count = 0; + var size = _1 === undefined ? _0 : _1; + var cleanupFunc = _1 === undefined ? null : _0; + this._pool = new Array(size); + this._cleanup = cleanupFunc; + } + _proto._get = function _get() { + if (this.count > 0) { + --this.count; + var cache = this._pool[this.count]; + this._pool[this.count] = null; + return cache; + } + return null; + }; + _proto.put = function put(obj) { + var pool = this._pool; + if (this.count < pool.length) { + if (this._cleanup && this._cleanup(obj) === false) { + return; + } + pool[this.count] = obj; + ++this.count; + } + }; + _proto.resize = function resize(length) { + if (length >= 0) { + this._pool.length = length; + if (this.count > length) { + this.count = length; + } + } + }; + return Pool; + }(); + + console.log("I'm instantiate-jit.ts"); + console.log('side-effect'); + var Impure = function Impure() { + console.log('side-effect'); + }; + new Impure(); + var Assignments = function () { + function Assignments(targetExpression) { + this._exps = []; + this._targetExp = targetExpression; + } + var _proto2 = Assignments.prototype; + _proto2.append = function append(key, expression) { + this._exps.push([key, expression]); + }; + _proto2.writeCode = function writeCode(codeArray) { + console.info("writeCode"); + }; + return Assignments; + }(); + Assignments.pool = void 0; + Assignments.pool = new Pool(function (obj) { + obj._exps.length = 0; + obj._targetExp = null; + }, 1); + Assignments.pool.get = function (targetExpression) { + var cache = this._get() || new Assignments(); + cache._targetExp = targetExpression; + return cache; + }; + function compile() { + console.log(">>> jit compile ..."); + } + + var Prefab = exports("Prefab", function () { + function Prefab() {} + var _proto = Prefab.prototype; + _proto._instantiate = function _instantiate() { + { + compile(); + } + }; + return Prefab; + }()); + + }) + }; +})); +" +`; + +exports[`engine-js HTML5 without treeshake option 1`] = ` +[ + "cc.js", +] +`; + +exports[`engine-js HTML5 without treeshake option 2`] = ` +"System.register([], (function (exports) { + 'use strict'; + return { + execute: (function () { + + function tryDefineGlobal (name, value) { + const _global = typeof window === 'undefined' ? global : window; + if (typeof _global[name] === 'undefined') { + return (_global[name] = value); + } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return _global[name]; + } + } + tryDefineGlobal('CC_WECHAT', false); + tryDefineGlobal('CC_BAIDU', false); + tryDefineGlobal('CC_XIAOMI', false); + tryDefineGlobal('CC_ALIPAY', false); + tryDefineGlobal('CC_BYTEDANCE', false); + tryDefineGlobal('CC_OPPO', false); + tryDefineGlobal('CC_VIVO', false); + tryDefineGlobal('CC_HUAWEI', false); + tryDefineGlobal('CC_COCOSPLAY', false); + tryDefineGlobal('CC_QTT', false); + tryDefineGlobal('CC_LINKSURE', false); + tryDefineGlobal('CC_EDITOR', false); + tryDefineGlobal('CC_PREVIEW', false); + tryDefineGlobal('CC_BUILD', true); + tryDefineGlobal('CC_TEST', false); + tryDefineGlobal('CC_DEBUG', false); + tryDefineGlobal('CC_DEV', false); + tryDefineGlobal('CC_MINIGAME', false); + tryDefineGlobal('CC_RUNTIME_BASED', false); + tryDefineGlobal('CC_SUPPORT_JIT', true); + tryDefineGlobal('CC_JSB', false); + + var Pool = function () { + var _proto = Pool.prototype; + _proto.get = function get() { + return this._get(); + }; + function Pool(_0, _1) { + this.count = 0; + var size = _1 === undefined ? _0 : _1; + var cleanupFunc = _1 === undefined ? null : _0; + this._pool = new Array(size); + this._cleanup = cleanupFunc; + } + _proto._get = function _get() { + if (this.count > 0) { + --this.count; + var cache = this._pool[this.count]; + this._pool[this.count] = null; + return cache; + } + return null; + }; + _proto.put = function put(obj) { + var pool = this._pool; + if (this.count < pool.length) { + if (this._cleanup && this._cleanup(obj) === false) { + return; + } + pool[this.count] = obj; + ++this.count; + } + }; + _proto.resize = function resize(length) { + if (length >= 0) { + this._pool.length = length; + if (this.count > length) { + this.count = length; + } + } + }; + return Pool; + }(); + + console.log("I'm instantiate-jit.ts"); + console.log('side-effect'); + var Impure = function Impure() { + console.log('side-effect'); + }; + new Impure(); + var Assignments = function () { + function Assignments(targetExpression) { + this._exps = []; + this._targetExp = targetExpression; + } + var _proto2 = Assignments.prototype; + _proto2.append = function append(key, expression) { + this._exps.push([key, expression]); + }; + _proto2.writeCode = function writeCode(codeArray) { + console.info("writeCode"); + }; + return Assignments; + }(); + Assignments.pool = void 0; + Assignments.pool = new Pool(function (obj) { + obj._exps.length = 0; + obj._targetExp = null; + }, 1); + Assignments.pool.get = function (targetExpression) { + var cache = this._get() || new Assignments(); + cache._targetExp = targetExpression; + return cache; + }; + function compile() { + console.log(">>> jit compile ..."); + } + + var Prefab = exports("Prefab", function () { + function Prefab() {} + var _proto = Prefab.prototype; + _proto._instantiate = function _instantiate() { + { + compile(); + } + }; + return Prefab; + }()); + + }) + }; +})); +" +`; + +exports[`engine-js WECHAT with treeshake option 1`] = ` +[ + "cc.js", +] +`; + +exports[`engine-js WECHAT with treeshake option 2`] = ` +"System.register([], (function (exports) { + 'use strict'; + return { + execute: (function () { + + function tryDefineGlobal (name, value) { + const _global = typeof window === 'undefined' ? global : window; + if (typeof _global[name] === 'undefined') { + return (_global[name] = value); + } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return _global[name]; + } + } + tryDefineGlobal('CC_WECHAT', true); + tryDefineGlobal('CC_BAIDU', false); + tryDefineGlobal('CC_XIAOMI', false); + tryDefineGlobal('CC_ALIPAY', false); + tryDefineGlobal('CC_BYTEDANCE', false); + tryDefineGlobal('CC_OPPO', false); + tryDefineGlobal('CC_VIVO', false); + tryDefineGlobal('CC_HUAWEI', false); + tryDefineGlobal('CC_COCOSPLAY', false); + tryDefineGlobal('CC_QTT', false); + tryDefineGlobal('CC_LINKSURE', false); + tryDefineGlobal('CC_EDITOR', false); + tryDefineGlobal('CC_PREVIEW', false); + tryDefineGlobal('CC_BUILD', true); + tryDefineGlobal('CC_TEST', false); + tryDefineGlobal('CC_DEBUG', false); + tryDefineGlobal('CC_DEV', false); + tryDefineGlobal('CC_MINIGAME', true); + tryDefineGlobal('CC_RUNTIME_BASED', false); + tryDefineGlobal('CC_SUPPORT_JIT', false); + tryDefineGlobal('CC_JSB', false); + + var Prefab = exports("Prefab", function () { + function Prefab() {} + var _proto = Prefab.prototype; + _proto._instantiate = function _instantiate() { + { + console.error("Dosn't support JIT"); + } + }; + return Prefab; + }()); + + }) + }; +})); +" +`; + +exports[`engine-js WECHAT without treeshake option 1`] = ` +[ + "cc.js", +] +`; + +exports[`engine-js WECHAT without treeshake option 2`] = ` +"System.register([], (function (exports) { + 'use strict'; + return { + execute: (function () { + + function tryDefineGlobal (name, value) { + const _global = typeof window === 'undefined' ? global : window; + if (typeof _global[name] === 'undefined') { + return (_global[name] = value); + } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return _global[name]; + } + } + tryDefineGlobal('CC_WECHAT', true); + tryDefineGlobal('CC_BAIDU', false); + tryDefineGlobal('CC_XIAOMI', false); + tryDefineGlobal('CC_ALIPAY', false); + tryDefineGlobal('CC_BYTEDANCE', false); + tryDefineGlobal('CC_OPPO', false); + tryDefineGlobal('CC_VIVO', false); + tryDefineGlobal('CC_HUAWEI', false); + tryDefineGlobal('CC_COCOSPLAY', false); + tryDefineGlobal('CC_QTT', false); + tryDefineGlobal('CC_LINKSURE', false); + tryDefineGlobal('CC_EDITOR', false); + tryDefineGlobal('CC_PREVIEW', false); + tryDefineGlobal('CC_BUILD', true); + tryDefineGlobal('CC_TEST', false); + tryDefineGlobal('CC_DEBUG', false); + tryDefineGlobal('CC_DEV', false); + tryDefineGlobal('CC_MINIGAME', true); + tryDefineGlobal('CC_RUNTIME_BASED', false); + tryDefineGlobal('CC_SUPPORT_JIT', false); + tryDefineGlobal('CC_JSB', false); + + var Pool = function () { + var _proto = Pool.prototype; + _proto.get = function get() { + return this._get(); + }; + function Pool(_0, _1) { + this.count = 0; + var size = _1 === undefined ? _0 : _1; + var cleanupFunc = _1 === undefined ? null : _0; + this._pool = new Array(size); + this._cleanup = cleanupFunc; + } + _proto._get = function _get() { + if (this.count > 0) { + --this.count; + var cache = this._pool[this.count]; + this._pool[this.count] = null; + return cache; + } + return null; + }; + _proto.put = function put(obj) { + var pool = this._pool; + if (this.count < pool.length) { + if (this._cleanup && this._cleanup(obj) === false) { + return; + } + pool[this.count] = obj; + ++this.count; + } + }; + _proto.resize = function resize(length) { + if (length >= 0) { + this._pool.length = length; + if (this.count > length) { + this.count = length; + } + } + }; + return Pool; + }(); + + console.log("I'm instantiate-jit.ts"); + console.log('side-effect'); + var Impure = function Impure() { + console.log('side-effect'); + }; + new Impure(); + var Assignments = function () { + function Assignments(targetExpression) { + this._exps = []; + this._targetExp = targetExpression; + } + var _proto2 = Assignments.prototype; + _proto2.append = function append(key, expression) { + this._exps.push([key, expression]); + }; + _proto2.writeCode = function writeCode(codeArray) { + console.info("writeCode"); + }; + return Assignments; + }(); + Assignments.pool = void 0; + Assignments.pool = new Pool(function (obj) { + obj._exps.length = 0; + obj._targetExp = null; + }, 1); + Assignments.pool.get = function (targetExpression) { + var cache = this._get() || new Assignments(); + cache._targetExp = targetExpression; + return cache; + }; + + var Prefab = exports("Prefab", function () { + function Prefab() {} + var _proto = Prefab.prototype; + _proto._instantiate = function _instantiate() { + { + console.error("Dosn't support JIT"); + } + }; + return Prefab; + }()); + + }) + }; +})); +" +`; + exports[`engine-js build WASM module on platform maybe supporting WASM 1`] = ` [ "assets/wasm_c-_JUdaL1r.wasm", diff --git a/test/build-engine/engine-js.test.ts b/test/build-engine/engine-js.test.ts index 7c43400..78bd9ed 100644 --- a/test/build-engine/engine-js.test.ts +++ b/test/build-engine/engine-js.test.ts @@ -315,4 +315,126 @@ describe('engine-js', () => { expect(await getOutputContent(ps.join(out, 'cc.js'))).toMatchSnapshot(); await del(out, { force: true }); }); + + test('WECHAT without treeshake option', async () => { + const out = ps.join(__dirname, './lib-js'); + + const options: buildEngine.Options = { + engine: ps.join(__dirname, '../test-engine-source'), + out, + platform: 'WECHAT', + moduleFormat: 'system', + compress: false, + split: false, + nativeCodeBundleMode: 'wasm', + assetURLFormat: 'runtime-resolved', + noDeprecatedFeatures: false, + sourceMap: false, + features: ['base'], + loose: true, + mode: 'BUILD', + flags: { + DEBUG: false, + WEBGPU: false + }, + }; + + await buildEngine(options); + expect(await getOutputDirStructure(out)).toMatchSnapshot(); + expect(await getOutputContent(ps.join(out, 'cc.js'))).toMatchSnapshot(); + await del(out, { force: true }); + }); + + test('WECHAT with treeshake option', async () => { + const out = ps.join(__dirname, './lib-js'); + + const options: buildEngine.Options = { + engine: ps.join(__dirname, '../test-engine-source'), + out, + platform: 'WECHAT', + moduleFormat: 'system', + compress: false, + split: false, + nativeCodeBundleMode: 'wasm', + assetURLFormat: 'runtime-resolved', + noDeprecatedFeatures: false, + sourceMap: false, + features: ['base'], + loose: true, + mode: 'BUILD', + flags: { + DEBUG: false, + WEBGPU: false + }, + treeshake: { + moduleSideEffects: (id) => !id.endsWith('instantiate-jit.ts'), + } + }; + + await buildEngine(options); + expect(await getOutputDirStructure(out)).toMatchSnapshot(); + expect(await getOutputContent(ps.join(out, 'cc.js'))).toMatchSnapshot(); + await del(out, { force: true }); + }); + + test('HTML5 without treeshake option', async () => { + const out = ps.join(__dirname, './lib-js'); + + const options: buildEngine.Options = { + engine: ps.join(__dirname, '../test-engine-source'), + out, + platform: 'HTML5', + moduleFormat: 'system', + compress: false, + split: false, + nativeCodeBundleMode: 'wasm', + assetURLFormat: 'runtime-resolved', + noDeprecatedFeatures: false, + sourceMap: false, + features: ['base'], + loose: true, + mode: 'BUILD', + flags: { + DEBUG: false, + WEBGPU: false + }, + }; + + await buildEngine(options); + expect(await getOutputDirStructure(out)).toMatchSnapshot(); + expect(await getOutputContent(ps.join(out, 'cc.js'))).toMatchSnapshot(); + await del(out, { force: true }); + }); + + test('HTML5 with treeshake option', async () => { + const out = ps.join(__dirname, './lib-js'); + + const options: buildEngine.Options = { + engine: ps.join(__dirname, '../test-engine-source'), + out, + platform: 'HTML5', + moduleFormat: 'system', + compress: false, + split: false, + nativeCodeBundleMode: 'wasm', + assetURLFormat: 'runtime-resolved', + noDeprecatedFeatures: false, + sourceMap: false, + features: ['base'], + loose: true, + mode: 'BUILD', + flags: { + DEBUG: false, + WEBGPU: false + }, + treeshake: { + moduleSideEffects: (id) => !id.endsWith('instantiate-jit.ts'), + } + }; + + await buildEngine(options); + expect(await getOutputDirStructure(out)).toMatchSnapshot(); + expect(await getOutputContent(ps.join(out, 'cc.js'))).toMatchSnapshot(); + await del(out, { force: true }); + }); }); \ No newline at end of file diff --git a/test/dts-bundler/__snapshots__/dts-bundler.test.ts.snap b/test/dts-bundler/__snapshots__/dts-bundler.test.ts.snap index 3b2e6ae..5107fcb 100644 --- a/test/dts-bundler/__snapshots__/dts-bundler.test.ts.snap +++ b/test/dts-bundler/__snapshots__/dts-bundler.test.ts.snap @@ -20,6 +20,8 @@ exports[`bundle dts: cc.d.ts content 1`] = ` } export class Path { } + export class Prefab { + } export const A = "A"; export namespace testAsm { function decodeVertexBuffer(target: Uint8Array, count: number, size: number, source: Uint8Array, filter?: string): void; diff --git a/test/test-engine-source/cc.config.json b/test/test-engine-source/cc.config.json index ce2f0b4..cac2143 100644 --- a/test/test-engine-source/cc.config.json +++ b/test/test-engine-source/cc.config.json @@ -40,6 +40,9 @@ }, "enums": { "modules": ["enums"] + }, + "base": { + "modules": ["base"] } }, "moduleOverrides": [ diff --git a/test/test-engine-source/cocos/core/utils/pool.ts b/test/test-engine-source/cocos/core/utils/pool.ts new file mode 100644 index 0000000..13cbc03 --- /dev/null +++ b/test/test-engine-source/cocos/core/utils/pool.ts @@ -0,0 +1,179 @@ +/* + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +type CleanUpFunction = (value: T) => boolean | void; + +/** + * @en + * A fixed-length object pool designed for general type.
+ * The implementation of this object pool is very simple. + * It can help you to improve your game performance for objects which need frequent release and recreate operations.
+ * @zh + * 长度固定的对象缓存池,可以用来缓存各种对象类型。
+ * 这个对象池的实现非常精简,它可以帮助您提高游戏性能,适用于优化对象的反复创建和销毁。 + * @class js.Pool + * @example + * ``` + * + * Example 1: + * + * function Details () { + * this.uuidList = []; + * }; + * Details.prototype.reset = function () { + * this.uuidList.length = 0; + * }; + * Details.pool = new js.Pool(function (obj) { + * obj.reset(); + * }, 5); + * Details.pool.get = function () { + * return this._get() || new Details(); + * }; + * + * var detail = Details.pool.get(); + * ... + * Details.pool.put(detail); + * + * Example 2: + * + * function Details (buffer) { + * this.uuidList = buffer; + * }; + * ... + * Details.pool.get = function (buffer) { + * var cached = this._get(); + * if (cached) { + * cached.uuidList = buffer; + * return cached; + * } + * else { + * return new Details(buffer); + * } + * }; + * + * var detail = Details.pool.get( [] ); + * ... + * ``` + */ +export class Pool { + /** + * @en + * The current number of available objects, the default is 0. It will gradually increase with the recycle of the object, + * the maximum will not exceed the size specified when the constructor is called. + * @zh + * 当前可用对象数量,一开始默认是 0,随着对象的回收会逐渐增大,最大不会超过调用构造函数时指定的 size。 + * @default 0 + */ + public count: number = 0; + + /** + * @en + * Gets an object from pool. + * @zh 从对象池中获取一个对象。 + * @returns @en An object or null if this pool doesn't contain any object. + * @zh 获取的对象。如果对象池中没有对象,返回 null。 + */ + public get (): T | null { + return this._get(); + } + + private declare _pool: Array; + private declare _cleanup: CleanUpFunction | null; + + /** + * @en Constructor. @zh 构造函数。 + * @param cleanupFunc @en Callback method used to process the cleanup logic when the object is recycled. + * @zh 当对象放入对象池时,用来执行清理逻辑的回调函数。 + * @param size @en Pool size. @zh 对象池大小。 + */ + constructor (cleanup: CleanUpFunction, size: number); + + /** + * @en Constructor. @zh 构造函数。 + * @param size @en Pool size. @zh 对象池大小。 + */ + constructor (size: number); + + /** + * @en Constructor. @zh 构造函数。 + * @param _0 @en If it is a number, it is the array size. Or it is a callback function used to process + * the cleanup logic when the object is recycled. + * @zh 如果是 number,那么它是对象池大小。否则是当对象放入对象池时,用来执行清理逻辑的回调函数。 + * @param _1 @en Array size if it is a valid number. @zh 如果是个有效的 number 类型,那么是对象池大小。 + */ + constructor (_0: CleanUpFunction | number, _1?: number) { + const size = (_1 === undefined) ? (_0 as number) : _1; + const cleanupFunc = (_1 === undefined) ? null : (_0 as CleanUpFunction); + this._pool = new Array(size); + this._cleanup = cleanupFunc; + } + + /** + * @en + * Gets an object from pool. + * @zh 从对象池中获取一个对象。 + * @returns @en An object or null if this pool doesn't contain any object. + * @zh 获取的对象。如果对象池中没有对象,返回 null。 + * @deprecated since v3.5.0, this is an engine private interface that will be removed in the future. + */ + public _get (): T | null { + if (this.count > 0) { + --this.count; + const cache = this._pool[this.count]; + this._pool[this.count] = null; + return cache; + } + return null; + } + + /** + * @en Put an object into the pool. + * @zh 向对象池返还一个不再需要的对象。 + */ + public put (obj: T): void { + const pool = this._pool; + if (this.count < pool.length) { + if (this._cleanup && this._cleanup(obj) === false) { + return; + } + pool[this.count] = obj; + ++this.count; + } + } + + /** + * @en Resize the pool. + * @zh 设置对象池容量。 + * @param length @en New pool size. + * @zh 新对象池大小。 + */ + public resize (length: number): void { + if (length >= 0) { + this._pool.length = length; + if (this.count > length) { + this.count = length; + } + } + } +} diff --git a/test/test-engine-source/cocos/scene-graph/index.ts b/test/test-engine-source/cocos/scene-graph/index.ts new file mode 100644 index 0000000..813c786 --- /dev/null +++ b/test/test-engine-source/cocos/scene-graph/index.ts @@ -0,0 +1 @@ +export { Prefab } from './prefab'; \ No newline at end of file diff --git a/test/test-engine-source/cocos/scene-graph/prefab/index.ts b/test/test-engine-source/cocos/scene-graph/prefab/index.ts new file mode 100644 index 0000000..fb5c579 --- /dev/null +++ b/test/test-engine-source/cocos/scene-graph/prefab/index.ts @@ -0,0 +1 @@ +export * from './prefab'; diff --git a/test/test-engine-source/cocos/scene-graph/prefab/prefab.ts b/test/test-engine-source/cocos/scene-graph/prefab/prefab.ts new file mode 100644 index 0000000..f7f48cb --- /dev/null +++ b/test/test-engine-source/cocos/scene-graph/prefab/prefab.ts @@ -0,0 +1,12 @@ +import { SUPPORT_JIT } from 'internal:constants'; +import { compile } from '../../serialization/instantiate-jit'; + +export class Prefab { + private _instantiate(): void { + if (SUPPORT_JIT) { + compile(); + } else { + console.error(`Dosn't support JIT`); + } + } +} \ No newline at end of file diff --git a/test/test-engine-source/cocos/serialization/instantiate-jit.ts b/test/test-engine-source/cocos/serialization/instantiate-jit.ts new file mode 100644 index 0000000..2427f58 --- /dev/null +++ b/test/test-engine-source/cocos/serialization/instantiate-jit.ts @@ -0,0 +1,71 @@ +import * as js from '../core/utils/pool'; + +console.log(`I'm instantiate-jit.ts`); + +/*@__PURE__*/ console.log('side-effect'); + +class Impure { + constructor() { + console.log('side-effect'); + } +} + +/*@__PURE__ There may be additional text in the comment */ new Impure(); + +const VAR = 'var '; + +class Declaration { + public declare varName: any; + public declare expression: any; + + constructor (varName, expression) { + this.varName = varName; + this.expression = expression; + } + + public toString (): string { + return `${VAR + this.varName}=${this.expression};`; + } +} + +function mergeDeclaration (statement, expression): any { + if (expression instanceof Declaration) { + return new Declaration(expression.varName, statement + expression.expression); + } else { + return statement + expression; + } +} + +class Assignments { + public static pool: js.Pool<{}>; + + private declare _exps: any[]; + private declare _targetExp: any; + + constructor (targetExpression?) { + this._exps = []; + this._targetExp = targetExpression; + } + public append (key, expression): void { + this._exps.push([key, expression]); + } + public writeCode (codeArray): void { + console.info(`writeCode`); + } +} + +Assignments.pool = new js.Pool((obj: any) => { + obj._exps.length = 0; + obj._targetExp = null; +}, 1); +// HACK: here we've changed the signature of get method +(Assignments.pool.get as any) = function (this: any, targetExpression): Assignments { + const cache: any = this._get() || new Assignments(); + cache._targetExp = targetExpression; + return cache as Assignments; +}; + + +export function compile(): void { + console.log(`>>> jit compile ...`); +} \ No newline at end of file diff --git a/test/test-engine-source/exports/base.ts b/test/test-engine-source/exports/base.ts new file mode 100644 index 0000000..2268182 --- /dev/null +++ b/test/test-engine-source/exports/base.ts @@ -0,0 +1 @@ +export * from '../cocos/scene-graph';