重构项目结构:整理gitignore,移动source目录到根目录,统一依赖管理

This commit is contained in:
YHH
2025-06-09 14:51:26 +08:00
parent ec5f70ecfc
commit f2d3880a06
89 changed files with 3912 additions and 6820 deletions

86
.gitignore vendored
View File

@@ -1,11 +1,75 @@
/source/node_modules # 依赖目录
/source/bin node_modules/
/demo/bin-debug npm-debug.log*
/demo/bin-release yarn-debug.log*
/.idea yarn-error.log*
/.vscode
/demo_wxgame # 构建输出
/demo/.wing bin/
/demo/.idea dist/
/demo/.vscode wasm/
/source/docs *.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/

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@esengine/ecs-framework", "name": "@esengine/ecs-framework",
"version": "2.1.7", "version": "2.1.9",
"description": "用于Laya、Cocos等游戏引擎的高性能ECS框架", "description": "用于Laya、Cocos等游戏引擎的高性能ECS框架",
"main": "bin/index.js", "main": "bin/index.js",
"types": "bin/index.d.ts", "types": "bin/index.d.ts",
@@ -27,10 +27,7 @@
"build": "npm run build:wasm && npm run build:ts", "build": "npm run build:wasm && npm run build:ts",
"build:watch": "tsc --watch", "build:watch": "tsc --watch",
"rebuild": "npm run clean && npm run clean:wasm && npm run build", "rebuild": "npm run clean && npm run clean:wasm && npm run build",
"bundle": "npm run build && node scripts/bundle.js", "build:npm": "npm run build && node scripts/build-rollup.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",
"test:benchmark": "npm run build && node bin/Testing/Performance/benchmark.js", "test:benchmark": "npm run build && node bin/Testing/Performance/benchmark.js",
"test:unit": "npm run build && node bin/Testing/test-runner.js", "test:unit": "npm run build && node bin/Testing/test-runner.js",
"benchmark": "node scripts/benchmark.js", "benchmark": "node scripts/benchmark.js",
@@ -43,11 +40,13 @@
"author": "yhh", "author": "yhh",
"license": "MIT", "license": "MIT",
"devDependencies": { "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", "@types/node": "^20.19.0",
"archiver": "^7.0.1",
"esbuild": "^0.25.5",
"rimraf": "^5.0.0", "rimraf": "^5.0.0",
"terser": "^5.41.0", "rollup": "^4.42.0",
"rollup-plugin-dts": "^6.2.1",
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"publishConfig": { "publishConfig": {

99
rollup.config.js Normal file
View 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
View 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);

View File

@@ -1,13 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
},
"modules": "auto"
}
]
]
}

92
source/.gitignore vendored
View File

@@ -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

View File

@@ -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.

View File

@@ -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
}
}

View File

@@ -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'] // 支持降级的模块
}
}
}
};

View File

@@ -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);

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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';

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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/

View File

@@ -1,2 +0,0 @@
[target.wasm32-unknown-unknown]
rustflags = ['--cfg', 'getrandom_backend="wasm_js"']

View File

@@ -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",
]

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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模块已加载");
}

View File

@@ -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();
}
}

View File

@@ -1,158 +1,158 @@
import type { IComponent } from '../Types'; import type { IComponent } from '../Types';
/** /**
* *
* *
* ECS架构中的组件Component * ECS架构中的组件Component
* *
* *
* @example * @example
* ```typescript * ```typescript
* class HealthComponent extends Component { * class HealthComponent extends Component {
* public health: number = 100; * public health: number = 100;
* *
* public takeDamage(damage: number): void { * public takeDamage(damage: number): void {
* this.health -= damage; * this.health -= damage;
* if (this.health <= 0) { * if (this.health <= 0) {
* this.entity.destroy(); * this.entity.destroy();
* } * }
* } * }
* } * }
* ``` * ```
*/ */
export abstract class Component implements IComponent { export abstract class Component implements IComponent {
/** /**
* ID生成器 * ID生成器
* *
* ID * ID
*/ */
public static _idGenerator: number = 0; public static _idGenerator: number = 0;
/** /**
* *
* *
* ID * ID
*/ */
public readonly id: number; public readonly id: number;
/** /**
* *
* *
* *
*/ */
public entity!: Entity; public entity!: Entity;
/** /**
* *
* *
* *
*/ */
private _enabled: boolean = true; private _enabled: boolean = true;
/** /**
* *
* *
* *
*/ */
private _updateOrder: number = 0; private _updateOrder: number = 0;
/** /**
* *
* *
* ID给组件 * ID给组件
*/ */
constructor() { constructor() {
this.id = Component._idGenerator++; this.id = Component._idGenerator++;
} }
/** /**
* *
* *
* *
* *
* @returns true * @returns true
*/ */
public get enabled(): boolean { public get enabled(): boolean {
return this.entity ? this.entity.enabled && this._enabled : this._enabled; return this.entity ? this.entity.enabled && this._enabled : this._enabled;
} }
/** /**
* *
* *
* *
* *
* @param value - * @param value -
*/ */
public set enabled(value: boolean) { public set enabled(value: boolean) {
if (this._enabled !== value) { if (this._enabled !== value) {
this._enabled = value; this._enabled = value;
if (this._enabled) { if (this._enabled) {
this.onEnabled(); this.onEnabled();
} else { } else {
this.onDisabled(); this.onDisabled();
} }
} }
} }
/** /**
* *
* *
* @returns * @returns
*/ */
public get updateOrder(): number { public get updateOrder(): number {
return this._updateOrder; return this._updateOrder;
} }
/** /**
* *
* *
* @param value - * @param value -
*/ */
public set updateOrder(value: number) { public set updateOrder(value: number) {
this._updateOrder = value; this._updateOrder = value;
} }
/** /**
* *
* *
* *
*/ */
public onAddedToEntity(): void { public onAddedToEntity(): void {
} }
/** /**
* *
* *
* *
*/ */
public onRemovedFromEntity(): void { public onRemovedFromEntity(): void {
} }
/** /**
* *
* *
* *
*/ */
public onEnabled(): void { public onEnabled(): void {
} }
/** /**
* *
* *
* *
*/ */
public onDisabled(): void { public onDisabled(): void {
} }
/** /**
* *
* *
* *
* *
*/ */
public update(): void { public update(): void {
} }
} }
// 避免循环引用在文件末尾导入Entity // 避免循环引用在文件末尾导入Entity
import type { Entity } from './Entity'; import type { Entity } from './Entity';

File diff suppressed because it is too large Load Diff

View File

