更新network库及core库优化
This commit is contained in:
60
packages/network-shared/README.md
Normal file
60
packages/network-shared/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# @esengine/ecs-framework-network-shared
|
||||
|
||||
ECS Framework 网络库 - 共享组件和类型定义
|
||||
|
||||
## 概述
|
||||
|
||||
这是 ECS Framework 网络库的共享包,包含了客户端和服务端通用的:
|
||||
|
||||
- 装饰器定义 (`@SyncVar`, `@ClientRpc`, `@ServerRpc` 等)
|
||||
- 类型定义和接口
|
||||
- 序列化/反序列化工具
|
||||
- Protobuf 自动生成机制
|
||||
- 网络消息基类
|
||||
|
||||
## 特性
|
||||
|
||||
- **装饰器驱动**: 基于装饰器自动生成网络协议
|
||||
- **类型安全**: 完整的 TypeScript 支持
|
||||
- **自动序列化**: 基于 Protobuf 的高性能序列化
|
||||
- **零配置**: 无需手写 .proto 文件
|
||||
- **ECS 集成**: 深度集成 ECS 框架的特性
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
npm install @esengine/ecs-framework-network-shared
|
||||
```
|
||||
|
||||
## 基本用法
|
||||
|
||||
```typescript
|
||||
import { NetworkComponent, SyncVar, ClientRpc, ServerRpc } from '@esengine/ecs-framework-network-shared';
|
||||
|
||||
@NetworkComponent()
|
||||
class PlayerController extends Component {
|
||||
@SyncVar({ onChanged: 'onHealthChanged' })
|
||||
public health: number = 100;
|
||||
|
||||
@SyncVar()
|
||||
public playerName: string = '';
|
||||
|
||||
@ClientRpc()
|
||||
public showDamage(damage: number): void {
|
||||
// 客户端显示伤害效果
|
||||
}
|
||||
|
||||
@ServerRpc()
|
||||
public movePlayer(direction: Vector3): void {
|
||||
// 服务端处理玩家移动
|
||||
}
|
||||
|
||||
private onHealthChanged(oldValue: number, newValue: number): void {
|
||||
console.log(`生命值从 ${oldValue} 变为 ${newValue}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
118
packages/network-shared/build-rollup.cjs
Normal file
118
packages/network-shared/build-rollup.cjs
Normal file
@@ -0,0 +1,118 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
console.log('🚀 使用 Rollup 构建 network-shared 包...');
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
if (fs.existsSync('./dist')) {
|
||||
console.log('🧹 清理旧的构建文件...');
|
||||
execSync('rimraf ./dist', { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
console.log('📦 执行 Rollup 构建...');
|
||||
execSync('rollup -c rollup.config.cjs', { stdio: 'inherit' });
|
||||
|
||||
console.log('📋 生成 package.json...');
|
||||
generatePackageJson();
|
||||
|
||||
console.log('📁 复制必要文件...');
|
||||
copyFiles();
|
||||
|
||||
showBuildResults();
|
||||
|
||||
console.log('✅ network-shared 构建完成!');
|
||||
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.cjs',
|
||||
module: 'index.mjs',
|
||||
unpkg: 'index.umd.js',
|
||||
types: 'index.d.ts',
|
||||
exports: {
|
||||
'.': {
|
||||
import: './index.mjs',
|
||||
require: './index.cjs',
|
||||
types: './index.d.ts'
|
||||
}
|
||||
},
|
||||
files: [
|
||||
'index.mjs',
|
||||
'index.mjs.map',
|
||||
'index.cjs',
|
||||
'index.cjs.map',
|
||||
'index.umd.js',
|
||||
'index.umd.js.map',
|
||||
'index.d.ts',
|
||||
'README.md',
|
||||
'LICENSE'
|
||||
],
|
||||
keywords: [
|
||||
'ecs',
|
||||
'networking',
|
||||
'shared',
|
||||
'decorators',
|
||||
'protobuf',
|
||||
'serialization',
|
||||
'game-engine',
|
||||
'typescript'
|
||||
],
|
||||
author: sourcePackage.author,
|
||||
license: sourcePackage.license,
|
||||
repository: sourcePackage.repository,
|
||||
dependencies: sourcePackage.dependencies,
|
||||
peerDependencies: sourcePackage.peerDependencies,
|
||||
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)}`);
|
||||
} else {
|
||||
console.log(` ⚠️ 文件不存在: ${src}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showBuildResults() {
|
||||
const distDir = './dist';
|
||||
const files = ['index.mjs', 'index.cjs', '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);
|
||||
53
packages/network-shared/jest.config.cjs
Normal file
53
packages/network-shared/jest.config.cjs
Normal file
@@ -0,0 +1,53 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/tests'],
|
||||
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/'],
|
||||
collectCoverage: false,
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.ts',
|
||||
'!src/index.ts',
|
||||
'!src/**/index.ts',
|
||||
'!**/*.d.ts',
|
||||
'!src/**/*.test.ts',
|
||||
'!src/**/*.spec.ts'
|
||||
],
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov', 'html'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 60,
|
||||
functions: 70,
|
||||
lines: 70,
|
||||
statements: 70
|
||||
},
|
||||
'./src/decorators/': {
|
||||
branches: 70,
|
||||
functions: 80,
|
||||
lines: 80,
|
||||
statements: 80
|
||||
}
|
||||
},
|
||||
verbose: true,
|
||||
transform: {
|
||||
'^.+\\.tsx?$': ['ts-jest', {
|
||||
tsconfig: 'tsconfig.json',
|
||||
useESM: false,
|
||||
}],
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
|
||||
testTimeout: 10000,
|
||||
clearMocks: true,
|
||||
restoreMocks: true,
|
||||
modulePathIgnorePatterns: [
|
||||
'<rootDir>/bin/',
|
||||
'<rootDir>/dist/',
|
||||
'<rootDir>/node_modules/'
|
||||
]
|
||||
};
|
||||
85
packages/network-shared/package.json
Normal file
85
packages/network-shared/package.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"name": "@esengine/ecs-framework-network-shared",
|
||||
"version": "1.0.15",
|
||||
"description": "ECS Framework 网络库 - 共享组件和类型定义",
|
||||
"type": "module",
|
||||
"main": "bin/index.js",
|
||||
"types": "bin/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./bin/index.d.ts",
|
||||
"import": "./bin/index.js",
|
||||
"development": {
|
||||
"types": "./src/index.ts",
|
||||
"import": "./src/index.ts"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"bin/**/*",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"keywords": [
|
||||
"ecs",
|
||||
"networking",
|
||||
"shared",
|
||||
"decorators",
|
||||
"protobuf",
|
||||
"serialization",
|
||||
"game-engine",
|
||||
"typescript"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf bin dist",
|
||||
"build:ts": "tsc",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "npm run build:ts",
|
||||
"build:watch": "tsc --watch",
|
||||
"rebuild": "npm run clean && npm run build",
|
||||
"build:npm": "npm run build && node build-rollup.cjs",
|
||||
"publish:npm": "npm run build:npm && cd dist && npm publish",
|
||||
"publish:patch": "npm version patch && npm run build:npm && cd dist && npm publish",
|
||||
"publish:minor": "npm version minor && npm run build:npm && cd dist && npm publish",
|
||||
"publish:major": "npm version major && npm run build:npm && cd dist && npm publish",
|
||||
"preversion": "npm run rebuild",
|
||||
"test": "jest --config jest.config.cjs",
|
||||
"test:watch": "jest --watch --config jest.config.cjs",
|
||||
"test:coverage": "jest --coverage --config jest.config.cjs",
|
||||
"test:ci": "jest --ci --coverage --config jest.config.cjs",
|
||||
"test:clear": "jest --clearCache"
|
||||
},
|
||||
"author": "yhh",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"protobufjs": "^7.5.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@esengine/ecs-framework": ">=2.1.29"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esengine/ecs-framework": "*",
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^20.19.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"rollup": "^4.42.0",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"ts-jest": "^29.4.0",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/esengine/ecs-framework.git",
|
||||
"directory": "packages/network-shared"
|
||||
}
|
||||
}
|
||||
129
packages/network-shared/rollup.config.cjs
Normal file
129
packages/network-shared/rollup.config.cjs
Normal file
@@ -0,0 +1,129 @@
|
||||
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-network-shared v${pkg.version}
|
||||
* ECS Framework 网络库 - 共享组件和类型定义
|
||||
*
|
||||
* @author ${pkg.author}
|
||||
* @license ${pkg.license}
|
||||
*/`;
|
||||
|
||||
const external = ['reflect-metadata', 'protobufjs', 'uuid', '@esengine/ecs-framework'];
|
||||
|
||||
const commonPlugins = [
|
||||
resolve({
|
||||
browser: true,
|
||||
preferBuiltins: false
|
||||
}),
|
||||
commonjs({
|
||||
include: /node_modules/
|
||||
})
|
||||
];
|
||||
|
||||
module.exports = [
|
||||
// ES模块构建
|
||||
{
|
||||
input: 'bin/index.js',
|
||||
output: {
|
||||
file: 'dist/index.mjs',
|
||||
format: 'es',
|
||||
banner,
|
||||
sourcemap: true,
|
||||
exports: 'named'
|
||||
},
|
||||
plugins: [
|
||||
...commonPlugins,
|
||||
terser({
|
||||
format: {
|
||||
comments: /^!/
|
||||
}
|
||||
})
|
||||
],
|
||||
external,
|
||||
treeshake: {
|
||||
moduleSideEffects: false,
|
||||
propertyReadSideEffects: false,
|
||||
unknownGlobalSideEffects: false
|
||||
}
|
||||
},
|
||||
|
||||
// CommonJS构建
|
||||
{
|
||||
input: 'bin/index.js',
|
||||
output: {
|
||||
file: 'dist/index.cjs',
|
||||
format: 'cjs',
|
||||
banner,
|
||||
sourcemap: true,
|
||||
exports: 'named'
|
||||
},
|
||||
plugins: [
|
||||
...commonPlugins,
|
||||
terser({
|
||||
format: {
|
||||
comments: /^!/
|
||||
}
|
||||
})
|
||||
],
|
||||
external,
|
||||
treeshake: {
|
||||
moduleSideEffects: false
|
||||
}
|
||||
},
|
||||
|
||||
// UMD构建
|
||||
{
|
||||
input: 'bin/index.js',
|
||||
output: {
|
||||
file: 'dist/index.umd.js',
|
||||
format: 'umd',
|
||||
name: 'ECSNetworkShared',
|
||||
banner,
|
||||
sourcemap: true,
|
||||
exports: 'named',
|
||||
globals: {
|
||||
'reflect-metadata': 'ReflectMetadata',
|
||||
'protobufjs': 'protobuf',
|
||||
'uuid': 'uuid',
|
||||
'@esengine/ecs-framework': 'ECS'
|
||||
}
|
||||
},
|
||||
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-network-shared v${pkg.version}
|
||||
* TypeScript definitions
|
||||
*/`
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
respectExternal: true
|
||||
})
|
||||
],
|
||||
external
|
||||
}
|
||||
];
|
||||
261
packages/network-shared/src/core/NetworkBehaviour.ts
Normal file
261
packages/network-shared/src/core/NetworkBehaviour.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* NetworkBehaviour 基类
|
||||
*
|
||||
* 所有网络组件的基类,提供网络功能的基础实现
|
||||
*/
|
||||
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { INetworkComponent, INetworkObject, SyncVarMetadata, RpcMetadata, Constructor } from '../types/NetworkTypes';
|
||||
import { getSyncVarMetadata, getDirtySyncVars, clearAllDirtySyncVars } from '../decorators/SyncVar';
|
||||
import { getClientRpcMetadata } from '../decorators/ClientRpc';
|
||||
import { getServerRpcMetadata } from '../decorators/ServerRpc';
|
||||
|
||||
/**
|
||||
* NetworkBehaviour 基类
|
||||
*
|
||||
* 提供网络组件的基础功能:
|
||||
* - SyncVar 支持
|
||||
* - RPC 调用支持
|
||||
* - 网络身份管理
|
||||
* - 权限控制
|
||||
*/
|
||||
export abstract class NetworkBehaviour extends Component implements INetworkComponent {
|
||||
/** 索引签名以支持动态属性访问 */
|
||||
[key: string]: unknown;
|
||||
|
||||
/** 网络对象引用 */
|
||||
public networkObject: INetworkObject | null = null;
|
||||
|
||||
/** 网络ID */
|
||||
public get networkId(): number {
|
||||
return this.networkObject?.networkId || 0;
|
||||
}
|
||||
|
||||
/** 是否拥有权威 */
|
||||
public get hasAuthority(): boolean {
|
||||
return this.networkObject?.hasAuthority || false;
|
||||
}
|
||||
|
||||
/** 组件类型名 */
|
||||
public get componentType(): string {
|
||||
return this.constructor.name;
|
||||
}
|
||||
|
||||
/** 是否为服务端 */
|
||||
public get isServer(): boolean {
|
||||
// 这个方法会被具体的客户端/服务端库重写
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 是否为客户端 */
|
||||
public get isClient(): boolean {
|
||||
// 这个方法会被具体的客户端/服务端库重写
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 是否为本地对象 */
|
||||
public get isLocal(): boolean {
|
||||
return this.networkObject?.isLocal || false;
|
||||
}
|
||||
|
||||
/** 所有者客户端ID */
|
||||
public get ownerId(): number {
|
||||
return this.networkObject?.ownerId || 0;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.setupSyncVarNotification();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 SyncVar 变化通知
|
||||
*/
|
||||
private setupSyncVarNotification(): void {
|
||||
// 添加 SyncVar 变化通知方法
|
||||
(this as any).notifySyncVarChanged = (
|
||||
propertyName: string,
|
||||
oldValue: any,
|
||||
newValue: any,
|
||||
metadata: SyncVarMetadata
|
||||
) => {
|
||||
this.onSyncVarChanged(propertyName, oldValue, newValue, metadata);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar 变化处理
|
||||
*/
|
||||
protected onSyncVarChanged(
|
||||
propertyName: string,
|
||||
oldValue: any,
|
||||
newValue: any,
|
||||
metadata: SyncVarMetadata
|
||||
): void {
|
||||
// 权限检查
|
||||
if (metadata.authorityOnly && !this.hasAuthority) {
|
||||
console.warn(`Authority required for SyncVar: ${this.componentType}.${propertyName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 通知网络管理器
|
||||
this.notifyNetworkManager('syncvar-changed', {
|
||||
networkId: this.networkId,
|
||||
componentType: this.componentType,
|
||||
propertyName,
|
||||
oldValue,
|
||||
newValue,
|
||||
metadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送客户端 RPC
|
||||
*/
|
||||
protected sendClientRpc(methodName: string, args: any[], options?: any, metadata?: RpcMetadata): any {
|
||||
if (!this.hasAuthority && !this.isServer) {
|
||||
console.warn(`Authority required for ClientRpc: ${this.componentType}.${methodName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return this.notifyNetworkManager('client-rpc', {
|
||||
networkId: this.networkId,
|
||||
componentType: this.componentType,
|
||||
methodName,
|
||||
args,
|
||||
options,
|
||||
metadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送服务端 RPC
|
||||
*/
|
||||
protected sendServerRpc(methodName: string, args: any[], options?: any, metadata?: RpcMetadata): any {
|
||||
if (!this.isClient) {
|
||||
console.warn(`ServerRpc can only be called from client: ${this.componentType}.${methodName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return this.notifyNetworkManager('server-rpc', {
|
||||
networkId: this.networkId,
|
||||
componentType: this.componentType,
|
||||
methodName,
|
||||
args,
|
||||
options,
|
||||
metadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知网络管理器
|
||||
*/
|
||||
private notifyNetworkManager(eventType: string, data: any): any {
|
||||
// 这个方法会被具体的客户端/服务端库重写
|
||||
// 用于与网络管理器通信
|
||||
if (typeof (globalThis as any).NetworkManager !== 'undefined') {
|
||||
return (globalThis as any).NetworkManager.handleNetworkEvent?.(eventType, data);
|
||||
}
|
||||
|
||||
console.warn(`NetworkManager not found for event: ${eventType}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 SyncVar 元数据
|
||||
*/
|
||||
public getSyncVars(): SyncVarMetadata[] {
|
||||
return getSyncVarMetadata(this.constructor as Constructor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有客户端 RPC 元数据
|
||||
*/
|
||||
public getClientRpcs(): RpcMetadata[] {
|
||||
return getClientRpcMetadata(this.constructor as Constructor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有服务端 RPC 元数据
|
||||
*/
|
||||
public getServerRpcs(): RpcMetadata[] {
|
||||
return getServerRpcMetadata(this.constructor as Constructor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有脏的 SyncVar
|
||||
*/
|
||||
public getDirtySyncVars() {
|
||||
return getDirtySyncVars(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有脏标记
|
||||
*/
|
||||
public clearDirtySyncVars(): void {
|
||||
clearAllDirtySyncVars(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化组件状态
|
||||
*/
|
||||
public serializeState(): any {
|
||||
const syncVars = this.getSyncVars();
|
||||
const state: any = {};
|
||||
|
||||
for (const syncVar of syncVars) {
|
||||
const value = (this as any)[`_${syncVar.propertyName}`];
|
||||
if (value !== undefined) {
|
||||
state[syncVar.propertyName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化组件状态
|
||||
*/
|
||||
public deserializeState(state: any): void {
|
||||
const syncVars = this.getSyncVars();
|
||||
|
||||
for (const syncVar of syncVars) {
|
||||
if (state.hasOwnProperty(syncVar.propertyName)) {
|
||||
// 直接设置内部值,跳过权限检查
|
||||
(this as any)[`_${syncVar.propertyName}`] = state[syncVar.propertyName];
|
||||
|
||||
// 调用变化回调
|
||||
if (syncVar.onChanged && typeof (this as any)[syncVar.onChanged] === 'function') {
|
||||
(this as any)[syncVar.onChanged](undefined, state[syncVar.propertyName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有权限执行操作
|
||||
*/
|
||||
protected checkAuthority(requiresOwnership = false): boolean {
|
||||
if (requiresOwnership && this.ownerId !== this.getLocalClientId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.hasAuthority;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地客户端ID
|
||||
* 这个方法会被具体实现重写
|
||||
*/
|
||||
protected getLocalClientId(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件销毁时的清理
|
||||
*/
|
||||
public onDestroy(): void {
|
||||
this.networkObject = null;
|
||||
// 清理网络资源(基类销毁由框架处理)
|
||||
}
|
||||
}
|
||||
324
packages/network-shared/src/core/NetworkIdentity.ts
Normal file
324
packages/network-shared/src/core/NetworkIdentity.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
/**
|
||||
* NetworkIdentity 类
|
||||
*
|
||||
* 标识网络对象的唯一身份,管理网络组件和权威性
|
||||
*/
|
||||
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { INetworkObject, INetworkComponent } from '../types/NetworkTypes';
|
||||
import { NetworkBehaviour } from './NetworkBehaviour';
|
||||
|
||||
/**
|
||||
* NetworkIdentity 组件
|
||||
*
|
||||
* 所有需要网络同步的实体都必须拥有此组件
|
||||
*/
|
||||
export class NetworkIdentity extends Component implements INetworkObject {
|
||||
/** 网络对象的唯一标识符 */
|
||||
public networkId: number = 0;
|
||||
|
||||
/** 所有者客户端ID,0 表示服务端拥有 */
|
||||
public ownerId: number = 0;
|
||||
|
||||
/** 是否拥有权威,权威端可以修改 SyncVar 和发送 RPC */
|
||||
public hasAuthority: boolean = false;
|
||||
|
||||
/** 是否为本地对象 */
|
||||
public isLocal: boolean = false;
|
||||
|
||||
/** 是否为本地玩家对象 */
|
||||
public isLocalPlayer: boolean = false;
|
||||
|
||||
/** 预制体名称(用于网络生成) */
|
||||
public prefabName: string = '';
|
||||
|
||||
/** 场景对象ID(用于场景中已存在的对象) */
|
||||
public sceneId: number = 0;
|
||||
|
||||
/** 挂载的网络组件列表 */
|
||||
public networkComponents: INetworkComponent[] = [];
|
||||
|
||||
/** 是否已在网络中生成 */
|
||||
public isSpawned: boolean = false;
|
||||
|
||||
/** 可见性距离(用于网络LOD) */
|
||||
public visibilityDistance: number = 100;
|
||||
|
||||
/** 网络更新频率覆盖(0 = 使用全局设置) */
|
||||
public updateRate: number = 0;
|
||||
|
||||
/** 是否总是相关(不受距离限制) */
|
||||
public alwaysRelevant: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件启动时初始化
|
||||
*/
|
||||
public override onEnabled(): void {
|
||||
super.onEnabled();
|
||||
this.gatherNetworkComponents();
|
||||
this.registerToNetworkManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集实体上的所有网络组件
|
||||
*/
|
||||
private gatherNetworkComponents(): void {
|
||||
if (!this.entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空现有列表
|
||||
this.networkComponents = [];
|
||||
|
||||
// 获取实体上的所有组件
|
||||
// 获取实体上的所有组件,简化类型处理
|
||||
const components = (this.entity as any).getComponents();
|
||||
|
||||
for (const component of components) {
|
||||
if (component instanceof NetworkBehaviour) {
|
||||
this.addNetworkComponent(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加网络组件
|
||||
*/
|
||||
public addNetworkComponent(component: INetworkComponent): void {
|
||||
if (this.networkComponents.includes(component)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.networkComponents.push(component);
|
||||
component.networkObject = this;
|
||||
|
||||
// 如果已经注册到网络,通知网络管理器
|
||||
if (this.isSpawned) {
|
||||
this.notifyNetworkManager('component-added', {
|
||||
networkId: this.networkId,
|
||||
componentType: component.componentType,
|
||||
component
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除网络组件
|
||||
*/
|
||||
public removeNetworkComponent(component: INetworkComponent): void {
|
||||
const index = this.networkComponents.indexOf(component);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.networkComponents.splice(index, 1);
|
||||
component.networkObject = null;
|
||||
|
||||
// 如果已经注册到网络,通知网络管理器
|
||||
if (this.isSpawned) {
|
||||
this.notifyNetworkManager('component-removed', {
|
||||
networkId: this.networkId,
|
||||
componentType: component.componentType,
|
||||
component
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置权威性
|
||||
*/
|
||||
public setAuthority(hasAuthority: boolean, ownerId: number = 0): void {
|
||||
const oldAuthority = this.hasAuthority;
|
||||
const oldOwner = this.ownerId;
|
||||
|
||||
this.hasAuthority = hasAuthority;
|
||||
this.ownerId = ownerId;
|
||||
this.isLocal = this.checkIsLocal();
|
||||
|
||||
// 如果权威性发生变化,通知相关系统
|
||||
if (oldAuthority !== hasAuthority || oldOwner !== ownerId) {
|
||||
this.onAuthorityChanged(oldAuthority, hasAuthority, oldOwner, ownerId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置为本地玩家
|
||||
*/
|
||||
public setAsLocalPlayer(): void {
|
||||
this.isLocalPlayer = true;
|
||||
this.hasAuthority = true;
|
||||
this.isLocal = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为本地对象
|
||||
*/
|
||||
private checkIsLocal(): boolean {
|
||||
const localClientId = this.getLocalClientId();
|
||||
return this.ownerId === localClientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地客户端ID
|
||||
*/
|
||||
private getLocalClientId(): number {
|
||||
// 这个方法会被具体实现重写
|
||||
if (typeof (globalThis as any).NetworkManager !== 'undefined') {
|
||||
return (globalThis as any).NetworkManager.getLocalClientId?.() || 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权威性变化处理
|
||||
*/
|
||||
private onAuthorityChanged(
|
||||
oldAuthority: boolean,
|
||||
newAuthority: boolean,
|
||||
oldOwner: number,
|
||||
newOwner: number
|
||||
): void {
|
||||
// 通知网络管理器
|
||||
this.notifyNetworkManager('authority-changed', {
|
||||
networkId: this.networkId,
|
||||
oldAuthority,
|
||||
newAuthority,
|
||||
oldOwner,
|
||||
newOwner
|
||||
});
|
||||
|
||||
// 通知所有网络组件
|
||||
for (const component of this.networkComponents) {
|
||||
if ('onAuthorityChanged' in component && typeof component.onAuthorityChanged === 'function') {
|
||||
component.onAuthorityChanged(newAuthority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的网络组件
|
||||
*/
|
||||
public getNetworkComponent<T extends INetworkComponent>(type: new (...args: any[]) => T): T | null {
|
||||
return this.networkComponents.find(c => c instanceof type) as T || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有指定类型的网络组件
|
||||
*/
|
||||
public getNetworkComponents<T extends INetworkComponent>(type: new (...args: any[]) => T): T[] {
|
||||
return this.networkComponents.filter(c => c instanceof type) as T[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化网络身份
|
||||
*/
|
||||
public serialize(): any {
|
||||
return {
|
||||
networkId: this.networkId,
|
||||
ownerId: this.ownerId,
|
||||
hasAuthority: this.hasAuthority,
|
||||
isLocal: this.isLocal,
|
||||
isLocalPlayer: this.isLocalPlayer,
|
||||
prefabName: this.prefabName,
|
||||
sceneId: this.sceneId,
|
||||
visibilityDistance: this.visibilityDistance,
|
||||
updateRate: this.updateRate,
|
||||
alwaysRelevant: this.alwaysRelevant
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化网络身份
|
||||
*/
|
||||
public deserialize(data: any): void {
|
||||
this.networkId = data.networkId || 0;
|
||||
this.ownerId = data.ownerId || 0;
|
||||
this.hasAuthority = data.hasAuthority || false;
|
||||
this.isLocal = data.isLocal || false;
|
||||
this.isLocalPlayer = data.isLocalPlayer || false;
|
||||
this.prefabName = data.prefabName || '';
|
||||
this.sceneId = data.sceneId || 0;
|
||||
this.visibilityDistance = data.visibilityDistance || 100;
|
||||
this.updateRate = data.updateRate || 0;
|
||||
this.alwaysRelevant = data.alwaysRelevant || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册到网络管理器
|
||||
*/
|
||||
private registerToNetworkManager(): void {
|
||||
this.notifyNetworkManager('register-network-object', {
|
||||
networkIdentity: this,
|
||||
networkId: this.networkId,
|
||||
components: this.networkComponents
|
||||
});
|
||||
this.isSpawned = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从网络管理器注销
|
||||
*/
|
||||
private unregisterFromNetworkManager(): void {
|
||||
this.notifyNetworkManager('unregister-network-object', {
|
||||
networkIdentity: this,
|
||||
networkId: this.networkId
|
||||
});
|
||||
this.isSpawned = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知网络管理器
|
||||
*/
|
||||
private notifyNetworkManager(eventType: string, data: any): void {
|
||||
if (typeof (globalThis as any).NetworkManager !== 'undefined') {
|
||||
(globalThis as any).NetworkManager.handleNetworkEvent?.(eventType, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查对象是否对指定客户端可见
|
||||
*/
|
||||
public isVisibleTo(clientId: number, clientPosition?: { x: number; y: number; z?: number }): boolean {
|
||||
// 如果总是相关,则对所有客户端可见
|
||||
if (this.alwaysRelevant) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果没有提供客户端位置,默认可见
|
||||
// 简单的可见性检查,暂时不依赖Transform组件
|
||||
if (!clientPosition) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 基于距离的可见性检查(需要自定义位置获取逻辑)
|
||||
const position = { x: 0, y: 0, z: 0 }; // 占位符
|
||||
const dx = position.x - clientPosition.x;
|
||||
const dy = position.y - clientPosition.y;
|
||||
const dz = (position.z || 0) - (clientPosition.z || 0);
|
||||
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
return distance <= this.visibilityDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件销毁时的清理
|
||||
*/
|
||||
public destroy(): void {
|
||||
// 从网络管理器注销
|
||||
if (this.isSpawned) {
|
||||
this.unregisterFromNetworkManager();
|
||||
}
|
||||
|
||||
// 清理所有网络组件的引用
|
||||
for (const component of this.networkComponents) {
|
||||
component.networkObject = null;
|
||||
}
|
||||
this.networkComponents = [];
|
||||
|
||||
// 清理网络资源(基类销毁由框架处理)
|
||||
}
|
||||
}
|
||||
6
packages/network-shared/src/core/index.ts
Normal file
6
packages/network-shared/src/core/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 核心类导出
|
||||
*/
|
||||
|
||||
export * from './NetworkBehaviour';
|
||||
export * from './NetworkIdentity';
|
||||
177
packages/network-shared/src/decorators/ClientRpc.ts
Normal file
177
packages/network-shared/src/decorators/ClientRpc.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* ClientRpc 装饰器
|
||||
*
|
||||
* 用于标记可以在服务端调用,在客户端执行的方法
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
import { RpcMetadata, DecoratorTarget, Constructor, RpcParameterType, RpcReturnType } from '../types/NetworkTypes';
|
||||
import { getNetworkComponentMetadata } from './NetworkComponent';
|
||||
|
||||
/**
|
||||
* ClientRpc 装饰器选项
|
||||
*/
|
||||
export interface ClientRpcOptions {
|
||||
/** 是否需要权限验证 */
|
||||
requiresAuth?: boolean;
|
||||
/** 是否可靠传输,默认为 true */
|
||||
reliable?: boolean;
|
||||
/** 是否需要响应 */
|
||||
requiresResponse?: boolean;
|
||||
/** 目标客户端筛选器 */
|
||||
targetFilter?: 'all' | 'others' | 'owner' | 'specific';
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 ClientRpc 元数据的 Symbol
|
||||
*/
|
||||
export const CLIENT_RPC_METADATA_KEY = Symbol('client_rpc_metadata');
|
||||
|
||||
/**
|
||||
* ClientRpc 装饰器
|
||||
*
|
||||
* @param options 装饰器选项
|
||||
* @returns 方法装饰器函数
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @NetworkComponent()
|
||||
* class PlayerController extends Component {
|
||||
* @ClientRpc({ targetFilter: 'all' })
|
||||
* public showDamageEffect(damage: number, position: Vector3): void {
|
||||
* // 在所有客户端显示伤害效果
|
||||
* console.log(`Showing damage: ${damage} at ${position}`);
|
||||
* }
|
||||
*
|
||||
* @ClientRpc({ targetFilter: 'owner', reliable: false })
|
||||
* public updateUI(data: UIData): void {
|
||||
* // 只在拥有者客户端更新UI,使用不可靠传输
|
||||
* }
|
||||
*
|
||||
* @ClientRpc({ requiresResponse: true })
|
||||
* public requestClientData(): ClientData {
|
||||
* // 请求客户端数据并等待响应
|
||||
* return this.getClientData();
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function ClientRpc(options: ClientRpcOptions = {}): MethodDecorator {
|
||||
return function (target: unknown, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
||||
if (typeof propertyKey !== 'string') {
|
||||
throw new Error('ClientRpc can only be applied to string method names');
|
||||
}
|
||||
|
||||
// 获取或创建元数据数组
|
||||
let metadata: RpcMetadata[] = Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, (target as { constructor: Constructor }).constructor);
|
||||
if (!metadata) {
|
||||
metadata = [];
|
||||
Reflect.defineMetadata(CLIENT_RPC_METADATA_KEY, metadata, (target as { constructor: Constructor }).constructor);
|
||||
}
|
||||
|
||||
// 创建 RPC 元数据
|
||||
const rpcMetadata: RpcMetadata = {
|
||||
methodName: propertyKey,
|
||||
rpcType: 'client-rpc',
|
||||
requiresAuth: options.requiresAuth || false,
|
||||
reliable: options.reliable !== false,
|
||||
requiresResponse: options.requiresResponse || false
|
||||
};
|
||||
|
||||
metadata.push(rpcMetadata);
|
||||
|
||||
// 更新 NetworkComponent 元数据
|
||||
const componentMetadata = getNetworkComponentMetadata((target as { constructor: Constructor }).constructor);
|
||||
if (componentMetadata) {
|
||||
const existingIndex = componentMetadata.rpcs.findIndex(rpc => rpc.methodName === propertyKey);
|
||||
if (existingIndex >= 0) {
|
||||
componentMetadata.rpcs[existingIndex] = rpcMetadata;
|
||||
} else {
|
||||
componentMetadata.rpcs.push(rpcMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存原方法
|
||||
const originalMethod = descriptor.value;
|
||||
if (typeof originalMethod !== 'function') {
|
||||
throw new Error(`ClientRpc can only be applied to methods, got ${typeof originalMethod}`);
|
||||
}
|
||||
|
||||
// 包装方法以添加网络调用逻辑
|
||||
descriptor.value = function (this: Record<string, unknown> & {
|
||||
isServer?: () => boolean;
|
||||
sendClientRpc?: (methodName: string, args: RpcParameterType[], options: ClientRpcOptions, metadata: RpcMetadata) => RpcReturnType;
|
||||
}, ...args: RpcParameterType[]): RpcReturnType {
|
||||
// 如果在服务端调用,发送到客户端
|
||||
const isServer = this.isServer?.() || (typeof window === 'undefined' && typeof process !== 'undefined');
|
||||
if (isServer) {
|
||||
return this.sendClientRpc?.(propertyKey, args, options, rpcMetadata) as RpcReturnType;
|
||||
}
|
||||
|
||||
// 如果在客户端,直接执行本地方法
|
||||
return (originalMethod as (...args: RpcParameterType[]) => RpcReturnType).apply(this, args);
|
||||
};
|
||||
|
||||
// 保存原方法的引用,供直接调用
|
||||
const decoratedFunction = descriptor.value as typeof descriptor.value & {
|
||||
__originalMethod: typeof originalMethod;
|
||||
__rpcMetadata: RpcMetadata;
|
||||
__rpcOptions: ClientRpcOptions;
|
||||
};
|
||||
decoratedFunction.__originalMethod = originalMethod;
|
||||
decoratedFunction.__rpcMetadata = rpcMetadata;
|
||||
decoratedFunction.__rpcOptions = options;
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的 ClientRpc 元数据
|
||||
*/
|
||||
export function getClientRpcMetadata(target: Constructor): RpcMetadata[] {
|
||||
return Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查方法是否为 ClientRpc
|
||||
*/
|
||||
export function isClientRpc(target: Constructor, methodName: string): boolean {
|
||||
const metadata = getClientRpcMetadata(target);
|
||||
return metadata.some(m => m.methodName === methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取特定方法的 ClientRpc 元数据
|
||||
*/
|
||||
export function getClientRpcMethodMetadata(target: Constructor, methodName: string): RpcMetadata | null {
|
||||
const metadata = getClientRpcMetadata(target);
|
||||
return metadata.find(m => m.methodName === methodName) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接调用原方法(跳过网络逻辑)
|
||||
*/
|
||||
export function invokeClientRpcLocally(instance: Record<string, unknown>, methodName: string, args: RpcParameterType[]): RpcReturnType {
|
||||
const method = instance[methodName] as { __originalMethod?: (...args: RpcParameterType[]) => RpcReturnType } | undefined;
|
||||
if (method && typeof method.__originalMethod === 'function') {
|
||||
return method.__originalMethod.apply(instance, args);
|
||||
}
|
||||
throw new Error(`Method ${methodName} is not a valid ClientRpc or original method not found`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 ClientRpc 是否需要响应
|
||||
*/
|
||||
export function clientRpcRequiresResponse(instance: Record<string, unknown>, methodName: string): boolean {
|
||||
const method = instance[methodName] as { __rpcMetadata?: RpcMetadata } | undefined;
|
||||
return method?.__rpcMetadata?.requiresResponse || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ClientRpc 的选项
|
||||
*/
|
||||
export function getClientRpcOptions(instance: Record<string, unknown>, methodName: string): ClientRpcOptions | null {
|
||||
const method = instance[methodName] as { __rpcOptions?: ClientRpcOptions } | undefined;
|
||||
return method?.__rpcOptions || null;
|
||||
}
|
||||
138
packages/network-shared/src/decorators/NetworkComponent.ts
Normal file
138
packages/network-shared/src/decorators/NetworkComponent.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* NetworkComponent 装饰器
|
||||
*
|
||||
* 用于标记网络组件,自动注册到网络系统
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
import { NetworkComponentMetadata } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* NetworkComponent 装饰器选项
|
||||
*/
|
||||
export interface NetworkComponentOptions {
|
||||
/** 是否自动生成 protobuf 协议 */
|
||||
autoGenerateProtocol?: boolean;
|
||||
/** 自定义组件类型名 */
|
||||
typeName?: string;
|
||||
/** 是否仅服务端存在 */
|
||||
serverOnly?: boolean;
|
||||
/** 是否仅客户端存在 */
|
||||
clientOnly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 NetworkComponent 元数据的 Symbol
|
||||
*/
|
||||
export const NETWORK_COMPONENT_METADATA_KEY = Symbol('network_component_metadata');
|
||||
|
||||
/**
|
||||
* NetworkComponent 装饰器
|
||||
*
|
||||
* @param options 装饰器选项
|
||||
* @returns 类装饰器函数
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @NetworkComponent({ autoGenerateProtocol: true })
|
||||
* class PlayerController extends Component implements INetworkComponent {
|
||||
* networkObject: INetworkObject | null = null;
|
||||
* networkId: number = 0;
|
||||
* hasAuthority: boolean = false;
|
||||
* componentType: string = 'PlayerController';
|
||||
*
|
||||
* @SyncVar()
|
||||
* public health: number = 100;
|
||||
*
|
||||
* @ClientRpc()
|
||||
* public showDamage(damage: number): void {
|
||||
* // 显示伤害效果
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function NetworkComponent(options: NetworkComponentOptions = {}): ClassDecorator {
|
||||
return function <T extends Function>(target: T) {
|
||||
const metadata: NetworkComponentMetadata = {
|
||||
componentType: options.typeName || target.name,
|
||||
syncVars: [],
|
||||
rpcs: [],
|
||||
autoGenerateProtocol: options.autoGenerateProtocol !== false,
|
||||
};
|
||||
|
||||
// 存储元数据
|
||||
Reflect.defineMetadata(NETWORK_COMPONENT_METADATA_KEY, metadata, target);
|
||||
|
||||
// 注册到全局组件注册表
|
||||
NetworkComponentRegistry.register(target as any, metadata);
|
||||
|
||||
return target;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的 NetworkComponent 元数据
|
||||
*/
|
||||
export function getNetworkComponentMetadata(target: any): NetworkComponentMetadata | null {
|
||||
return Reflect.getMetadata(NETWORK_COMPONENT_METADATA_KEY, target) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查类是否为 NetworkComponent
|
||||
*/
|
||||
export function isNetworkComponent(target: any): boolean {
|
||||
return Reflect.hasMetadata(NETWORK_COMPONENT_METADATA_KEY, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络组件注册表
|
||||
*/
|
||||
class NetworkComponentRegistry {
|
||||
private static components = new Map<string, {
|
||||
constructor: any;
|
||||
metadata: NetworkComponentMetadata;
|
||||
}>();
|
||||
|
||||
/**
|
||||
* 注册网络组件
|
||||
*/
|
||||
static register(constructor: any, metadata: NetworkComponentMetadata): void {
|
||||
this.components.set(metadata.componentType, {
|
||||
constructor,
|
||||
metadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件信息
|
||||
*/
|
||||
static getComponent(typeName: string) {
|
||||
return this.components.get(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有组件
|
||||
*/
|
||||
static getAllComponents() {
|
||||
return Array.from(this.components.entries()).map(([typeName, info]) => ({
|
||||
typeName,
|
||||
...info
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件是否已注册
|
||||
*/
|
||||
static hasComponent(typeName: string): boolean {
|
||||
return this.components.has(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空注册表 (主要用于测试)
|
||||
*/
|
||||
static clear(): void {
|
||||
this.components.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export { NetworkComponentRegistry };
|
||||
178
packages/network-shared/src/decorators/ServerRpc.ts
Normal file
178
packages/network-shared/src/decorators/ServerRpc.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* ServerRpc 装饰器
|
||||
*
|
||||
* 用于标记可以在客户端调用,在服务端执行的方法
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
import { RpcMetadata, DecoratorTarget, Constructor, RpcParameterType, RpcReturnType } from '../types/NetworkTypes';
|
||||
import { getNetworkComponentMetadata } from './NetworkComponent';
|
||||
|
||||
/**
|
||||
* ServerRpc 装饰器选项
|
||||
*/
|
||||
export interface ServerRpcOptions {
|
||||
/** 是否需要权限验证 */
|
||||
requiresAuth?: boolean;
|
||||
/** 是否可靠传输,默认为 true */
|
||||
reliable?: boolean;
|
||||
/** 是否需要响应 */
|
||||
requiresResponse?: boolean;
|
||||
/** 是否需要拥有者权限 */
|
||||
requiresOwnership?: boolean;
|
||||
/** 调用频率限制 (调用/秒) */
|
||||
rateLimit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 ServerRpc 元数据的 Symbol
|
||||
*/
|
||||
export const SERVER_RPC_METADATA_KEY = Symbol('server_rpc_metadata');
|
||||
|
||||
/**
|
||||
* ServerRpc 装饰器
|
||||
*
|
||||
* @param options 装饰器选项
|
||||
* @returns 方法装饰器函数
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @NetworkComponent()
|
||||
* class PlayerController extends Component {
|
||||
* @ServerRpc({ requiresOwnership: true, rateLimit: 10 })
|
||||
* public movePlayer(direction: Vector3): void {
|
||||
* // 在服务端处理玩家移动,需要拥有者权限,限制每秒10次调用
|
||||
* this.transform.position.add(direction);
|
||||
* }
|
||||
*
|
||||
* @ServerRpc({ requiresAuth: true })
|
||||
* public purchaseItem(itemId: string): boolean {
|
||||
* // 购买物品,需要认证
|
||||
* return this.inventory.tryPurchase(itemId);
|
||||
* }
|
||||
*
|
||||
* @ServerRpc({ requiresResponse: true })
|
||||
* public getPlayerStats(): PlayerStats {
|
||||
* // 获取玩家统计数据并返回给客户端
|
||||
* return this.stats.toObject();
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function ServerRpc(options: ServerRpcOptions = {}): MethodDecorator {
|
||||
return function (target: unknown, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
||||
if (typeof propertyKey !== 'string') {
|
||||
throw new Error('ServerRpc can only be applied to string method names');
|
||||
}
|
||||
|
||||
// 获取或创建元数据数组
|
||||
const targetConstructor = (target as { constructor: Constructor }).constructor;
|
||||
let metadata: RpcMetadata[] = Reflect.getMetadata(SERVER_RPC_METADATA_KEY, targetConstructor);
|
||||
if (!metadata) {
|
||||
metadata = [];
|
||||
Reflect.defineMetadata(SERVER_RPC_METADATA_KEY, metadata, targetConstructor);
|
||||
}
|
||||
|
||||
// 创建 RPC 元数据
|
||||
const rpcMetadata: RpcMetadata = {
|
||||
methodName: propertyKey,
|
||||
rpcType: 'server-rpc',
|
||||
requiresAuth: options.requiresAuth || false,
|
||||
reliable: options.reliable !== false,
|
||||
requiresResponse: options.requiresResponse || false
|
||||
};
|
||||
|
||||
metadata.push(rpcMetadata);
|
||||
|
||||
// 更新 NetworkComponent 元数据
|
||||
const componentMetadata = getNetworkComponentMetadata(targetConstructor);
|
||||
if (componentMetadata) {
|
||||
const existingIndex = componentMetadata.rpcs.findIndex(rpc => rpc.methodName === propertyKey);
|
||||
if (existingIndex >= 0) {
|
||||
componentMetadata.rpcs[existingIndex] = rpcMetadata;
|
||||
} else {
|
||||
componentMetadata.rpcs.push(rpcMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存原方法
|
||||
const originalMethod = descriptor.value;
|
||||
if (typeof originalMethod !== 'function') {
|
||||
throw new Error(`ServerRpc can only be applied to methods, got ${typeof originalMethod}`);
|
||||
}
|
||||
|
||||
// 包装方法以添加网络调用逻辑
|
||||
descriptor.value = function (this: any, ...args: any[]) {
|
||||
// 如果在客户端调用,发送到服务端
|
||||
const isClient = this.isClient?.() || (typeof window !== 'undefined');
|
||||
if (isClient) {
|
||||
return this.sendServerRpc?.(propertyKey, args, options, rpcMetadata);
|
||||
}
|
||||
|
||||
// 如果在服务端,直接执行本地方法
|
||||
return originalMethod.apply(this, args);
|
||||
};
|
||||
|
||||
// 保存原方法的引用,供直接调用
|
||||
(descriptor.value as any).__originalMethod = originalMethod;
|
||||
(descriptor.value as any).__rpcMetadata = rpcMetadata;
|
||||
(descriptor.value as any).__rpcOptions = options;
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Command 装饰器 (ServerRpc 的别名,用于兼容性)
|
||||
*/
|
||||
export const Command = ServerRpc;
|
||||
|
||||
/**
|
||||
* 获取类的 ServerRpc 元数据
|
||||
*/
|
||||
export function getServerRpcMetadata(target: Constructor): RpcMetadata[] {
|
||||
return Reflect.getMetadata(SERVER_RPC_METADATA_KEY, target) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查方法是否为 ServerRpc
|
||||
*/
|
||||
export function isServerRpc(target: Constructor, methodName: string): boolean {
|
||||
const metadata = getServerRpcMetadata(target);
|
||||
return metadata.some(m => m.methodName === methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取特定方法的 ServerRpc 元数据
|
||||
*/
|
||||
export function getServerRpcMethodMetadata(target: Constructor, methodName: string): RpcMetadata | null {
|
||||
const metadata = getServerRpcMetadata(target);
|
||||
return metadata.find(m => m.methodName === methodName) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接调用原方法(跳过网络逻辑)
|
||||
*/
|
||||
export function invokeServerRpcLocally(instance: any, methodName: string, args: any[]): any {
|
||||
const method = instance[methodName];
|
||||
if (method && typeof method.__originalMethod === 'function') {
|
||||
return method.__originalMethod.apply(instance, args);
|
||||
}
|
||||
throw new Error(`Method ${methodName} is not a valid ServerRpc or original method not found`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 ServerRpc 是否需要响应
|
||||
*/
|
||||
export function serverRpcRequiresResponse(instance: any, methodName: string): boolean {
|
||||
const method = instance[methodName];
|
||||
return method?.__rpcMetadata?.requiresResponse || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ServerRpc 的选项
|
||||
*/
|
||||
export function getServerRpcOptions(instance: any, methodName: string): ServerRpcOptions | null {
|
||||
const method = instance[methodName];
|
||||
return method?.__rpcOptions || null;
|
||||
}
|
||||
249
packages/network-shared/src/decorators/SyncVar.ts
Normal file
249
packages/network-shared/src/decorators/SyncVar.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* SyncVar 装饰器
|
||||
*
|
||||
* 用于标记需要在网络间自动同步的属性
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
import { SyncVarMetadata, NetworkValue, DecoratorTarget, Constructor } from '../types/NetworkTypes';
|
||||
import { getNetworkComponentMetadata } from './NetworkComponent';
|
||||
|
||||
/**
|
||||
* SyncVar 装饰器选项
|
||||
*/
|
||||
export interface SyncVarOptions {
|
||||
/** 是否仅权威端可修改,默认为 true */
|
||||
authorityOnly?: boolean;
|
||||
/** 变化回调函数名 */
|
||||
onChanged?: string;
|
||||
/** 序列化类型提示 */
|
||||
serializeType?: string;
|
||||
/** 是否使用增量同步 */
|
||||
deltaSync?: boolean;
|
||||
/** 同步优先级,数值越大优先级越高 */
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 SyncVar 元数据的 Symbol
|
||||
*/
|
||||
export const SYNCVAR_METADATA_KEY = Symbol('syncvar_metadata');
|
||||
|
||||
/**
|
||||
* SyncVar 装饰器
|
||||
*
|
||||
* @param options 装饰器选项
|
||||
* @returns 属性装饰器函数
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @NetworkComponent()
|
||||
* class PlayerController extends Component {
|
||||
* @SyncVar({ onChanged: 'onHealthChanged', priority: 10 })
|
||||
* public health: number = 100;
|
||||
*
|
||||
* @SyncVar({ authorityOnly: false })
|
||||
* public playerName: string = '';
|
||||
*
|
||||
* @SyncVar({ deltaSync: true })
|
||||
* public inventory: Item[] = [];
|
||||
*
|
||||
* private onHealthChanged(oldValue: number, newValue: number): void {
|
||||
* console.log(`Health changed from ${oldValue} to ${newValue}`);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function SyncVar<T extends NetworkValue = NetworkValue>(options: SyncVarOptions = {}): PropertyDecorator {
|
||||
return function (target: unknown, propertyKey: string | symbol) {
|
||||
if (typeof propertyKey !== 'string') {
|
||||
throw new Error('SyncVar can only be applied to string property keys');
|
||||
}
|
||||
|
||||
// 获取或创建元数据数组
|
||||
const targetConstructor = (target as { constructor: Constructor }).constructor;
|
||||
let metadata: SyncVarMetadata[] = Reflect.getMetadata(SYNCVAR_METADATA_KEY, targetConstructor);
|
||||
if (!metadata) {
|
||||
metadata = [];
|
||||
Reflect.defineMetadata(SYNCVAR_METADATA_KEY, metadata, targetConstructor);
|
||||
}
|
||||
|
||||
// 创建 SyncVar 元数据
|
||||
const syncVarMetadata: SyncVarMetadata = {
|
||||
propertyName: propertyKey,
|
||||
authorityOnly: options.authorityOnly !== false,
|
||||
onChanged: options.onChanged,
|
||||
serializeType: options.serializeType,
|
||||
deltaSync: options.deltaSync || false,
|
||||
priority: options.priority || 0
|
||||
};
|
||||
|
||||
metadata.push(syncVarMetadata);
|
||||
|
||||
// 更新 NetworkComponent 元数据
|
||||
const componentMetadata = getNetworkComponentMetadata(targetConstructor);
|
||||
if (componentMetadata) {
|
||||
const existingIndex = componentMetadata.syncVars.findIndex(sv => sv.propertyName === propertyKey);
|
||||
if (existingIndex >= 0) {
|
||||
componentMetadata.syncVars[existingIndex] = syncVarMetadata;
|
||||
} else {
|
||||
componentMetadata.syncVars.push(syncVarMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建属性的内部存储和变化跟踪
|
||||
const internalKey = `_${propertyKey}`;
|
||||
const dirtyKey = `_${propertyKey}_dirty`;
|
||||
const previousKey = `_${propertyKey}_previous`;
|
||||
|
||||
// 重新定义属性的 getter 和 setter
|
||||
Object.defineProperty(target, propertyKey, {
|
||||
get: function (this: Record<string, unknown>): T {
|
||||
return this[internalKey] as T;
|
||||
},
|
||||
set: function (this: Record<string, unknown>, newValue: T) {
|
||||
const oldValue = this[internalKey] as T;
|
||||
|
||||
// 检查值是否真的发生了变化
|
||||
if (oldValue === newValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 对于复杂对象,进行深度比较
|
||||
if (typeof newValue === 'object' && newValue !== null &&
|
||||
typeof oldValue === 'object' && oldValue !== null) {
|
||||
if (JSON.stringify(oldValue) === JSON.stringify(newValue)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存旧值用于回调
|
||||
this[previousKey] = oldValue;
|
||||
this[internalKey] = newValue;
|
||||
this[dirtyKey] = true;
|
||||
|
||||
// 调用变化回调
|
||||
if (options.onChanged && typeof (this[options.onChanged] as unknown) === 'function') {
|
||||
(this[options.onChanged] as (oldValue: T, newValue: T) => void)(oldValue, newValue);
|
||||
}
|
||||
|
||||
// 通知网络同步系统
|
||||
(this as { notifySyncVarChanged?: (key: string, oldValue: T, newValue: T, metadata: SyncVarMetadata) => void }).notifySyncVarChanged?.(propertyKey, oldValue, newValue, syncVarMetadata);
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
// 初始化内部属性
|
||||
const targetRecord = target as Record<string, unknown>;
|
||||
if (targetRecord[internalKey] === undefined) {
|
||||
targetRecord[internalKey] = targetRecord[propertyKey];
|
||||
}
|
||||
targetRecord[dirtyKey] = false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的 SyncVar 元数据
|
||||
*/
|
||||
export function getSyncVarMetadata(target: Constructor): SyncVarMetadata[] {
|
||||
return Reflect.getMetadata(SYNCVAR_METADATA_KEY, target) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查属性是否为 SyncVar
|
||||
*/
|
||||
export function isSyncVar(target: Constructor, propertyName: string): boolean {
|
||||
const metadata = getSyncVarMetadata(target);
|
||||
return metadata.some(m => m.propertyName === propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SyncVar 的脏标记
|
||||
*/
|
||||
export function isSyncVarDirty(instance: Record<string, unknown>, propertyName: string): boolean {
|
||||
return (instance[`_${propertyName}_dirty`] as boolean) || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除 SyncVar 的脏标记
|
||||
*/
|
||||
export function clearSyncVarDirty(instance: Record<string, unknown>, propertyName: string): void {
|
||||
instance[`_${propertyName}_dirty`] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SyncVar 的前一个值
|
||||
*/
|
||||
export function getSyncVarPreviousValue<T extends NetworkValue = NetworkValue>(instance: Record<string, unknown>, propertyName: string): T | undefined {
|
||||
return instance[`_${propertyName}_previous`] as T | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制设置 SyncVar 值(跳过权限检查和变化检测)
|
||||
*/
|
||||
export function setSyncVarValue<T extends NetworkValue = NetworkValue>(instance: Record<string, unknown>, propertyName: string, value: T, skipCallback = false): void {
|
||||
const internalKey = `_${propertyName}`;
|
||||
const dirtyKey = `_${propertyName}_dirty`;
|
||||
const previousKey = `_${propertyName}_previous`;
|
||||
|
||||
const oldValue = instance[internalKey] as T;
|
||||
instance[previousKey] = oldValue;
|
||||
instance[internalKey] = value;
|
||||
instance[dirtyKey] = false; // 网络接收的值不标记为脏
|
||||
|
||||
// 可选择性调用回调
|
||||
if (!skipCallback) {
|
||||
const metadata = getSyncVarMetadata((instance as { constructor: Constructor }).constructor);
|
||||
const syncVarMeta = metadata.find(m => m.propertyName === propertyName);
|
||||
|
||||
if (syncVarMeta?.onChanged && typeof (instance[syncVarMeta.onChanged] as unknown) === 'function') {
|
||||
(instance[syncVarMeta.onChanged] as (oldValue: T, newValue: T) => void)(oldValue, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取所有脏的 SyncVar
|
||||
*/
|
||||
export function getDirtySyncVars(instance: Record<string, unknown>): Array<{
|
||||
propertyName: string;
|
||||
oldValue: NetworkValue;
|
||||
newValue: NetworkValue;
|
||||
metadata: SyncVarMetadata;
|
||||
}> {
|
||||
const metadata = getSyncVarMetadata((instance as { constructor: Constructor }).constructor);
|
||||
const dirtyVars: Array<{
|
||||
propertyName: string;
|
||||
oldValue: NetworkValue;
|
||||
newValue: NetworkValue;
|
||||
metadata: SyncVarMetadata;
|
||||
}> = [];
|
||||
|
||||
for (const syncVar of metadata) {
|
||||
if (isSyncVarDirty(instance, syncVar.propertyName)) {
|
||||
const oldValue = getSyncVarPreviousValue(instance, syncVar.propertyName);
|
||||
const newValue = instance[`_${syncVar.propertyName}`] as NetworkValue;
|
||||
|
||||
dirtyVars.push({
|
||||
propertyName: syncVar.propertyName,
|
||||
oldValue: oldValue ?? newValue, // 使用空合并运算符处理undefined
|
||||
newValue: newValue,
|
||||
metadata: syncVar
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 按优先级排序,优先级高的先处理
|
||||
return dirtyVars.sort((a, b) => (b.metadata.priority || 0) - (a.metadata.priority || 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量清除所有脏标记
|
||||
*/
|
||||
export function clearAllDirtySyncVars(instance: Record<string, unknown>): void {
|
||||
const metadata = getSyncVarMetadata((instance as { constructor: Constructor }).constructor);
|
||||
for (const syncVar of metadata) {
|
||||
clearSyncVarDirty(instance, syncVar.propertyName);
|
||||
}
|
||||
}
|
||||
8
packages/network-shared/src/decorators/index.ts
Normal file
8
packages/network-shared/src/decorators/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 装饰器导出
|
||||
*/
|
||||
|
||||
export * from './NetworkComponent';
|
||||
export * from './SyncVar';
|
||||
export * from './ClientRpc';
|
||||
export * from './ServerRpc';
|
||||
43
packages/network-shared/src/index.ts
Normal file
43
packages/network-shared/src/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* ECS Framework Network Shared
|
||||
*
|
||||
* 共享的网络组件、装饰器和类型定义
|
||||
*/
|
||||
|
||||
// 确保 reflect-metadata 被导入
|
||||
import 'reflect-metadata';
|
||||
|
||||
// 类型定义
|
||||
export * from './types';
|
||||
|
||||
// 装饰器
|
||||
export * from './decorators';
|
||||
|
||||
// 核心类
|
||||
export * from './core';
|
||||
|
||||
// 序列化工具
|
||||
export * from './serialization';
|
||||
|
||||
// 协议编译器
|
||||
export * from './protocol';
|
||||
|
||||
// 工具函数
|
||||
export * from './utils';
|
||||
|
||||
// 版本信息
|
||||
export const VERSION = '1.0.0';
|
||||
|
||||
// 默认配置
|
||||
export const DEFAULT_NETWORK_CONFIG = {
|
||||
port: 7777,
|
||||
host: 'localhost',
|
||||
maxConnections: 100,
|
||||
syncRate: 20,
|
||||
snapshotRate: 5,
|
||||
compression: true,
|
||||
encryption: false,
|
||||
timeout: 30000,
|
||||
maxReconnectAttempts: 3,
|
||||
reconnectInterval: 5000
|
||||
};
|
||||
@@ -0,0 +1,663 @@
|
||||
/**
|
||||
* TypeScript 协议分析器
|
||||
*
|
||||
* 负责解析 TypeScript 代码中的网络组件装饰器,
|
||||
* 提取类型信息并构建协议定义
|
||||
*/
|
||||
|
||||
// TypeScript编译器API - 开发时依赖
|
||||
declare const require: any;
|
||||
|
||||
let ts: any;
|
||||
let path: any;
|
||||
let fs: any;
|
||||
|
||||
try {
|
||||
ts = require('typescript');
|
||||
path = require('path');
|
||||
fs = require('fs');
|
||||
} catch (e) {
|
||||
// 在运行时如果没有这些依赖,使用占位符
|
||||
ts = {
|
||||
ScriptTarget: { ES2020: 99 },
|
||||
ModuleKind: { ES2020: 99 },
|
||||
createProgram: () => ({ getSourceFiles: () => [] }),
|
||||
isClassDeclaration: () => false,
|
||||
isDecorator: () => false,
|
||||
isIdentifier: () => false,
|
||||
isCallExpression: () => false,
|
||||
forEachChild: () => {}
|
||||
};
|
||||
path = { join: (...args: string[]) => args.join('/') };
|
||||
fs = { existsSync: () => false, readFileSync: () => '{}' };
|
||||
}
|
||||
|
||||
import {
|
||||
ComponentProtocol,
|
||||
ProtocolField,
|
||||
ProtocolRpc,
|
||||
RpcParameter,
|
||||
SerializeType,
|
||||
ProtocolAnalysisResult,
|
||||
ProtocolError,
|
||||
ProtocolWarning,
|
||||
ProtocolCompilerConfig
|
||||
} from '../types/ProtocolTypes';
|
||||
|
||||
/**
|
||||
* TypeScript 协议分析器
|
||||
*/
|
||||
export class TypeScriptAnalyzer {
|
||||
private program: ts.Program;
|
||||
private typeChecker: ts.TypeChecker;
|
||||
private config: ProtocolCompilerConfig;
|
||||
|
||||
private components: ComponentProtocol[] = [];
|
||||
private errors: ProtocolError[] = [];
|
||||
private warnings: ProtocolWarning[] = [];
|
||||
private dependencies: Map<string, string[]> = new Map();
|
||||
|
||||
constructor(config: ProtocolCompilerConfig) {
|
||||
this.config = config;
|
||||
this.initializeTypeScript();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 TypeScript 编译器
|
||||
*/
|
||||
private initializeTypeScript(): void {
|
||||
if (!ts || !path || !fs) {
|
||||
throw new Error('TypeScript analyzer requires typescript, path, and fs modules');
|
||||
}
|
||||
|
||||
const configPath = this.config.tsconfigPath || path.join(this.config.inputDir, 'tsconfig.json');
|
||||
|
||||
let compilerOptions: ts.CompilerOptions = {
|
||||
target: ts.ScriptTarget.ES2020,
|
||||
module: ts.ModuleKind.ES2020,
|
||||
lib: ['ES2020'],
|
||||
experimentalDecorators: true,
|
||||
emitDecoratorMetadata: true,
|
||||
strict: true
|
||||
};
|
||||
|
||||
// 加载 tsconfig.json
|
||||
if (fs.existsSync(configPath)) {
|
||||
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
||||
if (configFile.error) {
|
||||
this.addError('syntax', `Failed to read tsconfig.json: ${configFile.error.messageText}`);
|
||||
} else {
|
||||
const parsedConfig = ts.parseJsonConfigFileContent(
|
||||
configFile.config,
|
||||
ts.sys,
|
||||
path.dirname(configPath)
|
||||
);
|
||||
compilerOptions = { ...compilerOptions, ...parsedConfig.options };
|
||||
}
|
||||
}
|
||||
|
||||
// 收集所有 TypeScript 文件
|
||||
const files = this.collectTypeScriptFiles(this.config.inputDir);
|
||||
|
||||
this.program = ts.createProgram(files, compilerOptions);
|
||||
this.typeChecker = this.program.getTypeChecker();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集 TypeScript 文件
|
||||
*/
|
||||
private collectTypeScriptFiles(dir: string): string[] {
|
||||
const files: string[] = [];
|
||||
const excludePatterns = this.config.excludePatterns || ['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**'];
|
||||
|
||||
function collectFiles(currentDir: string): void {
|
||||
const items = fs.readdirSync(currentDir);
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(currentDir, item);
|
||||
const stat = fs.statSync(fullPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
// 检查是否应该排除此目录
|
||||
const shouldExclude = excludePatterns.some(pattern =>
|
||||
fullPath.includes(pattern.replace('**/', '').replace('/**', ''))
|
||||
);
|
||||
|
||||
if (!shouldExclude) {
|
||||
collectFiles(fullPath);
|
||||
}
|
||||
} else if (item.endsWith('.ts') || item.endsWith('.tsx')) {
|
||||
// 检查是否应该排除此文件
|
||||
const shouldExclude = excludePatterns.some(pattern => {
|
||||
if (pattern.includes('**')) {
|
||||
const regex = new RegExp(pattern.replace('**/', '.*').replace('*', '.*'));
|
||||
return regex.test(fullPath);
|
||||
}
|
||||
return fullPath.endsWith(pattern.replace('*', ''));
|
||||
});
|
||||
|
||||
if (!shouldExclude) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectFiles(dir);
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析网络协议
|
||||
*/
|
||||
public analyze(): ProtocolAnalysisResult {
|
||||
this.components = [];
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
this.dependencies.clear();
|
||||
|
||||
const sourceFiles = this.program.getSourceFiles().filter(sf =>
|
||||
!sf.isDeclarationFile && sf.fileName.includes(this.config.inputDir)
|
||||
);
|
||||
|
||||
// 分析每个源文件
|
||||
for (const sourceFile of sourceFiles) {
|
||||
this.analyzeSourceFile(sourceFile);
|
||||
}
|
||||
|
||||
// 检查依赖关系
|
||||
this.validateDependencies();
|
||||
|
||||
return {
|
||||
files: sourceFiles.map(sf => sf.fileName),
|
||||
components: this.components,
|
||||
dependencies: this.dependencies,
|
||||
errors: this.errors,
|
||||
warnings: this.warnings
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析单个源文件
|
||||
*/
|
||||
private analyzeSourceFile(sourceFile: any): void {
|
||||
const visit = (node: any): void => {
|
||||
if (ts.isClassDeclaration(node) && this.isNetworkComponent(node)) {
|
||||
this.analyzeNetworkComponent(node, sourceFile);
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
visit(sourceFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为网络组件
|
||||
*/
|
||||
private isNetworkComponent(node: any): boolean {
|
||||
if (!node.modifiers) return false;
|
||||
|
||||
return node.modifiers.some((modifier: any) => {
|
||||
if (ts.isDecorator(modifier)) {
|
||||
const expression = modifier.expression;
|
||||
if (ts.isCallExpression(expression) || ts.isIdentifier(expression)) {
|
||||
const decoratorName = this.getDecoratorName(expression);
|
||||
return decoratorName === 'NetworkComponent';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取装饰器名称
|
||||
*/
|
||||
private getDecoratorName(expression: ts.Expression): string | null {
|
||||
if (ts.isIdentifier(expression)) {
|
||||
return expression.text;
|
||||
}
|
||||
if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {
|
||||
return expression.expression.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析网络组件
|
||||
*/
|
||||
private analyzeNetworkComponent(node: any, sourceFile: any): void {
|
||||
const className = node.name?.text;
|
||||
if (!className) {
|
||||
this.addError('syntax', 'NetworkComponent class must have a name', sourceFile, node);
|
||||
return;
|
||||
}
|
||||
|
||||
const componentProtocol: ComponentProtocol = {
|
||||
typeName: className,
|
||||
version: 1,
|
||||
syncVars: [],
|
||||
rpcs: [],
|
||||
batchEnabled: false,
|
||||
deltaEnabled: false
|
||||
};
|
||||
|
||||
// 分析类成员
|
||||
for (const member of node.members) {
|
||||
if (ts.isPropertyDeclaration(member)) {
|
||||
const syncVar = this.analyzeSyncVar(member, sourceFile);
|
||||
if (syncVar) {
|
||||
componentProtocol.syncVars.push(syncVar);
|
||||
}
|
||||
} else if (ts.isMethodDeclaration(member)) {
|
||||
const rpc = this.analyzeRpc(member, sourceFile);
|
||||
if (rpc) {
|
||||
componentProtocol.rpcs.push(rpc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分析装饰器选项
|
||||
this.analyzeComponentDecorator(node, componentProtocol, sourceFile);
|
||||
|
||||
this.components.push(componentProtocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析 SyncVar 属性
|
||||
*/
|
||||
private analyzeSyncVar(node: ts.PropertyDeclaration, sourceFile: ts.SourceFile): ProtocolField | null {
|
||||
if (!this.hasSyncVarDecorator(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const propertyName = this.getPropertyName(node);
|
||||
if (!propertyName) {
|
||||
this.addError('syntax', 'SyncVar property must have a name', sourceFile, node);
|
||||
return null;
|
||||
}
|
||||
|
||||
const type = this.typeChecker.getTypeAtLocation(node);
|
||||
const serializeType = this.inferSerializeType(type, node, sourceFile);
|
||||
|
||||
if (!serializeType) {
|
||||
this.addError('type', `Cannot infer serialize type for property: ${propertyName}`, sourceFile, node);
|
||||
return null;
|
||||
}
|
||||
|
||||
const field: ProtocolField = {
|
||||
name: propertyName,
|
||||
type: serializeType,
|
||||
id: this.generateFieldId(propertyName),
|
||||
optional: this.isOptionalProperty(node),
|
||||
repeated: this.isArrayType(type)
|
||||
};
|
||||
|
||||
// 分析装饰器选项
|
||||
this.analyzeSyncVarDecorator(node, field, sourceFile);
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析 RPC 方法
|
||||
*/
|
||||
private analyzeRpc(node: ts.MethodDeclaration, sourceFile: ts.SourceFile): ProtocolRpc | null {
|
||||
const rpcType = this.getRpcType(node);
|
||||
if (!rpcType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const methodName = this.getMethodName(node);
|
||||
if (!methodName) {
|
||||
this.addError('syntax', 'RPC method must have a name', sourceFile, node);
|
||||
return null;
|
||||
}
|
||||
|
||||
const parameters: RpcParameter[] = [];
|
||||
|
||||
// 分析参数
|
||||
if (node.parameters) {
|
||||
for (const param of node.parameters) {
|
||||
const paramName = param.name.getText();
|
||||
const paramType = this.typeChecker.getTypeAtLocation(param);
|
||||
const serializeType = this.inferSerializeType(paramType, param, sourceFile);
|
||||
|
||||
if (serializeType === null) {
|
||||
this.addError('type', `Cannot infer type for parameter: ${paramName}`, sourceFile, param);
|
||||
continue;
|
||||
}
|
||||
|
||||
parameters.push({
|
||||
name: paramName,
|
||||
type: serializeType,
|
||||
optional: param.questionToken !== undefined,
|
||||
isArray: this.isArrayType(paramType)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 分析返回类型
|
||||
let returnType: SerializeType | undefined;
|
||||
if (node.type && !this.isVoidType(node.type)) {
|
||||
const returnTypeNode = this.typeChecker.getTypeAtLocation(node.type);
|
||||
returnType = this.inferSerializeType(returnTypeNode, node.type, sourceFile);
|
||||
}
|
||||
|
||||
const rpc: ProtocolRpc = {
|
||||
name: methodName,
|
||||
id: this.generateRpcId(methodName),
|
||||
type: rpcType,
|
||||
parameters,
|
||||
returnType
|
||||
};
|
||||
|
||||
// 分析装饰器选项
|
||||
this.analyzeRpcDecorator(node, rpc, sourceFile);
|
||||
|
||||
return rpc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有 SyncVar 装饰器
|
||||
*/
|
||||
private hasSyncVarDecorator(node: ts.PropertyDeclaration): boolean {
|
||||
return this.hasDecorator(node, 'SyncVar');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 RPC 类型
|
||||
*/
|
||||
private getRpcType(node: ts.MethodDeclaration): 'client-rpc' | 'server-rpc' | null {
|
||||
if (this.hasDecorator(node, 'ClientRpc')) {
|
||||
return 'client-rpc';
|
||||
}
|
||||
if (this.hasDecorator(node, 'ServerRpc') || this.hasDecorator(node, 'Command')) {
|
||||
return 'server-rpc';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有特定装饰器
|
||||
*/
|
||||
private hasDecorator(node: ts.Node, decoratorName: string): boolean {
|
||||
if (!ts.canHaveModifiers(node) || !ts.getModifiers(node)) return false;
|
||||
|
||||
const modifiers = ts.getModifiers(node)!;
|
||||
return modifiers.some(modifier => {
|
||||
if (ts.isDecorator(modifier)) {
|
||||
const name = this.getDecoratorName(modifier.expression);
|
||||
return name === decoratorName;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 推导序列化类型
|
||||
*/
|
||||
private inferSerializeType(type: ts.Type, node: ts.Node, sourceFile: ts.SourceFile): SerializeType | null {
|
||||
const typeString = this.typeChecker.typeToString(type);
|
||||
|
||||
// 自定义类型映射
|
||||
if (this.config.typeMapping?.has(typeString)) {
|
||||
return this.config.typeMapping.get(typeString)!;
|
||||
}
|
||||
|
||||
// 基础类型推导
|
||||
if (type.flags & ts.TypeFlags.Boolean) return SerializeType.BOOLEAN;
|
||||
if (type.flags & ts.TypeFlags.Number) return SerializeType.FLOAT64;
|
||||
if (type.flags & ts.TypeFlags.String) return SerializeType.STRING;
|
||||
|
||||
// 对象类型推导
|
||||
if (type.flags & ts.TypeFlags.Object) {
|
||||
// 检查是否为数组
|
||||
if (this.typeChecker.isArrayType(type)) {
|
||||
return SerializeType.ARRAY;
|
||||
}
|
||||
|
||||
// 检查常见游戏类型
|
||||
if (typeString.includes('Vector2')) return SerializeType.VECTOR2;
|
||||
if (typeString.includes('Vector3')) return SerializeType.VECTOR3;
|
||||
if (typeString.includes('Quaternion')) return SerializeType.QUATERNION;
|
||||
if (typeString.includes('Color')) return SerializeType.COLOR;
|
||||
|
||||
// 默认为对象类型
|
||||
return SerializeType.OBJECT;
|
||||
}
|
||||
|
||||
this.addWarning('performance', `Unknown type: ${typeString}, falling back to JSON`, sourceFile, node);
|
||||
return SerializeType.JSON;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性名
|
||||
*/
|
||||
private getPropertyName(node: ts.PropertyDeclaration): string | null {
|
||||
if (ts.isIdentifier(node.name)) {
|
||||
return node.name.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法名
|
||||
*/
|
||||
private getMethodName(node: ts.MethodDeclaration): string | null {
|
||||
if (ts.isIdentifier(node.name)) {
|
||||
return node.name.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为可选属性
|
||||
*/
|
||||
private isOptionalProperty(node: ts.PropertyDeclaration): boolean {
|
||||
return node.questionToken !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为数组类型
|
||||
*/
|
||||
private isArrayType(type: ts.Type): boolean {
|
||||
return this.typeChecker.isArrayType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为 void 类型
|
||||
*/
|
||||
private isVoidType(node: ts.TypeNode): boolean {
|
||||
return ts.isTypeReferenceNode(node) && node.typeName.getText() === 'void';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成字段 ID
|
||||
*/
|
||||
private generateFieldId(fieldName: string): number {
|
||||
// 简单的哈希函数生成字段 ID
|
||||
let hash = 0;
|
||||
for (let i = 0; i < fieldName.length; i++) {
|
||||
const char = fieldName.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // 转换为 32位整数
|
||||
}
|
||||
return Math.abs(hash) % 10000 + 1; // 确保 ID 为正数且在合理范围内
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 RPC ID
|
||||
*/
|
||||
private generateRpcId(rpcName: string): number {
|
||||
return this.generateFieldId(rpcName) + 10000; // RPC ID 从 10000 开始
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析组件装饰器选项
|
||||
*/
|
||||
private analyzeComponentDecorator(
|
||||
node: ts.ClassDeclaration,
|
||||
protocol: ComponentProtocol,
|
||||
sourceFile: ts.SourceFile
|
||||
): void {
|
||||
const decorator = this.findDecorator(node, 'NetworkComponent');
|
||||
if (decorator && ts.isCallExpression(decorator.expression)) {
|
||||
const args = decorator.expression.arguments;
|
||||
if (args.length > 0 && ts.isObjectLiteralExpression(args[0])) {
|
||||
const options = args[0];
|
||||
|
||||
for (const prop of options.properties) {
|
||||
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
||||
const propName = prop.name.text;
|
||||
|
||||
if (propName === 'batchEnabled' && this.isBooleanLiteral(prop.initializer)) {
|
||||
protocol.batchEnabled = (prop.initializer as ts.BooleanLiteral).token === ts.SyntaxKind.TrueKeyword;
|
||||
}
|
||||
|
||||
if (propName === 'deltaEnabled' && this.isBooleanLiteral(prop.initializer)) {
|
||||
protocol.deltaEnabled = (prop.initializer as ts.BooleanLiteral).token === ts.SyntaxKind.TrueKeyword;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析 SyncVar 装饰器选项
|
||||
*/
|
||||
private analyzeSyncVarDecorator(
|
||||
node: ts.PropertyDeclaration,
|
||||
field: ProtocolField,
|
||||
sourceFile: ts.SourceFile
|
||||
): void {
|
||||
const decorator = this.findDecorator(node, 'SyncVar');
|
||||
if (decorator && ts.isCallExpression(decorator.expression)) {
|
||||
const args = decorator.expression.arguments;
|
||||
if (args.length > 0 && ts.isObjectLiteralExpression(args[0])) {
|
||||
const options = args[0];
|
||||
|
||||
for (const prop of options.properties) {
|
||||
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
||||
const propName = prop.name.text;
|
||||
|
||||
if (propName === 'serialize' && ts.isStringLiteral(prop.initializer)) {
|
||||
const serializeType = prop.initializer.text as SerializeType;
|
||||
if (Object.values(SerializeType).includes(serializeType)) {
|
||||
field.type = serializeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析 RPC 装饰器选项
|
||||
*/
|
||||
private analyzeRpcDecorator(
|
||||
node: ts.MethodDeclaration,
|
||||
rpc: ProtocolRpc,
|
||||
sourceFile: ts.SourceFile
|
||||
): void {
|
||||
const decoratorName = rpc.type === 'client-rpc' ? 'ClientRpc' : 'ServerRpc';
|
||||
const decorator = this.findDecorator(node, decoratorName) || this.findDecorator(node, 'Command');
|
||||
|
||||
if (decorator && ts.isCallExpression(decorator.expression)) {
|
||||
const args = decorator.expression.arguments;
|
||||
if (args.length > 0 && ts.isObjectLiteralExpression(args[0])) {
|
||||
const options = args[0];
|
||||
|
||||
for (const prop of options.properties) {
|
||||
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
||||
const propName = prop.name.text;
|
||||
|
||||
if (propName === 'requiresAuth' && this.isBooleanLiteral(prop.initializer)) {
|
||||
rpc.requiresAuth = (prop.initializer as ts.BooleanLiteral).token === ts.SyntaxKind.TrueKeyword;
|
||||
}
|
||||
|
||||
if (propName === 'reliable' && this.isBooleanLiteral(prop.initializer)) {
|
||||
rpc.reliable = (prop.initializer as ts.BooleanLiteral).token === ts.SyntaxKind.TrueKeyword;
|
||||
}
|
||||
|
||||
if (propName === 'rateLimit' && ts.isNumericLiteral(prop.initializer)) {
|
||||
rpc.rateLimit = parseInt(prop.initializer.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找装饰器
|
||||
*/
|
||||
private findDecorator(node: ts.Node, decoratorName: string): ts.Decorator | null {
|
||||
if (!ts.canHaveModifiers(node) || !ts.getModifiers(node)) return null;
|
||||
|
||||
const modifiers = ts.getModifiers(node)!;
|
||||
for (const modifier of modifiers) {
|
||||
if (ts.isDecorator(modifier)) {
|
||||
const name = this.getDecoratorName(modifier.expression);
|
||||
if (name === decoratorName) {
|
||||
return modifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为布尔字面量
|
||||
*/
|
||||
private isBooleanLiteral(node: ts.Node): node is ts.BooleanLiteral {
|
||||
return node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证依赖关系
|
||||
*/
|
||||
private validateDependencies(): void {
|
||||
// 检查循环依赖等问题
|
||||
// 这里可以添加更复杂的依赖分析逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加错误
|
||||
*/
|
||||
private addError(
|
||||
type: ProtocolError['type'],
|
||||
message: string,
|
||||
sourceFile?: ts.SourceFile,
|
||||
node?: ts.Node
|
||||
): void {
|
||||
const error: ProtocolError = {
|
||||
type,
|
||||
message,
|
||||
file: sourceFile?.fileName,
|
||||
line: node ? ts.getLineAndCharacterOfPosition(sourceFile!, node.getStart()).line + 1 : undefined,
|
||||
column: node ? ts.getLineAndCharacterOfPosition(sourceFile!, node.getStart()).character + 1 : undefined
|
||||
};
|
||||
this.errors.push(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加警告
|
||||
*/
|
||||
private addWarning(
|
||||
type: ProtocolWarning['type'],
|
||||
message: string,
|
||||
sourceFile?: ts.SourceFile,
|
||||
node?: ts.Node
|
||||
): void {
|
||||
const warning: ProtocolWarning = {
|
||||
type,
|
||||
message,
|
||||
file: sourceFile?.fileName,
|
||||
line: node ? ts.getLineAndCharacterOfPosition(sourceFile!, node.getStart()).line + 1 : undefined,
|
||||
column: node ? ts.getLineAndCharacterOfPosition(sourceFile!, node.getStart()).character + 1 : undefined
|
||||
};
|
||||
this.warnings.push(warning);
|
||||
}
|
||||
}
|
||||
5
packages/network-shared/src/protocol/analyzer/index.ts
Normal file
5
packages/network-shared/src/protocol/analyzer/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 协议分析器导出
|
||||
*/
|
||||
|
||||
export * from './TypeScriptAnalyzer';
|
||||
@@ -0,0 +1,576 @@
|
||||
/**
|
||||
* 协议推导引擎
|
||||
*
|
||||
* 负责从分析结果推导出最优的序列化协议,
|
||||
* 包括类型优化、字段重排序、兼容性检查等
|
||||
*/
|
||||
|
||||
import {
|
||||
ComponentProtocol,
|
||||
ProtocolField,
|
||||
ProtocolRpc,
|
||||
SerializeType,
|
||||
ProtocolSchema,
|
||||
ProtocolError,
|
||||
ProtocolWarning
|
||||
} from '../types/ProtocolTypes';
|
||||
|
||||
/**
|
||||
* 优化选项
|
||||
*/
|
||||
export interface InferenceOptions {
|
||||
/** 是否启用字段重排序优化 */
|
||||
enableFieldReordering?: boolean;
|
||||
/** 是否启用类型提升优化 */
|
||||
enableTypePromotion?: boolean;
|
||||
/** 是否启用批量处理优化 */
|
||||
enableBatchOptimization?: boolean;
|
||||
/** 是否启用向后兼容检查 */
|
||||
enableCompatibilityCheck?: boolean;
|
||||
/** 最大字段数量限制 */
|
||||
maxFieldCount?: number;
|
||||
/** 最大 RPC 数量限制 */
|
||||
maxRpcCount?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 协议推导引擎
|
||||
*/
|
||||
export class ProtocolInferenceEngine {
|
||||
private options: Required<InferenceOptions>;
|
||||
private errors: ProtocolError[] = [];
|
||||
private warnings: ProtocolWarning[] = [];
|
||||
|
||||
constructor(options: InferenceOptions = {}) {
|
||||
this.options = {
|
||||
enableFieldReordering: true,
|
||||
enableTypePromotion: true,
|
||||
enableBatchOptimization: true,
|
||||
enableCompatibilityCheck: true,
|
||||
maxFieldCount: 100,
|
||||
maxRpcCount: 50,
|
||||
...options
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 推导协议模式
|
||||
*/
|
||||
public inferSchema(components: ComponentProtocol[], version: string = '1.0.0'): ProtocolSchema {
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
|
||||
const optimizedComponents = new Map<string, ComponentProtocol>();
|
||||
const globalTypes = new Map<string, ProtocolField[]>();
|
||||
|
||||
// 第一遍:基础优化和验证
|
||||
for (const component of components) {
|
||||
const optimized = this.optimizeComponent(component);
|
||||
if (optimized) {
|
||||
optimizedComponents.set(component.typeName, optimized);
|
||||
}
|
||||
}
|
||||
|
||||
// 第二遍:跨组件优化
|
||||
this.performCrossComponentOptimizations(optimizedComponents);
|
||||
|
||||
// 提取全局类型
|
||||
this.extractGlobalTypes(optimizedComponents, globalTypes);
|
||||
|
||||
const schema: ProtocolSchema = {
|
||||
version,
|
||||
components: optimizedComponents,
|
||||
types: globalTypes,
|
||||
compatibility: {
|
||||
minVersion: version,
|
||||
maxVersion: version
|
||||
}
|
||||
};
|
||||
|
||||
// 最终验证
|
||||
this.validateSchema(schema);
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化单个组件
|
||||
*/
|
||||
private optimizeComponent(component: ComponentProtocol): ComponentProtocol | null {
|
||||
// 验证组件
|
||||
if (!this.validateComponent(component)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const optimized: ComponentProtocol = {
|
||||
...component,
|
||||
syncVars: [...component.syncVars],
|
||||
rpcs: [...component.rpcs]
|
||||
};
|
||||
|
||||
// 优化 SyncVar 字段
|
||||
if (this.options.enableFieldReordering) {
|
||||
optimized.syncVars = this.optimizeFieldOrdering(optimized.syncVars);
|
||||
}
|
||||
|
||||
if (this.options.enableTypePromotion) {
|
||||
optimized.syncVars = this.optimizeFieldTypes(optimized.syncVars);
|
||||
}
|
||||
|
||||
// 优化 RPC 方法
|
||||
optimized.rpcs = this.optimizeRpcs(optimized.rpcs);
|
||||
|
||||
// 启用批量处理优化
|
||||
if (this.options.enableBatchOptimization) {
|
||||
this.inferBatchOptimization(optimized);
|
||||
}
|
||||
|
||||
return optimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证组件
|
||||
*/
|
||||
private validateComponent(component: ComponentProtocol): boolean {
|
||||
let isValid = true;
|
||||
|
||||
// 检查字段数量限制
|
||||
if (component.syncVars.length > this.options.maxFieldCount) {
|
||||
this.addError('semantic', `Component ${component.typeName} has too many SyncVars (${component.syncVars.length}/${this.options.maxFieldCount})`);
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// 检查 RPC 数量限制
|
||||
if (component.rpcs.length > this.options.maxRpcCount) {
|
||||
this.addError('semantic', `Component ${component.typeName} has too many RPCs (${component.rpcs.length}/${this.options.maxRpcCount})`);
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// 检查字段名冲突
|
||||
const fieldNames = new Set<string>();
|
||||
for (const field of component.syncVars) {
|
||||
if (fieldNames.has(field.name)) {
|
||||
this.addError('semantic', `Duplicate SyncVar name: ${field.name} in ${component.typeName}`);
|
||||
isValid = false;
|
||||
}
|
||||
fieldNames.add(field.name);
|
||||
}
|
||||
|
||||
// 检查 RPC 名冲突
|
||||
const rpcNames = new Set<string>();
|
||||
for (const rpc of component.rpcs) {
|
||||
if (rpcNames.has(rpc.name)) {
|
||||
this.addError('semantic', `Duplicate RPC name: ${rpc.name} in ${component.typeName}`);
|
||||
isValid = false;
|
||||
}
|
||||
rpcNames.add(rpc.name);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化字段顺序
|
||||
* 将频繁变化的字段和固定大小的字段排在前面,以提高序列化效率
|
||||
*/
|
||||
private optimizeFieldOrdering(fields: ProtocolField[]): ProtocolField[] {
|
||||
const optimized = [...fields];
|
||||
|
||||
// 按照优化策略排序
|
||||
optimized.sort((a, b) => {
|
||||
// 优先级高的在前
|
||||
const priorityA = this.getFieldPriority(a);
|
||||
const priorityB = this.getFieldPriority(b);
|
||||
|
||||
if (priorityA !== priorityB) {
|
||||
return priorityB - priorityA; // 优先级高的在前
|
||||
}
|
||||
|
||||
// 固定大小类型在前
|
||||
const fixedA = this.isFixedSizeType(a.type) ? 1 : 0;
|
||||
const fixedB = this.isFixedSizeType(b.type) ? 1 : 0;
|
||||
|
||||
if (fixedA !== fixedB) {
|
||||
return fixedB - fixedA;
|
||||
}
|
||||
|
||||
// 按类型大小排序,小的在前
|
||||
const sizeA = this.getTypeSize(a.type);
|
||||
const sizeB = this.getTypeSize(b.type);
|
||||
|
||||
return sizeA - sizeB;
|
||||
});
|
||||
|
||||
// 重新分配字段 ID(保持顺序)
|
||||
optimized.forEach((field, index) => {
|
||||
field.id = index + 1;
|
||||
});
|
||||
|
||||
return optimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化字段类型
|
||||
* 将通用类型提升为更高效的序列化类型
|
||||
*/
|
||||
private optimizeFieldTypes(fields: ProtocolField[]): ProtocolField[] {
|
||||
return fields.map(field => {
|
||||
const optimized = { ...field };
|
||||
|
||||
// 类型提升规则
|
||||
switch (field.type) {
|
||||
case SerializeType.FLOAT64:
|
||||
// 检查是否可以使用 float32
|
||||
if (this.canUseFloat32(field)) {
|
||||
optimized.type = SerializeType.FLOAT32;
|
||||
this.addWarning('performance', `Promoted field ${field.name} from float64 to float32`);
|
||||
}
|
||||
break;
|
||||
|
||||
case SerializeType.INT64:
|
||||
// 检查是否可以使用 int32
|
||||
if (this.canUseInt32(field)) {
|
||||
optimized.type = SerializeType.INT32;
|
||||
this.addWarning('performance', `Promoted field ${field.name} from int64 to int32`);
|
||||
}
|
||||
break;
|
||||
|
||||
case SerializeType.JSON:
|
||||
// 检查是否可以使用更高效的类型
|
||||
const betterType = this.inferBetterType(field);
|
||||
if (betterType && betterType !== SerializeType.JSON) {
|
||||
optimized.type = betterType;
|
||||
this.addWarning('performance', `Promoted field ${field.name} from JSON to ${betterType}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return optimized;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化 RPC 方法
|
||||
*/
|
||||
private optimizeRpcs(rpcs: ProtocolRpc[]): ProtocolRpc[] {
|
||||
return rpcs.map(rpc => {
|
||||
const optimized = { ...rpc };
|
||||
|
||||
// 优化参数类型
|
||||
optimized.parameters = rpc.parameters.map(param => ({
|
||||
...param,
|
||||
type: this.optimizeParameterType(param.type)
|
||||
}));
|
||||
|
||||
// 设置默认选项
|
||||
if (optimized.reliable === undefined) {
|
||||
optimized.reliable = rpc.type === 'server-rpc'; // 服务端 RPC 默认可靠
|
||||
}
|
||||
|
||||
return optimized;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 推导批量处理优化
|
||||
*/
|
||||
private inferBatchOptimization(component: ComponentProtocol): void {
|
||||
// 检查是否适合批量处理
|
||||
const hasManyInstances = this.estimateInstanceCount(component) > 10;
|
||||
const hasSimpleTypes = component.syncVars.every(field =>
|
||||
this.isSimpleType(field.type) && !field.repeated
|
||||
);
|
||||
|
||||
if (hasManyInstances && hasSimpleTypes) {
|
||||
component.batchEnabled = true;
|
||||
this.addWarning('performance', `Enabled batch optimization for ${component.typeName}`);
|
||||
}
|
||||
|
||||
// 检查是否适合增量同步
|
||||
const hasLargeData = component.syncVars.some(field =>
|
||||
this.isLargeDataType(field.type) || field.repeated
|
||||
);
|
||||
|
||||
if (hasLargeData) {
|
||||
component.deltaEnabled = true;
|
||||
this.addWarning('performance', `Enabled delta synchronization for ${component.typeName}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨组件优化
|
||||
*/
|
||||
private performCrossComponentOptimizations(components: Map<string, ComponentProtocol>): void {
|
||||
// 检查重复字段模式,提取为全局类型
|
||||
const fieldPatterns = this.findCommonFieldPatterns(Array.from(components.values()));
|
||||
|
||||
for (const [pattern, count] of fieldPatterns) {
|
||||
if (count >= 3) { // 如果有3个或更多组件使用相同模式
|
||||
this.addWarning('style', `Common field pattern found: ${pattern} (used ${count} times). Consider extracting to a shared type.`);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 ID 冲突
|
||||
this.validateIdUniqueness(Array.from(components.values()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取全局类型
|
||||
*/
|
||||
private extractGlobalTypes(
|
||||
components: Map<string, ComponentProtocol>,
|
||||
globalTypes: Map<string, ProtocolField[]>
|
||||
): void {
|
||||
// 预定义常用游戏类型
|
||||
globalTypes.set('Vector2', [
|
||||
{ name: 'x', type: SerializeType.FLOAT32, id: 1 },
|
||||
{ name: 'y', type: SerializeType.FLOAT32, id: 2 }
|
||||
]);
|
||||
|
||||
globalTypes.set('Vector3', [
|
||||
{ name: 'x', type: SerializeType.FLOAT32, id: 1 },
|
||||
{ name: 'y', type: SerializeType.FLOAT32, id: 2 },
|
||||
{ name: 'z', type: SerializeType.FLOAT32, id: 3 }
|
||||
]);
|
||||
|
||||
globalTypes.set('Quaternion', [
|
||||
{ name: 'x', type: SerializeType.FLOAT32, id: 1 },
|
||||
{ name: 'y', type: SerializeType.FLOAT32, id: 2 },
|
||||
{ name: 'z', type: SerializeType.FLOAT32, id: 3 },
|
||||
{ name: 'w', type: SerializeType.FLOAT32, id: 4 }
|
||||
]);
|
||||
|
||||
globalTypes.set('Color', [
|
||||
{ name: 'r', type: SerializeType.FLOAT32, id: 1 },
|
||||
{ name: 'g', type: SerializeType.FLOAT32, id: 2 },
|
||||
{ name: 'b', type: SerializeType.FLOAT32, id: 3 },
|
||||
{ name: 'a', type: SerializeType.FLOAT32, id: 4, optional: true, defaultValue: 1.0 }
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证协议模式
|
||||
*/
|
||||
private validateSchema(schema: ProtocolSchema): void {
|
||||
// 检查版本兼容性
|
||||
if (this.options.enableCompatibilityCheck) {
|
||||
this.validateCompatibility(schema);
|
||||
}
|
||||
|
||||
// 检查全局一致性
|
||||
this.validateGlobalConsistency(schema);
|
||||
}
|
||||
|
||||
// 辅助方法
|
||||
|
||||
private getFieldPriority(field: ProtocolField): number {
|
||||
// 根据字段名推断优先级
|
||||
const highPriorityNames = ['position', 'rotation', 'health', 'transform'];
|
||||
const mediumPriorityNames = ['velocity', 'speed', 'direction'];
|
||||
|
||||
const fieldName = field.name.toLowerCase();
|
||||
|
||||
if (highPriorityNames.some(name => fieldName.includes(name))) {
|
||||
return 10;
|
||||
}
|
||||
if (mediumPriorityNames.some(name => fieldName.includes(name))) {
|
||||
return 5;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private isFixedSizeType(type: SerializeType): boolean {
|
||||
const fixedTypes = [
|
||||
SerializeType.BOOLEAN,
|
||||
SerializeType.INT8, SerializeType.UINT8,
|
||||
SerializeType.INT16, SerializeType.UINT16,
|
||||
SerializeType.INT32, SerializeType.UINT32,
|
||||
SerializeType.INT64, SerializeType.UINT64,
|
||||
SerializeType.FLOAT32, SerializeType.FLOAT64,
|
||||
SerializeType.VECTOR2, SerializeType.VECTOR3,
|
||||
SerializeType.QUATERNION, SerializeType.COLOR
|
||||
];
|
||||
return fixedTypes.includes(type);
|
||||
}
|
||||
|
||||
private getTypeSize(type: SerializeType): number {
|
||||
const sizes = {
|
||||
[SerializeType.BOOLEAN]: 1,
|
||||
[SerializeType.INT8]: 1,
|
||||
[SerializeType.UINT8]: 1,
|
||||
[SerializeType.INT16]: 2,
|
||||
[SerializeType.UINT16]: 2,
|
||||
[SerializeType.INT32]: 4,
|
||||
[SerializeType.UINT32]: 4,
|
||||
[SerializeType.INT64]: 8,
|
||||
[SerializeType.UINT64]: 8,
|
||||
[SerializeType.FLOAT32]: 4,
|
||||
[SerializeType.FLOAT64]: 8,
|
||||
[SerializeType.VECTOR2]: 8,
|
||||
[SerializeType.VECTOR3]: 12,
|
||||
[SerializeType.QUATERNION]: 16,
|
||||
[SerializeType.COLOR]: 16,
|
||||
[SerializeType.STRING]: 100, // 估算
|
||||
[SerializeType.BYTES]: 100,
|
||||
[SerializeType.ARRAY]: 200,
|
||||
[SerializeType.MAP]: 200,
|
||||
[SerializeType.OBJECT]: 500,
|
||||
[SerializeType.JSON]: 1000
|
||||
};
|
||||
return sizes[type] || 100;
|
||||
}
|
||||
|
||||
private canUseFloat32(field: ProtocolField): boolean {
|
||||
// 简单启发式:位置、旋转等游戏相关字段通常可以使用 float32
|
||||
const float32FriendlyNames = ['position', 'rotation', 'scale', 'velocity', 'speed'];
|
||||
return float32FriendlyNames.some(name => field.name.toLowerCase().includes(name));
|
||||
}
|
||||
|
||||
private canUseInt32(field: ProtocolField): boolean {
|
||||
// 大多数游戏中的整数值都可以用 int32 表示
|
||||
const int32FriendlyNames = ['id', 'count', 'level', 'score', 'health', 'mana'];
|
||||
return int32FriendlyNames.some(name => field.name.toLowerCase().includes(name));
|
||||
}
|
||||
|
||||
private inferBetterType(field: ProtocolField): SerializeType | null {
|
||||
// 根据字段名推断更好的类型
|
||||
const fieldName = field.name.toLowerCase();
|
||||
|
||||
if (fieldName.includes('position') || fieldName.includes('vector')) {
|
||||
return SerializeType.VECTOR3;
|
||||
}
|
||||
if (fieldName.includes('rotation') || fieldName.includes('quaternion')) {
|
||||
return SerializeType.QUATERNION;
|
||||
}
|
||||
if (fieldName.includes('color')) {
|
||||
return SerializeType.COLOR;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private optimizeParameterType(type: SerializeType): SerializeType {
|
||||
// RPC 参数类型优化
|
||||
if (type === SerializeType.FLOAT64) {
|
||||
return SerializeType.FLOAT32; // RPC 通常不需要高精度
|
||||
}
|
||||
if (type === SerializeType.INT64) {
|
||||
return SerializeType.INT32;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private estimateInstanceCount(component: ComponentProtocol): number {
|
||||
// 基于组件名称估算实例数量
|
||||
const highVolumeNames = ['transform', 'position', 'movement', 'particle'];
|
||||
const mediumVolumeNames = ['player', 'enemy', 'bullet', 'item'];
|
||||
|
||||
const typeName = component.typeName.toLowerCase();
|
||||
|
||||
if (highVolumeNames.some(name => typeName.includes(name))) {
|
||||
return 100;
|
||||
}
|
||||
if (mediumVolumeNames.some(name => typeName.includes(name))) {
|
||||
return 20;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
private isSimpleType(type: SerializeType): boolean {
|
||||
const simpleTypes = [
|
||||
SerializeType.BOOLEAN,
|
||||
SerializeType.INT32, SerializeType.UINT32,
|
||||
SerializeType.FLOAT32,
|
||||
SerializeType.VECTOR2, SerializeType.VECTOR3,
|
||||
SerializeType.QUATERNION
|
||||
];
|
||||
return simpleTypes.includes(type);
|
||||
}
|
||||
|
||||
private isLargeDataType(type: SerializeType): boolean {
|
||||
const largeTypes = [
|
||||
SerializeType.STRING,
|
||||
SerializeType.BYTES,
|
||||
SerializeType.ARRAY,
|
||||
SerializeType.MAP,
|
||||
SerializeType.OBJECT,
|
||||
SerializeType.JSON
|
||||
];
|
||||
return largeTypes.includes(type);
|
||||
}
|
||||
|
||||
private findCommonFieldPatterns(components: ComponentProtocol[]): Map<string, number> {
|
||||
const patterns = new Map<string, number>();
|
||||
|
||||
for (const component of components) {
|
||||
const pattern = component.syncVars
|
||||
.map(field => `${field.name}:${field.type}`)
|
||||
.sort()
|
||||
.join(',');
|
||||
|
||||
patterns.set(pattern, (patterns.get(pattern) || 0) + 1);
|
||||
}
|
||||
|
||||
return patterns;
|
||||
}
|
||||
|
||||
private validateIdUniqueness(components: ComponentProtocol[]): void {
|
||||
const fieldIds = new Map<number, string>();
|
||||
const rpcIds = new Map<number, string>();
|
||||
|
||||
for (const component of components) {
|
||||
// 检查字段 ID 冲突
|
||||
for (const field of component.syncVars) {
|
||||
const existing = fieldIds.get(field.id);
|
||||
if (existing && existing !== `${component.typeName}.${field.name}`) {
|
||||
this.addError('semantic', `Field ID conflict: ${field.id} used by both ${existing} and ${component.typeName}.${field.name}`);
|
||||
}
|
||||
fieldIds.set(field.id, `${component.typeName}.${field.name}`);
|
||||
}
|
||||
|
||||
// 检查 RPC ID 冲突
|
||||
for (const rpc of component.rpcs) {
|
||||
const existing = rpcIds.get(rpc.id);
|
||||
if (existing && existing !== `${component.typeName}.${rpc.name}`) {
|
||||
this.addError('semantic', `RPC ID conflict: ${rpc.id} used by both ${existing} and ${component.typeName}.${rpc.name}`);
|
||||
}
|
||||
rpcIds.set(rpc.id, `${component.typeName}.${rpc.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private validateCompatibility(schema: ProtocolSchema): void {
|
||||
// 这里可以添加向后兼容性检查逻辑
|
||||
// 比如检查字段删除、类型变更等
|
||||
}
|
||||
|
||||
private validateGlobalConsistency(schema: ProtocolSchema): void {
|
||||
// 检查全局类型的一致性使用
|
||||
for (const [typeName, fields] of schema.types) {
|
||||
const usageCount = Array.from(schema.components.values())
|
||||
.flatMap(comp => comp.syncVars)
|
||||
.filter(field => field.type === typeName as SerializeType)
|
||||
.length;
|
||||
|
||||
if (usageCount === 0) {
|
||||
this.addWarning('style', `Global type ${typeName} is defined but not used`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addError(type: ProtocolError['type'], message: string): void {
|
||||
this.errors.push({ type, message });
|
||||
}
|
||||
|
||||
private addWarning(type: ProtocolWarning['type'], message: string): void {
|
||||
this.warnings.push({ type, message });
|
||||
}
|
||||
|
||||
public getErrors(): ProtocolError[] {
|
||||
return [...this.errors];
|
||||
}
|
||||
|
||||
public getWarnings(): ProtocolWarning[] {
|
||||
return [...this.warnings];
|
||||
}
|
||||
}
|
||||
5
packages/network-shared/src/protocol/compiler/index.ts
Normal file
5
packages/network-shared/src/protocol/compiler/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 协议编译器导出
|
||||
*/
|
||||
|
||||
export * from './ProtocolInferenceEngine';
|
||||
8
packages/network-shared/src/protocol/index.ts
Normal file
8
packages/network-shared/src/protocol/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 协议编译器模块导出
|
||||
*/
|
||||
|
||||
export * from './types';
|
||||
// 协议分析器需要开发时依赖,暂时禁用
|
||||
// export * from './analyzer';
|
||||
export * from './compiler';
|
||||
289
packages/network-shared/src/protocol/types/ProtocolTypes.ts
Normal file
289
packages/network-shared/src/protocol/types/ProtocolTypes.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* 网络协议编译系统类型定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 序列化类型枚举
|
||||
*/
|
||||
export enum SerializeType {
|
||||
// 基础类型
|
||||
BOOLEAN = 'boolean',
|
||||
INT8 = 'int8',
|
||||
UINT8 = 'uint8',
|
||||
INT16 = 'int16',
|
||||
UINT16 = 'uint16',
|
||||
INT32 = 'int32',
|
||||
UINT32 = 'uint32',
|
||||
INT64 = 'int64',
|
||||
UINT64 = 'uint64',
|
||||
FLOAT32 = 'float32',
|
||||
FLOAT64 = 'float64',
|
||||
STRING = 'string',
|
||||
BYTES = 'bytes',
|
||||
|
||||
// 常用游戏类型
|
||||
VECTOR2 = 'Vector2',
|
||||
VECTOR3 = 'Vector3',
|
||||
QUATERNION = 'Quaternion',
|
||||
COLOR = 'Color',
|
||||
|
||||
// 容器类型
|
||||
ARRAY = 'array',
|
||||
MAP = 'map',
|
||||
|
||||
// 复杂类型
|
||||
OBJECT = 'object',
|
||||
JSON = 'json'
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段定义
|
||||
*/
|
||||
export interface ProtocolField {
|
||||
/** 字段名 */
|
||||
name: string;
|
||||
/** 序列化类型 */
|
||||
type: SerializeType;
|
||||
/** 字段ID(用于向后兼容) */
|
||||
id: number;
|
||||
/** 是否可选 */
|
||||
optional?: boolean;
|
||||
/** 是否重复(数组) */
|
||||
repeated?: boolean;
|
||||
/** 元素类型(用于数组和映射) */
|
||||
elementType?: SerializeType;
|
||||
/** 键类型(用于映射) */
|
||||
keyType?: SerializeType;
|
||||
/** 值类型(用于映射) */
|
||||
valueType?: SerializeType;
|
||||
/** 默认值 */
|
||||
defaultValue?: any;
|
||||
/** 自定义序列化器 */
|
||||
customSerializer?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC 参数定义
|
||||
*/
|
||||
export interface RpcParameter {
|
||||
/** 参数名 */
|
||||
name: string;
|
||||
/** 参数类型 */
|
||||
type: SerializeType;
|
||||
/** 是否可选 */
|
||||
optional?: boolean;
|
||||
/** 是否为数组 */
|
||||
isArray?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC 定义
|
||||
*/
|
||||
export interface ProtocolRpc {
|
||||
/** 方法名 */
|
||||
name: string;
|
||||
/** RPC ID */
|
||||
id: number;
|
||||
/** RPC 类型 */
|
||||
type: 'client-rpc' | 'server-rpc';
|
||||
/** 参数列表 */
|
||||
parameters: RpcParameter[];
|
||||
/** 返回类型 */
|
||||
returnType?: SerializeType;
|
||||
/** 是否需要权限 */
|
||||
requiresAuth?: boolean;
|
||||
/** 是否可靠传输 */
|
||||
reliable?: boolean;
|
||||
/** 频率限制 */
|
||||
rateLimit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络组件协议定义
|
||||
*/
|
||||
export interface ComponentProtocol {
|
||||
/** 组件类型名 */
|
||||
typeName: string;
|
||||
/** 协议版本 */
|
||||
version: number;
|
||||
/** SyncVar 字段 */
|
||||
syncVars: ProtocolField[];
|
||||
/** RPC 方法 */
|
||||
rpcs: ProtocolRpc[];
|
||||
/** 是否启用批量处理 */
|
||||
batchEnabled?: boolean;
|
||||
/** 是否启用增量同步 */
|
||||
deltaEnabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 协议模式定义
|
||||
*/
|
||||
export interface ProtocolSchema {
|
||||
/** 模式版本 */
|
||||
version: string;
|
||||
/** 组件协议映射 */
|
||||
components: Map<string, ComponentProtocol>;
|
||||
/** 全局类型定义 */
|
||||
types: Map<string, ProtocolField[]>;
|
||||
/** 协议兼容性信息 */
|
||||
compatibility: {
|
||||
minVersion: string;
|
||||
maxVersion: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化器接口
|
||||
*/
|
||||
export interface IProtocolSerializer {
|
||||
/** 序列化单个对象 */
|
||||
serialize(obj: any, type: SerializeType): Uint8Array;
|
||||
/** 反序列化单个对象 */
|
||||
deserialize(data: Uint8Array, type: SerializeType): any;
|
||||
/** 批量序列化 */
|
||||
serializeBatch(objects: any[], type: SerializeType): Uint8Array;
|
||||
/** 批量反序列化 */
|
||||
deserializeBatch(data: Uint8Array, type: SerializeType): any[];
|
||||
/** 增量序列化 */
|
||||
serializeDelta(oldObj: any, newObj: any, type: SerializeType): Uint8Array | null;
|
||||
/** 应用增量 */
|
||||
applyDelta(baseObj: any, delta: Uint8Array, type: SerializeType): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 协议编译器配置
|
||||
*/
|
||||
export interface ProtocolCompilerConfig {
|
||||
/** 输入目录 */
|
||||
inputDir: string;
|
||||
/** 输出目录 */
|
||||
outputDir: string;
|
||||
/** TypeScript 配置文件路径 */
|
||||
tsconfigPath?: string;
|
||||
/** 是否启用优化 */
|
||||
optimize?: boolean;
|
||||
/** 是否生成调试信息 */
|
||||
debug?: boolean;
|
||||
/** 自定义类型映射 */
|
||||
typeMapping?: Map<string, SerializeType>;
|
||||
/** 排除的文件模式 */
|
||||
excludePatterns?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 协议分析结果
|
||||
*/
|
||||
export interface ProtocolAnalysisResult {
|
||||
/** 分析的文件列表 */
|
||||
files: string[];
|
||||
/** 发现的网络组件 */
|
||||
components: ComponentProtocol[];
|
||||
/** 类型依赖图 */
|
||||
dependencies: Map<string, string[]>;
|
||||
/** 分析错误 */
|
||||
errors: ProtocolError[];
|
||||
/** 分析警告 */
|
||||
warnings: ProtocolWarning[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 协议错误
|
||||
*/
|
||||
export interface ProtocolError {
|
||||
/** 错误类型 */
|
||||
type: 'syntax' | 'type' | 'semantic' | 'compatibility';
|
||||
/** 错误消息 */
|
||||
message: string;
|
||||
/** 文件路径 */
|
||||
file?: string;
|
||||
/** 行号 */
|
||||
line?: number;
|
||||
/** 列号 */
|
||||
column?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 协议警告
|
||||
*/
|
||||
export interface ProtocolWarning {
|
||||
/** 警告类型 */
|
||||
type: 'performance' | 'compatibility' | 'style';
|
||||
/** 警告消息 */
|
||||
message: string;
|
||||
/** 文件路径 */
|
||||
file?: string;
|
||||
/** 行号 */
|
||||
line?: number;
|
||||
/** 列号 */
|
||||
column?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码生成选项
|
||||
*/
|
||||
export interface CodeGenerationOptions {
|
||||
/** 目标平台 */
|
||||
platform: 'node' | 'browser' | 'universal';
|
||||
/** 代码风格 */
|
||||
style: 'typescript' | 'javascript';
|
||||
/** 是否生成类型定义 */
|
||||
generateTypes?: boolean;
|
||||
/** 是否生成文档 */
|
||||
generateDocs?: boolean;
|
||||
/** 模块格式 */
|
||||
moduleFormat?: 'es' | 'cjs' | 'umd';
|
||||
/** 压缩级别 */
|
||||
minification?: 'none' | 'basic' | 'aggressive';
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时协议信息
|
||||
*/
|
||||
export interface RuntimeProtocolInfo {
|
||||
/** 协议版本 */
|
||||
version: string;
|
||||
/** 组件数量 */
|
||||
componentCount: number;
|
||||
/** 总字段数 */
|
||||
fieldCount: number;
|
||||
/** 总 RPC 数 */
|
||||
rpcCount: number;
|
||||
/** 内存使用情况 */
|
||||
memoryUsage: {
|
||||
schemas: number;
|
||||
serializers: number;
|
||||
cache: number;
|
||||
};
|
||||
/** 性能统计 */
|
||||
performance: {
|
||||
serializeTime: number;
|
||||
deserializeTime: number;
|
||||
cacheHits: number;
|
||||
cacheMisses: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 协议事件类型
|
||||
*/
|
||||
export type ProtocolEventType =
|
||||
| 'protocol-loaded'
|
||||
| 'protocol-updated'
|
||||
| 'serializer-registered'
|
||||
| 'compatibility-check'
|
||||
| 'performance-warning';
|
||||
|
||||
/**
|
||||
* 协议事件数据
|
||||
*/
|
||||
export interface ProtocolEventData {
|
||||
type: ProtocolEventType;
|
||||
timestamp: number;
|
||||
data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 协议事件处理器
|
||||
*/
|
||||
export type ProtocolEventHandler = (event: ProtocolEventData) => void;
|
||||
5
packages/network-shared/src/protocol/types/index.ts
Normal file
5
packages/network-shared/src/protocol/types/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 协议类型定义导出
|
||||
*/
|
||||
|
||||
export * from './ProtocolTypes';
|
||||
355
packages/network-shared/src/serialization/NetworkSerializer.ts
Normal file
355
packages/network-shared/src/serialization/NetworkSerializer.ts
Normal file
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* 网络序列化器
|
||||
*
|
||||
* 提供高效的网络消息序列化和反序列化
|
||||
*/
|
||||
|
||||
import { INetworkSerializer, NetworkValue, SerializationSchema } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 序列化类型映射
|
||||
*/
|
||||
interface SerializationTypeMap {
|
||||
[typeName: string]: SerializationSchema<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础网络序列化器实现
|
||||
*/
|
||||
export class NetworkSerializer implements INetworkSerializer {
|
||||
private typeMap: SerializationTypeMap = {};
|
||||
|
||||
constructor() {
|
||||
this.registerBuiltinTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册内置类型
|
||||
*/
|
||||
private registerBuiltinTypes(): void {
|
||||
// 基础类型
|
||||
this.registerType<string>('string', {
|
||||
serialize: (str: string) => new TextEncoder().encode(str),
|
||||
deserialize: (data: Uint8Array) => new TextDecoder().decode(data),
|
||||
getSize: (str: string) => new TextEncoder().encode(str).length
|
||||
});
|
||||
|
||||
this.registerType<number>('number', {
|
||||
serialize: (num: number) => {
|
||||
const buffer = new ArrayBuffer(8);
|
||||
const view = new DataView(buffer);
|
||||
view.setFloat64(0, num);
|
||||
return new Uint8Array(buffer);
|
||||
},
|
||||
deserialize: (data: Uint8Array) => {
|
||||
const view = new DataView(data.buffer);
|
||||
return view.getFloat64(0);
|
||||
},
|
||||
getSize: () => 8
|
||||
});
|
||||
|
||||
this.registerType<boolean>('boolean', {
|
||||
serialize: (bool: boolean) => new Uint8Array([bool ? 1 : 0]),
|
||||
deserialize: (data: Uint8Array) => data[0] === 1,
|
||||
getSize: () => 1
|
||||
});
|
||||
|
||||
this.registerType<number>('int32', {
|
||||
serialize: (num: number) => {
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
view.setInt32(0, num);
|
||||
return new Uint8Array(buffer);
|
||||
},
|
||||
deserialize: (data: Uint8Array) => {
|
||||
const view = new DataView(data.buffer);
|
||||
return view.getInt32(0);
|
||||
},
|
||||
getSize: () => 4
|
||||
});
|
||||
|
||||
this.registerType<number>('uint32', {
|
||||
serialize: (num: number) => {
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
view.setUint32(0, num);
|
||||
return new Uint8Array(buffer);
|
||||
},
|
||||
deserialize: (data: Uint8Array) => {
|
||||
const view = new DataView(data.buffer);
|
||||
return view.getUint32(0);
|
||||
},
|
||||
getSize: () => 4
|
||||
});
|
||||
|
||||
// Vector3 类型
|
||||
this.registerType<{x: number, y: number, z?: number}>('Vector3', {
|
||||
serialize: (vec: { x: number; y: number; z?: number }) => {
|
||||
const buffer = new ArrayBuffer(12);
|
||||
const view = new DataView(buffer);
|
||||
view.setFloat32(0, vec.x);
|
||||
view.setFloat32(4, vec.y);
|
||||
view.setFloat32(8, vec.z || 0);
|
||||
return new Uint8Array(buffer);
|
||||
},
|
||||
deserialize: (data: Uint8Array) => {
|
||||
const view = new DataView(data.buffer);
|
||||
return {
|
||||
x: view.getFloat32(0),
|
||||
y: view.getFloat32(4),
|
||||
z: view.getFloat32(8)
|
||||
};
|
||||
},
|
||||
getSize: () => 12
|
||||
});
|
||||
|
||||
// JSON 类型(用于复杂对象)
|
||||
this.registerType('json', {
|
||||
serialize: (obj: any) => {
|
||||
const jsonStr = JSON.stringify(obj);
|
||||
return new TextEncoder().encode(jsonStr);
|
||||
},
|
||||
deserialize: (data: Uint8Array) => {
|
||||
const jsonStr = new TextDecoder().decode(data);
|
||||
return JSON.parse(jsonStr);
|
||||
},
|
||||
getSize: (obj: any) => {
|
||||
const jsonStr = JSON.stringify(obj);
|
||||
return new TextEncoder().encode(jsonStr).length;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册序列化类型
|
||||
*/
|
||||
public registerType<T = NetworkValue>(typeName: string, typeSchema: SerializationSchema<T>): void {
|
||||
if (typeof typeSchema.serialize !== 'function' ||
|
||||
typeof typeSchema.deserialize !== 'function') {
|
||||
throw new Error(`Invalid type schema for ${typeName}: must have serialize and deserialize methods`);
|
||||
}
|
||||
|
||||
this.typeMap[typeName] = {
|
||||
serialize: typeSchema.serialize as any,
|
||||
deserialize: typeSchema.deserialize as any,
|
||||
getSize: typeSchema.getSize as any || ((obj: any) => this.serialize(obj, typeName).length)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化对象
|
||||
*/
|
||||
public serialize(obj: any, type?: string): Uint8Array {
|
||||
if (type && this.typeMap[type]) {
|
||||
return this.typeMap[type].serialize(obj);
|
||||
}
|
||||
|
||||
// 自动类型检测
|
||||
const detectedType = this.detectType(obj);
|
||||
if (this.typeMap[detectedType]) {
|
||||
return this.typeMap[detectedType].serialize(obj);
|
||||
}
|
||||
|
||||
// 默认使用 JSON 序列化
|
||||
const jsonHandler = this.typeMap['json'];
|
||||
if (jsonHandler?.serialize) {
|
||||
return jsonHandler.serialize(obj);
|
||||
}
|
||||
|
||||
// 最终回退方案
|
||||
return new TextEncoder().encode(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化对象
|
||||
*/
|
||||
public deserialize<T = any>(data: Uint8Array, type?: string): T {
|
||||
if (type && this.typeMap[type]) {
|
||||
return this.typeMap[type].deserialize(data);
|
||||
}
|
||||
|
||||
// 如果没有指定类型,尝试使用 JSON 反序列化
|
||||
try {
|
||||
const jsonHandler = this.typeMap['json'];
|
||||
if (jsonHandler?.deserialize) {
|
||||
return jsonHandler.deserialize(data);
|
||||
}
|
||||
|
||||
// 最终回退方案
|
||||
const jsonString = new TextDecoder().decode(data);
|
||||
return JSON.parse(jsonString);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to deserialize data: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取序列化后的大小
|
||||
*/
|
||||
public getSerializedSize(obj: any, type?: string): number {
|
||||
if (type && this.typeMap[type]?.getSize) {
|
||||
return this.typeMap[type].getSize(obj);
|
||||
}
|
||||
|
||||
const detectedType = this.detectType(obj);
|
||||
if (this.typeMap[detectedType]?.getSize) {
|
||||
return this.typeMap[detectedType].getSize(obj);
|
||||
}
|
||||
|
||||
const jsonHandler = this.typeMap['json'];
|
||||
return jsonHandler?.getSize ? jsonHandler.getSize(obj) : JSON.stringify(obj).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动检测对象类型
|
||||
*/
|
||||
private detectType(obj: any): string {
|
||||
if (typeof obj === 'string') return 'string';
|
||||
if (typeof obj === 'number') return 'number';
|
||||
if (typeof obj === 'boolean') return 'boolean';
|
||||
|
||||
if (obj && typeof obj === 'object') {
|
||||
// 检测 Vector3 类型
|
||||
if ('x' in obj && 'y' in obj && typeof obj.x === 'number' && typeof obj.y === 'number') {
|
||||
return 'Vector3';
|
||||
}
|
||||
}
|
||||
|
||||
return 'json';
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量序列化多个值
|
||||
*/
|
||||
public serializeBatch(values: Array<{ value: any; type?: string }>): Uint8Array {
|
||||
const serializedParts: Uint8Array[] = [];
|
||||
let totalSize = 0;
|
||||
|
||||
// 序列化每个值
|
||||
for (const item of values) {
|
||||
const serialized = this.serialize(item.value, item.type);
|
||||
serializedParts.push(serialized);
|
||||
totalSize += serialized.length + 4; // +4 为长度信息
|
||||
}
|
||||
|
||||
// 创建总缓冲区
|
||||
const result = new Uint8Array(totalSize + 4); // +4 为值的数量
|
||||
const view = new DataView(result.buffer);
|
||||
let offset = 0;
|
||||
|
||||
// 写入值的数量
|
||||
view.setUint32(offset, values.length);
|
||||
offset += 4;
|
||||
|
||||
// 写入每个序列化的值
|
||||
for (const serialized of serializedParts) {
|
||||
// 写入长度
|
||||
view.setUint32(offset, serialized.length);
|
||||
offset += 4;
|
||||
|
||||
// 写入数据
|
||||
result.set(serialized, offset);
|
||||
offset += serialized.length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量反序列化
|
||||
*/
|
||||
public deserializeBatch(data: Uint8Array, types?: string[]): any[] {
|
||||
const view = new DataView(data.buffer);
|
||||
let offset = 0;
|
||||
|
||||
// 读取值的数量
|
||||
const count = view.getUint32(offset);
|
||||
offset += 4;
|
||||
|
||||
const results: any[] = [];
|
||||
|
||||
// 读取每个值
|
||||
for (let i = 0; i < count; i++) {
|
||||
// 读取长度
|
||||
const length = view.getUint32(offset);
|
||||
offset += 4;
|
||||
|
||||
// 读取数据
|
||||
const valueData = data.slice(offset, offset + length);
|
||||
offset += length;
|
||||
|
||||
// 反序列化
|
||||
const type = types?.[i];
|
||||
const value = this.deserialize(valueData, type);
|
||||
results.push(value);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩序列化数据
|
||||
*/
|
||||
public compress(data: Uint8Array): Uint8Array {
|
||||
// 这里可以集成压缩算法,如 LZ4、gzip 等
|
||||
// 目前返回原数据
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压缩数据
|
||||
*/
|
||||
public decompress(data: Uint8Array): Uint8Array {
|
||||
// 这里可以集成解压缩算法
|
||||
// 目前返回原数据
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建增量序列化数据
|
||||
*/
|
||||
public serializeDelta(oldValue: any, newValue: any, type?: string): Uint8Array | null {
|
||||
// 基础实现:如果值相同则返回 null,否则序列化新值
|
||||
if (this.isEqual(oldValue, newValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.serialize(newValue, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用增量数据
|
||||
*/
|
||||
public applyDelta(_baseValue: any, deltaData: Uint8Array, type?: string): any {
|
||||
// 基础实现:直接反序列化增量数据
|
||||
// baseValue 在更复杂的增量实现中会被使用
|
||||
return this.deserialize(deltaData, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查两个值是否相等
|
||||
*/
|
||||
private isEqual(a: any, b: any): boolean {
|
||||
if (a === b) return true;
|
||||
|
||||
if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已注册的类型列表
|
||||
*/
|
||||
public getRegisteredTypes(): string[] {
|
||||
return Object.keys(this.typeMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查类型是否已注册
|
||||
*/
|
||||
public hasType(typeName: string): boolean {
|
||||
return typeName in this.typeMap;
|
||||
}
|
||||
}
|
||||
5
packages/network-shared/src/serialization/index.ts
Normal file
5
packages/network-shared/src/serialization/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 序列化工具导出
|
||||
*/
|
||||
|
||||
export * from './NetworkSerializer';
|
||||
375
packages/network-shared/src/types/NetworkTypes.ts
Normal file
375
packages/network-shared/src/types/NetworkTypes.ts
Normal file
@@ -0,0 +1,375 @@
|
||||
/**
|
||||
* 网络库核心类型定义
|
||||
*/
|
||||
|
||||
// 通用类型定义
|
||||
export type NetworkValue = string | number | boolean | NetworkValue[] | { [key: string]: NetworkValue };
|
||||
export type SerializableObject = Record<string, NetworkValue>;
|
||||
export type Constructor<T = {}> = new (...args: unknown[]) => T;
|
||||
export type MethodDecorator<T = unknown> = (target: unknown, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
|
||||
|
||||
// 装饰器目标类型 - 使用更灵活的定义
|
||||
export interface DecoratorTarget extends Record<string, unknown> {
|
||||
constructor: Constructor;
|
||||
}
|
||||
|
||||
// 网络数据类型约束
|
||||
export interface SerializedData {
|
||||
type: string;
|
||||
data: Uint8Array;
|
||||
checksum?: string;
|
||||
}
|
||||
|
||||
// RPC参数类型
|
||||
export type RpcParameterType = NetworkValue;
|
||||
export type RpcReturnType = NetworkValue | void | Promise<NetworkValue | void>;
|
||||
|
||||
// 序列化模式接口 - 使用泛型支持特定类型
|
||||
export interface SerializationSchema<T = NetworkValue> {
|
||||
serialize: (obj: T) => Uint8Array;
|
||||
deserialize: (data: Uint8Array) => T;
|
||||
getSize?: (obj: T) => number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络端类型
|
||||
*/
|
||||
export type NetworkSide = 'client' | 'server' | 'host';
|
||||
|
||||
/**
|
||||
* 网络连接状态
|
||||
*/
|
||||
export type NetworkConnectionState =
|
||||
| 'disconnected'
|
||||
| 'connecting'
|
||||
| 'connected'
|
||||
| 'disconnecting'
|
||||
| 'reconnecting'
|
||||
| 'failed';
|
||||
|
||||
/**
|
||||
* 网络消息类型
|
||||
*/
|
||||
export type NetworkMessageType =
|
||||
| 'syncvar'
|
||||
| 'client-rpc'
|
||||
| 'server-rpc'
|
||||
| 'spawn'
|
||||
| 'destroy'
|
||||
| 'ownership'
|
||||
| 'scene-change'
|
||||
| 'snapshot'
|
||||
| 'ping'
|
||||
| 'custom';
|
||||
|
||||
/**
|
||||
* 网络配置
|
||||
*/
|
||||
export interface NetworkConfig {
|
||||
/** 端口号 */
|
||||
port: number;
|
||||
/** 主机地址 */
|
||||
host: string;
|
||||
/** 最大连接数 */
|
||||
maxConnections: number;
|
||||
/** 同步频率 (Hz) */
|
||||
syncRate: number;
|
||||
/** 快照频率 (Hz) */
|
||||
snapshotRate: number;
|
||||
/** 是否启用压缩 */
|
||||
compression: boolean;
|
||||
/** 是否启用加密 */
|
||||
encryption: boolean;
|
||||
/** 网络超时时间 (ms) */
|
||||
timeout: number;
|
||||
/** 重连尝试次数 */
|
||||
maxReconnectAttempts: number;
|
||||
/** 重连间隔 (ms) */
|
||||
reconnectInterval: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络统计信息
|
||||
*/
|
||||
export interface NetworkStats {
|
||||
/** 连接数量 */
|
||||
connectionCount: number;
|
||||
/** 已发送字节数 */
|
||||
bytesSent: number;
|
||||
/** 已接收字节数 */
|
||||
bytesReceived: number;
|
||||
/** 已发送消息数 */
|
||||
messagesSent: number;
|
||||
/** 已接收消息数 */
|
||||
messagesReceived: number;
|
||||
/** 平均延迟 (ms) */
|
||||
averageLatency: number;
|
||||
/** 丢包率 (%) */
|
||||
packetLoss: number;
|
||||
/** 带宽使用率 (bytes/s) */
|
||||
bandwidth: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络消息基类
|
||||
*/
|
||||
export interface NetworkMessage {
|
||||
/** 消息类型 */
|
||||
type: NetworkMessageType;
|
||||
/** 网络对象ID */
|
||||
networkId: number;
|
||||
/** 消息数据 */
|
||||
data: SerializableObject;
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
/** 消息ID */
|
||||
messageId?: string;
|
||||
/** 发送者ID */
|
||||
senderId?: number;
|
||||
/** 接收者ID (可选,用于定向发送) */
|
||||
targetId?: number;
|
||||
/** 是否可靠传输 */
|
||||
reliable?: boolean;
|
||||
/** 优先级 */
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar 消息
|
||||
*/
|
||||
export interface SyncVarMessage extends NetworkMessage {
|
||||
type: 'syncvar';
|
||||
/** 组件类型名 */
|
||||
componentType: string;
|
||||
/** 属性名 */
|
||||
propertyName: string;
|
||||
/** 属性值 */
|
||||
value: NetworkValue;
|
||||
/** 变化类型 */
|
||||
changeType?: 'set' | 'add' | 'remove' | 'clear';
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC 消息
|
||||
*/
|
||||
export interface RpcMessage extends NetworkMessage {
|
||||
type: 'client-rpc' | 'server-rpc';
|
||||
/** 组件类型名 */
|
||||
componentType: string;
|
||||
/** 方法名 */
|
||||
methodName: string;
|
||||
/** 参数列表 */
|
||||
args: RpcParameterType[];
|
||||
/** RPC ID (用于响应) */
|
||||
rpcId?: string;
|
||||
/** 是否需要响应 */
|
||||
requiresResponse?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象生成消息
|
||||
*/
|
||||
export interface SpawnMessage extends NetworkMessage {
|
||||
type: 'spawn';
|
||||
/** 预制体名称或ID */
|
||||
prefabName: string;
|
||||
/** 生成位置 */
|
||||
position?: { x: number; y: number; z?: number };
|
||||
/** 生成旋转 */
|
||||
rotation?: { x: number; y: number; z: number; w: number };
|
||||
/** 所有者ID */
|
||||
ownerId: number;
|
||||
/** 初始数据 */
|
||||
initData?: SerializableObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象销毁消息
|
||||
*/
|
||||
export interface DestroyMessage extends NetworkMessage {
|
||||
type: 'destroy';
|
||||
/** 销毁原因 */
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有权转移消息
|
||||
*/
|
||||
export interface OwnershipMessage extends NetworkMessage {
|
||||
type: 'ownership';
|
||||
/** 新所有者ID */
|
||||
newOwnerId: number;
|
||||
/** 旧所有者ID */
|
||||
oldOwnerId: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 快照消息
|
||||
*/
|
||||
export interface SnapshotMessage extends NetworkMessage {
|
||||
type: 'snapshot';
|
||||
/** 快照ID */
|
||||
snapshotId: number;
|
||||
/** 快照数据 */
|
||||
snapshot: SerializableObject;
|
||||
/** 包含的网络对象ID列表 */
|
||||
networkIds: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar 元数据
|
||||
*/
|
||||
export interface SyncVarMetadata {
|
||||
/** 属性名 */
|
||||
propertyName: string;
|
||||
/** 是否仅权威端可修改 */
|
||||
authorityOnly: boolean;
|
||||
/** 变化回调函数名 */
|
||||
onChanged?: string;
|
||||
/** 序列化类型 */
|
||||
serializeType?: string;
|
||||
/** 是否使用增量同步 */
|
||||
deltaSync?: boolean;
|
||||
/** 同步优先级 */
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC 元数据
|
||||
*/
|
||||
export interface RpcMetadata {
|
||||
/** 方法名 */
|
||||
methodName: string;
|
||||
/** RPC 类型 */
|
||||
rpcType: 'client-rpc' | 'server-rpc';
|
||||
/** 是否需要权限验证 */
|
||||
requiresAuth?: boolean;
|
||||
/** 是否可靠传输 */
|
||||
reliable?: boolean;
|
||||
/** 是否需要响应 */
|
||||
requiresResponse?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络组件元数据
|
||||
*/
|
||||
export interface NetworkComponentMetadata {
|
||||
/** 组件类型名 */
|
||||
componentType: string;
|
||||
/** SyncVar 列表 */
|
||||
syncVars: SyncVarMetadata[];
|
||||
/** RPC 列表 */
|
||||
rpcs: RpcMetadata[];
|
||||
/** 是否自动生成协议 */
|
||||
autoGenerateProtocol?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络对象接口
|
||||
*/
|
||||
export interface INetworkObject {
|
||||
/** 网络ID */
|
||||
networkId: number;
|
||||
/** 所有者客户端ID */
|
||||
ownerId: number;
|
||||
/** 是否拥有权威 */
|
||||
hasAuthority: boolean;
|
||||
/** 是否为本地对象 */
|
||||
isLocal: boolean;
|
||||
/** 网络组件列表 */
|
||||
networkComponents: INetworkComponent[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络组件接口
|
||||
*/
|
||||
export interface INetworkComponent {
|
||||
/** 网络对象引用 */
|
||||
networkObject: INetworkObject | null;
|
||||
/** 网络ID */
|
||||
networkId: number;
|
||||
/** 是否拥有权威 */
|
||||
hasAuthority: boolean;
|
||||
/** 组件类型名 */
|
||||
componentType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络传输层接口
|
||||
*/
|
||||
export interface INetworkTransport {
|
||||
/** 启动服务端 */
|
||||
startServer(config: NetworkConfig): Promise<void>;
|
||||
/** 连接到服务端 */
|
||||
connectToServer(host: string, port: number): Promise<void>;
|
||||
/** 断开连接 */
|
||||
disconnect(): Promise<void>;
|
||||
/** 发送消息 */
|
||||
sendMessage(message: NetworkMessage, targetId?: number): Promise<void>;
|
||||
/** 广播消息 */
|
||||
broadcastMessage(message: NetworkMessage, excludeIds?: number[]): Promise<void>;
|
||||
/** 设置消息处理器 */
|
||||
onMessage(handler: (message: NetworkMessage, fromId?: number) => void): void;
|
||||
/** 设置连接事件处理器 */
|
||||
onConnection(handler: (clientId: number, isConnected: boolean) => void): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化器接口
|
||||
*/
|
||||
export interface INetworkSerializer {
|
||||
/** 序列化对象 */
|
||||
serialize(obj: NetworkValue, type?: string): Uint8Array;
|
||||
/** 反序列化对象 */
|
||||
deserialize<T extends NetworkValue = NetworkValue>(data: Uint8Array, type?: string): T;
|
||||
/** 注册类型 */
|
||||
registerType<T = NetworkValue>(typeName: string, typeSchema: SerializationSchema<T>): void;
|
||||
/** 获取序列化后的大小 */
|
||||
getSerializedSize(obj: NetworkValue, type?: string): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络事件处理器
|
||||
*/
|
||||
export interface NetworkEventHandlers {
|
||||
/** 连接成功 */
|
||||
onConnected?: () => void;
|
||||
/** 连接断开 */
|
||||
onDisconnected?: (reason?: string) => void;
|
||||
/** 客户端连接 */
|
||||
onClientConnected?: (clientId: number) => void;
|
||||
/** 客户端断开 */
|
||||
onClientDisconnected?: (clientId: number, reason?: string) => void;
|
||||
/** 网络错误 */
|
||||
onError?: (error: Error) => void;
|
||||
/** 延迟变化 */
|
||||
onLatencyUpdate?: (latency: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络调试信息
|
||||
*/
|
||||
export interface NetworkDebugInfo {
|
||||
/** 连接信息 */
|
||||
connections: {
|
||||
[clientId: number]: {
|
||||
id: number;
|
||||
address: string;
|
||||
latency: number;
|
||||
connected: boolean;
|
||||
lastSeen: number;
|
||||
};
|
||||
};
|
||||
/** 网络对象列表 */
|
||||
networkObjects: {
|
||||
[networkId: number]: {
|
||||
id: number;
|
||||
ownerId: number;
|
||||
componentTypes: string[];
|
||||
syncVarCount: number;
|
||||
rpcCount: number;
|
||||
};
|
||||
};
|
||||
/** 统计信息 */
|
||||
stats: NetworkStats;
|
||||
}
|
||||
4
packages/network-shared/src/types/index.ts
Normal file
4
packages/network-shared/src/types/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 类型定义导出
|
||||
*/
|
||||
export * from './NetworkTypes';
|
||||
16
packages/network-shared/src/types/reflect-metadata.d.ts
vendored
Normal file
16
packages/network-shared/src/types/reflect-metadata.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* reflect-metadata 类型扩展
|
||||
*/
|
||||
|
||||
/// <reference types="reflect-metadata" />
|
||||
|
||||
declare namespace Reflect {
|
||||
function defineMetadata(metadataKey: any, metadataValue: any, target: any, propertyKey?: string | symbol): void;
|
||||
function getMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): any;
|
||||
function getOwnMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): any;
|
||||
function hasMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean;
|
||||
function hasOwnMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean;
|
||||
function deleteMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean;
|
||||
function getMetadataKeys(target: any, propertyKey?: string | symbol): any[];
|
||||
function getOwnMetadataKeys(target: any, propertyKey?: string | symbol): any[];
|
||||
}
|
||||
242
packages/network-shared/src/utils/NetworkUtils.ts
Normal file
242
packages/network-shared/src/utils/NetworkUtils.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* 网络工具函数
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 生成网络ID
|
||||
*/
|
||||
export function generateNetworkId(): number {
|
||||
return Math.floor(Math.random() * 0x7FFFFFFF) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成消息ID
|
||||
*/
|
||||
export function generateMessageId(): string {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两点之间的距离
|
||||
*/
|
||||
export function calculateDistance(
|
||||
pos1: { x: number; y: number; z?: number },
|
||||
pos2: { x: number; y: number; z?: number }
|
||||
): number {
|
||||
const dx = pos1.x - pos2.x;
|
||||
const dy = pos1.y - pos2.y;
|
||||
const dz = (pos1.z || 0) - (pos2.z || 0);
|
||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查环境是否为 Node.js
|
||||
*/
|
||||
export function isNodeEnvironment(): boolean {
|
||||
return typeof process !== 'undefined' && process.versions && !!process.versions.node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查环境是否为浏览器
|
||||
*/
|
||||
export function isBrowserEnvironment(): boolean {
|
||||
return typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间戳(毫秒)
|
||||
*/
|
||||
export function getTimestamp(): number {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高精度时间戳(如果可用)
|
||||
*/
|
||||
export function getHighResTimestamp(): number {
|
||||
if (typeof performance !== 'undefined' && performance.now) {
|
||||
return performance.now();
|
||||
}
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制调用频率
|
||||
*/
|
||||
export function throttle<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
limit: number
|
||||
): T {
|
||||
let inThrottle: boolean;
|
||||
let context: any;
|
||||
|
||||
return (function(this: any, ...args: any[]) {
|
||||
context = this;
|
||||
if (!inThrottle) {
|
||||
func.apply(context, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => (inThrottle = false), limit);
|
||||
}
|
||||
}) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
*/
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
delay: number
|
||||
): T {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
let context: any;
|
||||
|
||||
return (function(this: any, ...args: any[]) {
|
||||
context = this;
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => func.apply(context, args), delay);
|
||||
}) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝对象
|
||||
*/
|
||||
export function deepClone<T>(obj: T): T {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (obj instanceof Date) {
|
||||
return new Date(obj.getTime()) as T;
|
||||
}
|
||||
|
||||
if (obj instanceof Array) {
|
||||
return obj.map(item => deepClone(item)) as T;
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
const cloned = {} as T;
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
cloned[key] = deepClone(obj[key]);
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查对象是否为空
|
||||
*/
|
||||
export function isEmpty(obj: any): boolean {
|
||||
if (obj === null || obj === undefined) return true;
|
||||
if (typeof obj === 'string' || Array.isArray(obj)) return obj.length === 0;
|
||||
if (typeof obj === 'object') return Object.keys(obj).length === 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字节大小
|
||||
*/
|
||||
export function formatBytes(bytes: number, decimals = 2): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化延迟时间
|
||||
*/
|
||||
export function formatLatency(milliseconds: number): string {
|
||||
if (milliseconds < 1000) {
|
||||
return `${Math.round(milliseconds)}ms`;
|
||||
} else {
|
||||
return `${(milliseconds / 1000).toFixed(1)}s`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网络质量描述
|
||||
*/
|
||||
export function getNetworkQuality(latency: number, packetLoss: number): string {
|
||||
if (latency < 50 && packetLoss < 1) return 'Excellent';
|
||||
if (latency < 100 && packetLoss < 2) return 'Good';
|
||||
if (latency < 200 && packetLoss < 5) return 'Fair';
|
||||
if (latency < 500 && packetLoss < 10) return 'Poor';
|
||||
return 'Very Poor';
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算网络统计平均值
|
||||
*/
|
||||
export function calculateNetworkAverage(values: number[], maxSamples = 100): number {
|
||||
if (values.length === 0) return 0;
|
||||
|
||||
// 保留最近的样本
|
||||
const samples = values.slice(-maxSamples);
|
||||
const sum = samples.reduce((acc, val) => acc + val, 0);
|
||||
return sum / samples.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证网络配置
|
||||
*/
|
||||
export function validateNetworkConfig(config: any): { valid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (typeof config.port !== 'number' || config.port <= 0 || config.port > 65535) {
|
||||
errors.push('Port must be a number between 1 and 65535');
|
||||
}
|
||||
|
||||
if (typeof config.host !== 'string' || config.host.length === 0) {
|
||||
errors.push('Host must be a non-empty string');
|
||||
}
|
||||
|
||||
if (typeof config.maxConnections !== 'number' || config.maxConnections <= 0) {
|
||||
errors.push('Max connections must be a positive number');
|
||||
}
|
||||
|
||||
if (typeof config.syncRate !== 'number' || config.syncRate <= 0) {
|
||||
errors.push('Sync rate must be a positive number');
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试函数
|
||||
*/
|
||||
export async function retry<T>(
|
||||
fn: () => Promise<T>,
|
||||
maxAttempts: number,
|
||||
delay: number = 1000
|
||||
): Promise<T> {
|
||||
let lastError: Error;
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
|
||||
if (attempt === maxAttempts) {
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, delay * attempt));
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError!;
|
||||
}
|
||||
5
packages/network-shared/src/utils/index.ts
Normal file
5
packages/network-shared/src/utils/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 工具函数导出
|
||||
*/
|
||||
|
||||
export * from './NetworkUtils';
|
||||
9
packages/network-shared/tests/setup.ts
Normal file
9
packages/network-shared/tests/setup.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
global.beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
global.afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
46
packages/network-shared/tsconfig.json
Normal file
46
packages/network-shared/tsconfig.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"allowImportingTsExtensions": false,
|
||||
"lib": ["ES2020", "DOM"],
|
||||
"outDir": "./bin",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"removeComments": false,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"importHelpers": false,
|
||||
"downlevelIteration": true,
|
||||
"isolatedModules": false,
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"bin",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"src/protocol/analyzer/**/*"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user