重构项目结构:整理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();
}
}