@@ -1,420 +1,420 @@
import { Entity } from './Entity'; import { Entity } from './Entity';
import { EntityList } from './Utils/EntityList'; import { EntityList } from './Utils/EntityList';
import { EntityProcessorList } from './Utils/EntityProcessorList'; import { EntityProcessorList } from './Utils/EntityProcessorList';
import { IdentifierPool } from './Utils/IdentifierPool'; import { IdentifierPool } from './Utils/IdentifierPool';
import { EntitySystem } from './Systems/EntitySystem'; import { EntitySystem } from './Systems/EntitySystem';
import { ComponentStorageManager } from './Core/ComponentStorage'; import { ComponentStorageManager } from './Core/ComponentStorage';
import { QuerySystem } from './Core/QuerySystem'; import { QuerySystem } from './Core/QuerySystem';
import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem'; import { TypeSafeEventSystem, GlobalEventSystem } from './Core/EventSystem';
import type { IScene } from '../Types'; import type { IScene } from '../Types';
/** /**
* *
* *
* *
* *
* *
* @example * @example
* ```typescript * ```typescript
* class GameScene extends Scene { * class GameScene extends Scene {
* public initialize(): void { * public initialize(): void {
* // 创建游戏实体 * // 创建游戏实体
* const player = this.createEntity("Player"); * const player = this.createEntity("Player");
* *
* // 添加系统 * // 添加系统
* this.addEntityProcessor(new MovementSystem()); * this.addEntityProcessor(new MovementSystem());
* } * }
* } * }
* ``` * ```
*/ */
export class Scene { export class Scene {
/** /**
* *
* *
* *
*/ */
public name: string = ""; public name: string = "";
/** /**
* *
* *
* *
*/ */
public readonly entities: EntityList; public readonly entities: EntityList;
/** /**
* *
* *
* *
*/ */
public readonly entityProcessors: EntityProcessorList; public readonly entityProcessors: EntityProcessorList;
/** /**
* ID池 * ID池
* *
* *
*/ */
public readonly identifierPool: IdentifierPool; public readonly identifierPool: IdentifierPool;
/** /**
* *
* *
* *
*/ */
public readonly componentStorageManager: ComponentStorageManager; public readonly componentStorageManager: ComponentStorageManager;
/** /**
* *
* *
* *
*/ */
public readonly querySystem: QuerySystem; public readonly querySystem: QuerySystem;
/** /**
* *
* *
* *
*/ */
public readonly eventSystem: TypeSafeEventSystem; public readonly eventSystem: TypeSafeEventSystem;
/** /**
* *
*/ */
private _didSceneBegin: boolean = false; private _didSceneBegin: boolean = false;
/** /**
* *
*/ */
public get systems(): EntitySystem[] { public get systems(): EntitySystem[] {
return this.entityProcessors.processors; return this.entityProcessors.processors;
} }
/** /**
* *
* @param enableWasmAcceleration WebAssembly加速true * @param enableWasmAcceleration WebAssembly加速true
*/ */
constructor(enableWasmAcceleration: boolean = true) { constructor(enableWasmAcceleration: boolean = true) {
this.entities = new EntityList(this); this.entities = new EntityList(this);
this.entityProcessors = new EntityProcessorList(); this.entityProcessors = new EntityProcessorList();
this.identifierPool = new IdentifierPool(); this.identifierPool = new IdentifierPool();
this.componentStorageManager = new ComponentStorageManager(); this.componentStorageManager = new ComponentStorageManager();
this.querySystem = new QuerySystem(); this.querySystem = new QuerySystem();
this.eventSystem = new TypeSafeEventSystem(); this.eventSystem = new TypeSafeEventSystem();
this.initialize(); this.initialize();
} }
/** /**
* *
* *
* *
*/ */
public initialize(): void { public initialize(): void {
} }
/** /**
* *
* *
* *
*/ */
public onStart(): void { public onStart(): void {
} }
/** /**
* *
* *
* *
*/ */
public unload(): void { public unload(): void {
} }
/** /**
* *
* *
* onStart方法 * onStart方法
*/ */
public begin() { public begin() {
// 启动实体处理器 // 启动实体处理器
if (this.entityProcessors != null) if (this.entityProcessors != null)
this.entityProcessors.begin(); this.entityProcessors.begin();
// 标记场景已开始运行并调用onStart方法 // 标记场景已开始运行并调用onStart方法
this._didSceneBegin = true; this._didSceneBegin = true;
this.onStart(); this.onStart();
} }
/** /**
* *
* *
* unload方法 * unload方法
*/ */
public end() { public end() {
// 标记场景已结束运行 // 标记场景已结束运行
this._didSceneBegin = false; this._didSceneBegin = false;
// 移除所有实体 // 移除所有实体
this.entities.removeAllEntities(); this.entities.removeAllEntities();
// 清空组件存储 // 清空组件存储
this.componentStorageManager.clear(); this.componentStorageManager.clear();
// 结束实体处理器 // 结束实体处理器
if (this.entityProcessors) if (this.entityProcessors)
this.entityProcessors.end(); this.entityProcessors.end();
// 调用卸载方法 // 调用卸载方法
this.unload(); this.unload();
} }
/** /**
* *
*/ */
public update() { public update() {
// 更新实体列表 // 更新实体列表
this.entities.updateLists(); this.entities.updateLists();
// 更新实体处理器 // 更新实体处理器
if (this.entityProcessors != null) if (this.entityProcessors != null)
this.entityProcessors.update(); this.entityProcessors.update();
// 更新实体组 // 更新实体组
this.entities.update(); this.entities.update();
// 更新实体处理器的后处理方法 // 更新实体处理器的后处理方法
if (this.entityProcessors != null) if (this.entityProcessors != null)
this.entityProcessors.lateUpdate(); this.entityProcessors.lateUpdate();
} }
/** /**
* *
* @param name * @param name
*/ */
public createEntity(name: string) { public createEntity(name: string) {
let entity = new Entity(name, this.identifierPool.checkOut()); let entity = new Entity(name, this.identifierPool.checkOut());
return this.addEntity(entity); return this.addEntity(entity);
} }
/** /**
* *
* @param entity * @param entity
* @param deferCacheClear * @param deferCacheClear
*/ */
public addEntity(entity: Entity, deferCacheClear: boolean = false) { public addEntity(entity: Entity, deferCacheClear: boolean = false) {
this.entities.add(entity); this.entities.add(entity);
entity.scene = this; entity.scene = this;
// 将实体添加到查询系统(可延迟缓存清理) // 将实体添加到查询系统(可延迟缓存清理)
this.querySystem.addEntity(entity, deferCacheClear); this.querySystem.addEntity(entity, deferCacheClear);
// 触发实体添加事件 // 触发实体添加事件
this.eventSystem.emitSync('entity:added', { entity, scene: this }); this.eventSystem.emitSync('entity:added', { entity, scene: this });
return entity; return entity;
} }
/** /**
* *
* @param count * @param count
* @param namePrefix * @param namePrefix
* @returns * @returns
*/ */
public createEntities(count: number, namePrefix: string = "Entity"): Entity[] { public createEntities(count: number, namePrefix: string = "Entity"): Entity[] {
const entities: Entity[] = []; const entities: Entity[] = [];
// 批量创建实体对象,不立即添加到系统 // 批量创建实体对象,不立即添加到系统
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut()); const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut());
entity.scene = this; entity.scene = this;
entities.push(entity); entities.push(entity);
} }
// 批量添加到实体列表 // 批量添加到实体列表
for (const entity of entities) { for (const entity of entities) {
this.entities.add(entity); this.entities.add(entity);
} }
// 批量添加到查询系统(无重复检查,性能最优) // 批量添加到查询系统(无重复检查,性能最优)
this.querySystem.addEntitiesUnchecked(entities); this.querySystem.addEntitiesUnchecked(entities);
// 批量触发事件(可选,减少事件开销) // 批量触发事件(可选,减少事件开销)
this.eventSystem.emitSync('entities:batch_added', { entities, scene: this, count }); this.eventSystem.emitSync('entities:batch_added', { entities, scene: this, count });
return entities; return entities;
} }
/** /**
* *
* @param count * @param count
* @param namePrefix * @param namePrefix
* @returns * @returns
*/ */
public createEntitiesOld(count: number, namePrefix: string = "Entity"): Entity[] { public createEntitiesOld(count: number, namePrefix: string = "Entity"): Entity[] {
const entities: Entity[] = []; const entities: Entity[] = [];
// 批量创建实体,延迟缓存清理 // 批量创建实体,延迟缓存清理
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut()); const entity = new Entity(`${namePrefix}_${i}`, this.identifierPool.checkOut());
entities.push(entity); entities.push(entity);
this.addEntity(entity, true); // 延迟缓存清理 this.addEntity(entity, true); // 延迟缓存清理
} }
// 最后统一清理缓存 // 最后统一清理缓存
this.querySystem.clearCache(); this.querySystem.clearCache();
return entities; return entities;
} }
/** /**
* *
*/ */
public destroyAllEntities() { public destroyAllEntities() {
for (let i = 0; i < this.entities.count; i++) { for (let i = 0; i < this.entities.count; i++) {
this.entities.buffer[i].destroy(); this.entities.buffer[i].destroy();
} }
} }
/** /**
* *
* @param name * @param name
*/ */
public findEntity(name: string): Entity | null { public findEntity(name: string): Entity | null {
return this.entities.findEntity(name); return this.entities.findEntity(name);
} }
/** /**
* ID查找实体 * ID查找实体
* @param id ID * @param id ID
*/ */
public findEntityById(id: number): Entity | null { public findEntityById(id: number): Entity | null {
return this.entities.findEntityById(id); return this.entities.findEntityById(id);
} }
/** /**
* *
* @param tag * @param tag
*/ */
public findEntitiesByTag(tag: number): Entity[] { public findEntitiesByTag(tag: number): Entity[] {
const result: Entity[] = []; const result: Entity[] = [];
for (const entity of this.entities.buffer) { for (const entity of this.entities.buffer) {
if (entity.tag === tag) { if (entity.tag === tag) {
result.push(entity); result.push(entity);
} }
} }
return result; return result;
} }
/** /**
* *
* @param name * @param name
*/ */
public getEntityByName(name: string): Entity | null { public getEntityByName(name: string): Entity | null {
return this.findEntity(name); return this.findEntity(name);
} }
/** /**
* *
* @param tag * @param tag
*/ */
public getEntitiesByTag(tag: number): Entity[] { public getEntitiesByTag(tag: number): Entity[] {
return this.findEntitiesByTag(tag); return this.findEntitiesByTag(tag);
} }
/** /**
* EntitySystem处理器 * EntitySystem处理器
* @param processor * @param processor
*/ */
public addEntityProcessor(processor: EntitySystem) { public addEntityProcessor(processor: EntitySystem) {
processor.scene = this; processor.scene = this;
this.entityProcessors.add(processor); this.entityProcessors.add(processor);
processor.setUpdateOrder(this.entityProcessors.count - 1); processor.setUpdateOrder(this.entityProcessors.count - 1);
return processor; return processor;
} }
/** /**
* addEntityProcessor的别名 * addEntityProcessor的别名
* @param system * @param system
*/ */
public addSystem(system: EntitySystem) { public addSystem(system: EntitySystem) {
return this.addEntityProcessor(system); return this.addEntityProcessor(system);
} }
/** /**
* EntitySystem处理器 * EntitySystem处理器
* @param processor * @param processor
*/ */
public removeEntityProcessor(processor: EntitySystem) { public removeEntityProcessor(processor: EntitySystem) {
this.entityProcessors.remove(processor); this.entityProcessors.remove(processor);
} }
/** /**
* EntitySystem处理器 * EntitySystem处理器
* @param type * @param type
*/ */
public getEntityProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T | null { public getEntityProcessor<T extends EntitySystem>(type: new (...args: any[]) => T): T | null {
return this.entityProcessors.getProcessor(type); return this.entityProcessors.getProcessor(type);
} }
/** /**
* *
*/ */
public getStats(): { public getStats(): {
entityCount: number; entityCount: number;
processorCount: number; processorCount: number;
componentStorageStats: Map<string, any>; componentStorageStats: Map<string, any>;
} { } {
return { return {
entityCount: this.entities.count, entityCount: this.entities.count,
processorCount: this.entityProcessors.count, processorCount: this.entityProcessors.count,
componentStorageStats: this.componentStorageManager.getAllStats() componentStorageStats: this.componentStorageManager.getAllStats()
}; };
} }
/** /**
* *
*/ */
public compactComponentStorage(): void { public compactComponentStorage(): void {
this.componentStorageManager.compactAll(); this.componentStorageManager.compactAll();
} }
/** /**
* *
*/ */
public getDebugInfo(): { public getDebugInfo(): {
name: string; name: string;
entityCount: number; entityCount: number;
processorCount: number; processorCount: number;
isRunning: boolean; isRunning: boolean;
entities: Array<{ entities: Array<{
name: string; name: string;
id: number; id: number;
componentCount: number; componentCount: number;
componentTypes: string[]; componentTypes: string[];
}>; }>;
processors: Array<{ processors: Array<{
name: string; name: string;
updateOrder: number; updateOrder: number;
entityCount: number; entityCount: number;
}>; }>;
componentStats: Map<string, any>; componentStats: Map<string, any>;
} { } {
return { return {
name: this.constructor.name, name: this.constructor.name,
entityCount: this.entities.count, entityCount: this.entities.count,
processorCount: this.entityProcessors.count, processorCount: this.entityProcessors.count,
isRunning: this._didSceneBegin, isRunning: this._didSceneBegin,
entities: this.entities.buffer.map(entity => ({ entities: this.entities.buffer.map(entity => ({
name: entity.name, name: entity.name,
id: entity.id, id: entity.id,
componentCount: entity.components.length, componentCount: entity.components.length,
componentTypes: entity.components.map(c => c.constructor.name) componentTypes: entity.components.map(c => c.constructor.name)
})), })),
processors: this.entityProcessors.processors.map(processor => ({ processors: this.entityProcessors.processors.map(processor => ({
name: processor.constructor.name, name: processor.constructor.name,
updateOrder: processor.updateOrder, updateOrder: processor.updateOrder,
entityCount: (processor as any)._entities?.length || 0 entityCount: (processor as any)._entities?.length || 0
})), })),
componentStats: this.componentStorageManager.getAllStats() componentStats: this.componentStorageManager.getAllStats()
}; };
} }
} }

