重构项目结构:整理gitignore,移动source目录到根目录,统一依赖管理
This commit is contained in:
86
.gitignore
vendored
86
.gitignore
vendored
@@ -1,11 +1,75 @@
|
||||
/source/node_modules
|
||||
/source/bin
|
||||
/demo/bin-debug
|
||||
/demo/bin-release
|
||||
/.idea
|
||||
/.vscode
|
||||
/demo_wxgame
|
||||
/demo/.wing
|
||||
/demo/.idea
|
||||
/demo/.vscode
|
||||
/source/docs
|
||||
# 依赖目录
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# 构建输出
|
||||
bin/
|
||||
dist/
|
||||
wasm/
|
||||
*.tgz
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
|
||||
# 临时文件
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
|
||||
# IDE 配置
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# 操作系统文件
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# 日志文件
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# 环境配置
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# 测试覆盖率
|
||||
coverage/
|
||||
*.lcov
|
||||
|
||||
# Rust 构建文件
|
||||
src/wasm/*/target/
|
||||
src/wasm/*/pkg/
|
||||
Cargo.lock
|
||||
|
||||
# 包管理器锁文件(保留npm的,忽略其他的)
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
|
||||
# 文档生成
|
||||
docs/api/
|
||||
docs/build/
|
||||
|
||||
# 备份文件
|
||||
*.bak
|
||||
*.backup
|
||||
|
||||
# 演示项目构建产物
|
||||
/demo/bin-debug/
|
||||
/demo/bin-release/
|
||||
/demo/.wing/
|
||||
/demo/.idea/
|
||||
/demo/.vscode/
|
||||
/demo_wxgame/
|
||||
|
||||
1441
source/package-lock.json → package-lock.json
generated
1441
source/package-lock.json → package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/ecs-framework",
|
||||
"version": "2.1.7",
|
||||
"version": "2.1.9",
|
||||
"description": "用于Laya、Cocos等游戏引擎的高性能ECS框架",
|
||||
"main": "bin/index.js",
|
||||
"types": "bin/index.d.ts",
|
||||
@@ -27,10 +27,7 @@
|
||||
"build": "npm run build:wasm && npm run build:ts",
|
||||
"build:watch": "tsc --watch",
|
||||
"rebuild": "npm run clean && npm run clean:wasm && npm run build",
|
||||
"bundle": "npm run build && node scripts/bundle.js",
|
||||
"compress": "npm run build && node scripts/compress.js",
|
||||
"package": "npm run bundle && npm run compress",
|
||||
"build:npm": "npm run build && node scripts/build-single.js",
|
||||
"build:npm": "npm run build && node scripts/build-rollup.js",
|
||||
"test:benchmark": "npm run build && node bin/Testing/Performance/benchmark.js",
|
||||
"test:unit": "npm run build && node bin/Testing/test-runner.js",
|
||||
"benchmark": "node scripts/benchmark.js",
|
||||
@@ -43,11 +40,13 @@
|
||||
"author": "yhh",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/node": "^20.19.0",
|
||||
"archiver": "^7.0.1",
|
||||
"esbuild": "^0.25.5",
|
||||
"rimraf": "^5.0.0",
|
||||
"terser": "^5.41.0",
|
||||
"rollup": "^4.42.0",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
99
rollup.config.js
Normal file
99
rollup.config.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const resolve = require('@rollup/plugin-node-resolve');
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const terser = require('@rollup/plugin-terser');
|
||||
const dts = require('rollup-plugin-dts').default;
|
||||
const { readFileSync } = require('fs');
|
||||
|
||||
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
|
||||
|
||||
const banner = `/**
|
||||
* @esengine/ecs-framework v${pkg.version}
|
||||
* 高性能ECS框架 - 适用于Cocos Creator和Laya引擎
|
||||
*
|
||||
* @author ${pkg.author}
|
||||
* @license ${pkg.license}
|
||||
*/`;
|
||||
|
||||
const external = [];
|
||||
|
||||
const commonPlugins = [
|
||||
resolve({
|
||||
browser: true,
|
||||
preferBuiltins: false
|
||||
}),
|
||||
commonjs({
|
||||
include: /node_modules/
|
||||
})
|
||||
];
|
||||
|
||||
module.exports = [
|
||||
// ES模块构建
|
||||
{
|
||||
input: 'bin/index.js',
|
||||
output: {
|
||||
file: 'dist/index.js',
|
||||
format: 'es',
|
||||
banner,
|
||||
sourcemap: true,
|
||||
exports: 'named'
|
||||
},
|
||||
plugins: [
|
||||
...commonPlugins,
|
||||
terser({
|
||||
format: {
|
||||
comments: /^!/
|
||||
}
|
||||
})
|
||||
],
|
||||
external,
|
||||
treeshake: {
|
||||
moduleSideEffects: false,
|
||||
propertyReadSideEffects: false,
|
||||
unknownGlobalSideEffects: false
|
||||
}
|
||||
},
|
||||
|
||||
// UMD构建(可选)
|
||||
{
|
||||
input: 'bin/index.js',
|
||||
output: {
|
||||
file: 'dist/index.umd.js',
|
||||
format: 'umd',
|
||||
name: 'ECSFramework',
|
||||
banner,
|
||||
sourcemap: true,
|
||||
exports: 'named'
|
||||
},
|
||||
plugins: [
|
||||
...commonPlugins,
|
||||
terser({
|
||||
format: {
|
||||
comments: /^!/
|
||||
}
|
||||
})
|
||||
],
|
||||
external,
|
||||
treeshake: {
|
||||
moduleSideEffects: false
|
||||
}
|
||||
},
|
||||
|
||||
// 类型定义构建
|
||||
{
|
||||
input: 'bin/index.d.ts',
|
||||
output: {
|
||||
file: 'dist/index.d.ts',
|
||||
format: 'es',
|
||||
banner: `/**
|
||||
* @esengine/ecs-framework v${pkg.version}
|
||||
* TypeScript definitions
|
||||
*/`
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
respectExternal: true
|
||||
})
|
||||
],
|
||||
external: []
|
||||
}
|
||||
];
|
||||
142
scripts/build-rollup.js
Normal file
142
scripts/build-rollup.js
Normal file
@@ -0,0 +1,142 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
console.log('🚀 使用 Rollup 构建npm包...');
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// 清理旧的dist目录
|
||||
if (fs.existsSync('./dist')) {
|
||||
console.log('🧹 清理旧的构建文件...');
|
||||
execSync('rimraf ./dist', { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
// 执行Rollup构建
|
||||
console.log('📦 执行 Rollup 构建...');
|
||||
execSync('rollup -c', { stdio: 'inherit' });
|
||||
|
||||
// 生成package.json
|
||||
console.log('📋 生成 package.json...');
|
||||
generatePackageJson();
|
||||
|
||||
// 复制其他文件
|
||||
console.log('📁 复制必要文件...');
|
||||
copyFiles();
|
||||
|
||||
// 输出构建结果
|
||||
showBuildResults();
|
||||
|
||||
console.log('✅ 构建完成!');
|
||||
console.log('\n🚀 发布命令:');
|
||||
console.log('cd dist && npm publish');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 构建失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function generatePackageJson() {
|
||||
const sourcePackage = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
|
||||
const distPackage = {
|
||||
name: sourcePackage.name,
|
||||
version: sourcePackage.version,
|
||||
description: sourcePackage.description,
|
||||
main: 'index.js',
|
||||
module: 'index.js',
|
||||
types: 'index.d.ts',
|
||||
type: 'module',
|
||||
exports: {
|
||||
'.': {
|
||||
import: './index.js',
|
||||
types: './index.d.ts'
|
||||
}
|
||||
},
|
||||
files: [
|
||||
'index.js',
|
||||
'index.js.map',
|
||||
'index.umd.js',
|
||||
'index.umd.js.map',
|
||||
'index.d.ts',
|
||||
'wasm',
|
||||
'README.md',
|
||||
'LICENSE'
|
||||
],
|
||||
keywords: [
|
||||
'ecs',
|
||||
'entity-component-system',
|
||||
'game-engine',
|
||||
'typescript',
|
||||
'cocos-creator',
|
||||
'laya',
|
||||
'rollup'
|
||||
],
|
||||
author: sourcePackage.author,
|
||||
license: sourcePackage.license,
|
||||
repository: sourcePackage.repository,
|
||||
bugs: sourcePackage.bugs,
|
||||
homepage: sourcePackage.homepage,
|
||||
engines: {
|
||||
node: '>=16.0.0'
|
||||
},
|
||||
sideEffects: false
|
||||
};
|
||||
|
||||
fs.writeFileSync('./dist/package.json', JSON.stringify(distPackage, null, 2));
|
||||
}
|
||||
|
||||
function copyFiles() {
|
||||
const filesToCopy = [
|
||||
{ src: '../README.md', dest: './dist/README.md' },
|
||||
{ src: '../LICENSE', dest: './dist/LICENSE' }
|
||||
];
|
||||
|
||||
filesToCopy.forEach(({ src, dest }) => {
|
||||
if (fs.existsSync(src)) {
|
||||
fs.copyFileSync(src, dest);
|
||||
console.log(` ✓ 复制: ${path.basename(dest)}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 复制WASM文件(过滤.gitignore)
|
||||
const wasmDir = './bin/wasm';
|
||||
if (fs.existsSync(wasmDir)) {
|
||||
const distWasmDir = './dist/wasm';
|
||||
if (!fs.existsSync(distWasmDir)) {
|
||||
fs.mkdirSync(distWasmDir);
|
||||
}
|
||||
|
||||
let copiedCount = 0;
|
||||
fs.readdirSync(wasmDir).forEach(file => {
|
||||
// 过滤掉.gitignore文件
|
||||
if (file !== '.gitignore') {
|
||||
fs.copyFileSync(
|
||||
path.join(wasmDir, file),
|
||||
path.join(distWasmDir, file)
|
||||
);
|
||||
copiedCount++;
|
||||
}
|
||||
});
|
||||
if (copiedCount > 0) {
|
||||
console.log(` ✓ 复制: ${copiedCount}个WASM文件`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showBuildResults() {
|
||||
const distDir = './dist';
|
||||
const files = ['index.js', 'index.umd.js', 'index.d.ts'];
|
||||
|
||||
console.log('\n📊 构建结果:');
|
||||
files.forEach(file => {
|
||||
const filePath = path.join(distDir, file);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const size = fs.statSync(filePath).size;
|
||||
console.log(` ${file}: ${(size / 1024).toFixed(1)}KB`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
|
||||
},
|
||||
"modules": "auto"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
92
source/.gitignore
vendored
92
source/.gitignore
vendored
@@ -1,92 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# Build output
|
||||
bin/
|
||||
dev-bin/
|
||||
dist/
|
||||
|
||||
# WASM build artifacts
|
||||
src/wasm/rust-ecs-core/target/
|
||||
src/wasm/rust-ecs-core/pkg/
|
||||
*.wasm
|
||||
wasm-pack.log
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Environment files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
201
source/LICENSE
201
source/LICENSE
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"targets": {
|
||||
"debug": {
|
||||
"outFile": "bin/ecs-core.wasm",
|
||||
"textFile": "bin/ecs-core.wat",
|
||||
"sourceMap": true,
|
||||
"debug": true
|
||||
},
|
||||
"release": {
|
||||
"outFile": "bin/ecs-core.wasm",
|
||||
"optimizeLevel": 3,
|
||||
"shrinkLevel": 2,
|
||||
"converge": false,
|
||||
"noAssert": false
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"bindings": "esm",
|
||||
"exportRuntime": true
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/**
|
||||
* ECS框架构建配置
|
||||
* 针对Laya、Cocos等游戏引擎优化
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
// 输入配置
|
||||
input: {
|
||||
entry: './src/index.ts',
|
||||
tsconfig: './tsconfig.json'
|
||||
},
|
||||
|
||||
// 输出配置
|
||||
output: {
|
||||
dir: './bin',
|
||||
formats: ['es', 'cjs', 'umd'], // ES模块、CommonJS、UMD
|
||||
filename: {
|
||||
es: 'ecs-framework.esm.js',
|
||||
cjs: 'ecs-framework.cjs.js',
|
||||
umd: 'ecs-framework.umd.js'
|
||||
},
|
||||
minify: true,
|
||||
sourcemap: true
|
||||
},
|
||||
|
||||
// WebAssembly支持配置
|
||||
wasm: {
|
||||
enabled: false, // 暂时禁用,未来启用
|
||||
modules: {
|
||||
// 计划迁移到WebAssembly的模块
|
||||
core: {
|
||||
entry: './src/wasm/core.ts',
|
||||
output: 'ecs-core.wasm',
|
||||
features: ['query-system', 'math-utils']
|
||||
},
|
||||
physics: {
|
||||
entry: './src/wasm/physics.ts',
|
||||
output: 'ecs-physics.wasm',
|
||||
features: ['collision-detection', 'spatial-hash']
|
||||
}
|
||||
},
|
||||
// AssemblyScript配置
|
||||
assemblyscript: {
|
||||
target: 'wasm32',
|
||||
optimize: true,
|
||||
runtime: 'minimal'
|
||||
}
|
||||
},
|
||||
|
||||
// 游戏引擎特定配置
|
||||
engines: {
|
||||
laya: {
|
||||
// Laya引擎特定优化
|
||||
target: 'es5',
|
||||
polyfills: ['Promise', 'Object.assign'],
|
||||
globals: ['Laya'],
|
||||
wasm: {
|
||||
// Laya环境下的WebAssembly配置
|
||||
loader: 'laya-wasm-loader',
|
||||
fallback: true // 支持降级到JavaScript
|
||||
}
|
||||
},
|
||||
|
||||
cocos: {
|
||||
// Cocos引擎特定优化
|
||||
target: 'es6',
|
||||
polyfills: [],
|
||||
globals: ['cc'],
|
||||
wasm: {
|
||||
// Cocos环境下的WebAssembly配置
|
||||
loader: 'cocos-wasm-loader',
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 小游戏平台优化
|
||||
platforms: {
|
||||
wechat: {
|
||||
// 微信小游戏优化
|
||||
maxSize: '4MB',
|
||||
treeshaking: true,
|
||||
compression: 'gzip',
|
||||
wasm: {
|
||||
// 微信小游戏WebAssembly支持
|
||||
enabled: true,
|
||||
maxWasmSize: '2MB', // WebAssembly模块大小限制
|
||||
preload: ['ecs-core.wasm'] // 预加载核心模块
|
||||
}
|
||||
},
|
||||
alipay: {
|
||||
// 支付宝小游戏优化
|
||||
maxSize: '4MB',
|
||||
treeshaking: true,
|
||||
compression: 'gzip',
|
||||
wasm: {
|
||||
enabled: true,
|
||||
maxWasmSize: '2MB'
|
||||
}
|
||||
},
|
||||
bytedance: {
|
||||
// 字节跳动小游戏优化
|
||||
maxSize: '4MB',
|
||||
treeshaking: true,
|
||||
compression: 'gzip',
|
||||
wasm: {
|
||||
enabled: true,
|
||||
maxWasmSize: '2MB'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 性能优化配置
|
||||
optimization: {
|
||||
// 启用Tree Shaking
|
||||
treeshaking: true,
|
||||
// 代码分割
|
||||
codeSplitting: false, // 小游戏通常不需要代码分割
|
||||
// 压缩配置
|
||||
minify: {
|
||||
removeComments: true,
|
||||
removeConsole: false, // 保留console用于调试
|
||||
removeDebugger: true
|
||||
},
|
||||
// 内联小文件
|
||||
inlineThreshold: 1024,
|
||||
// WebAssembly优化
|
||||
wasm: {
|
||||
// 启用WebAssembly优化
|
||||
optimize: true,
|
||||
// 内存配置
|
||||
memory: {
|
||||
initial: 1, // 初始内存页数 (64KB per page)
|
||||
maximum: 16, // 最大内存页数
|
||||
shared: false // 是否共享内存
|
||||
},
|
||||
// 导出配置
|
||||
exports: {
|
||||
memory: true,
|
||||
table: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 开发配置
|
||||
development: {
|
||||
sourcemap: true,
|
||||
hotReload: false, // 小游戏不支持热重载
|
||||
debugMode: true
|
||||
},
|
||||
|
||||
// 生产配置
|
||||
production: {
|
||||
sourcemap: false,
|
||||
minify: true,
|
||||
optimization: true,
|
||||
bundleAnalyzer: true
|
||||
},
|
||||
|
||||
// 实验性功能
|
||||
experimental: {
|
||||
// 混合架构支持
|
||||
hybrid: {
|
||||
enabled: true,
|
||||
// 自动检测WebAssembly支持
|
||||
autoDetect: true,
|
||||
// 性能基准测试
|
||||
benchmark: true,
|
||||
// 降级策略
|
||||
fallback: {
|
||||
strategy: 'graceful', // 优雅降级
|
||||
modules: ['core', 'physics'] // 支持降级的模块
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,162 +0,0 @@
|
||||
'use strict';
|
||||
const gulp = require('gulp');
|
||||
const { series, parallel } = require('gulp');
|
||||
const terser = require('gulp-terser');
|
||||
const inject = require('gulp-inject-string');
|
||||
const ts = require('gulp-typescript');
|
||||
const merge = require('merge2');
|
||||
const typescript = require('gulp-typescript');
|
||||
const concat = require('gulp-concat');
|
||||
const replace = require('gulp-string-replace');
|
||||
|
||||
// TypeScript项目配置
|
||||
const tsProject = ts.createProject('tsconfig.json', {
|
||||
module: 'es2020',
|
||||
target: 'es2018'
|
||||
});
|
||||
|
||||
function buildJs() {
|
||||
return tsProject.src()
|
||||
.pipe(tsProject())
|
||||
.js.pipe(inject.replace('var es;', ''))
|
||||
.pipe(inject.prepend('window.es = {};\n'))
|
||||
.pipe(inject.replace('var __extends =', 'window.__extends ='))
|
||||
.pipe(terser())
|
||||
.pipe(gulp.dest('./bin'));
|
||||
}
|
||||
|
||||
function buildDts() {
|
||||
return tsProject.src()
|
||||
.pipe(tsProject())
|
||||
// .dts.pipe(inject.append('import e = framework;'))
|
||||
.pipe(gulp.dest('./bin'));
|
||||
}
|
||||
|
||||
// 构建ES模块版本
|
||||
gulp.task('build-esm', function() {
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(tsProject())
|
||||
.pipe(concat('ecs-framework.esm.js'))
|
||||
.pipe(inject.prepend('// ECS Framework - ES Module Version\n'))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
// 构建UMD版本(兼容各种游戏引擎)
|
||||
gulp.task('build-umd', function() {
|
||||
const umdProject = ts.createProject('tsconfig.json', {
|
||||
module: 'umd',
|
||||
target: 'es5'
|
||||
});
|
||||
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(umdProject())
|
||||
.pipe(concat('ecs-framework.umd.js'))
|
||||
.pipe(inject.prepend(`// ECS Framework - UMD Version
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(global = global || self, factory(global.ECS = {}));
|
||||
}(this, (function (exports) { 'use strict';
|
||||
`))
|
||||
.pipe(inject.append('\n})));'))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
// 构建Laya引擎专用版本
|
||||
gulp.task('build-laya', function() {
|
||||
const layaProject = ts.createProject('tsconfig.json', {
|
||||
module: 'none',
|
||||
target: 'es5'
|
||||
});
|
||||
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(layaProject())
|
||||
.pipe(concat('ecs-framework.laya.js'))
|
||||
.pipe(replace(/export\s+/g, ''))
|
||||
.pipe(inject.prepend(`// ECS Framework - Laya Engine Version
|
||||
var ECS = ECS || {};
|
||||
(function(ECS) {
|
||||
`))
|
||||
.pipe(inject.append('\n})(ECS);'))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 构建Cocos引擎专用版本
|
||||
gulp.task('build-cocos', function() {
|
||||
const cocosProject = ts.createProject('tsconfig.json', {
|
||||
module: 'es2020',
|
||||
target: 'es2018'
|
||||
});
|
||||
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(cocosProject())
|
||||
.pipe(concat('ecs-framework.cocos.js'))
|
||||
.pipe(inject.prepend('// ECS Framework - Cocos Engine Version\n'))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
// 压缩所有构建文件
|
||||
gulp.task('minify', function() {
|
||||
return gulp.src(['bin/*.js', '!bin/*.min.js'])
|
||||
.pipe(terser({
|
||||
compress: {
|
||||
drop_console: false, // 保留console用于调试
|
||||
drop_debugger: true,
|
||||
pure_funcs: ['console.log'] // 可选择性移除console.log
|
||||
},
|
||||
mangle: {
|
||||
keep_fnames: true // 保留函数名用于调试
|
||||
},
|
||||
format: {
|
||||
comments: false
|
||||
}
|
||||
}))
|
||||
.pipe(concat(function(file) {
|
||||
return file.basename.replace('.js', '.min.js');
|
||||
}))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
// 生成类型定义文件
|
||||
gulp.task('build-types', function() {
|
||||
const dtsProject = ts.createProject('tsconfig.json', {
|
||||
declaration: true,
|
||||
emitDeclarationOnly: true
|
||||
});
|
||||
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(dtsProject())
|
||||
.pipe(concat('ecs-framework.d.ts'))
|
||||
.pipe(gulp.dest('bin/'));
|
||||
});
|
||||
|
||||
// 清理构建目录
|
||||
gulp.task('clean', function() {
|
||||
const del = require('del');
|
||||
return del(['bin/**/*']);
|
||||
});
|
||||
|
||||
// 开发构建(快速,包含源码映射)
|
||||
gulp.task('build-dev', gulp.series('clean', 'build-esm', 'build-types'));
|
||||
|
||||
// 生产构建(完整,包含所有版本和压缩)
|
||||
gulp.task('build', gulp.series(
|
||||
'clean',
|
||||
gulp.parallel('build-esm', 'build-umd', 'build-laya', 'build-cocos'),
|
||||
'build-types',
|
||||
'minify'
|
||||
));
|
||||
|
||||
// 监听文件变化
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch('src/**/*.ts', gulp.series('build-dev'));
|
||||
});
|
||||
|
||||
// 默认任务
|
||||
gulp.task('default', gulp.series('build'));
|
||||
|
||||
exports.buildJs = buildJs;
|
||||
exports.buildDts = buildDts;
|
||||
exports.build = series(buildJs, buildDts);
|
||||
@@ -1,285 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const esbuild = require('esbuild');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* ECS Framework 单文件构建脚本
|
||||
* 专门用于npm包发布的单文件版本
|
||||
*/
|
||||
|
||||
const config = {
|
||||
// 输入配置
|
||||
entryPoint: './bin/index.js',
|
||||
|
||||
// 输出配置
|
||||
outputDir: './dist',
|
||||
outputFile: 'index.js',
|
||||
|
||||
// 压缩配置
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
|
||||
// 目标环境 - 支持BigInt等ES2020特性
|
||||
target: ['es2020'],
|
||||
format: 'esm',
|
||||
|
||||
// npm包配置
|
||||
generatePackageJson: true,
|
||||
generateTypes: true
|
||||
};
|
||||
|
||||
async function buildSingleFile() {
|
||||
console.log('🚀 构建单文件npm包...');
|
||||
|
||||
try {
|
||||
// 确保输出目录存在
|
||||
if (!fs.existsSync(config.outputDir)) {
|
||||
fs.mkdirSync(config.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 第一步:使用esbuild打包
|
||||
console.log('📦 使用 esbuild 打包...');
|
||||
|
||||
const result = await esbuild.build({
|
||||
entryPoints: [config.entryPoint],
|
||||
bundle: true,
|
||||
minify: config.minify,
|
||||
sourcemap: config.sourcemap,
|
||||
target: config.target,
|
||||
format: config.format,
|
||||
outfile: path.join(config.outputDir, config.outputFile),
|
||||
platform: 'browser', // 浏览器环境
|
||||
|
||||
// 外部依赖
|
||||
external: [],
|
||||
|
||||
// 定义Node.js模块的浏览器替代
|
||||
inject: [],
|
||||
|
||||
// 定义全局变量
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
'require': 'undefined',
|
||||
'__filename': '""',
|
||||
'__dirname': '""'
|
||||
},
|
||||
|
||||
// 元信息
|
||||
metafile: true,
|
||||
|
||||
// 日志级别
|
||||
logLevel: 'info',
|
||||
|
||||
// 保持类名(便于调试)
|
||||
keepNames: true,
|
||||
|
||||
// 生成更兼容的代码
|
||||
legalComments: 'none'
|
||||
});
|
||||
|
||||
// 显示打包结果
|
||||
if (result.metafile) {
|
||||
const analysis = await esbuild.analyzeMetafile(result.metafile);
|
||||
console.log('📊 打包分析:');
|
||||
console.log(analysis);
|
||||
}
|
||||
|
||||
// 第二步:生成类型定义文件
|
||||
if (config.generateTypes) {
|
||||
console.log('📝 生成类型定义文件...');
|
||||
await generateTypeDefinitions();
|
||||
}
|
||||
|
||||
// 第三步:生成package.json
|
||||
if (config.generatePackageJson) {
|
||||
console.log('📋 生成package.json...');
|
||||
await generatePackageJson();
|
||||
}
|
||||
|
||||
// 第四步:复制必要文件
|
||||
await copyEssentialFiles();
|
||||
|
||||
console.log('✅ 单文件构建完成!');
|
||||
console.log(`📄 主文件: ${path.join(config.outputDir, config.outputFile)}`);
|
||||
|
||||
// 显示文件大小
|
||||
const stats = fs.statSync(path.join(config.outputDir, config.outputFile));
|
||||
console.log(`📏 文件大小: ${(stats.size / 1024).toFixed(2)} KB`);
|
||||
|
||||
console.log('\n🚀 发布到npm:');
|
||||
console.log('cd dist && npm publish');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 构建失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成类型定义文件
|
||||
*/
|
||||
async function generateTypeDefinitions() {
|
||||
const sourceTypesFile = './bin/index.d.ts';
|
||||
const targetTypesFile = path.join(config.outputDir, 'index.d.ts');
|
||||
|
||||
if (fs.existsSync(sourceTypesFile)) {
|
||||
// 读取原始类型定义
|
||||
let typesContent = fs.readFileSync(sourceTypesFile, 'utf8');
|
||||
|
||||
// 处理相对路径导入,将其转换为绝对导入
|
||||
typesContent = typesContent.replace(/from ['"]\.\//g, "from './");
|
||||
typesContent = typesContent.replace(/from ['"]\.\.\//g, "from './");
|
||||
|
||||
// 添加版本信息注释
|
||||
const header = `/**
|
||||
* @esengine/ecs-framework
|
||||
* 高性能ECS框架 - 适用于Cocos Creator和Laya引擎
|
||||
* 版本: ${require('../package.json').version}
|
||||
* 构建时间: ${new Date().toISOString()}
|
||||
*/
|
||||
|
||||
`;
|
||||
|
||||
fs.writeFileSync(targetTypesFile, header + typesContent);
|
||||
console.log(` ✓ 生成: ${targetTypesFile}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成npm包的package.json
|
||||
*/
|
||||
async function generatePackageJson() {
|
||||
const sourcePackage = require('../package.json');
|
||||
|
||||
// 创建完全干净的package.json,只包含发布必需的字段
|
||||
const distPackage = {};
|
||||
|
||||
// 按顺序添加字段,确保没有任何开发相关字段
|
||||
distPackage.name = sourcePackage.name;
|
||||
distPackage.version = sourcePackage.version;
|
||||
distPackage.description = sourcePackage.description;
|
||||
distPackage.main = 'index.js';
|
||||
distPackage.types = 'index.d.ts';
|
||||
distPackage.module = 'index.js';
|
||||
distPackage.type = 'module';
|
||||
|
||||
// 导出配置
|
||||
distPackage.exports = {
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./index.d.ts"
|
||||
}
|
||||
};
|
||||
|
||||
// 文件配置
|
||||
distPackage.files = [
|
||||
'index.js',
|
||||
'index.js.map',
|
||||
'index.d.ts',
|
||||
'wasm/**/*',
|
||||
'README.md',
|
||||
'LICENSE'
|
||||
];
|
||||
|
||||
// 关键词
|
||||
distPackage.keywords = [
|
||||
...sourcePackage.keywords,
|
||||
'single-file',
|
||||
'bundled',
|
||||
'minified'
|
||||
];
|
||||
|
||||
// 元信息
|
||||
distPackage.author = sourcePackage.author;
|
||||
distPackage.license = sourcePackage.license;
|
||||
|
||||
// Repository信息
|
||||
distPackage.repository = {
|
||||
type: 'git',
|
||||
url: 'git+https://github.com/esengine/ecs-framework.git'
|
||||
};
|
||||
|
||||
// 发布配置
|
||||
distPackage.publishConfig = {
|
||||
access: 'public'
|
||||
};
|
||||
|
||||
// 引擎兼容性
|
||||
distPackage.engines = {
|
||||
node: '>=16.0.0'
|
||||
};
|
||||
|
||||
const packagePath = path.join(config.outputDir, 'package.json');
|
||||
fs.writeFileSync(packagePath, JSON.stringify(distPackage, null, 2));
|
||||
console.log(` ✓ 生成: ${packagePath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制必要文件
|
||||
*/
|
||||
async function copyEssentialFiles() {
|
||||
console.log('📁 复制必要文件...');
|
||||
|
||||
const filesToCopy = [
|
||||
{ src: '../README.md', dest: 'README.md' },
|
||||
{ src: '../LICENSE', dest: 'LICENSE', optional: true }
|
||||
];
|
||||
|
||||
for (const file of filesToCopy) {
|
||||
const srcPath = path.resolve(file.src);
|
||||
const destPath = path.join(config.outputDir, file.dest);
|
||||
|
||||
if (fs.existsSync(srcPath)) {
|
||||
fs.copyFileSync(srcPath, destPath);
|
||||
console.log(` ✓ 复制: ${file.dest}`);
|
||||
} else if (!file.optional) {
|
||||
console.warn(` ⚠️ 文件不存在: ${srcPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 复制WASM文件
|
||||
await copyWasmFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制WASM文件
|
||||
*/
|
||||
async function copyWasmFiles() {
|
||||
const wasmSrcDir = './bin/wasm';
|
||||
const wasmDestDir = path.join(config.outputDir, 'wasm');
|
||||
|
||||
if (fs.existsSync(wasmSrcDir)) {
|
||||
console.log('📦 复制WASM文件...');
|
||||
|
||||
// 创建目标目录
|
||||
if (!fs.existsSync(wasmDestDir)) {
|
||||
fs.mkdirSync(wasmDestDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 复制所有WASM相关文件
|
||||
const wasmFiles = fs.readdirSync(wasmSrcDir);
|
||||
for (const file of wasmFiles) {
|
||||
// 排除.gitignore文件
|
||||
if (file === '.gitignore') continue;
|
||||
|
||||
const srcPath = path.join(wasmSrcDir, file);
|
||||
const destPath = path.join(wasmDestDir, file);
|
||||
|
||||
if (fs.statSync(srcPath).isFile()) {
|
||||
fs.copyFileSync(srcPath, destPath);
|
||||
console.log(` ✓ 复制WASM: ${file}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn(' ⚠️ WASM目录不存在: ' + wasmSrcDir);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行构建
|
||||
if (require.main === module) {
|
||||
buildSingleFile();
|
||||
}
|
||||
|
||||
module.exports = { buildSingleFile, config };
|
||||
@@ -1,210 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const esbuild = require('esbuild');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { minify } = require('terser');
|
||||
|
||||
/**
|
||||
* ECS Framework 打包脚本
|
||||
* 将bin目录中的所有文件合并成一个压缩文件
|
||||
*/
|
||||
|
||||
const config = {
|
||||
// 输入配置
|
||||
entryPoint: './bin/index.js',
|
||||
|
||||
// 输出配置
|
||||
outputDir: './dist',
|
||||
outputFile: 'ecs-framework.min.js',
|
||||
|
||||
// 压缩配置
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
|
||||
// 包含WASM文件
|
||||
includeWasm: true,
|
||||
|
||||
// 目标环境
|
||||
target: ['es2017'],
|
||||
format: 'esm'
|
||||
};
|
||||
|
||||
async function createBundle() {
|
||||
console.log('🚀 开始打包 ECS Framework...');
|
||||
|
||||
try {
|
||||
// 确保输出目录存在
|
||||
if (!fs.existsSync(config.outputDir)) {
|
||||
fs.mkdirSync(config.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 第一步:使用esbuild打包
|
||||
console.log('📦 使用 esbuild 打包...');
|
||||
|
||||
const result = await esbuild.build({
|
||||
entryPoints: [config.entryPoint],
|
||||
bundle: true,
|
||||
minify: config.minify,
|
||||
sourcemap: config.sourcemap,
|
||||
target: config.target,
|
||||
format: config.format,
|
||||
outfile: path.join(config.outputDir, config.outputFile),
|
||||
platform: 'browser',
|
||||
|
||||
// 外部依赖(如果有的话)
|
||||
external: [],
|
||||
|
||||
// 定义全局变量
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
},
|
||||
|
||||
// 处理WASM文件
|
||||
loader: {
|
||||
'.wasm': 'binary'
|
||||
},
|
||||
|
||||
// 插件配置
|
||||
plugins: [
|
||||
{
|
||||
name: 'wasm-loader',
|
||||
setup(build) {
|
||||
// 处理WASM文件导入
|
||||
build.onLoad({ filter: /\.wasm$/ }, async (args) => {
|
||||
const wasmBuffer = await fs.promises.readFile(args.path);
|
||||
const base64 = wasmBuffer.toString('base64');
|
||||
|
||||
return {
|
||||
contents: `
|
||||
const wasmBase64 = "${base64}";
|
||||
const wasmBinary = Uint8Array.from(atob(wasmBase64), c => c.charCodeAt(0));
|
||||
export default wasmBinary;
|
||||
`,
|
||||
loader: 'js'
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// 元信息
|
||||
metafile: true,
|
||||
|
||||
// 日志级别
|
||||
logLevel: 'info'
|
||||
});
|
||||
|
||||
// 显示打包结果
|
||||
if (result.metafile) {
|
||||
const analysis = await esbuild.analyzeMetafile(result.metafile);
|
||||
console.log('📊 打包分析:');
|
||||
console.log(analysis);
|
||||
}
|
||||
|
||||
// 第二步:复制WASM文件到dist目录
|
||||
if (config.includeWasm) {
|
||||
console.log('📁 复制 WASM 文件...');
|
||||
await copyWasmFiles();
|
||||
}
|
||||
|
||||
// 第三步:生成压缩包信息
|
||||
await generateBundleInfo();
|
||||
|
||||
console.log('✅ 打包完成!');
|
||||
console.log(`📄 输出文件: ${path.join(config.outputDir, config.outputFile)}`);
|
||||
|
||||
// 显示文件大小
|
||||
const stats = fs.statSync(path.join(config.outputDir, config.outputFile));
|
||||
console.log(`📏 文件大小: ${(stats.size / 1024).toFixed(2)} KB`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 打包失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制WASM文件到dist目录
|
||||
*/
|
||||
async function copyWasmFiles() {
|
||||
const wasmDir = './bin/wasm';
|
||||
const distWasmDir = path.join(config.outputDir, 'wasm');
|
||||
|
||||
if (fs.existsSync(wasmDir)) {
|
||||
if (!fs.existsSync(distWasmDir)) {
|
||||
fs.mkdirSync(distWasmDir, { recursive: true });
|
||||
}
|
||||
|
||||
const wasmFiles = fs.readdirSync(wasmDir);
|
||||
for (const file of wasmFiles) {
|
||||
// 排除 .gitignore 文件
|
||||
if (file === '.gitignore') {
|
||||
console.log(` ⏭️ 跳过: ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const srcPath = path.join(wasmDir, file);
|
||||
const destPath = path.join(distWasmDir, file);
|
||||
|
||||
if (fs.statSync(srcPath).isFile()) {
|
||||
fs.copyFileSync(srcPath, destPath);
|
||||
console.log(` ✓ 复制: ${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成打包信息文件
|
||||
*/
|
||||
async function generateBundleInfo() {
|
||||
const bundleInfo = {
|
||||
name: '@esengine/ecs-framework',
|
||||
version: require('../package.json').version,
|
||||
buildTime: new Date().toISOString(),
|
||||
files: {
|
||||
main: config.outputFile,
|
||||
sourcemap: config.outputFile + '.map',
|
||||
wasm: 'wasm/'
|
||||
},
|
||||
target: config.target,
|
||||
format: config.format,
|
||||
minified: config.minify,
|
||||
size: {
|
||||
main: fs.statSync(path.join(config.outputDir, config.outputFile)).size,
|
||||
wasm: getWasmSize()
|
||||
}
|
||||
};
|
||||
|
||||
const infoPath = path.join(config.outputDir, 'bundle-info.json');
|
||||
fs.writeFileSync(infoPath, JSON.stringify(bundleInfo, null, 2));
|
||||
console.log(`📋 生成打包信息: ${infoPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取WASM文件总大小
|
||||
*/
|
||||
function getWasmSize() {
|
||||
const wasmDir = path.join(config.outputDir, 'wasm');
|
||||
let totalSize = 0;
|
||||
|
||||
if (fs.existsSync(wasmDir)) {
|
||||
const files = fs.readdirSync(wasmDir);
|
||||
for (const file of files) {
|
||||
const filePath = path.join(wasmDir, file);
|
||||
if (fs.statSync(filePath).isFile()) {
|
||||
totalSize += fs.statSync(filePath).size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
// 运行打包
|
||||
if (require.main === module) {
|
||||
createBundle();
|
||||
}
|
||||
|
||||
module.exports = { createBundle, config };
|
||||
@@ -1,165 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const archiver = require('archiver');
|
||||
|
||||
/**
|
||||
* ECS Framework 压缩脚本
|
||||
* 将bin目录压缩成ZIP文件
|
||||
*/
|
||||
|
||||
const config = {
|
||||
sourceDir: './bin',
|
||||
outputDir: './dist',
|
||||
outputFile: 'ecs-framework-bin.zip',
|
||||
compressionLevel: 9, // 最高压缩级别
|
||||
includeSourceMaps: false // 是否包含source map文件
|
||||
};
|
||||
|
||||
async function createCompressedArchive() {
|
||||
console.log('🗜️ 开始压缩 bin 目录...');
|
||||
|
||||
try {
|
||||
// 确保输出目录存在
|
||||
if (!fs.existsSync(config.outputDir)) {
|
||||
fs.mkdirSync(config.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const outputPath = path.join(config.outputDir, config.outputFile);
|
||||
const output = fs.createWriteStream(outputPath);
|
||||
const archive = archiver('zip', {
|
||||
zlib: { level: config.compressionLevel }
|
||||
});
|
||||
|
||||
// 监听事件
|
||||
output.on('close', () => {
|
||||
const sizeKB = (archive.pointer() / 1024).toFixed(2);
|
||||
console.log('✅ 压缩完成!');
|
||||
console.log(`📄 输出文件: ${outputPath}`);
|
||||
console.log(`📏 压缩后大小: ${sizeKB} KB`);
|
||||
console.log(`📊 压缩了 ${archive.pointer()} 字节`);
|
||||
|
||||
// 生成压缩信息
|
||||
generateCompressionInfo(outputPath, archive.pointer());
|
||||
});
|
||||
|
||||
output.on('end', () => {
|
||||
console.log('数据已全部写入');
|
||||
});
|
||||
|
||||
archive.on('warning', (err) => {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.warn('⚠️ 警告:', err);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
archive.on('error', (err) => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// 连接输出流
|
||||
archive.pipe(output);
|
||||
|
||||
// 添加bin目录中的所有文件
|
||||
console.log('📁 添加文件到压缩包...');
|
||||
|
||||
// 递归添加目录
|
||||
archive.directory(config.sourceDir, false, (entry) => {
|
||||
// 过滤文件
|
||||
if (!config.includeSourceMaps && entry.name.endsWith('.map')) {
|
||||
console.log(` ⏭️ 跳过: ${entry.name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 排除 .gitignore 文件
|
||||
if (entry.name === '.gitignore' || entry.name.endsWith('/.gitignore')) {
|
||||
console.log(` ⏭️ 跳过: ${entry.name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(` ✓ 添加: ${entry.name}`);
|
||||
return entry;
|
||||
});
|
||||
|
||||
// 完成压缩
|
||||
await archive.finalize();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 压缩失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成压缩信息文件
|
||||
*/
|
||||
function generateCompressionInfo(outputPath, compressedSize) {
|
||||
const originalSize = getDirectorySize(config.sourceDir);
|
||||
const compressionRatio = ((originalSize - compressedSize) / originalSize * 100).toFixed(2);
|
||||
|
||||
const compressionInfo = {
|
||||
name: '@esengine/ecs-framework',
|
||||
version: require('../package.json').version,
|
||||
compressionTime: new Date().toISOString(),
|
||||
files: {
|
||||
archive: config.outputFile
|
||||
},
|
||||
size: {
|
||||
original: originalSize,
|
||||
compressed: compressedSize,
|
||||
ratio: `${compressionRatio}%`
|
||||
},
|
||||
settings: {
|
||||
compressionLevel: config.compressionLevel,
|
||||
includeSourceMaps: config.includeSourceMaps
|
||||
}
|
||||
};
|
||||
|
||||
const infoPath = path.join(config.outputDir, 'compression-info.json');
|
||||
fs.writeFileSync(infoPath, JSON.stringify(compressionInfo, null, 2));
|
||||
console.log(`📋 生成压缩信息: ${infoPath}`);
|
||||
console.log(`📈 压缩率: ${compressionRatio}%`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取目录总大小
|
||||
*/
|
||||
function getDirectorySize(dirPath) {
|
||||
let totalSize = 0;
|
||||
|
||||
function calculateSize(currentPath) {
|
||||
const stats = fs.statSync(currentPath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
const files = fs.readdirSync(currentPath);
|
||||
for (const file of files) {
|
||||
calculateSize(path.join(currentPath, file));
|
||||
}
|
||||
} else {
|
||||
// 过滤source map文件
|
||||
if (!config.includeSourceMaps && currentPath.endsWith('.map')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 排除 .gitignore 文件
|
||||
if (currentPath.endsWith('.gitignore')) {
|
||||
return;
|
||||
}
|
||||
|
||||
totalSize += stats.size;
|
||||
}
|
||||
}
|
||||
|
||||
calculateSize(dirPath);
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
// 运行压缩
|
||||
if (require.main === module) {
|
||||
createCompressedArchive();
|
||||
}
|
||||
|
||||
module.exports = { createCompressedArchive, config };
|
||||
@@ -1,291 +0,0 @@
|
||||
/**
|
||||
* WASM ECS核心模块
|
||||
*/
|
||||
|
||||
import { EntityId, ComponentMask, QueryResult, PerformanceStats, WasmEcsCoreInstance, WasmModule } from './types';
|
||||
import { WasmLoader } from './loader';
|
||||
import { JavaScriptFallback } from './fallback';
|
||||
|
||||
export class WasmEcsCore {
|
||||
private wasmLoader: WasmLoader;
|
||||
private jsFallback: JavaScriptFallback;
|
||||
private initialized = false;
|
||||
private usingWasm = false;
|
||||
|
||||
constructor() {
|
||||
this.wasmLoader = new WasmLoader();
|
||||
this.jsFallback = new JavaScriptFallback();
|
||||
}
|
||||
|
||||
public setSilent(silent: boolean): void {
|
||||
this.wasmLoader.setSilent(silent);
|
||||
}
|
||||
|
||||
async initialize(): Promise<boolean> {
|
||||
if (this.initialized) return true;
|
||||
|
||||
console.log('🔄 初始化ECS核心...');
|
||||
this.usingWasm = await this.wasmLoader.loadWasmModule();
|
||||
this.initialized = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
createEntity(): EntityId {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
return wasmCore ? wasmCore.create_entity() : this.jsFallback.createEntity();
|
||||
}
|
||||
|
||||
return this.jsFallback.createEntity();
|
||||
}
|
||||
|
||||
destroyEntity(entityId: EntityId): boolean {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
return wasmCore ? wasmCore.destroy_entity(entityId) : this.jsFallback.destroyEntity(entityId);
|
||||
}
|
||||
|
||||
return this.jsFallback.destroyEntity(entityId);
|
||||
}
|
||||
|
||||
updateEntityMask(entityId: EntityId, mask: ComponentMask): void {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
if (wasmCore) {
|
||||
wasmCore.update_entity_mask(entityId, mask);
|
||||
} else {
|
||||
this.jsFallback.updateEntityMask(entityId, mask);
|
||||
}
|
||||
} else {
|
||||
this.jsFallback.updateEntityMask(entityId, mask);
|
||||
}
|
||||
}
|
||||
|
||||
batchUpdateMasks(entityIds: EntityId[], masks: ComponentMask[]): void {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
if (wasmCore) {
|
||||
const entityIdArray = new Uint32Array(entityIds);
|
||||
const maskArray = new BigUint64Array(masks);
|
||||
wasmCore.batch_update_masks(entityIdArray, maskArray);
|
||||
} else {
|
||||
this.jsFallback.batchUpdateMasks(entityIds, masks);
|
||||
}
|
||||
} else {
|
||||
this.jsFallback.batchUpdateMasks(entityIds, masks);
|
||||
}
|
||||
}
|
||||
|
||||
queryEntities(mask: ComponentMask, maxResults: number = 10000): QueryResult {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
if (wasmCore) {
|
||||
const resultPtr = wasmCore.query_entities(mask, maxResults);
|
||||
const count = wasmCore.get_query_result_count();
|
||||
|
||||
const wasmModule = this.wasmLoader.getWasmModule();
|
||||
if (wasmModule && wasmModule.memory) {
|
||||
const memory = new Uint32Array(wasmModule.memory.buffer);
|
||||
const entities = new Uint32Array(count);
|
||||
for (let i = 0; i < count; i++) {
|
||||
entities[i] = memory[resultPtr / 4 + i];
|
||||
}
|
||||
return { entities, count };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.jsFallback.queryEntities(mask, maxResults);
|
||||
}
|
||||
|
||||
queryCached(mask: ComponentMask): QueryResult {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
if (wasmCore) {
|
||||
const resultPtr = wasmCore.query_cached(mask);
|
||||
const count = wasmCore.get_cached_query_count(mask);
|
||||
|
||||
const wasmModule = this.wasmLoader.getWasmModule();
|
||||
if (wasmModule && wasmModule.memory) {
|
||||
const memory = new Uint32Array(wasmModule.memory.buffer);
|
||||
const entities = new Uint32Array(count);
|
||||
for (let i = 0; i < count; i++) {
|
||||
entities[i] = memory[resultPtr / 4 + i];
|
||||
}
|
||||
return { entities, count };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.jsFallback.queryCached(mask);
|
||||
}
|
||||
|
||||
queryMultipleComponents(masks: ComponentMask[], maxResults: number = 10000): QueryResult {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
if (wasmCore) {
|
||||
const maskArray = new BigUint64Array(masks);
|
||||
const resultPtr = wasmCore.query_multiple_components(maskArray, maxResults);
|
||||
const count = wasmCore.get_query_result_count();
|
||||
|
||||
const wasmModule = this.wasmLoader.getWasmModule();
|
||||
if (wasmModule && wasmModule.memory) {
|
||||
const memory = new Uint32Array(wasmModule.memory.buffer);
|
||||
const entities = new Uint32Array(count);
|
||||
for (let i = 0; i < count; i++) {
|
||||
entities[i] = memory[resultPtr / 4 + i];
|
||||
}
|
||||
return { entities, count };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.jsFallback.queryMultipleComponents(masks, maxResults);
|
||||
}
|
||||
|
||||
queryWithExclusion(includeMask: ComponentMask, excludeMask: ComponentMask, maxResults: number = 10000): QueryResult {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
if (wasmCore) {
|
||||
const resultPtr = wasmCore.query_with_exclusion(includeMask, excludeMask, maxResults);
|
||||
const count = wasmCore.get_query_result_count();
|
||||
|
||||
const wasmModule = this.wasmLoader.getWasmModule();
|
||||
if (wasmModule && wasmModule.memory) {
|
||||
const memory = new Uint32Array(wasmModule.memory.buffer);
|
||||
const entities = new Uint32Array(count);
|
||||
for (let i = 0; i < count; i++) {
|
||||
entities[i] = memory[resultPtr / 4 + i];
|
||||
}
|
||||
return { entities, count };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.jsFallback.queryWithExclusion(includeMask, excludeMask, maxResults);
|
||||
}
|
||||
|
||||
getEntityMask(entityId: EntityId): ComponentMask | null {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
if (wasmCore) {
|
||||
return wasmCore.get_entity_mask(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
return this.jsFallback.getEntityMask(entityId);
|
||||
}
|
||||
|
||||
entityExists(entityId: EntityId): boolean {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
if (wasmCore) {
|
||||
return wasmCore.entity_exists(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
return this.jsFallback.entityExists(entityId);
|
||||
}
|
||||
|
||||
createComponentMask(componentIds: number[]): ComponentMask {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmModule = this.wasmLoader.getWasmModule();
|
||||
if (wasmModule) {
|
||||
const componentIdArray = new Uint32Array(componentIds);
|
||||
return wasmModule.create_component_mask(componentIdArray);
|
||||
}
|
||||
}
|
||||
|
||||
return this.jsFallback.createComponentMask(componentIds);
|
||||
}
|
||||
|
||||
maskContainsComponent(mask: ComponentMask, componentId: number): boolean {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmModule = this.wasmLoader.getWasmModule();
|
||||
if (wasmModule) {
|
||||
return wasmModule.mask_contains_component(mask, componentId);
|
||||
}
|
||||
}
|
||||
|
||||
return this.jsFallback.maskContainsComponent(mask, componentId);
|
||||
}
|
||||
|
||||
getPerformanceStats(): PerformanceStats {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
if (wasmCore) {
|
||||
const stats = wasmCore.get_performance_stats();
|
||||
return {
|
||||
entityCount: stats[0] || 0,
|
||||
indexCount: stats[1] || 0,
|
||||
queryCount: stats[2] || 0,
|
||||
updateCount: stats[3] || 0,
|
||||
wasmEnabled: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return this.jsFallback.getPerformanceStats();
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (this.usingWasm) {
|
||||
const wasmCore = this.wasmLoader.getWasmCore();
|
||||
if (wasmCore) {
|
||||
wasmCore.clear();
|
||||
}
|
||||
}
|
||||
|
||||
this.jsFallback.clear();
|
||||
}
|
||||
|
||||
isUsingWasm(): boolean {
|
||||
return this.usingWasm;
|
||||
}
|
||||
|
||||
isInitialized(): boolean {
|
||||
return this.initialized;
|
||||
}
|
||||
|
||||
private ensureInitialized(): void {
|
||||
if (!this.initialized) {
|
||||
throw new Error('ECS核心未初始化,请先调用 initialize() 方法');
|
||||
}
|
||||
}
|
||||
|
||||
cleanup(): void {
|
||||
this.wasmLoader.cleanup();
|
||||
this.jsFallback.clear();
|
||||
this.initialized = false;
|
||||
this.usingWasm = false;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/**
|
||||
* JavaScript回退实现
|
||||
*/
|
||||
|
||||
import { EntityId, ComponentMask, QueryResult, PerformanceStats } from './types';
|
||||
|
||||
export class JavaScriptFallback {
|
||||
private jsEntityMasks = new Map<EntityId, ComponentMask>();
|
||||
private jsNextEntityId = 1;
|
||||
private jsQueryCount = 0;
|
||||
private jsUpdateCount = 0;
|
||||
|
||||
createEntity(): EntityId {
|
||||
const entityId = this.jsNextEntityId++;
|
||||
this.jsEntityMasks.set(entityId, 0n);
|
||||
return entityId;
|
||||
}
|
||||
|
||||
destroyEntity(entityId: EntityId): boolean {
|
||||
return this.jsEntityMasks.delete(entityId);
|
||||
}
|
||||
|
||||
updateEntityMask(entityId: EntityId, mask: ComponentMask): void {
|
||||
this.jsEntityMasks.set(entityId, mask);
|
||||
this.jsUpdateCount++;
|
||||
}
|
||||
|
||||
batchUpdateMasks(entityIds: EntityId[], masks: ComponentMask[]): void {
|
||||
for (let i = 0; i < entityIds.length && i < masks.length; i++) {
|
||||
this.jsEntityMasks.set(entityIds[i], masks[i]);
|
||||
}
|
||||
this.jsUpdateCount += Math.min(entityIds.length, masks.length);
|
||||
}
|
||||
|
||||
queryEntities(mask: ComponentMask, maxResults: number = 10000): QueryResult {
|
||||
const results: number[] = [];
|
||||
|
||||
for (const [entityId, entityMask] of this.jsEntityMasks) {
|
||||
if ((entityMask & mask) === mask) {
|
||||
results.push(entityId);
|
||||
if (results.length >= maxResults) break;
|
||||
}
|
||||
}
|
||||
|
||||
this.jsQueryCount++;
|
||||
return {
|
||||
entities: new Uint32Array(results),
|
||||
count: results.length
|
||||
};
|
||||
}
|
||||
|
||||
queryCached(mask: ComponentMask): QueryResult {
|
||||
return this.queryEntities(mask);
|
||||
}
|
||||
queryMultipleComponents(masks: ComponentMask[], maxResults: number = 10000): QueryResult {
|
||||
const results: number[] = [];
|
||||
|
||||
for (const [entityId, entityMask] of this.jsEntityMasks) {
|
||||
let matches = false;
|
||||
for (const mask of masks) {
|
||||
if ((entityMask & mask) === mask) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matches) {
|
||||
results.push(entityId);
|
||||
if (results.length >= maxResults) break;
|
||||
}
|
||||
}
|
||||
|
||||
this.jsQueryCount++;
|
||||
return {
|
||||
entities: new Uint32Array(results),
|
||||
count: results.length
|
||||
};
|
||||
}
|
||||
|
||||
queryWithExclusion(includeMask: ComponentMask, excludeMask: ComponentMask, maxResults: number = 10000): QueryResult {
|
||||
const results: number[] = [];
|
||||
|
||||
for (const [entityId, entityMask] of this.jsEntityMasks) {
|
||||
if ((entityMask & includeMask) === includeMask && (entityMask & excludeMask) === 0n) {
|
||||
results.push(entityId);
|
||||
if (results.length >= maxResults) break;
|
||||
}
|
||||
}
|
||||
|
||||
this.jsQueryCount++;
|
||||
return {
|
||||
entities: new Uint32Array(results),
|
||||
count: results.length
|
||||
};
|
||||
}
|
||||
|
||||
getEntityMask(entityId: EntityId): ComponentMask | null {
|
||||
return this.jsEntityMasks.get(entityId) || null;
|
||||
}
|
||||
|
||||
entityExists(entityId: EntityId): boolean {
|
||||
return this.jsEntityMasks.has(entityId);
|
||||
}
|
||||
createComponentMask(componentIds: number[]): ComponentMask {
|
||||
let mask = 0n;
|
||||
for (const id of componentIds) {
|
||||
mask |= (1n << BigInt(id));
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
maskContainsComponent(mask: ComponentMask, componentId: number): boolean {
|
||||
return (mask & (1n << BigInt(componentId))) !== 0n;
|
||||
}
|
||||
getPerformanceStats(): PerformanceStats {
|
||||
return {
|
||||
entityCount: this.jsEntityMasks.size,
|
||||
indexCount: 0,
|
||||
queryCount: this.jsQueryCount,
|
||||
updateCount: this.jsUpdateCount,
|
||||
wasmEnabled: false
|
||||
};
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.jsEntityMasks.clear();
|
||||
this.jsNextEntityId = 1;
|
||||
this.jsQueryCount = 0;
|
||||
this.jsUpdateCount = 0;
|
||||
}
|
||||
|
||||
getEntityCount(): number {
|
||||
return this.jsEntityMasks.size;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* WASM模块导出
|
||||
*/
|
||||
|
||||
export * from './types';
|
||||
export { WasmLoader } from './loader';
|
||||
export { JavaScriptFallback } from './fallback';
|
||||
export { WasmEcsCore } from './core';
|
||||
export { ecsCore, initializeEcs } from './instance';
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* WASM ECS核心全局实例
|
||||
*/
|
||||
|
||||
import { WasmEcsCore } from './core';
|
||||
|
||||
export const ecsCore = new WasmEcsCore();
|
||||
|
||||
export async function initializeEcs(silent: boolean = false): Promise<boolean> {
|
||||
ecsCore.setSilent(silent);
|
||||
return await ecsCore.initialize();
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* WASM模块加载器
|
||||
*/
|
||||
|
||||
import { WasmModule, WasmEcsCoreInstance } from './types';
|
||||
|
||||
export class WasmLoader {
|
||||
private wasmModule: WasmModule | null = null;
|
||||
private wasmCore: WasmEcsCoreInstance | null = null;
|
||||
private silent = false;
|
||||
|
||||
public setSilent(silent: boolean): void {
|
||||
this.silent = silent;
|
||||
}
|
||||
public async loadWasmModule(): Promise<boolean> {
|
||||
try {
|
||||
const wasmPath = '../../bin/wasm/ecs_wasm_core';
|
||||
this.wasmModule = await import(wasmPath);
|
||||
|
||||
if (this.wasmModule) {
|
||||
await this.initializeWasmModule();
|
||||
this.wasmCore = new this.wasmModule.EcsCore();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (!this.silent) {
|
||||
console.warn('WASM加载失败,使用JavaScript实现');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeWasmModule(): Promise<void> {
|
||||
if (!this.wasmModule) return;
|
||||
|
||||
if (typeof require !== 'undefined') {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const currentDir = path.dirname(__filename);
|
||||
const wasmPath = path.resolve(currentDir, '../../bin/wasm/ecs_wasm_core_bg.wasm');
|
||||
|
||||
if (fs.existsSync(wasmPath)) {
|
||||
const wasmBytes = fs.readFileSync(wasmPath);
|
||||
if (this.wasmModule.initSync) {
|
||||
this.wasmModule.initSync(wasmBytes);
|
||||
} else {
|
||||
await this.wasmModule.default({ module_or_path: wasmBytes });
|
||||
}
|
||||
} else {
|
||||
throw new Error(`WASM文件不存在: ${wasmPath}`);
|
||||
}
|
||||
} else {
|
||||
await this.wasmModule.default();
|
||||
}
|
||||
}
|
||||
|
||||
public getWasmCore(): WasmEcsCoreInstance | null {
|
||||
return this.wasmCore;
|
||||
}
|
||||
|
||||
public getWasmModule(): WasmModule | null {
|
||||
return this.wasmModule;
|
||||
}
|
||||
|
||||
public cleanup(): void {
|
||||
if (this.wasmCore && this.wasmCore.free) {
|
||||
this.wasmCore.free();
|
||||
}
|
||||
this.wasmCore = null;
|
||||
this.wasmModule = null;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* WASM ECS核心类型定义
|
||||
*/
|
||||
|
||||
export type EntityId = number;
|
||||
export type ComponentMask = bigint;
|
||||
|
||||
export interface QueryResult {
|
||||
entities: Uint32Array;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface PerformanceStats {
|
||||
entityCount: number;
|
||||
indexCount: number;
|
||||
queryCount: number;
|
||||
updateCount: number;
|
||||
wasmEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface WasmEcsCoreInstance {
|
||||
create_entity(): number;
|
||||
destroy_entity(entity_id: number): boolean;
|
||||
update_entity_mask(entity_id: number, mask: bigint): void;
|
||||
batch_update_masks(entity_ids: Uint32Array, masks: BigUint64Array): void;
|
||||
query_entities(mask: bigint, max_results: number): number;
|
||||
get_query_result_count(): number;
|
||||
query_cached(mask: bigint): number;
|
||||
get_cached_query_count(mask: bigint): number;
|
||||
query_multiple_components(masks: BigUint64Array, max_results: number): number;
|
||||
query_with_exclusion(include_mask: bigint, exclude_mask: bigint, max_results: number): number;
|
||||
get_entity_mask(entity_id: number): bigint;
|
||||
entity_exists(entity_id: number): boolean;
|
||||
get_entity_count(): number;
|
||||
get_performance_stats(): Array<any>;
|
||||
clear(): void;
|
||||
rebuild_query_cache(): void;
|
||||
free?(): void;
|
||||
}
|
||||
|
||||
export interface WasmModule {
|
||||
EcsCore: new () => WasmEcsCoreInstance;
|
||||
create_component_mask: (componentIds: Uint32Array) => ComponentMask;
|
||||
mask_contains_component: (mask: ComponentMask, componentId: number) => boolean;
|
||||
default: (input?: any) => Promise<any>;
|
||||
initSync?: (input: any) => any;
|
||||
memory?: WebAssembly.Memory;
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
# Rust WebAssembly 编译指南
|
||||
|
||||
本指南将帮助您从零开始安装Rust环境并编译WASM模块。
|
||||
|
||||
## 📋 前置要求
|
||||
|
||||
- Windows 10/11 或 macOS/Linux
|
||||
- 稳定的网络连接
|
||||
- 管理员权限(用于安装软件)
|
||||
|
||||
## 🚀 第一步:安装 Rust
|
||||
|
||||
### Windows 用户
|
||||
|
||||
1. **下载 Rust 安装器**
|
||||
- 访问 https://rustup.rs/
|
||||
- 点击 "DOWNLOAD RUSTUP-INIT.EXE (64-BIT)"
|
||||
- 或者直接下载:https://win.rustup.rs/x86_64
|
||||
|
||||
2. **运行安装器**
|
||||
```cmd
|
||||
# 下载后运行
|
||||
rustup-init.exe
|
||||
```
|
||||
|
||||
3. **选择安装选项**
|
||||
- 出现提示时,选择 "1) Proceed with installation (default)"
|
||||
- 等待安装完成
|
||||
|
||||
4. **重启命令行**
|
||||
- 关闭当前命令行窗口
|
||||
- 重新打开 cmd 或 PowerShell
|
||||
|
||||
### macOS/Linux 用户
|
||||
|
||||
```bash
|
||||
# 使用官方安装脚本
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
|
||||
# 重新加载环境变量
|
||||
source ~/.cargo/env
|
||||
```
|
||||
|
||||
## 🔧 第二步:安装 wasm-pack
|
||||
|
||||
wasm-pack 是编译 Rust 到 WebAssembly 的官方工具。
|
||||
|
||||
### Windows 用户
|
||||
|
||||
```cmd
|
||||
# 方法1:使用 cargo 安装(推荐)
|
||||
cargo install wasm-pack
|
||||
|
||||
# 方法2:下载预编译版本
|
||||
# 访问 https://github.com/rustwasm/wasm-pack/releases
|
||||
# 下载最新的 Windows 版本
|
||||
```
|
||||
|
||||
### macOS/Linux 用户
|
||||
|
||||
```bash
|
||||
# 方法1:使用官方安装脚本
|
||||
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
# 方法2:使用 cargo 安装
|
||||
cargo install wasm-pack
|
||||
```
|
||||
|
||||
## ✅ 第三步:验证安装
|
||||
|
||||
打开新的命令行窗口,运行以下命令验证安装:
|
||||
|
||||
```cmd
|
||||
# 检查 Rust 版本
|
||||
rustc --version
|
||||
|
||||
# 检查 Cargo 版本
|
||||
cargo --version
|
||||
|
||||
# 检查 wasm-pack 版本
|
||||
wasm-pack --version
|
||||
```
|
||||
|
||||
如果所有命令都能正常显示版本号,说明安装成功!
|
||||
|
||||
## 🏗️ 第四步:编译 WASM 模块
|
||||
|
||||
现在可以编译我们的 Rust WASM 模块了:
|
||||
|
||||
### 使用批处理文件(Windows 推荐)
|
||||
|
||||
```cmd
|
||||
# 进入项目目录
|
||||
cd D:\project\ecs-framework\source\src\wasm\rust-ecs-core
|
||||
|
||||
# 运行批处理文件
|
||||
build.bat
|
||||
```
|
||||
|
||||
### 使用命令行(跨平台)
|
||||
|
||||
```bash
|
||||
# 进入项目目录
|
||||
cd source/src/wasm/rust-ecs-core
|
||||
|
||||
# 编译 WASM 模块
|
||||
wasm-pack build --target web --out-dir pkg --release
|
||||
```
|
||||
|
||||
### 编译选项说明
|
||||
|
||||
- `--target web`: 生成适用于浏览器的模块
|
||||
- `--out-dir pkg`: 输出到 pkg 目录
|
||||
- `--release`: 发布模式,启用优化
|
||||
|
||||
## 📦 第五步:验证编译结果
|
||||
|
||||
编译成功后,`pkg` 目录应该包含以下文件:
|
||||
|
||||
```
|
||||
pkg/
|
||||
├── ecs_wasm_core.js # JavaScript 绑定
|
||||
├── ecs_wasm_core_bg.wasm # WebAssembly 二进制文件
|
||||
├── ecs_wasm_core.d.ts # TypeScript 类型定义
|
||||
├── package.json # NPM 包配置
|
||||
└── README.md # 包说明
|
||||
```
|
||||
|
||||
## 🧪 第六步:测试 WASM 模块
|
||||
|
||||
创建一个简单的测试文件来验证模块是否正常工作:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WASM ECS 测试</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Rust WASM ECS 测试</h1>
|
||||
<div id="output"></div>
|
||||
|
||||
<script type="module">
|
||||
import init, { EcsCore } from './pkg/ecs_wasm_core.js';
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
// 初始化 WASM 模块
|
||||
await init();
|
||||
|
||||
// 创建 ECS 核心实例
|
||||
const ecs = new EcsCore();
|
||||
|
||||
// 创建实体
|
||||
const entity = ecs.create_entity();
|
||||
console.log('创建实体:', entity);
|
||||
|
||||
// 显示结果
|
||||
document.getElementById('output').innerHTML =
|
||||
`✅ WASM 模块加载成功!<br>创建的实体ID: ${entity}`;
|
||||
|
||||
} catch (error) {
|
||||
console.error('错误:', error);
|
||||
document.getElementById('output').innerHTML =
|
||||
`❌ 错误: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **"rustc 不是内部或外部命令"**
|
||||
- 重启命令行窗口
|
||||
- 检查环境变量是否正确设置
|
||||
- 重新安装 Rust
|
||||
|
||||
2. **"wasm-pack 不是内部或外部命令"**
|
||||
- 确保 wasm-pack 安装成功
|
||||
- 重启命令行窗口
|
||||
- 尝试使用 `cargo install wasm-pack` 重新安装
|
||||
|
||||
3. **编译错误**
|
||||
- 检查 Rust 版本是否为最新稳定版
|
||||
- 运行 `rustup update` 更新 Rust
|
||||
- 检查网络连接,确保能下载依赖
|
||||
|
||||
4. **WASM 模块加载失败**
|
||||
- 确保使用 HTTP 服务器而不是直接打开文件
|
||||
- 检查浏览器是否支持 WebAssembly
|
||||
- 查看浏览器控制台的错误信息
|
||||
|
||||
### 更新工具
|
||||
|
||||
```bash
|
||||
# 更新 Rust
|
||||
rustup update
|
||||
|
||||
# 更新 wasm-pack
|
||||
cargo install wasm-pack --force
|
||||
```
|
||||
|
||||
## 🎯 下一步
|
||||
|
||||
编译成功后,您可以:
|
||||
|
||||
1. 在项目中使用 `WasmLoader` 加载模块
|
||||
2. 运行性能基准测试
|
||||
3. 集成到您的游戏或应用中
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
如果遇到问题,可以:
|
||||
|
||||
1. 查看 Rust 官方文档:https://doc.rust-lang.org/
|
||||
2. 查看 wasm-pack 文档:https://rustwasm.github.io/wasm-pack/
|
||||
3. 检查项目的 GitHub Issues
|
||||
4. 在 Rust 社区寻求帮助:https://users.rust-lang.org/
|
||||
@@ -1,2 +0,0 @@
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ['--cfg', 'getrandom_backend="wasm_js"']
|
||||
286
source/src/wasm/rust-ecs-core/Cargo.lock
generated
286
source/src/wasm/rust-ecs-core/Cargo.lock
generated
@@ -1,286 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "ecs-wasm-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"smallvec",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
@@ -1,50 +0,0 @@
|
||||
[package]
|
||||
name = "ecs-wasm-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "*"
|
||||
js-sys = "*"
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
serde-wasm-bindgen = "*"
|
||||
|
||||
# 高性能哈希表,避免getrandom版本冲突
|
||||
ahash = "*"
|
||||
# 使用标准库的HashMap,避免getrandom版本冲突
|
||||
smallvec = "*" # 栈分配的小向量
|
||||
# 为 WASM 环境配置 getrandom
|
||||
getrandom = { version = "*", features = ["wasm_js"] }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
features = [
|
||||
"console",
|
||||
"Performance",
|
||||
"Window",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
# 优化WASM二进制大小和性能
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[profile.release.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
# 配置 wasm-pack 行为
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
# 如果下载失败,可以暂时禁用 wasm-opt 优化
|
||||
wasm-opt = false
|
||||
# 或者指定本地 wasm-opt 路径(如果已安装)
|
||||
# wasm-opt = ["-O4", "--enable-simd"]
|
||||
|
||||
# 配置网络超时和重试
|
||||
[package.metadata.wasm-pack]
|
||||
# 增加下载超时时间
|
||||
timeout = 300
|
||||
@@ -1,56 +0,0 @@
|
||||
@echo off
|
||||
echo 正在构建 WASM 模块...
|
||||
|
||||
REM 方法1:尝试正常构建
|
||||
echo 尝试正常构建...
|
||||
wasm-pack build --target web --out-dir pkg --release
|
||||
if %ERRORLEVEL% == 0 (
|
||||
echo ✅ 构建成功!
|
||||
goto :success
|
||||
)
|
||||
|
||||
echo ❌ 正常构建失败,尝试其他方法...
|
||||
|
||||
REM 方法2:设置代理(如果有的话)
|
||||
REM set HTTPS_PROXY=http://127.0.0.1:7890
|
||||
REM set HTTP_PROXY=http://127.0.0.1:7890
|
||||
|
||||
REM 方法3:禁用 wasm-opt 优化
|
||||
echo 尝试禁用 wasm-opt 优化...
|
||||
wasm-pack build --target web --out-dir pkg --release -- --no-default-features
|
||||
if %ERRORLEVEL% == 0 (
|
||||
echo ✅ 构建成功(已禁用优化)!
|
||||
goto :success
|
||||
)
|
||||
|
||||
REM 方法4:手动下载 binaryen
|
||||
echo 尝试手动处理 binaryen...
|
||||
if not exist "tools\binaryen" (
|
||||
echo 请手动下载 binaryen 到 tools 目录
|
||||
echo 下载地址: https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_117-x86_64-windows.tar.gz
|
||||
echo 或者使用国内镜像源
|
||||
)
|
||||
|
||||
REM 方法5:使用环境变量跳过下载
|
||||
echo 尝试跳过 binaryen 下载...
|
||||
set WASM_PACK_CACHE_DISABLE=1
|
||||
wasm-pack build --target web --out-dir pkg --release --mode no-install
|
||||
if %ERRORLEVEL% == 0 (
|
||||
echo ✅ 构建成功(跳过下载)!
|
||||
goto :success
|
||||
)
|
||||
|
||||
echo ❌ 所有方法都失败了
|
||||
echo 建议:
|
||||
echo 1. 检查网络连接
|
||||
echo 2. 使用 VPN 或代理
|
||||
echo 3. 手动下载 binaryen 工具
|
||||
echo 4. 临时禁用 wasm-opt 优化
|
||||
goto :end
|
||||
|
||||
:success
|
||||
echo 🎉 WASM 模块构建完成!
|
||||
echo 输出目录: pkg/
|
||||
|
||||
:end
|
||||
pause
|
||||
@@ -1,65 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
REM Rust WASM构建脚本 (Windows版本)
|
||||
echo 开始构建Rust ECS WASM模块...
|
||||
|
||||
REM 检查是否安装了必要的工具
|
||||
where wasm-pack >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo 错误:未找到wasm-pack,请先安装:
|
||||
echo curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf ^| sh
|
||||
echo 或者访问: https://rustwasm.github.io/wasm-pack/installer/
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 检查是否安装了Rust
|
||||
where rustc >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo 错误:未找到Rust,请先安装:
|
||||
echo 访问: https://rustup.rs/
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 清理之前的构建缓存
|
||||
echo 清理之前的构建缓存...
|
||||
if exist Cargo.lock del Cargo.lock
|
||||
if exist target rmdir /s /q target
|
||||
if exist pkg rmdir /s /q pkg
|
||||
cargo clean
|
||||
|
||||
echo 更新依赖...
|
||||
cargo update
|
||||
|
||||
REM 设置环境变量解决getrandom问题
|
||||
set RUSTFLAGS=--cfg getrandom_backend="wasm_js"
|
||||
|
||||
REM 构建WASM模块
|
||||
echo 正在编译WASM模块...
|
||||
wasm-pack build --target web --out-dir pkg --release
|
||||
|
||||
REM 检查构建是否成功
|
||||
if %errorlevel% equ 0 (
|
||||
echo ✅ WASM模块构建成功!
|
||||
echo 生成的文件位于 pkg/ 目录:
|
||||
dir pkg
|
||||
|
||||
echo.
|
||||
echo 📦 生成的文件说明:
|
||||
echo - ecs_wasm_core.js: JavaScript绑定
|
||||
echo - ecs_wasm_core_bg.wasm: WebAssembly二进制文件
|
||||
echo - ecs_wasm_core.d.ts: TypeScript类型定义
|
||||
|
||||
echo.
|
||||
echo 🚀 使用方法:
|
||||
echo import init, { EcsCore } from './pkg/ecs_wasm_core.js';
|
||||
echo await init^(^);
|
||||
echo const ecs = new EcsCore^(^);
|
||||
) else (
|
||||
echo ❌ 构建失败!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Rust WASM构建脚本
|
||||
echo "开始构建Rust ECS WASM模块..."
|
||||
|
||||
# 检查是否安装了必要的工具
|
||||
if ! command -v wasm-pack &> /dev/null; then
|
||||
echo "错误:未找到wasm-pack,请先安装:"
|
||||
echo "curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 构建WASM模块
|
||||
echo "正在编译WASM模块..."
|
||||
wasm-pack build --target web --out-dir pkg --release
|
||||
|
||||
# 检查构建是否成功
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ WASM模块构建成功!"
|
||||
echo "生成的文件位于 pkg/ 目录:"
|
||||
ls -la pkg/
|
||||
|
||||
echo ""
|
||||
echo "📦 生成的文件说明:"
|
||||
echo " - ecs_wasm_core.js: JavaScript绑定"
|
||||
echo " - ecs_wasm_core_bg.wasm: WebAssembly二进制文件"
|
||||
echo " - ecs_wasm_core.d.ts: TypeScript类型定义"
|
||||
|
||||
echo ""
|
||||
echo "🚀 使用方法:"
|
||||
echo "import init, { EcsCore } from './pkg/ecs_wasm_core.js';"
|
||||
echo "await init();"
|
||||
echo "const ecs = new EcsCore();"
|
||||
else
|
||||
echo "❌ 构建失败!"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,185 +0,0 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
use js_sys::Array;
|
||||
|
||||
mod query;
|
||||
use query::QueryEngine;
|
||||
|
||||
// 当wasm-bindgen功能启用时,提供console.log绑定
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
// 定义一个宏来简化日志记录
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
/// 实体ID类型
|
||||
pub type EntityId = u32;
|
||||
|
||||
/// 组件掩码类型
|
||||
pub type ComponentMask = u64;
|
||||
|
||||
/// 高性能ECS核心,专注于实体查询和掩码管理
|
||||
#[wasm_bindgen]
|
||||
pub struct EcsCore {
|
||||
/// 查询引擎
|
||||
query_engine: QueryEngine,
|
||||
/// 下一个可用的实体ID
|
||||
next_entity_id: EntityId,
|
||||
/// 更新计数
|
||||
update_count: u32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl EcsCore {
|
||||
/// 创建新的ECS核心
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> EcsCore {
|
||||
EcsCore {
|
||||
query_engine: QueryEngine::new(),
|
||||
next_entity_id: 1,
|
||||
update_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建新实体
|
||||
#[wasm_bindgen]
|
||||
pub fn create_entity(&mut self) -> EntityId {
|
||||
let entity_id = self.next_entity_id;
|
||||
self.next_entity_id += 1;
|
||||
self.query_engine.add_entity(entity_id, 0);
|
||||
entity_id
|
||||
}
|
||||
|
||||
/// 删除实体
|
||||
#[wasm_bindgen]
|
||||
pub fn destroy_entity(&mut self, entity_id: EntityId) -> bool {
|
||||
self.query_engine.remove_entity(entity_id)
|
||||
}
|
||||
|
||||
/// 更新实体的组件掩码
|
||||
#[wasm_bindgen]
|
||||
pub fn update_entity_mask(&mut self, entity_id: EntityId, mask: ComponentMask) {
|
||||
self.query_engine.update_entity_mask(entity_id, mask);
|
||||
self.update_count += 1;
|
||||
}
|
||||
|
||||
/// 批量更新实体掩码
|
||||
#[wasm_bindgen]
|
||||
pub fn batch_update_masks(&mut self, entity_ids: &[u32], masks: &[u64]) {
|
||||
self.query_engine.batch_update_masks(entity_ids, masks);
|
||||
self.update_count += entity_ids.len() as u32;
|
||||
}
|
||||
|
||||
/// 查询实体
|
||||
#[wasm_bindgen]
|
||||
pub fn query_entities(&mut self, mask: ComponentMask, max_results: u32) -> *const u32 {
|
||||
let results = self.query_engine.query_entities(mask, max_results as usize);
|
||||
results.as_ptr()
|
||||
}
|
||||
|
||||
/// 获取查询结果数量
|
||||
#[wasm_bindgen]
|
||||
pub fn get_query_result_count(&self) -> usize {
|
||||
self.query_engine.get_last_query_result_count()
|
||||
}
|
||||
|
||||
/// 缓存查询实体
|
||||
#[wasm_bindgen]
|
||||
pub fn query_cached(&mut self, mask: ComponentMask) -> *const u32 {
|
||||
let results = self.query_engine.query_cached(mask);
|
||||
results.as_ptr()
|
||||
}
|
||||
|
||||
/// 获取缓存查询结果数量
|
||||
#[wasm_bindgen]
|
||||
pub fn get_cached_query_count(&mut self, mask: ComponentMask) -> usize {
|
||||
self.query_engine.query_cached(mask).len()
|
||||
}
|
||||
|
||||
/// 多组件查询
|
||||
#[wasm_bindgen]
|
||||
pub fn query_multiple_components(&mut self, masks: &[u64], max_results: u32) -> *const u32 {
|
||||
let results = self.query_engine.query_multiple_components(masks, max_results as usize);
|
||||
results.as_ptr()
|
||||
}
|
||||
|
||||
/// 排除查询
|
||||
#[wasm_bindgen]
|
||||
pub fn query_with_exclusion(&mut self, include_mask: ComponentMask, exclude_mask: ComponentMask, max_results: u32) -> *const u32 {
|
||||
let results = self.query_engine.query_with_exclusion(include_mask, exclude_mask, max_results as usize);
|
||||
results.as_ptr()
|
||||
}
|
||||
|
||||
/// 获取实体的组件掩码
|
||||
#[wasm_bindgen]
|
||||
pub fn get_entity_mask(&self, entity_id: EntityId) -> ComponentMask {
|
||||
self.query_engine.get_entity_mask(entity_id)
|
||||
}
|
||||
|
||||
/// 检查实体是否存在
|
||||
#[wasm_bindgen]
|
||||
pub fn entity_exists(&self, entity_id: EntityId) -> bool {
|
||||
self.query_engine.entity_exists(entity_id)
|
||||
}
|
||||
|
||||
/// 获取实体数量
|
||||
#[wasm_bindgen]
|
||||
pub fn get_entity_count(&self) -> u32 {
|
||||
self.query_engine.get_entity_count()
|
||||
}
|
||||
|
||||
/// 获取性能统计信息
|
||||
#[wasm_bindgen]
|
||||
pub fn get_performance_stats(&self) -> Array {
|
||||
let stats = Array::new();
|
||||
stats.push(&JsValue::from(self.query_engine.get_entity_count())); // 实体数量
|
||||
stats.push(&JsValue::from(self.query_engine.get_query_count())); // 查询次数
|
||||
stats.push(&JsValue::from(self.update_count)); // 更新次数
|
||||
stats
|
||||
}
|
||||
|
||||
/// 清理所有数据
|
||||
#[wasm_bindgen]
|
||||
pub fn clear(&mut self) {
|
||||
self.query_engine.clear();
|
||||
self.next_entity_id = 1;
|
||||
self.update_count = 0;
|
||||
}
|
||||
|
||||
/// 重建查询缓存
|
||||
#[wasm_bindgen]
|
||||
pub fn rebuild_query_cache(&mut self) {
|
||||
self.query_engine.force_rebuild_cache();
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建组件掩码的辅助函数
|
||||
#[wasm_bindgen]
|
||||
pub fn create_component_mask(component_ids: &[u32]) -> ComponentMask {
|
||||
let mut mask = 0u64;
|
||||
for &id in component_ids {
|
||||
if id < 64 {
|
||||
mask |= 1u64 << id;
|
||||
}
|
||||
}
|
||||
mask
|
||||
}
|
||||
|
||||
/// 检查掩码是否包含指定组件
|
||||
#[wasm_bindgen]
|
||||
pub fn mask_contains_component(mask: ComponentMask, component_id: u32) -> bool {
|
||||
if component_id >= 64 {
|
||||
return false;
|
||||
}
|
||||
(mask & (1u64 << component_id)) != 0
|
||||
}
|
||||
|
||||
/// 初始化函数
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn main() {
|
||||
console_log!("Rust ECS WASM模块已加载");
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
use crate::{EntityId, ComponentMask};
|
||||
use ahash::AHashMap;
|
||||
|
||||
/// 查询引擎,负责高性能的实体查询
|
||||
pub struct QueryEngine {
|
||||
/// 实体到组件掩码的映射
|
||||
entity_masks: AHashMap<EntityId, ComponentMask>,
|
||||
|
||||
/// 常用查询掩码的缓存
|
||||
cached_queries: AHashMap<ComponentMask, Vec<EntityId>>,
|
||||
|
||||
/// 查询结果缓冲区
|
||||
query_buffer: Vec<EntityId>,
|
||||
|
||||
/// 缓存有效性标志
|
||||
cache_dirty: bool,
|
||||
|
||||
/// 性能统计
|
||||
query_count: u32,
|
||||
|
||||
/// 最后查询结果数量
|
||||
last_query_result_count: usize,
|
||||
}
|
||||
|
||||
impl QueryEngine {
|
||||
pub fn new() -> Self {
|
||||
QueryEngine {
|
||||
entity_masks: AHashMap::with_capacity(50000),
|
||||
cached_queries: AHashMap::with_capacity(32),
|
||||
query_buffer: Vec::with_capacity(50000),
|
||||
cache_dirty: true,
|
||||
query_count: 0,
|
||||
last_query_result_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// 添加实体掩码
|
||||
pub fn add_entity(&mut self, entity_id: EntityId, mask: ComponentMask) {
|
||||
self.entity_masks.insert(entity_id, mask);
|
||||
self.cache_dirty = true;
|
||||
}
|
||||
|
||||
/// 移除实体
|
||||
pub fn remove_entity(&mut self, entity_id: EntityId) -> bool {
|
||||
if self.entity_masks.remove(&entity_id).is_some() {
|
||||
self.cache_dirty = true;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新实体掩码
|
||||
pub fn update_entity_mask(&mut self, entity_id: EntityId, mask: ComponentMask) {
|
||||
if self.entity_masks.contains_key(&entity_id) {
|
||||
self.entity_masks.insert(entity_id, mask);
|
||||
self.cache_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// 批量更新实体掩码
|
||||
pub fn batch_update_masks(&mut self, entity_ids: &[EntityId], masks: &[ComponentMask]) {
|
||||
if entity_ids.len() != masks.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i, &entity_id) in entity_ids.iter().enumerate() {
|
||||
if self.entity_masks.contains_key(&entity_id) {
|
||||
self.entity_masks.insert(entity_id, masks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
self.cache_dirty = true;
|
||||
}
|
||||
|
||||
/// 重建查询缓存
|
||||
pub fn rebuild_cache(&mut self) {
|
||||
if !self.cache_dirty {
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空所有缓存
|
||||
for cached_entities in self.cached_queries.values_mut() {
|
||||
cached_entities.clear();
|
||||
}
|
||||
|
||||
// 重建所有缓存的查询
|
||||
for (&query_mask, cached_entities) in &mut self.cached_queries {
|
||||
for (&entity_id, &entity_mask) in &self.entity_masks {
|
||||
if (entity_mask & query_mask) == query_mask {
|
||||
cached_entities.push(entity_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.cache_dirty = false;
|
||||
}
|
||||
|
||||
/// 通用查询方法
|
||||
pub fn query_entities(&mut self, mask: ComponentMask, max_results: usize) -> &[EntityId] {
|
||||
self.query_buffer.clear();
|
||||
self.query_count += 1;
|
||||
|
||||
for (&entity_id, &entity_mask) in &self.entity_masks {
|
||||
if (entity_mask & mask) == mask {
|
||||
self.query_buffer.push(entity_id);
|
||||
if self.query_buffer.len() >= max_results {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.last_query_result_count = self.query_buffer.len();
|
||||
&self.query_buffer
|
||||
}
|
||||
|
||||
/// 查询指定掩码的实体(带缓存)
|
||||
pub fn query_cached(&mut self, mask: ComponentMask) -> &[EntityId] {
|
||||
// 如果缓存中没有这个查询,添加它
|
||||
if !self.cached_queries.contains_key(&mask) {
|
||||
self.cached_queries.insert(mask, Vec::new());
|
||||
self.cache_dirty = true;
|
||||
}
|
||||
|
||||
self.rebuild_cache();
|
||||
self.query_count += 1;
|
||||
|
||||
self.cached_queries.get(&mask).unwrap()
|
||||
}
|
||||
|
||||
/// 多组件查询
|
||||
pub fn query_multiple_components(&mut self, masks: &[ComponentMask], max_results: usize) -> &[EntityId] {
|
||||
self.query_buffer.clear();
|
||||
self.query_count += 1;
|
||||
|
||||
if masks.is_empty() {
|
||||
return &self.query_buffer;
|
||||
}
|
||||
|
||||
for (&entity_id, &entity_mask) in &self.entity_masks {
|
||||
let mut matches_all = true;
|
||||
for &mask in masks {
|
||||
if (entity_mask & mask) != mask {
|
||||
matches_all = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if matches_all {
|
||||
self.query_buffer.push(entity_id);
|
||||
if self.query_buffer.len() >= max_results {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&self.query_buffer
|
||||
}
|
||||
|
||||
/// 带排除条件的查询
|
||||
pub fn query_with_exclusion(&mut self, include_mask: ComponentMask, exclude_mask: ComponentMask, max_results: usize) -> &[EntityId] {
|
||||
self.query_buffer.clear();
|
||||
self.query_count += 1;
|
||||
|
||||
for (&entity_id, &entity_mask) in &self.entity_masks {
|
||||
if (entity_mask & include_mask) == include_mask && (entity_mask & exclude_mask) == 0 {
|
||||
self.query_buffer.push(entity_id);
|
||||
if self.query_buffer.len() >= max_results {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&self.query_buffer
|
||||
}
|
||||
|
||||
/// 获取实体的组件掩码
|
||||
pub fn get_entity_mask(&self, entity_id: EntityId) -> ComponentMask {
|
||||
self.entity_masks.get(&entity_id).copied().unwrap_or(0)
|
||||
}
|
||||
|
||||
/// 检查实体是否存在
|
||||
pub fn entity_exists(&self, entity_id: EntityId) -> bool {
|
||||
self.entity_masks.contains_key(&entity_id)
|
||||
}
|
||||
|
||||
/// 获取实体数量
|
||||
pub fn get_entity_count(&self) -> u32 {
|
||||
self.entity_masks.len() as u32
|
||||
}
|
||||
|
||||
/// 获取查询统计
|
||||
pub fn get_query_count(&self) -> u32 {
|
||||
self.query_count
|
||||
}
|
||||
|
||||
/// 获取最后查询结果数量
|
||||
pub fn get_last_query_result_count(&self) -> usize {
|
||||
self.last_query_result_count
|
||||
}
|
||||
|
||||
/// 清理所有数据
|
||||
pub fn clear(&mut self) {
|
||||
self.entity_masks.clear();
|
||||
self.cached_queries.clear();
|
||||
self.query_buffer.clear();
|
||||
|
||||
self.cache_dirty = true;
|
||||
self.query_count = 0;
|
||||
self.last_query_result_count = 0;
|
||||
}
|
||||
|
||||
/// 强制重建查询缓存
|
||||
pub fn force_rebuild_cache(&mut self) {
|
||||
self.cache_dirty = true;
|
||||
self.rebuild_cache();
|
||||
}
|
||||
}
|
||||
@@ -1,158 +1,158 @@
|
||||
import type { IComponent } from '../Types';
|
||||
|
||||
/**
|
||||
* 游戏组件基类
|
||||
*
|
||||
* ECS架构中的组件(Component),用于实现具体的游戏功能。
|
||||
* 组件包含数据和行为,可以被添加到实体上以扩展实体的功能。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class HealthComponent extends Component {
|
||||
* public health: number = 100;
|
||||
*
|
||||
* public takeDamage(damage: number): void {
|
||||
* this.health -= damage;
|
||||
* if (this.health <= 0) {
|
||||
* this.entity.destroy();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export abstract class Component implements IComponent {
|
||||
/**
|
||||
* 组件ID生成器
|
||||
*
|
||||
* 用于为每个组件分配唯一的ID。
|
||||
*/
|
||||
public static _idGenerator: number = 0;
|
||||
|
||||
/**
|
||||
* 组件唯一标识符
|
||||
*
|
||||
* 在整个游戏生命周期中唯一的数字ID。
|
||||
*/
|
||||
public readonly id: number;
|
||||
|
||||
/**
|
||||
* 组件所属的实体
|
||||
*
|
||||
* 指向拥有此组件的实体实例。
|
||||
*/
|
||||
public entity!: Entity;
|
||||
|
||||
/**
|
||||
* 组件启用状态
|
||||
*
|
||||
* 控制组件是否参与更新循环。
|
||||
*/
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 更新顺序
|
||||
*
|
||||
* 决定组件在更新循环中的执行顺序。
|
||||
*/
|
||||
private _updateOrder: number = 0;
|
||||
|
||||
/**
|
||||
* 创建组件实例
|
||||
*
|
||||
* 自动分配唯一ID给组件。
|
||||
*/
|
||||
constructor() {
|
||||
this.id = Component._idGenerator++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件启用状态
|
||||
*
|
||||
* 组件的实际启用状态取决于自身状态和所属实体的状态。
|
||||
*
|
||||
* @returns 如果组件和所属实体都启用则返回true
|
||||
*/
|
||||
public get enabled(): boolean {
|
||||
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置组件启用状态
|
||||
*
|
||||
* 当状态改变时会触发相应的生命周期回调。
|
||||
*
|
||||
* @param value - 新的启用状态
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
if (this._enabled !== value) {
|
||||
this._enabled = value;
|
||||
if (this._enabled) {
|
||||
this.onEnabled();
|
||||
} else {
|
||||
this.onDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取更新顺序
|
||||
*
|
||||
* @returns 组件的更新顺序值
|
||||
*/
|
||||
public get updateOrder(): number {
|
||||
return this._updateOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置更新顺序
|
||||
*
|
||||
* @param value - 新的更新顺序值
|
||||
*/
|
||||
public set updateOrder(value: number) {
|
||||
this._updateOrder = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件添加到实体时的回调
|
||||
*
|
||||
* 当组件被添加到实体时调用,可以在此方法中进行初始化操作。
|
||||
*/
|
||||
public onAddedToEntity(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件从实体移除时的回调
|
||||
*
|
||||
* 当组件从实体中移除时调用,可以在此方法中进行清理操作。
|
||||
*/
|
||||
public onRemovedFromEntity(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件启用时的回调
|
||||
*
|
||||
* 当组件被启用时调用。
|
||||
*/
|
||||
public onEnabled(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件禁用时的回调
|
||||
*
|
||||
* 当组件被禁用时调用。
|
||||
*/
|
||||
public onDisabled(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新组件
|
||||
*
|
||||
* 每帧调用,用于更新组件的逻辑。
|
||||
* 子类应该重写此方法来实现具体的更新逻辑。
|
||||
*/
|
||||
public update(): void {
|
||||
}
|
||||
}
|
||||
|
||||
// 避免循环引用,在文件末尾导入Entity
|
||||
import type { IComponent } from '../Types';
|
||||
|
||||
/**
|
||||
* 游戏组件基类
|
||||
*
|
||||
* ECS架构中的组件(Component),用于实现具体的游戏功能。
|
||||
* 组件包含数据和行为,可以被添加到实体上以扩展实体的功能。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class HealthComponent extends Component {
|
||||
* public health: number = 100;
|
||||
*
|
||||
* public takeDamage(damage: number): void {
|
||||
* this.health -= damage;
|
||||
* if (this.health <= 0) {
|
||||
* this.entity.destroy();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export abstract class Component implements IComponent {
|
||||
/**
|
||||
* 组件ID生成器
|
||||
*
|
||||
* 用于为每个组件分配唯一的ID。
|
||||
*/
|
||||
public static _idGenerator: number = 0;
|
||||
|
||||
/**
|
||||
* 组件唯一标识符
|
||||
*
|
||||
* 在整个游戏生命周期中唯一的数字ID。
|
||||
*/
|
||||
public readonly id: number;
|
||||
|
||||
/**
|
||||
* 组件所属的实体
|
||||
*
|
||||
* 指向拥有此组件的实体实例。
|
||||
*/
|
||||
public entity!: Entity;
|
||||
|
||||
/**
|
||||
* 组件启用状态
|
||||
*
|
||||
* 控制组件是否参与更新循环。
|
||||
*/
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 更新顺序
|
||||
*
|
||||
* 决定组件在更新循环中的执行顺序。
|
||||
*/
|
||||
private _updateOrder: number = 0;
|
||||
|
||||
/**
|
||||
* 创建组件实例
|
||||
*
|
||||
* 自动分配唯一ID给组件。
|
||||
*/
|
||||
constructor() {
|
||||
this.id = Component._idGenerator++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件启用状态
|
||||
*
|
||||
* 组件的实际启用状态取决于自身状态和所属实体的状态。
|
||||
*
|
||||
* @returns 如果组件和所属实体都启用则返回true
|
||||
*/
|
||||
public get enabled(): boolean {
|
||||
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置组件启用状态
|
||||
*
|
||||
* 当状态改变时会触发相应的生命周期回调。
|
||||
*
|
||||
* @param value - 新的启用状态
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
if (this._enabled !== value) {
|
||||
this._enabled = value;
|
||||
if (this._enabled) {
|
||||
this.onEnabled();
|
||||
} else {
|
||||
this.onDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取更新顺序
|
||||
*
|
||||
* @returns 组件的更新顺序值
|
||||
*/
|
||||
public get updateOrder(): number {
|
||||
return this._updateOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置更新顺序
|
||||
*
|
||||
* @param value - 新的更新顺序值
|
||||
*/
|
||||
public set updateOrder(value: number) {
|
||||
this._updateOrder = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件添加到实体时的回调
|
||||
*
|
||||
* 当组件被添加到实体时调用,可以在此方法中进行初始化操作。
|
||||
*/
|
||||
public onAddedToEntity(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件从实体移除时的回调
|
||||
*
|
||||
* 当组件从实体中移除时调用,可以在此方法中进行清理操作。
|
||||
*/
|
||||
public onRemovedFromEntity(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件启用时的回调
|
||||
*
|
||||
* 当组件被启用时调用。
|
||||
*/
|
||||
public onEnabled(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件禁用时的回调
|
||||
*
|
||||
* 当组件被禁用时调用。
|
||||
*/
|
||||
public onDisabled(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新组件
|
||||
*
|
||||
* 每帧调用,用于更新组件的逻辑。
|
||||
* 子类应该重写此方法来实现具体的更新逻辑。
|
||||
*/
|
||||
public update(): void {
|
||||
}
|
||||
}
|
||||
|
||||
// 避免循环引用,在文件末尾导入Entity
|
||||
import type { Entity } from './Entity';
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,420 +1,420 @@
|
||||
import { Entity } from './Entity';
|
||||
import { EntityList } from './Utils/EntityList';
|
||||
import { EntityProcessorList } from './Utils/EntityProcessorList';
|
||||
import { IdentifierPool } from './Utils/IdentifierPool';
|
||||
import { EntitySystem } from './Systems/EntitySystem';
|
||||
import { ComponentStorageManager } from './Core/ComponentStorage';
|
||||
import { QuerySystem } from './Core/QuerySystem';
|
||||
import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem';
|
||||
import type { IScene } from '../Types';
|
||||
|
||||
/**
|
||||
* 游戏场景类
|
||||
*
|
||||
* 管理游戏场景中的所有实体和系统,提供场景生命周期管理。
|
||||
* 场景是游戏世界的容器,负责协调实体和系统的运行。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class GameScene extends Scene {
|
||||
* public initialize(): void {
|
||||
* // 创建游戏实体
|
||||
* const player = this.createEntity("Player");
|
||||
*
|
||||
* // 添加系统
|
||||
* this.addEntityProcessor(new MovementSystem());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class Scene {
|
||||
/**
|
||||
* 场景名称
|
||||
*
|
||||
* 用于标识和调试的友好名称。
|
||||
*/
|
||||
public name: string = "";
|
||||
|
||||
/**
|
||||
* 场景中的实体集合
|
||||
*
|
||||
* 管理场景内所有实体的生命周期。
|
||||
*/
|
||||
public readonly entities: EntityList;
|
||||
|
||||
/**
|
||||
* 实体系统处理器集合
|
||||
*
|
||||
* 管理场景内所有实体系统的执行。
|
||||
*/
|
||||
public readonly entityProcessors: EntityProcessorList;
|
||||
|
||||
/**
|
||||
* 实体ID池
|
||||
*
|
||||
* 用于分配和回收实体的唯一标识符。
|
||||
*/
|
||||
public readonly identifierPool: IdentifierPool;
|
||||
|
||||
/**
|
||||
* 组件存储管理器
|
||||
*
|
||||
* 高性能的组件存储和查询系统。
|
||||
*/
|
||||
public readonly componentStorageManager: ComponentStorageManager;
|
||||
|
||||
/**
|
||||
* 查询系统
|
||||
*
|
||||
* 基于位掩码的高性能实体查询系统。
|
||||
*/
|
||||
public readonly querySystem: QuerySystem;
|
||||
|
||||
/**
|
||||
* 事件系统
|
||||
*
|
||||
* 类型安全的事件系统。
|
||||
*/
|
||||
public readonly eventSystem: TypeSafeEventSystem;
|
||||
|
||||
/**
|
||||
* 场景是否已开始运行
|
||||
*/
|
||||
private _didSceneBegin: boolean = false;
|
||||
|
||||
/**
|
||||
* 获取系统列表(兼容性属性)
|
||||
*/
|
||||
public get systems(): EntitySystem[] {
|
||||
return this.entityProcessors.processors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建场景实例
|
||||
* @param enableWasmAcceleration 是否启用WebAssembly加速,默认为true
|
||||
*/
|
||||
constructor(enableWasmAcceleration: boolean = true) {
|
||||
this.entities = new EntityList(this);
|
||||
this.entityProcessors = new EntityProcessorList();
|
||||
this.identifierPool = new IdentifierPool();
|
||||
this.componentStorageManager = new ComponentStorageManager();
|
||||
this.querySystem = new QuerySystem();
|
||||
this.eventSystem = new TypeSafeEventSystem();
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化场景
|
||||
*
|
||||
* 在场景创建时调用,子类可以重写此方法来设置初始实体和组件。
|
||||
*/
|
||||
public initialize(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景开始运行时的回调
|
||||
*
|
||||
* 在场景开始运行时调用,可以在此方法中执行场景启动逻辑。
|
||||
*/
|
||||
public onStart(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景卸载时的回调
|
||||
*
|
||||
* 在场景被销毁时调用,可以在此方法中执行清理工作。
|
||||
*/
|
||||
public unload(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始场景,启动实体处理器等
|
||||
*
|
||||
* 这个方法会启动场景。它将启动实体处理器等,并调用onStart方法。
|
||||
*/
|
||||
public begin() {
|
||||
// 启动实体处理器
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.begin();
|
||||
|
||||
// 标记场景已开始运行并调用onStart方法
|
||||
this._didSceneBegin = true;
|
||||
this.onStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束场景,清除实体、实体处理器等
|
||||
*
|
||||
* 这个方法会结束场景。它将移除所有实体,结束实体处理器等,并调用unload方法。
|
||||
*/
|
||||
public end() {
|
||||
// 标记场景已结束运行
|
||||
this._didSceneBegin = false;
|
||||
|
||||
// 移除所有实体
|
||||
this.entities.removeAllEntities();
|
||||
|
||||
// 清空组件存储
|
||||
this.componentStorageManager.clear();
|
||||
|
||||
// 结束实体处理器
|
||||
if (this.entityProcessors)
|
||||
this.entityProcessors.end();
|
||||
|
||||
// 调用卸载方法
|
||||
this.unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新场景,更新实体组件、实体处理器等
|
||||
*/
|
||||
public update() {
|
||||
// 更新实体列表
|
||||
this.entities.updateLists();
|
||||
|
||||
// 更新实体处理器
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.update();
|
||||
|
||||
// 更新实体组
|
||||
this.entities.update();
|
||||
|
||||
// 更新实体处理器的后处理方法
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.lateUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将实体添加到此场景,并返回它
|
||||
* @param name 实体名称
|
||||
*/
|
||||
public createEntity(name: string) {
|
||||
let entity = new Entity(name, this.identifierPool.checkOut());
|
||||
return this.addEntity(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景的实体列表中添加一个实体
|
||||
* @param entity 要添加的实体
|
||||
* @param deferCacheClear 是否延迟缓存清理(用于批量操作)
|
||||
*/
|
||||
public addEntity(entity: Entity, deferCacheClear: boolean = false) {
|
||||
this.entities.add(entity);
|
||||
entity.scene = this;
|
||||
|
||||
// 将实体添加到查询系统(可延迟缓存清理)
|
||||
this.querySystem.addEntity(entity, deferCacheClear);
|
||||
|
||||
// 触发实体添加事件
|
||||
this.eventSystem.emitSync('entity:added', { entity, scene: this });
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建实体(高性能版本)
|
||||
* @param count 要创建的实体数量
|
||||
* @param namePrefix 实体名称前缀
|
||||
* @returns 创建的实体列表
|
||||
*/
|
||||
public createEntities(count: number, namePrefix: string = "Entity"): Entity[] {
|
||||
const entities: Entity[] = [];
|
||||
|
||||
// 批量创建实体对象,不立即添加到系统
|
||||
for (let i = 0; i < count; i++) {
|
||||
const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut());
|
||||
entity.scene = this;
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
// 批量添加到实体列表
|
||||
for (const entity of entities) {
|
||||
this.entities.add(entity);
|
||||
}
|
||||
|
||||
// 批量添加到查询系统(无重复检查,性能最优)
|
||||
this.querySystem.addEntitiesUnchecked(entities);
|
||||
|
||||
// 批量触发事件(可选,减少事件开销)
|
||||
this.eventSystem.emitSync('entities:batch_added', { entities, scene: this, count });
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建实体
|
||||
* @param count 要创建的实体数量
|
||||
* @param namePrefix 实体名称前缀
|
||||
* @returns 创建的实体列表
|
||||
*/
|
||||
public createEntitiesOld(count: number, namePrefix: string = "Entity"): Entity[] {
|
||||
const entities: Entity[] = [];
|
||||
|
||||
// 批量创建实体,延迟缓存清理
|
||||
for (let i = 0; i < count; i++) {
|
||||
const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut());
|
||||
entities.push(entity);
|
||||
this.addEntity(entity, true); // 延迟缓存清理
|
||||
}
|
||||
|
||||
// 最后统一清理缓存
|
||||
this.querySystem.clearCache();
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除所有实体
|
||||
*/
|
||||
public destroyAllEntities() {
|
||||
for (let i = 0; i < this.entities.count; i++) {
|
||||
this.entities.buffer[i].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索并返回第一个具有名称的实体
|
||||
* @param name 实体名称
|
||||
*/
|
||||
public findEntity(name: string): Entity | null {
|
||||
return this.entities.findEntity(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查找实体
|
||||
* @param id 实体ID
|
||||
*/
|
||||
public findEntityById(id: number): Entity | null {
|
||||
return this.entities.findEntityById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签查找实体
|
||||
* @param tag 实体标签
|
||||
*/
|
||||
public findEntitiesByTag(tag: number): Entity[] {
|
||||
const result: Entity[] = [];
|
||||
for (const entity of this.entities.buffer) {
|
||||
if (entity.tag === tag) {
|
||||
result.push(entity);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称查找实体(别名方法)
|
||||
* @param name 实体名称
|
||||
*/
|
||||
public getEntityByName(name: string): Entity | null {
|
||||
return this.findEntity(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签查找实体(别名方法)
|
||||
* @param tag 实体标签
|
||||
*/
|
||||
public getEntitiesByTag(tag: number): Entity[] {
|
||||
return this.findEntitiesByTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景中添加一个EntitySystem处理器
|
||||
* @param processor 处理器
|
||||
*/
|
||||
public addEntityProcessor(processor: EntitySystem) {
|
||||
processor.scene = this;
|
||||
this.entityProcessors.add(processor);
|
||||
|
||||
processor.setUpdateOrder(this.entityProcessors.count - 1);
|
||||
return processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加系统到场景(addEntityProcessor的别名)
|
||||
* @param system 系统
|
||||
*/
|
||||
public addSystem(system: EntitySystem) {
|
||||
return this.addEntityProcessor(system);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除EntitySystem处理器
|
||||
* @param processor 要删除的处理器
|
||||
*/
|
||||
public removeEntityProcessor(processor: EntitySystem) {
|
||||
this.entityProcessors.remove(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的EntitySystem处理器
|
||||
* @param type 处理器类型
|
||||
*/
|
||||
public getEntityProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T | null {
|
||||
return this.entityProcessors.getProcessor(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
entityCount: number;
|
||||
processorCount: number;
|
||||
componentStorageStats: Map<string, any>;
|
||||
} {
|
||||
return {
|
||||
entityCount: this.entities.count,
|
||||
processorCount: this.entityProcessors.count,
|
||||
componentStorageStats: this.componentStorageManager.getAllStats()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩组件存储(清理碎片)
|
||||
*/
|
||||
public compactComponentStorage(): void {
|
||||
this.componentStorageManager.compactAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景的调试信息
|
||||
*/
|
||||
public getDebugInfo(): {
|
||||
name: string;
|
||||
entityCount: number;
|
||||
processorCount: number;
|
||||
isRunning: boolean;
|
||||
entities: Array<{
|
||||
name: string;
|
||||
id: number;
|
||||
componentCount: number;
|
||||
componentTypes: string[];
|
||||
}>;
|
||||
processors: Array<{
|
||||
name: string;
|
||||
updateOrder: number;
|
||||
entityCount: number;
|
||||
}>;
|
||||
componentStats: Map<string, any>;
|
||||
} {
|
||||
return {
|
||||
name: this.constructor.name,
|
||||
entityCount: this.entities.count,
|
||||
processorCount: this.entityProcessors.count,
|
||||
isRunning: this._didSceneBegin,
|
||||
entities: this.entities.buffer.map(entity => ({
|
||||
name: entity.name,
|
||||
id: entity.id,
|
||||
componentCount: entity.components.length,
|
||||
componentTypes: entity.components.map(c => c.constructor.name)
|
||||
})),
|
||||
processors: this.entityProcessors.processors.map(processor => ({
|
||||
name: processor.constructor.name,
|
||||
updateOrder: processor.updateOrder,
|
||||
entityCount: (processor as any)._entities?.length || 0
|
||||
})),
|
||||
componentStats: this.componentStorageManager.getAllStats()
|
||||
};
|
||||
}
|
||||
import { Entity } from './Entity';
|
||||
import { EntityList } from './Utils/EntityList';
|
||||
import { EntityProcessorList } from './Utils/EntityProcessorList';
|
||||
import { IdentifierPool } from './Utils/IdentifierPool';
|
||||
import { EntitySystem } from './Systems/EntitySystem';
|
||||
import { ComponentStorageManager } from './Core/ComponentStorage';
|
||||
import { QuerySystem } from './Core/QuerySystem';
|
||||
import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem';
|
||||
import type { IScene } from '../Types';
|
||||
|
||||
/**
|
||||
* 游戏场景类
|
||||
*
|
||||
* 管理游戏场景中的所有实体和系统,提供场景生命周期管理。
|
||||
* 场景是游戏世界的容器,负责协调实体和系统的运行。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class GameScene extends Scene {
|
||||
* public initialize(): void {
|
||||
* // 创建游戏实体
|
||||
* const player = this.createEntity("Player");
|
||||
*
|
||||
* // 添加系统
|
||||
* this.addEntityProcessor(new MovementSystem());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class Scene {
|
||||
/**
|
||||
* 场景名称
|
||||
*
|
||||
* 用于标识和调试的友好名称。
|
||||
*/
|
||||
public name: string = "";
|
||||
|
||||
/**
|
||||
* 场景中的实体集合
|
||||
*
|
||||
* 管理场景内所有实体的生命周期。
|
||||
*/
|
||||
public readonly entities: EntityList;
|
||||
|
||||
/**
|
||||
* 实体系统处理器集合
|
||||
*
|
||||
* 管理场景内所有实体系统的执行。
|
||||
*/
|
||||
public readonly entityProcessors: EntityProcessorList;
|
||||
|
||||
/**
|
||||
* 实体ID池
|
||||
*
|
||||
* 用于分配和回收实体的唯一标识符。
|
||||
*/
|
||||
public readonly identifierPool: IdentifierPool;
|
||||
|
||||
/**
|
||||
* 组件存储管理器
|
||||
*
|
||||
* 高性能的组件存储和查询系统。
|
||||
*/
|
||||
public readonly componentStorageManager: ComponentStorageManager;
|
||||
|
||||
/**
|
||||
* 查询系统
|
||||
*
|
||||
* 基于位掩码的高性能实体查询系统。
|
||||
*/
|
||||
public readonly querySystem: QuerySystem;
|
||||
|
||||
/**
|
||||
* 事件系统
|
||||
*
|
||||
* 类型安全的事件系统。
|
||||
*/
|
||||
public readonly eventSystem: TypeSafeEventSystem;
|
||||
|
||||
/**
|
||||
* 场景是否已开始运行
|
||||
*/
|
||||
private _didSceneBegin: boolean = false;
|
||||
|
||||
/**
|
||||
* 获取系统列表(兼容性属性)
|
||||
*/
|
||||
public get systems(): EntitySystem[] {
|
||||
return this.entityProcessors.processors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建场景实例
|
||||
* @param enableWasmAcceleration 是否启用WebAssembly加速,默认为true
|
||||
*/
|
||||
constructor(enableWasmAcceleration: boolean = true) {
|
||||
this.entities = new EntityList(this);
|
||||
this.entityProcessors = new EntityProcessorList();
|
||||
this.identifierPool = new IdentifierPool();
|
||||
this.componentStorageManager = new ComponentStorageManager();
|
||||
this.querySystem = new QuerySystem();
|
||||
this.eventSystem = new TypeSafeEventSystem();
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化场景
|
||||
*
|
||||
* 在场景创建时调用,子类可以重写此方法来设置初始实体和组件。
|
||||
*/
|
||||
public initialize(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景开始运行时的回调
|
||||
*
|
||||
* 在场景开始运行时调用,可以在此方法中执行场景启动逻辑。
|
||||
*/
|
||||
public onStart(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景卸载时的回调
|
||||
*
|
||||
* 在场景被销毁时调用,可以在此方法中执行清理工作。
|
||||
*/
|
||||
public unload(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始场景,启动实体处理器等
|
||||
*
|
||||
* 这个方法会启动场景。它将启动实体处理器等,并调用onStart方法。
|
||||
*/
|
||||
public begin() {
|
||||
// 启动实体处理器
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.begin();
|
||||
|
||||
// 标记场景已开始运行并调用onStart方法
|
||||
this._didSceneBegin = true;
|
||||
this.onStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束场景,清除实体、实体处理器等
|
||||
*
|
||||
* 这个方法会结束场景。它将移除所有实体,结束实体处理器等,并调用unload方法。
|
||||
*/
|
||||
public end() {
|
||||
// 标记场景已结束运行
|
||||
this._didSceneBegin = false;
|
||||
|
||||
// 移除所有实体
|
||||
this.entities.removeAllEntities();
|
||||
|
||||
// 清空组件存储
|
||||
this.componentStorageManager.clear();
|
||||
|
||||
// 结束实体处理器
|
||||
if (this.entityProcessors)
|
||||
this.entityProcessors.end();
|
||||
|
||||
// 调用卸载方法
|
||||
this.unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新场景,更新实体组件、实体处理器等
|
||||
*/
|
||||
public update() {
|
||||
// 更新实体列表
|
||||
this.entities.updateLists();
|
||||
|
||||
// 更新实体处理器
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.update();
|
||||
|
||||
// 更新实体组
|
||||
this.entities.update();
|
||||
|
||||
// 更新实体处理器的后处理方法
|
||||
if (this.entityProcessors != null)
|
||||
this.entityProcessors.lateUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将实体添加到此场景,并返回它
|
||||
* @param name 实体名称
|
||||
*/
|
||||
public createEntity(name: string) {
|
||||
let entity = new Entity(name, this.identifierPool.checkOut());
|
||||
return this.addEntity(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景的实体列表中添加一个实体
|
||||
* @param entity 要添加的实体
|
||||
* @param deferCacheClear 是否延迟缓存清理(用于批量操作)
|
||||
*/
|
||||
public addEntity(entity: Entity, deferCacheClear: boolean = false) {
|
||||
this.entities.add(entity);
|
||||
entity.scene = this;
|
||||
|
||||
// 将实体添加到查询系统(可延迟缓存清理)
|
||||
this.querySystem.addEntity(entity, deferCacheClear);
|
||||
|
||||
// 触发实体添加事件
|
||||
this.eventSystem.emitSync('entity:added', { entity, scene: this });
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建实体(高性能版本)
|
||||
* @param count 要创建的实体数量
|
||||
* @param namePrefix 实体名称前缀
|
||||
* @returns 创建的实体列表
|
||||
*/
|
||||
public createEntities(count: number, namePrefix: string = "Entity"): Entity[] {
|
||||
const entities: Entity[] = [];
|
||||
|
||||
// 批量创建实体对象,不立即添加到系统
|
||||
for (let i = 0; i < count; i++) {
|
||||
const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut());
|
||||
entity.scene = this;
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
// 批量添加到实体列表
|
||||
for (const entity of entities) {
|
||||
this.entities.add(entity);
|
||||
}
|
||||
|
||||
// 批量添加到查询系统(无重复检查,性能最优)
|
||||
this.querySystem.addEntitiesUnchecked(entities);
|
||||
|
||||
// 批量触发事件(可选,减少事件开销)
|
||||
this.eventSystem.emitSync('entities:batch_added', { entities, scene: this, count });
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建实体
|
||||
* @param count 要创建的实体数量
|
||||
* @param namePrefix 实体名称前缀
|
||||
* @returns 创建的实体列表
|
||||
*/
|
||||
public createEntitiesOld(count: number, namePrefix: string = "Entity"): Entity[] {
|
||||
const entities: Entity[] = [];
|
||||
|
||||
// 批量创建实体,延迟缓存清理
|
||||
for (let i = 0; i < count; i++) {
|
||||
const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut());
|
||||
entities.push(entity);
|
||||
this.addEntity(entity, true); // 延迟缓存清理
|
||||
}
|
||||
|
||||
// 最后统一清理缓存
|
||||
this.querySystem.clearCache();
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除所有实体
|
||||
*/
|
||||
public destroyAllEntities() {
|
||||
for (let i = 0; i < this.entities.count; i++) {
|
||||
this.entities.buffer[i].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索并返回第一个具有名称的实体
|
||||
* @param name 实体名称
|
||||
*/
|
||||
public findEntity(name: string): Entity | null {
|
||||
return this.entities.findEntity(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查找实体
|
||||
* @param id 实体ID
|
||||
*/
|
||||
public findEntityById(id: number): Entity | null {
|
||||
return this.entities.findEntityById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签查找实体
|
||||
* @param tag 实体标签
|
||||
*/
|
||||
public findEntitiesByTag(tag: number): Entity[] {
|
||||
const result: Entity[] = [];
|
||||
for (const entity of this.entities.buffer) {
|
||||
if (entity.tag === tag) {
|
||||
result.push(entity);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称查找实体(别名方法)
|
||||
* @param name 实体名称
|
||||
*/
|
||||
public getEntityByName(name: string): Entity | null {
|
||||
return this.findEntity(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签查找实体(别名方法)
|
||||
* @param tag 实体标签
|
||||
*/
|
||||
public getEntitiesByTag(tag: number): Entity[] {
|
||||
return this.findEntitiesByTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在场景中添加一个EntitySystem处理器
|
||||
* @param processor 处理器
|
||||
*/
|
||||
public addEntityProcessor(processor: EntitySystem) {
|
||||
processor.scene = this;
|
||||
this.entityProcessors.add(processor);
|
||||
|
||||
processor.setUpdateOrder(this.entityProcessors.count - 1);
|
||||
return processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加系统到场景(addEntityProcessor的别名)
|
||||
* @param system 系统
|
||||
*/
|
||||
public addSystem(system: EntitySystem) {
|
||||
return this.addEntityProcessor(system);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从场景中删除EntitySystem处理器
|
||||
* @param processor 要删除的处理器
|
||||
*/
|
||||
public removeEntityProcessor(processor: EntitySystem) {
|
||||
this.entityProcessors.remove(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的EntitySystem处理器
|
||||
* @param type 处理器类型
|
||||
*/
|
||||
public getEntityProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T | null {
|
||||
return this.entityProcessors.getProcessor(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
entityCount: number;
|
||||
processorCount: number;
|
||||
componentStorageStats: Map<string, any>;
|
||||
} {
|
||||
return {
|
||||
entityCount: this.entities.count,
|
||||
processorCount: this.entityProcessors.count,
|
||||
componentStorageStats: this.componentStorageManager.getAllStats()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩组件存储(清理碎片)
|
||||
*/
|
||||
public compactComponentStorage(): void {
|
||||
this.componentStorageManager.compactAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景的调试信息
|
||||
*/
|
||||
public getDebugInfo(): {
|
||||
name: string;
|
||||
entityCount: number;
|
||||
processorCount: number;
|
||||
isRunning: boolean;
|
||||
entities: Array<{
|
||||
name: string;
|
||||
id: number;
|
||||
componentCount: number;
|
||||
componentTypes: string[];
|
||||
}>;
|
||||
processors: Array<{
|
||||
name: string;
|
||||
updateOrder: number;
|
||||
entityCount: number;
|
||||
}>;
|
||||
componentStats: Map<string, any>;
|
||||
} {
|
||||
return {
|
||||
name: this.constructor.name,
|
||||
entityCount: this.entities.count,
|
||||
processorCount: this.entityProcessors.count,
|
||||
isRunning: this._didSceneBegin,
|
||||
entities: this.entities.buffer.map(entity => ({
|
||||
name: entity.name,
|
||||
id: entity.id,
|
||||
componentCount: entity.components.length,
|
||||
componentTypes: entity.components.map(c => c.constructor.name)
|
||||
})),
|
||||
processors: this.entityProcessors.processors.map(processor => ({
|
||||
name: processor.constructor.name,
|
||||
updateOrder: processor.updateOrder,
|
||||
entityCount: (processor as any)._entities?.length || 0
|
||||
})),
|
||||
componentStats: this.componentStorageManager.getAllStats()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,319 +1,319 @@
|
||||
import { Entity } from '../Entity';
|
||||
import { Core } from '../../Core';
|
||||
import { Matcher } from '../Utils/Matcher';
|
||||
import { PerformanceMonitor } from '../../Utils/PerformanceMonitor';
|
||||
import type { Scene } from '../Scene';
|
||||
import type { ISystemBase } from '../../Types';
|
||||
|
||||
/**
|
||||
* 实体系统的基类
|
||||
*
|
||||
* 用于处理一组符合特定条件的实体。系统是ECS架构中的逻辑处理单元,
|
||||
* 负责对拥有特定组件组合的实体执行业务逻辑。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class MovementSystem extends EntitySystem {
|
||||
* constructor() {
|
||||
* super(Matcher.empty().all(Transform, Velocity));
|
||||
* }
|
||||
*
|
||||
* protected process(entities: Entity[]): void {
|
||||
* for (const entity of entities) {
|
||||
* const transform = entity.getComponent(Transform);
|
||||
* const velocity = entity.getComponent(Velocity);
|
||||
* transform.position.add(velocity.value);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export abstract class EntitySystem implements ISystemBase {
|
||||
private _entities: Entity[] = [];
|
||||
private _updateOrder: number = 0;
|
||||
private _enabled: boolean = true;
|
||||
private _performanceMonitor = PerformanceMonitor.instance;
|
||||
private _systemName: string;
|
||||
|
||||
/**
|
||||
* 获取系统处理的实体列表
|
||||
*/
|
||||
public get entities(): readonly Entity[] {
|
||||
return this._entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的更新时序
|
||||
*/
|
||||
public get updateOrder(): number {
|
||||
return this._updateOrder;
|
||||
}
|
||||
|
||||
public set updateOrder(value: number) {
|
||||
this.setUpdateOrder(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的启用状态
|
||||
*/
|
||||
public get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置系统的启用状态
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
this._enabled = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统名称
|
||||
*/
|
||||
public get systemName(): string {
|
||||
return this._systemName;
|
||||
}
|
||||
|
||||
constructor(matcher?: Matcher) {
|
||||
this._matcher = matcher ? matcher : Matcher.empty();
|
||||
this._systemName = this.constructor.name;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
private _scene!: Scene;
|
||||
|
||||
/**
|
||||
* 这个系统所属的场景
|
||||
*/
|
||||
public get scene(): Scene {
|
||||
return this._scene;
|
||||
}
|
||||
|
||||
public set scene(value: Scene) {
|
||||
this._scene = value;
|
||||
this._entities = [];
|
||||
}
|
||||
|
||||
private _matcher: Matcher;
|
||||
|
||||
/**
|
||||
* 获取实体匹配器
|
||||
*/
|
||||
public get matcher(): Matcher {
|
||||
return this._matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置更新时序
|
||||
* @param order 更新时序
|
||||
*/
|
||||
public setUpdateOrder(order: number): void {
|
||||
this._updateOrder = order;
|
||||
this.scene.entityProcessors.setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统初始化
|
||||
*
|
||||
* 在系统创建时调用,子类可以重写此方法进行初始化操作。
|
||||
*/
|
||||
public initialize(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体的组件发生变化时调用
|
||||
*
|
||||
* 检查实体是否仍然符合系统的匹配条件,并相应地添加或移除实体。
|
||||
*
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
public onChanged(entity: Entity): void {
|
||||
const contains = this._entities.includes(entity);
|
||||
const interest = this._matcher.isInterestedEntity(entity);
|
||||
|
||||
if (interest && !contains) {
|
||||
this.add(entity);
|
||||
} else if (!interest && contains) {
|
||||
this.remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加实体到系统
|
||||
*
|
||||
* @param entity 要添加的实体
|
||||
*/
|
||||
public add(entity: Entity): void {
|
||||
if (!this._entities.includes(entity)) {
|
||||
this._entities.push(entity);
|
||||
this.onAdded(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体被添加到系统时调用
|
||||
*
|
||||
* 子类可以重写此方法来处理实体添加事件。
|
||||
*
|
||||
* @param entity 被添加的实体
|
||||
*/
|
||||
protected onAdded(entity: Entity): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 从系统中移除实体
|
||||
*
|
||||
* @param entity 要移除的实体
|
||||
*/
|
||||
public remove(entity: Entity): void {
|
||||
const index = this._entities.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
this._entities.splice(index, 1);
|
||||
this.onRemoved(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体从系统中移除时调用
|
||||
*
|
||||
* 子类可以重写此方法来处理实体移除事件。
|
||||
*
|
||||
* @param entity 被移除的实体
|
||||
*/
|
||||
protected onRemoved(entity: Entity): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统
|
||||
*
|
||||
* 在每帧调用,处理系统的主要逻辑。
|
||||
*/
|
||||
public update(): void {
|
||||
if (!this._enabled || !this.checkProcessing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = this._performanceMonitor.startMonitoring(this._systemName);
|
||||
|
||||
try {
|
||||
this.begin();
|
||||
this.process(this._entities);
|
||||
} finally {
|
||||
this._performanceMonitor.endMonitoring(this._systemName, startTime, this._entities.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后期更新系统
|
||||
*
|
||||
* 在所有系统的update方法执行完毕后调用。
|
||||
*/
|
||||
public lateUpdate(): void {
|
||||
if (!this._enabled || !this.checkProcessing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = this._performanceMonitor.startMonitoring(`${this._systemName}_Late`);
|
||||
|
||||
try {
|
||||
this.lateProcess(this._entities);
|
||||
this.end();
|
||||
} finally {
|
||||
this._performanceMonitor.endMonitoring(`${this._systemName}_Late`, startTime, this._entities.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在系统处理开始前调用
|
||||
*
|
||||
* 子类可以重写此方法进行预处理操作。
|
||||
*/
|
||||
protected begin(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理实体列表
|
||||
*
|
||||
* 系统的核心逻辑,子类必须实现此方法来定义具体的处理逻辑。
|
||||
*
|
||||
* @param entities 要处理的实体列表
|
||||
*/
|
||||
protected process(entities: Entity[]): void {
|
||||
// 子类必须实现此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 后期处理实体列表
|
||||
*
|
||||
* 在主要处理逻辑之后执行,子类可以重写此方法。
|
||||
*
|
||||
* @param entities 要处理的实体列表
|
||||
*/
|
||||
protected lateProcess(entities: Entity[]): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统处理完毕后调用
|
||||
*
|
||||
* 子类可以重写此方法进行后处理操作。
|
||||
*/
|
||||
protected end(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查系统是否需要处理
|
||||
*
|
||||
* 在启用系统时有用,但仅偶尔需要处理。
|
||||
* 这只影响处理,不影响事件或订阅列表。
|
||||
*
|
||||
* @returns 如果系统应该处理,则为true,如果不处理则为false
|
||||
*/
|
||||
protected checkProcessing(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的性能数据
|
||||
*
|
||||
* @returns 性能数据或undefined
|
||||
*/
|
||||
public getPerformanceData() {
|
||||
return this._performanceMonitor.getSystemData(this._systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的性能统计
|
||||
*
|
||||
* @returns 性能统计或undefined
|
||||
*/
|
||||
public getPerformanceStats() {
|
||||
return this._performanceMonitor.getSystemStats(this._systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置系统的性能数据
|
||||
*/
|
||||
public resetPerformanceData(): void {
|
||||
this._performanceMonitor.resetSystem(this._systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统信息的字符串表示
|
||||
*
|
||||
* @returns 系统信息字符串
|
||||
*/
|
||||
public toString(): string {
|
||||
const entityCount = this._entities.length;
|
||||
const perfData = this.getPerformanceData();
|
||||
const perfInfo = perfData ? ` (${perfData.executionTime.toFixed(2)}ms)` : '';
|
||||
|
||||
return `${this._systemName}[${entityCount} entities]${perfInfo}`;
|
||||
}
|
||||
}
|
||||
|
||||
import { Entity } from '../Entity';
|
||||
import { Core } from '../../Core';
|
||||
import { Matcher } from '../Utils/Matcher';
|
||||
import { PerformanceMonitor } from '../../Utils/PerformanceMonitor';
|
||||
import type { Scene } from '../Scene';
|
||||
import type { ISystemBase } from '../../Types';
|
||||
|
||||
/**
|
||||
* 实体系统的基类
|
||||
*
|
||||
* 用于处理一组符合特定条件的实体。系统是ECS架构中的逻辑处理单元,
|
||||
* 负责对拥有特定组件组合的实体执行业务逻辑。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class MovementSystem extends EntitySystem {
|
||||
* constructor() {
|
||||
* super(Matcher.empty().all(Transform, Velocity));
|
||||
* }
|
||||
*
|
||||
* protected process(entities: Entity[]): void {
|
||||
* for (const entity of entities) {
|
||||
* const transform = entity.getComponent(Transform);
|
||||
* const velocity = entity.getComponent(Velocity);
|
||||
* transform.position.add(velocity.value);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export abstract class EntitySystem implements ISystemBase {
|
||||
private _entities: Entity[] = [];
|
||||
private _updateOrder: number = 0;
|
||||
private _enabled: boolean = true;
|
||||
private _performanceMonitor = PerformanceMonitor.instance;
|
||||
private _systemName: string;
|
||||
|
||||
/**
|
||||
* 获取系统处理的实体列表
|
||||
*/
|
||||
public get entities(): readonly Entity[] {
|
||||
return this._entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的更新时序
|
||||
*/
|
||||
public get updateOrder(): number {
|
||||
return this._updateOrder;
|
||||
}
|
||||
|
||||
public set updateOrder(value: number) {
|
||||
this.setUpdateOrder(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的启用状态
|
||||
*/
|
||||
public get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置系统的启用状态
|
||||
*/
|
||||
public set enabled(value: boolean) {
|
||||
this._enabled = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统名称
|
||||
*/
|
||||
public get systemName(): string {
|
||||
return this._systemName;
|
||||
}
|
||||
|
||||
constructor(matcher?: Matcher) {
|
||||
this._matcher = matcher ? matcher : Matcher.empty();
|
||||
this._systemName = this.constructor.name;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
private _scene!: Scene;
|
||||
|
||||
/**
|
||||
* 这个系统所属的场景
|
||||
*/
|
||||
public get scene(): Scene {
|
||||
return this._scene;
|
||||
}
|
||||
|
||||
public set scene(value: Scene) {
|
||||
this._scene = value;
|
||||
this._entities = [];
|
||||
}
|
||||
|
||||
private _matcher: Matcher;
|
||||
|
||||
/**
|
||||
* 获取实体匹配器
|
||||
*/
|
||||
public get matcher(): Matcher {
|
||||
return this._matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置更新时序
|
||||
* @param order 更新时序
|
||||
*/
|
||||
public setUpdateOrder(order: number): void {
|
||||
this._updateOrder = order;
|
||||
this.scene.entityProcessors.setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统初始化
|
||||
*
|
||||
* 在系统创建时调用,子类可以重写此方法进行初始化操作。
|
||||
*/
|
||||
public initialize(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体的组件发生变化时调用
|
||||
*
|
||||
* 检查实体是否仍然符合系统的匹配条件,并相应地添加或移除实体。
|
||||
*
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
public onChanged(entity: Entity): void {
|
||||
const contains = this._entities.includes(entity);
|
||||
const interest = this._matcher.isInterestedEntity(entity);
|
||||
|
||||
if (interest && !contains) {
|
||||
this.add(entity);
|
||||
} else if (!interest && contains) {
|
||||
this.remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加实体到系统
|
||||
*
|
||||
* @param entity 要添加的实体
|
||||
*/
|
||||
public add(entity: Entity): void {
|
||||
if (!this._entities.includes(entity)) {
|
||||
this._entities.push(entity);
|
||||
this.onAdded(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体被添加到系统时调用
|
||||
*
|
||||
* 子类可以重写此方法来处理实体添加事件。
|
||||
*
|
||||
* @param entity 被添加的实体
|
||||
*/
|
||||
protected onAdded(entity: Entity): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 从系统中移除实体
|
||||
*
|
||||
* @param entity 要移除的实体
|
||||
*/
|
||||
public remove(entity: Entity): void {
|
||||
const index = this._entities.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
this._entities.splice(index, 1);
|
||||
this.onRemoved(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体从系统中移除时调用
|
||||
*
|
||||
* 子类可以重写此方法来处理实体移除事件。
|
||||
*
|
||||
* @param entity 被移除的实体
|
||||
*/
|
||||
protected onRemoved(entity: Entity): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统
|
||||
*
|
||||
* 在每帧调用,处理系统的主要逻辑。
|
||||
*/
|
||||
public update(): void {
|
||||
if (!this._enabled || !this.checkProcessing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = this._performanceMonitor.startMonitoring(this._systemName);
|
||||
|
||||
try {
|
||||
this.begin();
|
||||
this.process(this._entities);
|
||||
} finally {
|
||||
this._performanceMonitor.endMonitoring(this._systemName, startTime, this._entities.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后期更新系统
|
||||
*
|
||||
* 在所有系统的update方法执行完毕后调用。
|
||||
*/
|
||||
public lateUpdate(): void {
|
||||
if (!this._enabled || !this.checkProcessing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = this._performanceMonitor.startMonitoring(`${this._systemName}_Late`);
|
||||
|
||||
try {
|
||||
this.lateProcess(this._entities);
|
||||
this.end();
|
||||
} finally {
|
||||
this._performanceMonitor.endMonitoring(`${this._systemName}_Late`, startTime, this._entities.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在系统处理开始前调用
|
||||
*
|
||||
* 子类可以重写此方法进行预处理操作。
|
||||
*/
|
||||
protected begin(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理实体列表
|
||||
*
|
||||
* 系统的核心逻辑,子类必须实现此方法来定义具体的处理逻辑。
|
||||
*
|
||||
* @param entities 要处理的实体列表
|
||||
*/
|
||||
protected process(entities: Entity[]): void {
|
||||
// 子类必须实现此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 后期处理实体列表
|
||||
*
|
||||
* 在主要处理逻辑之后执行,子类可以重写此方法。
|
||||
*
|
||||
* @param entities 要处理的实体列表
|
||||
*/
|
||||
protected lateProcess(entities: Entity[]): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统处理完毕后调用
|
||||
*
|
||||
* 子类可以重写此方法进行后处理操作。
|
||||
*/
|
||||
protected end(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查系统是否需要处理
|
||||
*
|
||||
* 在启用系统时有用,但仅偶尔需要处理。
|
||||
* 这只影响处理,不影响事件或订阅列表。
|
||||
*
|
||||
* @returns 如果系统应该处理,则为true,如果不处理则为false
|
||||
*/
|
||||
protected checkProcessing(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的性能数据
|
||||
*
|
||||
* @returns 性能数据或undefined
|
||||
*/
|
||||
public getPerformanceData() {
|
||||
return this._performanceMonitor.getSystemData(this._systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的性能统计
|
||||
*
|
||||
* @returns 性能统计或undefined
|
||||
*/
|
||||
public getPerformanceStats() {
|
||||
return this._performanceMonitor.getSystemStats(this._systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置系统的性能数据
|
||||
*/
|
||||
public resetPerformanceData(): void {
|
||||
this._performanceMonitor.resetSystem(this._systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统信息的字符串表示
|
||||
*
|
||||
* @returns 系统信息字符串
|
||||
*/
|
||||
public toString(): string {
|
||||
const entityCount = this._entities.length;
|
||||
const perfData = this.getPerformanceData();
|
||||
const perfInfo = perfData ? ` (${perfData.executionTime.toFixed(2)}ms)` : '';
|
||||
|
||||
return `${this._systemName}[${entityCount} entities]${perfInfo}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { EntitySystem } from './EntitySystem';
|
||||
import { Entity } from '../Entity';
|
||||
|
||||
/**
|
||||
* 被动实体系统
|
||||
* 定义一个被动的实体系统,继承自EntitySystem类
|
||||
* 被动的实体系统不会对实体进行任何修改,只会被动地接收实体的变化事件
|
||||
*/
|
||||
export abstract class PassiveSystem extends EntitySystem {
|
||||
/**
|
||||
* 当实体发生变化时,不进行任何操作
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
public override onChanged(entity: Entity): void { }
|
||||
|
||||
/**
|
||||
* 不进行任何处理
|
||||
* @param entities 实体数组,未被使用
|
||||
*/
|
||||
protected override process(entities: Entity[]): void {
|
||||
// 被动系统不进行任何处理
|
||||
}
|
||||
}
|
||||
import { EntitySystem } from './EntitySystem';
|
||||
import { Entity } from '../Entity';
|
||||
|
||||
/**
|
||||
* 被动实体系统
|
||||
* 定义一个被动的实体系统,继承自EntitySystem类
|
||||
* 被动的实体系统不会对实体进行任何修改,只会被动地接收实体的变化事件
|
||||
*/
|
||||
export abstract class PassiveSystem extends EntitySystem {
|
||||
/**
|
||||
* 当实体发生变化时,不进行任何操作
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
public override onChanged(entity: Entity): void { }
|
||||
|
||||
/**
|
||||
* 不进行任何处理
|
||||
* @param entities 实体数组,未被使用
|
||||
*/
|
||||
protected override process(entities: Entity[]): void {
|
||||
// 被动系统不进行任何处理
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,29 @@
|
||||
import { EntitySystem } from './EntitySystem';
|
||||
import { Entity } from '../Entity';
|
||||
|
||||
/**
|
||||
* 处理系统抽象类
|
||||
* 定义一个处理实体的抽象类,继承自EntitySystem类
|
||||
* 子类需要实现processSystem方法,用于实现具体的处理逻辑
|
||||
*/
|
||||
export abstract class ProcessingSystem extends EntitySystem {
|
||||
/**
|
||||
* 当实体发生变化时,不进行任何操作
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
public override onChanged(entity: Entity): void { }
|
||||
|
||||
/**
|
||||
* 处理实体,每帧调用processSystem方法进行处理
|
||||
* @param entities 实体数组,未被使用
|
||||
*/
|
||||
protected override process(entities: Entity[]): void {
|
||||
// 调用子类实现的processSystem方法进行实体处理
|
||||
this.processSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理实体的具体方法,由子类实现
|
||||
*/
|
||||
public abstract processSystem(): void;
|
||||
}
|
||||
import { EntitySystem } from './EntitySystem';
|
||||
import { Entity } from '../Entity';
|
||||
|
||||
/**
|
||||
* 处理系统抽象类
|
||||
* 定义一个处理实体的抽象类,继承自EntitySystem类
|
||||
* 子类需要实现processSystem方法,用于实现具体的处理逻辑
|
||||
*/
|
||||
export abstract class ProcessingSystem extends EntitySystem {
|
||||
/**
|
||||
* 当实体发生变化时,不进行任何操作
|
||||
* @param entity 发生变化的实体
|
||||
*/
|
||||
public override onChanged(entity: Entity): void { }
|
||||
|
||||
/**
|
||||
* 处理实体,每帧调用processSystem方法进行处理
|
||||
* @param entities 实体数组,未被使用
|
||||
*/
|
||||
protected override process(entities: Entity[]): void {
|
||||
// 调用子类实现的processSystem方法进行实体处理
|
||||
this.processSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理实体的具体方法,由子类实现
|
||||
*/
|
||||
public abstract processSystem(): void;
|
||||
}
|
||||
@@ -1,99 +1,99 @@
|
||||
import { Component } from '../Component';
|
||||
import { Bits } from './Bits';
|
||||
|
||||
/**
|
||||
* 组件类型管理器
|
||||
* 负责管理组件类型的注册和ID分配
|
||||
*/
|
||||
export class ComponentTypeManager {
|
||||
private static _instance: ComponentTypeManager;
|
||||
private _componentTypes = new Map<Function, number>();
|
||||
private _typeNames = new Map<number, string>();
|
||||
private _nextTypeId = 0;
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
public static get instance(): ComponentTypeManager {
|
||||
if (!ComponentTypeManager._instance) {
|
||||
ComponentTypeManager._instance = new ComponentTypeManager();
|
||||
}
|
||||
return ComponentTypeManager._instance;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* 获取组件类型的ID
|
||||
* @param componentType 组件类型构造函数
|
||||
* @returns 组件类型ID
|
||||
*/
|
||||
public getTypeId<T extends Component>(componentType: new (...args: any[]) => T): number {
|
||||
let typeId = this._componentTypes.get(componentType);
|
||||
|
||||
if (typeId === undefined) {
|
||||
typeId = this._nextTypeId++;
|
||||
this._componentTypes.set(componentType, typeId);
|
||||
this._typeNames.set(typeId, componentType.name);
|
||||
}
|
||||
|
||||
return typeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件类型名称
|
||||
* @param typeId 组件类型ID
|
||||
* @returns 组件类型名称
|
||||
*/
|
||||
public getTypeName(typeId: number): string {
|
||||
return this._typeNames.get(typeId) || 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建包含指定组件类型的Bits对象
|
||||
* @param componentTypes 组件类型构造函数数组
|
||||
* @returns Bits对象
|
||||
*/
|
||||
public createBits(...componentTypes: (new (...args: any[]) => Component)[]): Bits {
|
||||
const bits = new Bits();
|
||||
|
||||
for (const componentType of componentTypes) {
|
||||
const typeId = this.getTypeId(componentType);
|
||||
bits.set(typeId);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体的组件位掩码
|
||||
* @param components 组件数组
|
||||
* @returns Bits对象
|
||||
*/
|
||||
public getEntityBits(components: Component[]): Bits {
|
||||
const bits = new Bits();
|
||||
|
||||
for (const component of components) {
|
||||
const typeId = this.getTypeId(component.constructor as new (...args: any[]) => Component);
|
||||
bits.set(typeId);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置管理器(主要用于测试)
|
||||
*/
|
||||
public reset(): void {
|
||||
this._componentTypes.clear();
|
||||
this._typeNames.clear();
|
||||
this._nextTypeId = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已注册的组件类型数量
|
||||
*/
|
||||
public get registeredTypeCount(): number {
|
||||
return this._componentTypes.size;
|
||||
}
|
||||
import { Component } from '../Component';
|
||||
import { Bits } from './Bits';
|
||||
|
||||
/**
|
||||
* 组件类型管理器
|
||||
* 负责管理组件类型的注册和ID分配
|
||||
*/
|
||||
export class ComponentTypeManager {
|
||||
private static _instance: ComponentTypeManager;
|
||||
private _componentTypes = new Map<Function, number>();
|
||||
private _typeNames = new Map<number, string>();
|
||||
private _nextTypeId = 0;
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
public static get instance(): ComponentTypeManager {
|
||||
if (!ComponentTypeManager._instance) {
|
||||
ComponentTypeManager._instance = new ComponentTypeManager();
|
||||
}
|
||||
return ComponentTypeManager._instance;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* 获取组件类型的ID
|
||||
* @param componentType 组件类型构造函数
|
||||
* @returns 组件类型ID
|
||||
*/
|
||||
public getTypeId<T extends Component>(componentType: new (...args: any[]) => T): number {
|
||||
let typeId = this._componentTypes.get(componentType);
|
||||
|
||||
if (typeId === undefined) {
|
||||
typeId = this._nextTypeId++;
|
||||
this._componentTypes.set(componentType, typeId);
|
||||
this._typeNames.set(typeId, componentType.name);
|
||||
}
|
||||
|
||||
return typeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件类型名称
|
||||
* @param typeId 组件类型ID
|
||||
* @returns 组件类型名称
|
||||
*/
|
||||
public getTypeName(typeId: number): string {
|
||||
return this._typeNames.get(typeId) || 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建包含指定组件类型的Bits对象
|
||||
* @param componentTypes 组件类型构造函数数组
|
||||
* @returns Bits对象
|
||||
*/
|
||||
public createBits(...componentTypes: (new (...args: any[]) => Component)[]): Bits {
|
||||
const bits = new Bits();
|
||||
|
||||
for (const componentType of componentTypes) {
|
||||
const typeId = this.getTypeId(componentType);
|
||||
bits.set(typeId);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体的组件位掩码
|
||||
* @param components 组件数组
|
||||
* @returns Bits对象
|
||||
*/
|
||||
public getEntityBits(components: Component[]): Bits {
|
||||
const bits = new Bits();
|
||||
|
||||
for (const component of components) {
|
||||
const typeId = this.getTypeId(component.constructor as new (...args: any[]) => Component);
|
||||
bits.set(typeId);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置管理器(主要用于测试)
|
||||
*/
|
||||
public reset(): void {
|
||||
this._componentTypes.clear();
|
||||
this._typeNames.clear();
|
||||
this._nextTypeId = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已注册的组件类型数量
|
||||
*/
|
||||
public get registeredTypeCount(): number {
|
||||
return this._componentTypes.size;
|
||||
}
|
||||
}
|
||||
@@ -1,288 +1,288 @@
|
||||
import { Entity } from '../Entity';
|
||||
import { Component } from '../Component';
|
||||
|
||||
/**
|
||||
* 高性能实体列表管理器
|
||||
* 管理场景中的所有实体,支持快速查找和批量操作
|
||||
*/
|
||||
export class EntityList {
|
||||
public buffer: Entity[] = [];
|
||||
private _scene: any; // 临时使用any,避免循环依赖
|
||||
|
||||
// 索引映射,提升查找性能
|
||||
private _idToEntity = new Map<number, Entity>();
|
||||
private _nameToEntities = new Map<string, Entity[]>();
|
||||
|
||||
// 延迟操作队列
|
||||
private _entitiesToAdd: Entity[] = [];
|
||||
private _entitiesToRemove: Entity[] = [];
|
||||
private _isUpdating = false;
|
||||
|
||||
public get count(): number {
|
||||
return this.buffer.length;
|
||||
}
|
||||
|
||||
constructor(scene: any) {
|
||||
this._scene = scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加实体(立即添加或延迟添加)
|
||||
* @param entity 要添加的实体
|
||||
*/
|
||||
public add(entity: Entity): void {
|
||||
if (this._isUpdating) {
|
||||
// 如果正在更新中,延迟添加
|
||||
this._entitiesToAdd.push(entity);
|
||||
} else {
|
||||
this.addImmediate(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即添加实体
|
||||
* @param entity 要添加的实体
|
||||
*/
|
||||
private addImmediate(entity: Entity): void {
|
||||
// 检查是否已存在
|
||||
if (this._idToEntity.has(entity.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buffer.push(entity);
|
||||
this._idToEntity.set(entity.id, entity);
|
||||
|
||||
// 更新名称索引
|
||||
this.updateNameIndex(entity, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除实体(立即移除或延迟移除)
|
||||
* @param entity 要移除的实体
|
||||
*/
|
||||
public remove(entity: Entity): void {
|
||||
if (this._isUpdating) {
|
||||
// 如果正在更新中,延迟移除
|
||||
this._entitiesToRemove.push(entity);
|
||||
} else {
|
||||
this.removeImmediate(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即移除实体
|
||||
* @param entity 要移除的实体
|
||||
*/
|
||||
private removeImmediate(entity: Entity): void {
|
||||
const index = this.buffer.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
this.buffer.splice(index, 1);
|
||||
this._idToEntity.delete(entity.id);
|
||||
|
||||
// 更新名称索引
|
||||
this.updateNameIndex(entity, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有实体
|
||||
*/
|
||||
public removeAllEntities(): void {
|
||||
for (let i = this.buffer.length - 1; i >= 0; i--) {
|
||||
this.buffer[i].destroy();
|
||||
}
|
||||
|
||||
this.buffer.length = 0;
|
||||
this._idToEntity.clear();
|
||||
this._nameToEntities.clear();
|
||||
this._entitiesToAdd.length = 0;
|
||||
this._entitiesToRemove.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新实体列表,处理延迟操作
|
||||
*/
|
||||
public updateLists(): void {
|
||||
// 处理延迟添加的实体
|
||||
if (this._entitiesToAdd.length > 0) {
|
||||
for (const entity of this._entitiesToAdd) {
|
||||
this.addImmediate(entity);
|
||||
}
|
||||
this._entitiesToAdd.length = 0;
|
||||
}
|
||||
|
||||
// 处理延迟移除的实体
|
||||
if (this._entitiesToRemove.length > 0) {
|
||||
for (const entity of this._entitiesToRemove) {
|
||||
this.removeImmediate(entity);
|
||||
}
|
||||
this._entitiesToRemove.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有实体
|
||||
*/
|
||||
public update(): void {
|
||||
this._isUpdating = true;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < this.buffer.length; i++) {
|
||||
const entity = this.buffer[i];
|
||||
if (entity.enabled && !entity.isDestroyed) {
|
||||
entity.update();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this._isUpdating = false;
|
||||
}
|
||||
|
||||
// 处理延迟操作
|
||||
this.updateLists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称查找实体(使用索引,O(1)复杂度)
|
||||
* @param name 实体名称
|
||||
* @returns 找到的第一个实体或null
|
||||
*/
|
||||
public findEntity(name: string): Entity | null {
|
||||
const entities = this._nameToEntities.get(name);
|
||||
return entities && entities.length > 0 ? entities[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称查找所有实体
|
||||
* @param name 实体名称
|
||||
* @returns 找到的所有实体数组
|
||||
*/
|
||||
public findEntitiesByName(name: string): Entity[] {
|
||||
return this._nameToEntities.get(name) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查找实体(使用索引,O(1)复杂度)
|
||||
* @param id 实体ID
|
||||
* @returns 找到的实体或null
|
||||
*/
|
||||
public findEntityById(id: number): Entity | null {
|
||||
return this._idToEntity.get(id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签查找实体
|
||||
* @param tag 标签
|
||||
* @returns 找到的所有实体数组
|
||||
*/
|
||||
public findEntitiesByTag(tag: number): Entity[] {
|
||||
const result: Entity[] = [];
|
||||
|
||||
for (const entity of this.buffer) {
|
||||
if (entity.tag === tag) {
|
||||
result.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据组件类型查找实体
|
||||
* @param componentType 组件类型
|
||||
* @returns 找到的所有实体数组
|
||||
*/
|
||||
public findEntitiesWithComponent<T extends Component>(componentType: new (...args: any[]) => T): Entity[] {
|
||||
const result: Entity[] = [];
|
||||
|
||||
for (const entity of this.buffer) {
|
||||
if (entity.hasComponent(componentType)) {
|
||||
result.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量操作:对所有实体执行指定操作
|
||||
* @param action 要执行的操作
|
||||
*/
|
||||
public forEach(action: (entity: Entity) => void): void {
|
||||
for (const entity of this.buffer) {
|
||||
action(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量操作:对符合条件的实体执行指定操作
|
||||
* @param predicate 筛选条件
|
||||
* @param action 要执行的操作
|
||||
*/
|
||||
public forEachWhere(predicate: (entity: Entity) => boolean, action: (entity: Entity) => void): void {
|
||||
for (const entity of this.buffer) {
|
||||
if (predicate(entity)) {
|
||||
action(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新名称索引
|
||||
* @param entity 实体
|
||||
* @param isAdd 是否为添加操作
|
||||
*/
|
||||
private updateNameIndex(entity: Entity, isAdd: boolean): void {
|
||||
if (!entity.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAdd) {
|
||||
let entities = this._nameToEntities.get(entity.name);
|
||||
if (!entities) {
|
||||
entities = [];
|
||||
this._nameToEntities.set(entity.name, entities);
|
||||
}
|
||||
entities.push(entity);
|
||||
} else {
|
||||
const entities = this._nameToEntities.get(entity.name);
|
||||
if (entities) {
|
||||
const index = entities.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
entities.splice(index, 1);
|
||||
|
||||
// 如果数组为空,删除映射
|
||||
if (entities.length === 0) {
|
||||
this._nameToEntities.delete(entity.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体列表的统计信息
|
||||
* @returns 统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
totalEntities: number;
|
||||
activeEntities: number;
|
||||
pendingAdd: number;
|
||||
pendingRemove: number;
|
||||
nameIndexSize: number;
|
||||
} {
|
||||
let activeCount = 0;
|
||||
for (const entity of this.buffer) {
|
||||
if (entity.enabled && !entity.isDestroyed) {
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totalEntities: this.buffer.length,
|
||||
activeEntities: activeCount,
|
||||
pendingAdd: this._entitiesToAdd.length,
|
||||
pendingRemove: this._entitiesToRemove.length,
|
||||
nameIndexSize: this._nameToEntities.size
|
||||
};
|
||||
}
|
||||
}
|
||||
import { Entity } from '../Entity';
|
||||
import { Component } from '../Component';
|
||||
|
||||
/**
|
||||
* 高性能实体列表管理器
|
||||
* 管理场景中的所有实体,支持快速查找和批量操作
|
||||
*/
|
||||
export class EntityList {
|
||||
public buffer: Entity[] = [];
|
||||
private _scene: any; // 临时使用any,避免循环依赖
|
||||
|
||||
// 索引映射,提升查找性能
|
||||
private _idToEntity = new Map<number, Entity>();
|
||||
private _nameToEntities = new Map<string, Entity[]>();
|
||||
|
||||
// 延迟操作队列
|
||||
private _entitiesToAdd: Entity[] = [];
|
||||
private _entitiesToRemove: Entity[] = [];
|
||||
private _isUpdating = false;
|
||||
|
||||
public get count(): number {
|
||||
return this.buffer.length;
|
||||
}
|
||||
|
||||
constructor(scene: any) {
|
||||
this._scene = scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加实体(立即添加或延迟添加)
|
||||
* @param entity 要添加的实体
|
||||
*/
|
||||
public add(entity: Entity): void {
|
||||
if (this._isUpdating) {
|
||||
// 如果正在更新中,延迟添加
|
||||
this._entitiesToAdd.push(entity);
|
||||
} else {
|
||||
this.addImmediate(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即添加实体
|
||||
* @param entity 要添加的实体
|
||||
*/
|
||||
private addImmediate(entity: Entity): void {
|
||||
// 检查是否已存在
|
||||
if (this._idToEntity.has(entity.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buffer.push(entity);
|
||||
this._idToEntity.set(entity.id, entity);
|
||||
|
||||
// 更新名称索引
|
||||
this.updateNameIndex(entity, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除实体(立即移除或延迟移除)
|
||||
* @param entity 要移除的实体
|
||||
*/
|
||||
public remove(entity: Entity): void {
|
||||
if (this._isUpdating) {
|
||||
// 如果正在更新中,延迟移除
|
||||
this._entitiesToRemove.push(entity);
|
||||
} else {
|
||||
this.removeImmediate(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即移除实体
|
||||
* @param entity 要移除的实体
|
||||
*/
|
||||
private removeImmediate(entity: Entity): void {
|
||||
const index = this.buffer.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
this.buffer.splice(index, 1);
|
||||
this._idToEntity.delete(entity.id);
|
||||
|
||||
// 更新名称索引
|
||||
this.updateNameIndex(entity, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有实体
|
||||
*/
|
||||
public removeAllEntities(): void {
|
||||
for (let i = this.buffer.length - 1; i >= 0; i--) {
|
||||
this.buffer[i].destroy();
|
||||
}
|
||||
|
||||
this.buffer.length = 0;
|
||||
this._idToEntity.clear();
|
||||
this._nameToEntities.clear();
|
||||
this._entitiesToAdd.length = 0;
|
||||
this._entitiesToRemove.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新实体列表,处理延迟操作
|
||||
*/
|
||||
public updateLists(): void {
|
||||
// 处理延迟添加的实体
|
||||
if (this._entitiesToAdd.length > 0) {
|
||||
for (const entity of this._entitiesToAdd) {
|
||||
this.addImmediate(entity);
|
||||
}
|
||||
this._entitiesToAdd.length = 0;
|
||||
}
|
||||
|
||||
// 处理延迟移除的实体
|
||||
if (this._entitiesToRemove.length > 0) {
|
||||
for (const entity of this._entitiesToRemove) {
|
||||
this.removeImmediate(entity);
|
||||
}
|
||||
this._entitiesToRemove.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有实体
|
||||
*/
|
||||
public update(): void {
|
||||
this._isUpdating = true;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < this.buffer.length; i++) {
|
||||
const entity = this.buffer[i];
|
||||
if (entity.enabled && !entity.isDestroyed) {
|
||||
entity.update();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this._isUpdating = false;
|
||||
}
|
||||
|
||||
// 处理延迟操作
|
||||
this.updateLists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称查找实体(使用索引,O(1)复杂度)
|
||||
* @param name 实体名称
|
||||
* @returns 找到的第一个实体或null
|
||||
*/
|
||||
public findEntity(name: string): Entity | null {
|
||||
const entities = this._nameToEntities.get(name);
|
||||
return entities && entities.length > 0 ? entities[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称查找所有实体
|
||||
* @param name 实体名称
|
||||
* @returns 找到的所有实体数组
|
||||
*/
|
||||
public findEntitiesByName(name: string): Entity[] {
|
||||
return this._nameToEntities.get(name) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查找实体(使用索引,O(1)复杂度)
|
||||
* @param id 实体ID
|
||||
* @returns 找到的实体或null
|
||||
*/
|
||||
public findEntityById(id: number): Entity | null {
|
||||
return this._idToEntity.get(id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签查找实体
|
||||
* @param tag 标签
|
||||
* @returns 找到的所有实体数组
|
||||
*/
|
||||
public findEntitiesByTag(tag: number): Entity[] {
|
||||
const result: Entity[] = [];
|
||||
|
||||
for (const entity of this.buffer) {
|
||||
if (entity.tag === tag) {
|
||||
result.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据组件类型查找实体
|
||||
* @param componentType 组件类型
|
||||
* @returns 找到的所有实体数组
|
||||
*/
|
||||
public findEntitiesWithComponent<T extends Component>(componentType: new (...args: any[]) => T): Entity[] {
|
||||
const result: Entity[] = [];
|
||||
|
||||
for (const entity of this.buffer) {
|
||||
if (entity.hasComponent(componentType)) {
|
||||
result.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量操作:对所有实体执行指定操作
|
||||
* @param action 要执行的操作
|
||||
*/
|
||||
public forEach(action: (entity: Entity) => void): void {
|
||||
for (const entity of this.buffer) {
|
||||
action(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量操作:对符合条件的实体执行指定操作
|
||||
* @param predicate 筛选条件
|
||||
* @param action 要执行的操作
|
||||
*/
|
||||
public forEachWhere(predicate: (entity: Entity) => boolean, action: (entity: Entity) => void): void {
|
||||
for (const entity of this.buffer) {
|
||||
if (predicate(entity)) {
|
||||
action(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新名称索引
|
||||
* @param entity 实体
|
||||
* @param isAdd 是否为添加操作
|
||||
*/
|
||||
private updateNameIndex(entity: Entity, isAdd: boolean): void {
|
||||
if (!entity.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAdd) {
|
||||
let entities = this._nameToEntities.get(entity.name);
|
||||
if (!entities) {
|
||||
entities = [];
|
||||
this._nameToEntities.set(entity.name, entities);
|
||||
}
|
||||
entities.push(entity);
|
||||
} else {
|
||||
const entities = this._nameToEntities.get(entity.name);
|
||||
if (entities) {
|
||||
const index = entities.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
entities.splice(index, 1);
|
||||
|
||||
// 如果数组为空,删除映射
|
||||
if (entities.length === 0) {
|
||||
this._nameToEntities.delete(entity.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体列表的统计信息
|
||||
* @returns 统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
totalEntities: number;
|
||||
activeEntities: number;
|
||||
pendingAdd: number;
|
||||
pendingRemove: number;
|
||||
nameIndexSize: number;
|
||||
} {
|
||||
let activeCount = 0;
|
||||
for (const entity of this.buffer) {
|
||||
if (entity.enabled && !entity.isDestroyed) {
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totalEntities: this.buffer.length,
|
||||
activeEntities: activeCount,
|
||||
pendingAdd: this._entitiesToAdd.length,
|
||||
pendingRemove: this._entitiesToRemove.length,
|
||||
nameIndexSize: this._nameToEntities.size
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,168 +1,168 @@
|
||||
import { Entity } from '../Entity';
|
||||
import { Component } from '../Component';
|
||||
import { Bits } from './Bits';
|
||||
import { ComponentTypeManager } from './ComponentTypeManager';
|
||||
|
||||
/**
|
||||
* 高性能实体匹配器
|
||||
* 用于快速匹配符合条件的实体
|
||||
*/
|
||||
export class Matcher {
|
||||
protected allSet: (new (...args: any[]) => Component)[] = [];
|
||||
protected exclusionSet: (new (...args: any[]) => Component)[] = [];
|
||||
protected oneSet: (new (...args: any[]) => Component)[] = [];
|
||||
|
||||
// 缓存的位掩码,避免重复计算
|
||||
private _allBits?: Bits;
|
||||
private _exclusionBits?: Bits;
|
||||
private _oneBits?: Bits;
|
||||
private _isDirty = true;
|
||||
|
||||
public static empty(): Matcher {
|
||||
return new Matcher();
|
||||
}
|
||||
|
||||
public getAllSet(): (new (...args: any[]) => Component)[] {
|
||||
return this.allSet;
|
||||
}
|
||||
|
||||
public getExclusionSet(): (new (...args: any[]) => Component)[] {
|
||||
return this.exclusionSet;
|
||||
}
|
||||
|
||||
public getOneSet(): (new (...args: any[]) => Component)[] {
|
||||
return this.oneSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否匹配条件
|
||||
* @param entity 要检查的实体
|
||||
* @returns 是否匹配
|
||||
*/
|
||||
public isInterestedEntity(entity: Entity): boolean {
|
||||
const entityBits = this.getEntityBits(entity);
|
||||
return this.isInterested(entityBits);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件位掩码是否匹配条件
|
||||
* @param componentBits 组件位掩码
|
||||
* @returns 是否匹配
|
||||
*/
|
||||
public isInterested(componentBits: Bits): boolean {
|
||||
this.updateBitsIfDirty();
|
||||
|
||||
// 检查必须包含的组件
|
||||
if (this._allBits && !componentBits.containsAll(this._allBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查排除的组件
|
||||
if (this._exclusionBits && componentBits.intersects(this._exclusionBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查至少包含其中之一的组件
|
||||
if (this._oneBits && !componentBits.intersects(this._oneBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加所有包含的组件类型
|
||||
* @param types 所有包含的组件类型列表
|
||||
*/
|
||||
public all(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.allSet.push(...types);
|
||||
this._isDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加排除包含的组件类型
|
||||
* @param types 排除包含的组件类型列表
|
||||
*/
|
||||
public exclude(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.exclusionSet.push(...types);
|
||||
this._isDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加至少包含其中之一的组件类型
|
||||
* @param types 至少包含其中之一的组件类型列表
|
||||
*/
|
||||
public one(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.oneSet.push(...types);
|
||||
this._isDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体的组件位掩码
|
||||
* @param entity 实体
|
||||
* @returns 组件位掩码
|
||||
*/
|
||||
private getEntityBits(entity: Entity): Bits {
|
||||
const components = entity.components;
|
||||
return ComponentTypeManager.instance.getEntityBits(components);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果位掩码已过期,则更新它们
|
||||
*/
|
||||
private updateBitsIfDirty(): void {
|
||||
if (!this._isDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeManager = ComponentTypeManager.instance;
|
||||
|
||||
// 更新必须包含的组件位掩码
|
||||
if (this.allSet.length > 0) {
|
||||
this._allBits = typeManager.createBits(...this.allSet);
|
||||
} else {
|
||||
this._allBits = undefined;
|
||||
}
|
||||
|
||||
// 更新排除的组件位掩码
|
||||
if (this.exclusionSet.length > 0) {
|
||||
this._exclusionBits = typeManager.createBits(...this.exclusionSet);
|
||||
} else {
|
||||
this._exclusionBits = undefined;
|
||||
}
|
||||
|
||||
// 更新至少包含其中之一的组件位掩码
|
||||
if (this.oneSet.length > 0) {
|
||||
this._oneBits = typeManager.createBits(...this.oneSet);
|
||||
} else {
|
||||
this._oneBits = undefined;
|
||||
}
|
||||
|
||||
this._isDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建匹配器的字符串表示(用于调试)
|
||||
* @returns 字符串表示
|
||||
*/
|
||||
public toString(): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
if (this.allSet.length > 0) {
|
||||
parts.push(`all: [${this.allSet.map(t => t.name).join(', ')}]`);
|
||||
}
|
||||
|
||||
if (this.exclusionSet.length > 0) {
|
||||
parts.push(`exclude: [${this.exclusionSet.map(t => t.name).join(', ')}]`);
|
||||
}
|
||||
|
||||
if (this.oneSet.length > 0) {
|
||||
parts.push(`one: [${this.oneSet.map(t => t.name).join(', ')}]`);
|
||||
}
|
||||
|
||||
return `Matcher(${parts.join(', ')})`;
|
||||
}
|
||||
}
|
||||
import { Entity } from '../Entity';
|
||||
import { Component } from '../Component';
|
||||
import { Bits } from './Bits';
|
||||
import { ComponentTypeManager } from './ComponentTypeManager';
|
||||
|
||||
/**
|
||||
* 高性能实体匹配器
|
||||
* 用于快速匹配符合条件的实体
|
||||
*/
|
||||
export class Matcher {
|
||||
protected allSet: (new (...args: any[]) => Component)[] = [];
|
||||
protected exclusionSet: (new (...args: any[]) => Component)[] = [];
|
||||
protected oneSet: (new (...args: any[]) => Component)[] = [];
|
||||
|
||||
// 缓存的位掩码,避免重复计算
|
||||
private _allBits?: Bits;
|
||||
private _exclusionBits?: Bits;
|
||||
private _oneBits?: Bits;
|
||||
private _isDirty = true;
|
||||
|
||||
public static empty(): Matcher {
|
||||
return new Matcher();
|
||||
}
|
||||
|
||||
public getAllSet(): (new (...args: any[]) => Component)[] {
|
||||
return this.allSet;
|
||||
}
|
||||
|
||||
public getExclusionSet(): (new (...args: any[]) => Component)[] {
|
||||
return this.exclusionSet;
|
||||
}
|
||||
|
||||
public getOneSet(): (new (...args: any[]) => Component)[] {
|
||||
return this.oneSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否匹配条件
|
||||
* @param entity 要检查的实体
|
||||
* @returns 是否匹配
|
||||
*/
|
||||
public isInterestedEntity(entity: Entity): boolean {
|
||||
const entityBits = this.getEntityBits(entity);
|
||||
return this.isInterested(entityBits);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件位掩码是否匹配条件
|
||||
* @param componentBits 组件位掩码
|
||||
* @returns 是否匹配
|
||||
*/
|
||||
public isInterested(componentBits: Bits): boolean {
|
||||
this.updateBitsIfDirty();
|
||||
|
||||
// 检查必须包含的组件
|
||||
if (this._allBits && !componentBits.containsAll(this._allBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查排除的组件
|
||||
if (this._exclusionBits && componentBits.intersects(this._exclusionBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查至少包含其中之一的组件
|
||||
if (this._oneBits && !componentBits.intersects(this._oneBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加所有包含的组件类型
|
||||
* @param types 所有包含的组件类型列表
|
||||
*/
|
||||
public all(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.allSet.push(...types);
|
||||
this._isDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加排除包含的组件类型
|
||||
* @param types 排除包含的组件类型列表
|
||||
*/
|
||||
public exclude(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.exclusionSet.push(...types);
|
||||
this._isDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加至少包含其中之一的组件类型
|
||||
* @param types 至少包含其中之一的组件类型列表
|
||||
*/
|
||||
public one(...types: (new (...args: any[]) => Component)[]): Matcher {
|
||||
this.oneSet.push(...types);
|
||||
this._isDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体的组件位掩码
|
||||
* @param entity 实体
|
||||
* @returns 组件位掩码
|
||||
*/
|
||||
private getEntityBits(entity: Entity): Bits {
|
||||
const components = entity.components;
|
||||
return ComponentTypeManager.instance.getEntityBits(components);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果位掩码已过期,则更新它们
|
||||
*/
|
||||
private updateBitsIfDirty(): void {
|
||||
if (!this._isDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeManager = ComponentTypeManager.instance;
|
||||
|
||||
// 更新必须包含的组件位掩码
|
||||
if (this.allSet.length > 0) {
|
||||
this._allBits = typeManager.createBits(...this.allSet);
|
||||
} else {
|
||||
this._allBits = undefined;
|
||||
}
|
||||
|
||||
// 更新排除的组件位掩码
|
||||
if (this.exclusionSet.length > 0) {
|
||||
this._exclusionBits = typeManager.createBits(...this.exclusionSet);
|
||||
} else {
|
||||
this._exclusionBits = undefined;
|
||||
}
|
||||
|
||||
// 更新至少包含其中之一的组件位掩码
|
||||
if (this.oneSet.length > 0) {
|
||||
this._oneBits = typeManager.createBits(...this.oneSet);
|
||||
} else {
|
||||
this._oneBits = undefined;
|
||||
}
|
||||
|
||||
this._isDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建匹配器的字符串表示(用于调试)
|
||||
* @returns 字符串表示
|
||||
*/
|
||||
public toString(): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
if (this.allSet.length > 0) {
|
||||
parts.push(`all: [${this.allSet.map(t => t.name).join(', ')}]`);
|
||||
}
|
||||
|
||||
if (this.exclusionSet.length > 0) {
|
||||
parts.push(`exclude: [${this.exclusionSet.map(t => t.name).join(', ')}]`);
|
||||
}
|
||||
|
||||
if (this.oneSet.length > 0) {
|
||||
parts.push(`one: [${this.oneSet.map(t => t.name).join(', ')}]`);
|
||||
}
|
||||
|
||||
return `Matcher(${parts.join(', ')})`;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +1,81 @@
|
||||
/**
|
||||
* 用于包装事件的一个小类
|
||||
*/
|
||||
export class FuncPack {
|
||||
/** 函数 */
|
||||
public func: Function;
|
||||
/** 上下文 */
|
||||
public context: any;
|
||||
|
||||
constructor(func: Function, context: any) {
|
||||
this.func = func;
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于事件管理
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
private _messageTable: Map<T, FuncPack[]>;
|
||||
|
||||
constructor() {
|
||||
this._messageTable = new Map<T, FuncPack[]>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始监听项
|
||||
* @param eventType 监听类型
|
||||
* @param handler 监听函数
|
||||
* @param context 监听上下文
|
||||
*/
|
||||
public addObserver(eventType: T, handler: Function, context: any) {
|
||||
let list = this._messageTable.get(eventType);
|
||||
if (!list) {
|
||||
list = [];
|
||||
this._messageTable.set(eventType, list);
|
||||
}
|
||||
|
||||
if (!this.hasObserver(eventType, handler)) {
|
||||
list.push(new FuncPack(handler, context));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听项
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public removeObserver(eventType: T, handler: Function) {
|
||||
let messageData = this._messageTable.get(eventType);
|
||||
if (messageData) {
|
||||
let index = messageData.findIndex(data => data.func == handler);
|
||||
if (index != -1)
|
||||
messageData.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发该事件
|
||||
* @param eventType 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
public emit(eventType: T, ...data: any[]) {
|
||||
let list = this._messageTable.get(eventType);
|
||||
if (list) {
|
||||
for (let observer of list) {
|
||||
observer.func.call(observer.context, ...data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在该类型的观察者
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public hasObserver(eventType: T, handler: Function): boolean {
|
||||
let list = this._messageTable.get(eventType);
|
||||
return list ? list.some(observer => observer.func === handler) : false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 用于包装事件的一个小类
|
||||
*/
|
||||
export class FuncPack {
|
||||
/** 函数 */
|
||||
public func: Function;
|
||||
/** 上下文 */
|
||||
public context: any;
|
||||
|
||||
constructor(func: Function, context: any) {
|
||||
this.func = func;
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于事件管理
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
private _messageTable: Map<T, FuncPack[]>;
|
||||
|
||||
constructor() {
|
||||
this._messageTable = new Map<T, FuncPack[]>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始监听项
|
||||
* @param eventType 监听类型
|
||||
* @param handler 监听函数
|
||||
* @param context 监听上下文
|
||||
*/
|
||||
public addObserver(eventType: T, handler: Function, context: any) {
|
||||
let list = this._messageTable.get(eventType);
|
||||
if (!list) {
|
||||
list = [];
|
||||
this._messageTable.set(eventType, list);
|
||||
}
|
||||
|
||||
if (!this.hasObserver(eventType, handler)) {
|
||||
list.push(new FuncPack(handler, context));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听项
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public removeObserver(eventType: T, handler: Function) {
|
||||
let messageData = this._messageTable.get(eventType);
|
||||
if (messageData) {
|
||||
let index = messageData.findIndex(data => data.func == handler);
|
||||
if (index != -1)
|
||||
messageData.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发该事件
|
||||
* @param eventType 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
public emit(eventType: T, ...data: any[]) {
|
||||
let list = this._messageTable.get(eventType);
|
||||
if (list) {
|
||||
for (let observer of list) {
|
||||
observer.func.call(observer.context, ...data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在该类型的观察者
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public hasObserver(eventType: T, handler: Function): boolean {
|
||||
let list = this._messageTable.get(eventType);
|
||||
return list ? list.some(observer => observer.func === handler) : false;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
/**
|
||||
* 全局管理器的基类。所有全局管理器都应该从此类继承。
|
||||
*/
|
||||
export class GlobalManager {
|
||||
/**
|
||||
* 表示管理器是否启用
|
||||
*/
|
||||
public _enabled: boolean = false;
|
||||
|
||||
/**
|
||||
* 获取或设置管理器是否启用
|
||||
*/
|
||||
public get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置管理器是否启用
|
||||
* @param isEnabled 如果为true,则启用管理器;否则禁用管理器
|
||||
*/
|
||||
public setEnabled(isEnabled: boolean) {
|
||||
if (this._enabled != isEnabled) {
|
||||
this._enabled = isEnabled;
|
||||
if (this._enabled) {
|
||||
// 如果启用了管理器,则调用onEnabled方法
|
||||
this.onEnabled();
|
||||
} else {
|
||||
// 如果禁用了管理器,则调用onDisabled方法
|
||||
this.onDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在启用管理器时调用的回调方法
|
||||
*/
|
||||
protected onEnabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在禁用管理器时调用的回调方法
|
||||
*/
|
||||
protected onDisabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新管理器状态的方法
|
||||
*/
|
||||
public update() {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 全局管理器的基类。所有全局管理器都应该从此类继承。
|
||||
*/
|
||||
export class GlobalManager {
|
||||
/**
|
||||
* 表示管理器是否启用
|
||||
*/
|
||||
public _enabled: boolean = false;
|
||||
|
||||
/**
|
||||
* 获取或设置管理器是否启用
|
||||
*/
|
||||
public get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置管理器是否启用
|
||||
* @param isEnabled 如果为true,则启用管理器;否则禁用管理器
|
||||
*/
|
||||
public setEnabled(isEnabled: boolean) {
|
||||
if (this._enabled != isEnabled) {
|
||||
this._enabled = isEnabled;
|
||||
if (this._enabled) {
|
||||
// 如果启用了管理器,则调用onEnabled方法
|
||||
this.onEnabled();
|
||||
} else {
|
||||
// 如果禁用了管理器,则调用onDisabled方法
|
||||
this.onDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在启用管理器时调用的回调方法
|
||||
*/
|
||||
protected onEnabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在禁用管理器时调用的回调方法
|
||||
*/
|
||||
protected onDisabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新管理器状态的方法
|
||||
*/
|
||||
public update() {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user