View File

@@ -1,319 +1,319 @@
import { Entity } from '../Entity'; import { Entity } from '../Entity';
import { Core } from '../../Core'; import { Core } from '../../Core';
import { Matcher } from '../Utils/Matcher'; import { Matcher } from '../Utils/Matcher';
import { PerformanceMonitor } from '../../Utils/PerformanceMonitor'; import { PerformanceMonitor } from '../../Utils/PerformanceMonitor';
import type { Scene } from '../Scene'; import type { Scene } from '../Scene';
import type { ISystemBase } from '../../Types'; import type { ISystemBase } from '../../Types';
/** /**
* *
* *
* ECS架构中的逻辑处理单元 * ECS架构中的逻辑处理单元
* *
* *
* @example * @example
* ```typescript * ```typescript
* class MovementSystem extends EntitySystem { * class MovementSystem extends EntitySystem {
* constructor() { * constructor() {
* super(Matcher.empty().all(Transform, Velocity)); * super(Matcher.empty().all(Transform, Velocity));
* } * }
* *
* protected process(entities: Entity[]): void { * protected process(entities: Entity[]): void {
* for (const entity of entities) { * for (const entity of entities) {
* const transform = entity.getComponent(Transform); * const transform = entity.getComponent(Transform);
* const velocity = entity.getComponent(Velocity); * const velocity = entity.getComponent(Velocity);
* transform.position.add(velocity.value); * transform.position.add(velocity.value);
* } * }
* } * }
* } * }
* ``` * ```
*/ */
export abstract class EntitySystem implements ISystemBase { export abstract class EntitySystem implements ISystemBase {
private _entities: Entity[] = []; private _entities: Entity[] = [];
private _updateOrder: number = 0; private _updateOrder: number = 0;
private _enabled: boolean = true; private _enabled: boolean = true;
private _performanceMonitor = PerformanceMonitor.instance; private _performanceMonitor = PerformanceMonitor.instance;
private _systemName: string; private _systemName: string;
/** /**
* *
*/ */
public get entities(): readonly Entity[] { public get entities(): readonly Entity[] {
return this._entities; return this._entities;
} }
/** /**
* *
*/ */
public get updateOrder(): number { public get updateOrder(): number {
return this._updateOrder; return this._updateOrder;
} }
public set updateOrder(value: number) { public set updateOrder(value: number) {
this.setUpdateOrder(value); this.setUpdateOrder(value);
} }
/** /**
* *
*/ */
public get enabled(): boolean { public get enabled(): boolean {
return this._enabled; return this._enabled;
} }
/** /**
* *
*/ */
public set enabled(value: boolean) { public set enabled(value: boolean) {
this._enabled = value; this._enabled = value;
} }
/** /**
* *
*/ */
public get systemName(): string { public get systemName(): string {
return this._systemName; return this._systemName;
} }
constructor(matcher?: Matcher) { constructor(matcher?: Matcher) {
this._matcher = matcher ? matcher : Matcher.empty(); this._matcher = matcher ? matcher : Matcher.empty();
this._systemName = this.constructor.name; this._systemName = this.constructor.name;
this.initialize(); this.initialize();
} }
private _scene!: Scene; private _scene!: Scene;
/** /**
* *
*/ */
public get scene(): Scene { public get scene(): Scene {
return this._scene; return this._scene;
} }
public set scene(value: Scene) { public set scene(value: Scene) {
this._scene = value; this._scene = value;
this._entities = []; this._entities = [];
} }
private _matcher: Matcher; private _matcher: Matcher;
/** /**
* *
*/ */
public get matcher(): Matcher { public get matcher(): Matcher {
return this._matcher; return this._matcher;
} }
/** /**
* *
* @param order * @param order
*/ */
public setUpdateOrder(order: number): void { public setUpdateOrder(order: number): void {
this._updateOrder = order; this._updateOrder = order;
this.scene.entityProcessors.setDirty(); this.scene.entityProcessors.setDirty();
} }
/** /**
* *
* *
* *
*/ */
public initialize(): void { public initialize(): void {
// 子类可以重写此方法 // 子类可以重写此方法
} }
/** /**
* *
* *
* *
* *
* @param entity * @param entity
*/ */
public onChanged(entity: Entity): void { public onChanged(entity: Entity): void {
const contains = this._entities.includes(entity); const contains = this._entities.includes(entity);
const interest = this._matcher.isInterestedEntity(entity); const interest = this._matcher.isInterestedEntity(entity);
if (interest && !contains) { if (interest && !contains) {
this.add(entity); this.add(entity);
} else if (!interest && contains) { } else if (!interest && contains) {
this.remove(entity); this.remove(entity);
} }
} }
/** /**
* *
* *
* @param entity * @param entity
*/ */
public add(entity: Entity): void { public add(entity: Entity): void {
if (!this._entities.includes(entity)) { if (!this._entities.includes(entity)) {
this._entities.push(entity); this._entities.push(entity);
this.onAdded(entity); this.onAdded(entity);
} }
} }
/** /**
* *
* *
* *
* *
* @param entity * @param entity
*/ */
protected onAdded(entity: Entity): void { protected onAdded(entity: Entity): void {
// 子类可以重写此方法 // 子类可以重写此方法
} }
/** /**
* *
* *
* @param entity * @param entity
*/ */
public remove(entity: Entity): void { public remove(entity: Entity): void {
const index = this._entities.indexOf(entity); const index = this._entities.indexOf(entity);
if (index !== -1) { if (index !== -1) {
this._entities.splice(index, 1); this._entities.splice(index, 1);
this.onRemoved(entity); this.onRemoved(entity);
} }
} }
/** /**
* *
* *
* *
* *
* @param entity * @param entity
*/ */
protected onRemoved(entity: Entity): void { protected onRemoved(entity: Entity): void {
// 子类可以重写此方法 // 子类可以重写此方法
} }
/** /**
* *
* *
* *
*/ */
public update(): void { public update(): void {
if (!this._enabled || !this.checkProcessing()) { if (!this._enabled || !this.checkProcessing()) {
return; return;
} }
const startTime = this._performanceMonitor.startMonitoring(this._systemName); const startTime = this._performanceMonitor.startMonitoring(this._systemName);
try { try {
this.begin(); this.begin();
this.process(this._entities); this.process(this._entities);
} finally { } finally {
this._performanceMonitor.endMonitoring(this._systemName, startTime, this._entities.length); this._performanceMonitor.endMonitoring(this._systemName, startTime, this._entities.length);
} }
} }
/** /**
* *
* *
* update方法执行完毕后调用 * update方法执行完毕后调用
*/ */
public lateUpdate(): void { public lateUpdate(): void {
if (!this._enabled || !this.checkProcessing()) { if (!this._enabled || !this.checkProcessing()) {
return; return;
} }
const startTime = this._performanceMonitor.startMonitoring(`${this._systemName}_Late`); const startTime = this._performanceMonitor.startMonitoring(`${this._systemName}_Late`);
try { try {
this.lateProcess(this._entities); this.lateProcess(this._entities);
this.end(); this.end();
} finally { } finally {
this._performanceMonitor.endMonitoring(`${this._systemName}_Late`, startTime, this._entities.length); this._performanceMonitor.endMonitoring(`${this._systemName}_Late`, startTime, this._entities.length);
} }
} }
/** /**
* *
* *
* *
*/ */
protected begin(): void { protected begin(): void {
// 子类可以重写此方法 // 子类可以重写此方法
} }
/** /**
* *
* *
* *
* *
* @param entities * @param entities
*/ */
protected process(entities: Entity[]): void { protected process(entities: Entity[]): void {
// 子类必须实现此方法 // 子类必须实现此方法
} }
/** /**
* *
* *
* *
* *
* @param entities * @param entities
*/ */
protected lateProcess(entities: Entity[]): void { protected lateProcess(entities: Entity[]): void {
// 子类可以重写此方法 // 子类可以重写此方法
} }
/** /**
* *
* *
* *
*/ */
protected end(): void { protected end(): void {
// 子类可以重写此方法 // 子类可以重写此方法
} }
/** /**
* *
* *
* *
* *
* *
* @returns truefalse * @returns truefalse
*/ */
protected checkProcessing(): boolean { protected checkProcessing(): boolean {
return true; return true;
} }
/** /**
* *
* *
* @returns undefined * @returns undefined
*/ */
public getPerformanceData() { public getPerformanceData() {
return this._performanceMonitor.getSystemData(this._systemName); return this._performanceMonitor.getSystemData(this._systemName);
} }
/** /**
* *
* *
* @returns undefined * @returns undefined
*/ */
public getPerformanceStats() { public getPerformanceStats() {
return this._performanceMonitor.getSystemStats(this._systemName); return this._performanceMonitor.getSystemStats(this._systemName);
} }
/** /**
* *
*/ */
public resetPerformanceData(): void { public resetPerformanceData(): void {
this._performanceMonitor.resetSystem(this._systemName); this._performanceMonitor.resetSystem(this._systemName);
} }
/** /**
* *
* *
* @returns * @returns
*/ */
public toString(): string { public toString(): string {
const entityCount = this._entities.length; const entityCount = this._entities.length;
const perfData = this.getPerformanceData(); const perfData = this.getPerformanceData();
const perfInfo = perfData ? ` (${perfData.executionTime.toFixed(2)}ms)` : ''; const perfInfo = perfData ? ` (${perfData.executionTime.toFixed(2)}ms)` : '';
return `${this._systemName}[${entityCount} entities]${perfInfo}`; return `${this._systemName}[${entityCount} entities]${perfInfo}`;
} }
} }

View File

@@ -1,23 +1,23 @@
import { EntitySystem } from './EntitySystem'; import { EntitySystem } from './EntitySystem';
import { Entity } from '../Entity'; import { Entity } from '../Entity';
/** /**
* *
* EntitySystem类 * EntitySystem类
* *
*/ */
export abstract class PassiveSystem extends EntitySystem { export abstract class PassiveSystem extends EntitySystem {
/** /**
* *
* @param entity * @param entity
*/ */
public override onChanged(entity: Entity): void { } public override onChanged(entity: Entity): void { }
/** /**
* *
* @param entities 使 * @param entities 使
*/ */
protected override process(entities: Entity[]): void { protected override process(entities: Entity[]): void {
// 被动系统不进行任何处理 // 被动系统不进行任何处理
} }
} }

View File

@@ -1,29 +1,29 @@
import { EntitySystem } from './EntitySystem'; import { EntitySystem } from './EntitySystem';
import { Entity } from '../Entity'; import { Entity } from '../Entity';
/** /**
* *
* EntitySystem类 * EntitySystem类
* processSystem方法 * processSystem方法
*/ */
export abstract class ProcessingSystem extends EntitySystem { export abstract class ProcessingSystem extends EntitySystem {
/** /**
* *
* @param entity * @param entity
*/ */
public override onChanged(entity: Entity): void { } public override onChanged(entity: Entity): void { }
/** /**
* processSystem方法进行处理 * processSystem方法进行处理
* @param entities 使 * @param entities 使
*/ */
protected override process(entities: Entity[]): void { protected override process(entities: Entity[]): void {
// 调用子类实现的processSystem方法进行实体处理 // 调用子类实现的processSystem方法进行实体处理
this.processSystem(); this.processSystem();
} }
/** /**
* *
*/ */
public abstract processSystem(): void; public abstract processSystem(): void;
} }

View File

@@ -1,99 +1,99 @@
import { Component } from '../Component'; import { Component } from '../Component';
import { Bits } from './Bits'; import { Bits } from './Bits';
/** /**
* *
* ID分配 * ID分配
*/ */
export class ComponentTypeManager { export class ComponentTypeManager {
private static _instance: ComponentTypeManager; private static _instance: ComponentTypeManager;
private _componentTypes = new Map<Function, number>(); private _componentTypes = new Map<Function, number>();
private _typeNames = new Map<number, string>(); private _typeNames = new Map<number, string>();
private _nextTypeId = 0; private _nextTypeId = 0;
/** /**
* *
*/ */
public static get instance(): ComponentTypeManager { public static get instance(): ComponentTypeManager {
if (!ComponentTypeManager._instance) { if (!ComponentTypeManager._instance) {
ComponentTypeManager._instance = new ComponentTypeManager(); ComponentTypeManager._instance = new ComponentTypeManager();
} }
return ComponentTypeManager._instance; return ComponentTypeManager._instance;
} }
private constructor() {} private constructor() {}
/** /**
* ID * ID
* @param componentType * @param componentType
* @returns ID * @returns ID
*/ */
public getTypeId<T extends Component>(componentType: new (...args: any[]) => T): number { public getTypeId<T extends Component>(componentType: new (...args: any[]) => T): number {
let typeId = this._componentTypes.get(componentType); let typeId = this._componentTypes.get(componentType);
if (typeId === undefined) { if (typeId === undefined) {
typeId = this._nextTypeId++; typeId = this._nextTypeId++;
this._componentTypes.set(componentType, typeId); this._componentTypes.set(componentType, typeId);
this._typeNames.set(typeId, componentType.name); this._typeNames.set(typeId, componentType.name);
} }
return typeId; return typeId;
} }
/** /**
* *
* @param typeId ID * @param typeId ID
* @returns * @returns
*/ */
public getTypeName(typeId: number): string { public getTypeName(typeId: number): string {
return this._typeNames.get(typeId) || 'Unknown'; return this._typeNames.get(typeId) || 'Unknown';
} }
/** /**
* Bits对象 * Bits对象
* @param componentTypes * @param componentTypes
* @returns Bits对象 * @returns Bits对象
*/ */
public createBits(...componentTypes: (new (...args: any[]) => Component)[]): Bits { public createBits(...componentTypes: (new (...args: any[]) => Component)[]): Bits {
const bits = new Bits(); const bits = new Bits();
for (const componentType of componentTypes) { for (const componentType of componentTypes) {
const typeId = this.getTypeId(componentType); const typeId = this.getTypeId(componentType);
bits.set(typeId); bits.set(typeId);
} }
return bits; return bits;
} }
/** /**
* *
* @param components * @param components
* @returns Bits对象 * @returns Bits对象
*/ */
public getEntityBits(components: Component[]): Bits { public getEntityBits(components: Component[]): Bits {
const bits = new Bits(); const bits = new Bits();
for (const component of components) { for (const component of components) {
const typeId = this.getTypeId(component.constructor as new (...args: any[]) => Component); const typeId = this.getTypeId(component.constructor as new (...args: any[]) => Component);
bits.set(typeId); bits.set(typeId);
} }
return bits; return bits;
} }
/** /**
* *
*/ */
public reset(): void { public reset(): void {
this._componentTypes.clear(); this._componentTypes.clear();
this._typeNames.clear(); this._typeNames.clear();
this._nextTypeId = 0; this._nextTypeId = 0;
} }
/** /**
* *
*/ */
public get registeredTypeCount(): number { public get registeredTypeCount(): number {
return this._componentTypes.size; return this._componentTypes.size;
} }
} }

View File

@@ -1,288 +1,288 @@
import { Entity } from '../Entity'; import { Entity } from '../Entity';
import { Component } from '../Component'; import { Component } from '../Component';
/** /**
* *
* *
*/ */
export class EntityList { export class EntityList {
public buffer: Entity[] = []; public buffer: Entity[] = [];
private _scene: any; // 临时使用any避免循环依赖 private _scene: any; // 临时使用any避免循环依赖
// 索引映射,提升查找性能 // 索引映射,提升查找性能
private _idToEntity = new Map<number, Entity>(); private _idToEntity = new Map<number, Entity>();
private _nameToEntities = new Map<string, Entity[]>(); private _nameToEntities = new Map<string, Entity[]>();
// 延迟操作队列 // 延迟操作队列
private _entitiesToAdd: Entity[] = []; private _entitiesToAdd: Entity[] = [];
private _entitiesToRemove: Entity[] = []; private _entitiesToRemove: Entity[] = [];
private _isUpdating = false; private _isUpdating = false;
public get count(): number { public get count(): number {
return this.buffer.length; return this.buffer.length;
} }
constructor(scene: any) { constructor(scene: any) {
this._scene = scene; this._scene = scene;
} }
/** /**
* *
* @param entity * @param entity
*/ */
public add(entity: Entity): void { public add(entity: Entity): void {
if (this._isUpdating) { if (this._isUpdating) {
// 如果正在更新中,延迟添加 // 如果正在更新中,延迟添加
this._entitiesToAdd.push(entity); this._entitiesToAdd.push(entity);
} else { } else {
this.addImmediate(entity); this.addImmediate(entity);
} }
} }
/** /**
* *
* @param entity * @param entity
*/ */
private addImmediate(entity: Entity): void { private addImmediate(entity: Entity): void {
// 检查是否已存在 // 检查是否已存在
if (this._idToEntity.has(entity.id)) { if (this._idToEntity.has(entity.id)) {
return; return;
} }
this.buffer.push(entity); this.buffer.push(entity);
this._idToEntity.set(entity.id, entity); this._idToEntity.set(entity.id, entity);
// 更新名称索引 // 更新名称索引
this.updateNameIndex(entity, true); this.updateNameIndex(entity, true);
} }
/** /**
* *
* @param entity * @param entity
*/ */
public remove(entity: Entity): void { public remove(entity: Entity): void {
if (this._isUpdating) { if (this._isUpdating) {
// 如果正在更新中,延迟移除 // 如果正在更新中,延迟移除
this._entitiesToRemove.push(entity); this._entitiesToRemove.push(entity);
} else { } else {
this.removeImmediate(entity); this.removeImmediate(entity);
} }
} }
/** /**
* *
* @param entity * @param entity
*/ */
private removeImmediate(entity: Entity): void { private removeImmediate(entity: Entity): void {
const index = this.buffer.indexOf(entity); const index = this.buffer.indexOf(entity);
if (index !== -1) { if (index !== -1) {
this.buffer.splice(index, 1); this.buffer.splice(index, 1);
this._idToEntity.delete(entity.id); this._idToEntity.delete(entity.id);
// 更新名称索引 // 更新名称索引
this.updateNameIndex(entity, false); this.updateNameIndex(entity, false);
} }
} }
/** /**
* *
*/ */
public removeAllEntities(): void { public removeAllEntities(): void {
for (let i = this.buffer.length - 1; i >= 0; i--) { for (let i = this.buffer.length - 1; i >= 0; i--) {
this.buffer[i].destroy(); this.buffer[i].destroy();
} }
this.buffer.length = 0; this.buffer.length = 0;
this._idToEntity.clear(); this._idToEntity.clear();
this._nameToEntities.clear(); this._nameToEntities.clear();
this._entitiesToAdd.length = 0; this._entitiesToAdd.length = 0;
this._entitiesToRemove.length = 0; this._entitiesToRemove.length = 0;
} }
/** /**
* *
*/ */
public updateLists(): void { public updateLists(): void {
// 处理延迟添加的实体 // 处理延迟添加的实体
if (this._entitiesToAdd.length > 0) { if (this._entitiesToAdd.length > 0) {
for (const entity of this._entitiesToAdd) { for (const entity of this._entitiesToAdd) {
this.addImmediate(entity); this.addImmediate(entity);
} }
this._entitiesToAdd.length = 0; this._entitiesToAdd.length = 0;
} }
// 处理延迟移除的实体 // 处理延迟移除的实体
if (this._entitiesToRemove.length > 0) { if (this._entitiesToRemove.length > 0) {
for (const entity of this._entitiesToRemove) { for (const entity of this._entitiesToRemove) {
this.removeImmediate(entity); this.removeImmediate(entity);
} }
this._entitiesToRemove.length = 0; this._entitiesToRemove.length = 0;
} }
} }
/** /**
* *
*/ */
public update(): void { public update(): void {
this._isUpdating = true; this._isUpdating = true;
try { try {
for (let i = 0; i < this.buffer.length; i++) { for (let i = 0; i < this.buffer.length; i++) {
const entity = this.buffer[i]; const entity = this.buffer[i];
if (entity.enabled && !entity.isDestroyed) { if (entity.enabled && !entity.isDestroyed) {
entity.update(); entity.update();
} }
} }
} finally { } finally {
this._isUpdating = false; this._isUpdating = false;
} }
// 处理延迟操作 // 处理延迟操作
this.updateLists(); this.updateLists();
} }
/** /**
* 使O(1) * 使O(1)
* @param name * @param name
* @returns null * @returns null
*/ */
public findEntity(name: string): Entity | null { public findEntity(name: string): Entity | null {
const entities = this._nameToEntities.get(name); const entities = this._nameToEntities.get(name);
return entities && entities.length > 0 ? entities[0] : null; return entities && entities.length > 0 ? entities[0] : null;
} }
/** /**
* *
* @param name * @param name
* @returns * @returns
*/ */
public findEntitiesByName(name: string): Entity[] { public findEntitiesByName(name: string): Entity[] {
return this._nameToEntities.get(name) || []; return this._nameToEntities.get(name) || [];
} }
/** /**
* ID查找实体使O(1) * ID查找实体使O(1)
* @param id ID * @param id ID
* @returns null * @returns null
*/ */
public findEntityById(id: number): Entity | null { public findEntityById(id: number): Entity | null {
return this._idToEntity.get(id) || null; return this._idToEntity.get(id) || null;
} }
/** /**
* *
* @param tag * @param tag
* @returns * @returns
*/ */
public findEntitiesByTag(tag: number): Entity[] { public findEntitiesByTag(tag: number): Entity[] {
const result: Entity[] = []; const result: Entity[] = [];
for (const entity of this.buffer) { for (const entity of this.buffer) {
if (entity.tag === tag) { if (entity.tag === tag) {
result.push(entity); result.push(entity);
} }
} }
return result; return result;
} }
/** /**
* *
* @param componentType * @param componentType
* @returns * @returns
*/ */
public findEntitiesWithComponent<T extends Component>(componentType: new (...args: any[]) => T): Entity[] { public findEntitiesWithComponent<T extends Component>(componentType: new (...args: any[]) => T): Entity[] {
const result: Entity[] = []; const result: Entity[] = [];
for (const entity of this.buffer) { for (const entity of this.buffer) {
if (entity.hasComponent(componentType)) { if (entity.hasComponent(componentType)) {
result.push(entity); result.push(entity);
} }
} }
return result; return result;
} }
/** /**
* *
* @param action * @param action
*/ */
public forEach(action: (entity: Entity) => void): void { public forEach(action: (entity: Entity) => void): void {
for (const entity of this.buffer) { for (const entity of this.buffer) {
action(entity); action(entity);
} }
} }
/** /**
* *
* @param predicate * @param predicate
* @param action * @param action
*/ */
public forEachWhere(predicate: (entity: Entity) => boolean, action: (entity: Entity) => void): void { public forEachWhere(predicate: (entity: Entity) => boolean, action: (entity: Entity) => void): void {
for (const entity of this.buffer) { for (const entity of this.buffer) {
if (predicate(entity)) { if (predicate(entity)) {
action(entity); action(entity);
} }
} }
} }
/** /**
* *
* @param entity * @param entity
* @param isAdd * @param isAdd
*/ */
private updateNameIndex(entity: Entity, isAdd: boolean): void { private updateNameIndex(entity: Entity, isAdd: boolean): void {
if (!entity.name) { if (!entity.name) {
return; return;
} }
if (isAdd) { if (isAdd) {
let entities = this._nameToEntities.get(entity.name); let entities = this._nameToEntities.get(entity.name);
if (!entities) { if (!entities) {
entities = []; entities = [];
this._nameToEntities.set(entity.name, entities); this._nameToEntities.set(entity.name, entities);
} }
entities.push(entity); entities.push(entity);
} else { } else {
const entities = this._nameToEntities.get(entity.name); const entities = this._nameToEntities.get(entity.name);
if (entities) { if (entities) {
const index = entities.indexOf(entity); const index = entities.indexOf(entity);
if (index !== -1) { if (index !== -1) {
entities.splice(index, 1); entities.splice(index, 1);
// 如果数组为空,删除映射 // 如果数组为空,删除映射
if (entities.length === 0) { if (entities.length === 0) {
this._nameToEntities.delete(entity.name); this._nameToEntities.delete(entity.name);
} }
} }
} }
} }
} }
/** /**
* *
* @returns * @returns
*/ */
public getStats(): { public getStats(): {
totalEntities: number; totalEntities: number;
activeEntities: number; activeEntities: number;
pendingAdd: number; pendingAdd: number;
pendingRemove: number; pendingRemove: number;
nameIndexSize: number; nameIndexSize: number;
} { } {
let activeCount = 0; let activeCount = 0;
for (const entity of this.buffer) { for (const entity of this.buffer) {
if (entity.enabled && !entity.isDestroyed) { if (entity.enabled && !entity.isDestroyed) {
activeCount++; activeCount++;
} }
} }
return { return {
totalEntities: this.buffer.length, totalEntities: this.buffer.length,
activeEntities: activeCount, activeEntities: activeCount,
pendingAdd: this._entitiesToAdd.length, pendingAdd: this._entitiesToAdd.length,
pendingRemove: this._entitiesToRemove.length, pendingRemove: this._entitiesToRemove.length,
nameIndexSize: this._nameToEntities.size nameIndexSize: this._nameToEntities.size
}; };
} }
} }

View File

@@ -1,168 +1,168 @@
import { Entity } from '../Entity'; import { Entity } from '../Entity';
import { Component } from '../Component'; import { Component } from '../Component';
import { Bits } from './Bits'; import { Bits } from './Bits';
import { ComponentTypeManager } from './ComponentTypeManager'; import { ComponentTypeManager } from './ComponentTypeManager';
/** /**
* *
* *
*/ */
export class Matcher { export class Matcher {
protected allSet: (new (...args: any[]) => Component)[] = []; protected allSet: (new (...args: any[]) => Component)[] = [];
protected exclusionSet: (new (...args: any[]) => Component)[] = []; protected exclusionSet: (new (...args: any[]) => Component)[] = [];
protected oneSet: (new (...args: any[]) => Component)[] = []; protected oneSet: (new (...args: any[]) => Component)[] = [];
// 缓存的位掩码,避免重复计算 // 缓存的位掩码,避免重复计算
private _allBits?: Bits; private _allBits?: Bits;
private _exclusionBits?: Bits; private _exclusionBits?: Bits;
private _oneBits?: Bits; private _oneBits?: Bits;
private _isDirty = true; private _isDirty = true;
public static empty(): Matcher { public static empty(): Matcher {
return new Matcher(); return new Matcher();
} }
public getAllSet(): (new (...args: any[]) => Component)[] { public getAllSet(): (new (...args: any[]) => Component)[] {
return this.allSet; return this.allSet;
} }
public getExclusionSet(): (new (...args: any[]) => Component)[] { public getExclusionSet(): (new (...args: any[]) => Component)[] {
return this.exclusionSet; return this.exclusionSet;
} }
public getOneSet(): (new (...args: any[]) => Component)[] { public getOneSet(): (new (...args: any[]) => Component)[] {
return this.oneSet; return this.oneSet;
} }
/** /**
* *
* @param entity * @param entity
* @returns * @returns
*/ */
public isInterestedEntity(entity: Entity): boolean { public isInterestedEntity(entity: Entity): boolean {
const entityBits = this.getEntityBits(entity); const entityBits = this.getEntityBits(entity);
return this.isInterested(entityBits); return this.isInterested(entityBits);
} }
/** /**
* *
* @param componentBits * @param componentBits
* @returns * @returns
*/ */
public isInterested(componentBits: Bits): boolean { public isInterested(componentBits: Bits): boolean {
this.updateBitsIfDirty(); this.updateBitsIfDirty();
// 检查必须包含的组件 // 检查必须包含的组件
if (this._allBits && !componentBits.containsAll(this._allBits)) { if (this._allBits && !componentBits.containsAll(this._allBits)) {
return false; return false;
} }
// 检查排除的组件 // 检查排除的组件
if (this._exclusionBits && componentBits.intersects(this._exclusionBits)) { if (this._exclusionBits && componentBits.intersects(this._exclusionBits)) {
return false; return false;
} }
// 检查至少包含其中之一的组件 // 检查至少包含其中之一的组件
if (this._oneBits && !componentBits.intersects(this._oneBits)) { if (this._oneBits && !componentBits.intersects(this._oneBits)) {
return false; return false;
} }
return true; return true;
} }
/** /**
* *
* @param types * @param types
*/ */
public all(...types: (new (...args: any[]) => Component)[]): Matcher { public all(...types: (new (...args: any[]) => Component)[]): Matcher {
this.allSet.push(...types); this.allSet.push(...types);
this._isDirty = true; this._isDirty = true;
return this; return this;
} }
/** /**
* *
* @param types * @param types
*/ */
public exclude(...types: (new (...args: any[]) => Component)[]): Matcher { public exclude(...types: (new (...args: any[]) => Component)[]): Matcher {
this.exclusionSet.push(...types); this.exclusionSet.push(...types);
this._isDirty = true; this._isDirty = true;
return this; return this;
} }
/** /**
* *
* @param types * @param types
*/ */
public one(...types: (new (...args: any[]) => Component)[]): Matcher { public one(...types: (new (...args: any[]) => Component)[]): Matcher {
this.oneSet.push(...types); this.oneSet.push(...types);
this._isDirty = true; this._isDirty = true;
return this; return this;
} }
/** /**
* *
* @param entity * @param entity
* @returns * @returns
*/ */
private getEntityBits(entity: Entity): Bits { private getEntityBits(entity: Entity): Bits {
const components = entity.components; const components = entity.components;
return ComponentTypeManager.instance.getEntityBits(components); return ComponentTypeManager.instance.getEntityBits(components);
} }
/** /**
* *
*/ */
private updateBitsIfDirty(): void { private updateBitsIfDirty(): void {
if (!this._isDirty) { if (!this._isDirty) {
return; return;
} }
const typeManager = ComponentTypeManager.instance; const typeManager = ComponentTypeManager.instance;
// 更新必须包含的组件位掩码 // 更新必须包含的组件位掩码
if (this.allSet.length > 0) { if (this.allSet.length > 0) {
this._allBits = typeManager.createBits(...this.allSet); this._allBits = typeManager.createBits(...this.allSet);
} else { } else {
this._allBits = undefined; this._allBits = undefined;
} }
// 更新排除的组件位掩码 // 更新排除的组件位掩码
if (this.exclusionSet.length > 0) { if (this.exclusionSet.length > 0) {
this._exclusionBits = typeManager.createBits(...this.exclusionSet); this._exclusionBits = typeManager.createBits(...this.exclusionSet);
} else { } else {
this._exclusionBits = undefined; this._exclusionBits = undefined;
} }
// 更新至少包含其中之一的组件位掩码 // 更新至少包含其中之一的组件位掩码
if (this.oneSet.length > 0) { if (this.oneSet.length > 0) {
this._oneBits = typeManager.createBits(...this.oneSet); this._oneBits = typeManager.createBits(...this.oneSet);
} else { } else {
this._oneBits = undefined; this._oneBits = undefined;
} }
this._isDirty = false; this._isDirty = false;
} }
/** /**
* *
* @returns * @returns
*/ */
public toString(): string { public toString(): string {
const parts: string[] = []; const parts: string[] = [];
if (this.allSet.length > 0) { if (this.allSet.length > 0) {
parts.push(`all: [${this.allSet.map(t => t.name).join(', ')}]`); parts.push(`all: [${this.allSet.map(t => t.name).join(', ')}]`);
} }
if (this.exclusionSet.length > 0) { if (this.exclusionSet.length > 0) {
parts.push(`exclude: [${this.exclusionSet.map(t => t.name).join(', ')}]`); parts.push(`exclude: [${this.exclusionSet.map(t => t.name).join(', ')}]`);
} }
if (this.oneSet.length > 0) { if (this.oneSet.length > 0) {
parts.push(`one: [${this.oneSet.map(t => t.name).join(', ')}]`); parts.push(`one: [${this.oneSet.map(t => t.name).join(', ')}]`);
} }
return `Matcher(${parts.join(', ')})`; return `Matcher(${parts.join(', ')})`;
} }
} }

View File

@@ -1,81 +1,81 @@
/** /**
* *
*/ */
export class FuncPack { export class FuncPack {
/** 函数 */ /** 函数 */
public func: Function; public func: Function;
/** 上下文 */ /** 上下文 */
public context: any; public context: any;
constructor(func: Function, context: any) { constructor(func: Function, context: any) {
this.func = func; this.func = func;
this.context = context; this.context = context;
} }
} }
/** /**
* *
*/ */
export class Emitter<T> { export class Emitter<T> {
private _messageTable: Map<T, FuncPack[]>; private _messageTable: Map<T, FuncPack[]>;
constructor() { constructor() {
this._messageTable = new Map<T, FuncPack[]>(); this._messageTable = new Map<T, FuncPack[]>();
} }
/** /**
* *
* @param eventType * @param eventType
* @param handler * @param handler
* @param context * @param context
*/ */
public addObserver(eventType: T, handler: Function, context: any) { public addObserver(eventType: T, handler: Function, context: any) {
let list = this._messageTable.get(eventType); let list = this._messageTable.get(eventType);
if (!list) { if (!list) {
list = []; list = [];
this._messageTable.set(eventType, list); this._messageTable.set(eventType, list);
} }
if (!this.hasObserver(eventType, handler)) { if (!this.hasObserver(eventType, handler)) {
list.push(new FuncPack(handler, context)); list.push(new FuncPack(handler, context));
} }
} }
/** /**
* *
* @param eventType * @param eventType
* @param handler * @param handler
*/ */
public removeObserver(eventType: T, handler: Function) { public removeObserver(eventType: T, handler: Function) {
let messageData = this._messageTable.get(eventType); let messageData = this._messageTable.get(eventType);
if (messageData) { if (messageData) {
let index = messageData.findIndex(data => data.func == handler); let index = messageData.findIndex(data => data.func == handler);
if (index != -1) if (index != -1)
messageData.splice(index, 1); messageData.splice(index, 1);
} }
} }
/** /**
* *
* @param eventType * @param eventType
* @param data * @param data
*/ */
public emit(eventType: T, ...data: any[]) { public emit(eventType: T, ...data: any[]) {
let list = this._messageTable.get(eventType); let list = this._messageTable.get(eventType);
if (list) { if (list) {
for (let observer of list) { for (let observer of list) {
observer.func.call(observer.context, ...data); observer.func.call(observer.context, ...data);
} }
} }
} }
/** /**
* *
* @param eventType * @param eventType
* @param handler * @param handler
*/ */
public hasObserver(eventType: T, handler: Function): boolean { public hasObserver(eventType: T, handler: Function): boolean {
let list = this._messageTable.get(eventType); let list = this._messageTable.get(eventType);
return list ? list.some(observer => observer.func === handler) : false; return list ? list.some(observer => observer.func === handler) : false;
} }
} }

View File

@@ -1,55 +1,55 @@
/** /**
* *
*/ */
export class GlobalManager { export class GlobalManager {
/** /**
* *
*/ */
public _enabled: boolean = false; public _enabled: boolean = false;
/** /**
* *
*/ */
public get enabled() { public get enabled() {
return this._enabled; return this._enabled;
} }
public set enabled(value: boolean) { public set enabled(value: boolean) {
this.setEnabled(value); this.setEnabled(value);
} }
/** /**
* *
* @param isEnabled true * @param isEnabled true
*/ */
public setEnabled(isEnabled: boolean) { public setEnabled(isEnabled: boolean) {
if (this._enabled != isEnabled) { if (this._enabled != isEnabled) {
this._enabled = isEnabled; this._enabled = isEnabled;
if (this._enabled) { if (this._enabled) {
// 如果启用了管理器则调用onEnabled方法 // 如果启用了管理器则调用onEnabled方法
this.onEnabled(); this.onEnabled();
} else { } else {
// 如果禁用了管理器则调用onDisabled方法 // 如果禁用了管理器则调用onDisabled方法
this.onDisabled(); this.onDisabled();
} }
} }
} }
/** /**
* *
*/ */
protected onEnabled() { protected onEnabled() {
} }
/** /**
* *
*/ */
protected onDisabled() { protected onDisabled() {
} }
/** /**
* *
*/ */
public update() { public update() {
} }
} }