Feature/render pipeline (#232)
* refactor(engine): 重构2D渲染管线坐标系统 * feat(engine): 完善2D渲染管线和编辑器视口功能 * feat(editor): 实现Viewport变换工具系统 * feat(editor): 优化Inspector渲染性能并修复Gizmo变换工具显示 * feat(editor): 实现Run on Device移动预览功能 * feat(editor): 添加组件属性控制和依赖关系系统 * feat(editor): 实现动画预览功能和优化SpriteAnimator编辑器 * feat(editor): 修复SpriteAnimator动画预览功能并迁移CI到pnpm * feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm * feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm * feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm * feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm * feat(ci): 迁移项目到pnpm并修复CI构建问题 * chore: 迁移CI工作流到pnpm并添加WASM构建支持 * chore: 迁移CI工作流到pnpm并添加WASM构建支持 * chore: 迁移CI工作流到pnpm并添加WASM构建支持 * chore: 迁移CI工作流到pnpm并添加WASM构建支持 * chore: 迁移CI工作流到pnpm并添加WASM构建支持 * chore: 迁移CI工作流到pnpm并添加WASM构建支持 * chore: 移除 network 相关包 * chore: 移除 network 相关包
This commit is contained in:
@@ -1,124 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
console.log('🚀 使用 Rollup 构建 @esengine/network-shared 包...');
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// 清理旧的dist目录
|
||||
if (fs.existsSync('./dist')) {
|
||||
console.log('🧹 清理旧的构建文件...');
|
||||
execSync('rimraf ./dist', { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
// 执行Rollup构建
|
||||
console.log('📦 执行 Rollup 构建...');
|
||||
execSync('npx rollup -c rollup.config.cjs', { stdio: 'inherit' });
|
||||
|
||||
// 生成package.json
|
||||
console.log('📋 生成 package.json...');
|
||||
generatePackageJson();
|
||||
|
||||
// 复制其他文件
|
||||
console.log('📁 复制必要文件...');
|
||||
copyFiles();
|
||||
|
||||
// 输出构建结果
|
||||
showBuildResults();
|
||||
|
||||
console.log('✅ @esengine/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',
|
||||
'network',
|
||||
'multiplayer',
|
||||
'game',
|
||||
'shared',
|
||||
'typescript',
|
||||
'cocos-creator',
|
||||
'laya'
|
||||
],
|
||||
author: sourcePackage.author,
|
||||
license: sourcePackage.license,
|
||||
repository: sourcePackage.repository,
|
||||
peerDependencies: sourcePackage.peerDependencies,
|
||||
dependencies: sourcePackage.dependencies,
|
||||
publishConfig: sourcePackage.publishConfig,
|
||||
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);
|
||||
@@ -1,51 +0,0 @@
|
||||
/** @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: 10,
|
||||
functions: 20,
|
||||
lines: 25,
|
||||
statements: 25
|
||||
}
|
||||
},
|
||||
verbose: true,
|
||||
transform: {
|
||||
'^.+\\.tsx?$': ['ts-jest', {
|
||||
tsconfig: 'tsconfig.json',
|
||||
useESM: false,
|
||||
}],
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
'^@esengine/ecs-framework$': '<rootDir>/../core/src/index.ts',
|
||||
},
|
||||
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/'
|
||||
],
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(.*\\.mjs$|@esengine))'
|
||||
]
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"name": "@esengine/network-shared",
|
||||
"version": "1.0.2",
|
||||
"description": "ECS Framework网络层 - 共享组件和协议",
|
||||
"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",
|
||||
"network",
|
||||
"multiplayer",
|
||||
"game",
|
||||
"typescript",
|
||||
"shared"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf bin dist tsconfig.tsbuildinfo",
|
||||
"build:ts": "tsc --build",
|
||||
"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",
|
||||
"pretest:ci": "npm run build",
|
||||
"test:ci": "jest --ci --coverage --config jest.config.cjs"
|
||||
},
|
||||
"author": "yhh",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@esengine/ecs-framework": "file:../core",
|
||||
"reflect-metadata": "^0.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^20.19.0",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
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/network-shared v${pkg.version}
|
||||
* ECS网络层共享组件和协议
|
||||
*
|
||||
* @author ${pkg.author}
|
||||
* @license ${pkg.license}
|
||||
*/`;
|
||||
|
||||
// 外部依赖,不打包进bundle
|
||||
const external = ['@esengine/ecs-framework', 'reflect-metadata'];
|
||||
|
||||
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: {
|
||||
'@esengine/ecs-framework': 'ECS',
|
||||
'reflect-metadata': 'Reflect'
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
...commonPlugins,
|
||||
terser({
|
||||
format: {
|
||||
comments: /^!/
|
||||
}
|
||||
})
|
||||
],
|
||||
external,
|
||||
treeshake: {
|
||||
moduleSideEffects: false
|
||||
}
|
||||
},
|
||||
|
||||
// 类型定义构建
|
||||
{
|
||||
input: 'bin/index.d.ts',
|
||||
output: {
|
||||
file: 'dist/index.d.ts',
|
||||
format: 'es',
|
||||
banner: `/**
|
||||
* @esengine/network-shared v${pkg.version}
|
||||
* TypeScript definitions
|
||||
*/`
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
respectExternal: true
|
||||
})
|
||||
],
|
||||
external
|
||||
}
|
||||
];
|
||||
@@ -1,317 +0,0 @@
|
||||
/**
|
||||
* 网络身份组件
|
||||
*/
|
||||
import { Component, Emitter } from '@esengine/ecs-framework';
|
||||
import { AuthorityType, NetworkScope } from '../types/NetworkTypes';
|
||||
import {
|
||||
NetworkEventType,
|
||||
NetworkIdentityEventData,
|
||||
NetworkEventUtils
|
||||
} from '../events/NetworkEvents';
|
||||
|
||||
/**
|
||||
* 网络身份组件
|
||||
*
|
||||
* 为实体提供网络同步能力的核心组件。
|
||||
* 每个需要网络同步的实体都必须拥有此组件。
|
||||
*
|
||||
* 集成了事件系统,当属性变化时自动发射事件用于网络同步。
|
||||
*/
|
||||
export class NetworkIdentity extends Component {
|
||||
/**
|
||||
* 事件发射器
|
||||
* 用于发射网络相关事件
|
||||
*/
|
||||
private eventEmitter = new Emitter<NetworkEventType, NetworkIdentity>();
|
||||
/**
|
||||
* 网络ID (全局唯一)
|
||||
* 用于在网络中标识实体
|
||||
*/
|
||||
public networkId: number = 0;
|
||||
|
||||
/**
|
||||
* 拥有者ID
|
||||
* 表示哪个客户端拥有此实体的控制权
|
||||
*/
|
||||
public ownerId: string = '';
|
||||
|
||||
/**
|
||||
* 权限类型
|
||||
* 决定哪一端对此实体有控制权
|
||||
*/
|
||||
public authority: AuthorityType = AuthorityType.Server;
|
||||
|
||||
/**
|
||||
* 同步频率 (Hz)
|
||||
* 每秒同步的次数
|
||||
*/
|
||||
public syncRate: number = 20;
|
||||
|
||||
/**
|
||||
* 网络作用域
|
||||
* 决定哪些客户端可以看到此实体
|
||||
*/
|
||||
public scope: NetworkScope = NetworkScope.Room;
|
||||
|
||||
/**
|
||||
* 是否是本地玩家
|
||||
* 标识此实体是否代表本地玩家
|
||||
*/
|
||||
public isLocalPlayer: boolean = false;
|
||||
|
||||
/**
|
||||
* 是否启用网络同步
|
||||
* 临时禁用/启用同步
|
||||
*/
|
||||
public syncEnabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 同步优先级
|
||||
* 影响同步的顺序和频率,数值越高优先级越高
|
||||
*/
|
||||
public priority: number = 0;
|
||||
|
||||
/**
|
||||
* 距离阈值
|
||||
* 用于附近同步模式,超过此距离的客户端不会收到同步
|
||||
*/
|
||||
public distanceThreshold: number = 100;
|
||||
|
||||
/**
|
||||
* 最后同步时间
|
||||
* 记录上次同步的时间戳
|
||||
*/
|
||||
public lastSyncTime: number = 0;
|
||||
|
||||
/**
|
||||
* 是否可见
|
||||
* 控制实体是否对其他客户端可见
|
||||
*/
|
||||
public visible: boolean = true;
|
||||
|
||||
/**
|
||||
* 自定义同步过滤器
|
||||
* 用于自定义作用域的同步逻辑
|
||||
*/
|
||||
public customSyncFilter?: (clientId: string) => boolean;
|
||||
|
||||
/**
|
||||
* 获取实体的同步权重
|
||||
* 基于优先级和距离计算
|
||||
*/
|
||||
public getSyncWeight(distance?: number): number {
|
||||
let weight = this.priority;
|
||||
|
||||
if (distance !== undefined && this.scope === NetworkScope.Nearby) {
|
||||
// 距离越近权重越高
|
||||
const distanceFactor = Math.max(0, 1 - (distance / this.distanceThreshold));
|
||||
weight *= distanceFactor;
|
||||
}
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该同步给指定客户端
|
||||
*/
|
||||
public shouldSyncToClient(clientId: string, distance?: number): boolean {
|
||||
if (!this.syncEnabled || !this.visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (this.scope) {
|
||||
case NetworkScope.Global:
|
||||
return true;
|
||||
|
||||
case NetworkScope.Room:
|
||||
return true; // 由房间管理器控制
|
||||
|
||||
case NetworkScope.Owner:
|
||||
return clientId === this.ownerId;
|
||||
|
||||
case NetworkScope.Nearby:
|
||||
return distance !== undefined && distance <= this.distanceThreshold;
|
||||
|
||||
case NetworkScope.Custom:
|
||||
return this.customSyncFilter ? this.customSyncFilter(clientId) : false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查客户端是否有权限修改此实体
|
||||
*/
|
||||
public hasAuthority(clientId: string): boolean {
|
||||
switch (this.authority) {
|
||||
case AuthorityType.Server:
|
||||
return false; // 只有服务端有权限
|
||||
|
||||
case AuthorityType.Client:
|
||||
return clientId === this.ownerId;
|
||||
|
||||
case AuthorityType.Shared:
|
||||
return true; // 任何人都可以修改
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置拥有者
|
||||
*/
|
||||
public setOwner(clientId: string): void {
|
||||
const oldOwner = this.ownerId;
|
||||
this.ownerId = clientId;
|
||||
|
||||
// 发射拥有者变化事件
|
||||
this.emitEvent(
|
||||
NetworkEventType.IDENTITY_OWNER_CHANGED,
|
||||
NetworkEventUtils.createIdentityEventData(
|
||||
this.networkId,
|
||||
clientId,
|
||||
oldOwner,
|
||||
clientId
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置权限类型
|
||||
*/
|
||||
public setAuthority(authority: AuthorityType): void {
|
||||
const oldAuthority = this.authority;
|
||||
this.authority = authority;
|
||||
|
||||
// 发射权限变化事件
|
||||
this.emitEvent(
|
||||
NetworkEventType.IDENTITY_AUTHORITY_CHANGED,
|
||||
NetworkEventUtils.createIdentityEventData(
|
||||
this.networkId,
|
||||
this.ownerId,
|
||||
oldAuthority,
|
||||
authority
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置同步状态
|
||||
*/
|
||||
public setSyncEnabled(enabled: boolean): void {
|
||||
const oldEnabled = this.syncEnabled;
|
||||
this.syncEnabled = enabled;
|
||||
|
||||
// 发射同步状态变化事件
|
||||
const eventType = enabled
|
||||
? NetworkEventType.IDENTITY_SYNC_ENABLED
|
||||
: NetworkEventType.IDENTITY_SYNC_DISABLED;
|
||||
|
||||
this.emitEvent(
|
||||
eventType,
|
||||
NetworkEventUtils.createIdentityEventData(
|
||||
this.networkId,
|
||||
this.ownerId,
|
||||
oldEnabled,
|
||||
enabled
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置同步频率
|
||||
*/
|
||||
public setSyncRate(rate: number): void {
|
||||
const oldRate = this.syncRate;
|
||||
this.syncRate = rate;
|
||||
|
||||
// 发射同步频率变化事件
|
||||
this.emitEvent(
|
||||
NetworkEventType.SYNC_RATE_CHANGED,
|
||||
NetworkEventUtils.createIdentityEventData(
|
||||
this.networkId,
|
||||
this.ownerId,
|
||||
oldRate,
|
||||
rate
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
*/
|
||||
public addEventListener(eventType: NetworkEventType, handler: (data: NetworkIdentityEventData) => void): void {
|
||||
this.eventEmitter.addObserver(eventType, handler, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
*/
|
||||
public removeEventListener(eventType: NetworkEventType, handler: (data: NetworkIdentityEventData) => void): void {
|
||||
this.eventEmitter.removeObserver(eventType, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发射事件
|
||||
* @private
|
||||
*/
|
||||
private emitEvent(eventType: NetworkEventType, data: NetworkIdentityEventData): void {
|
||||
this.eventEmitter.emit(eventType, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听属性变化事件
|
||||
*/
|
||||
public onPropertyChanged(handler: (data: NetworkIdentityEventData) => void): void {
|
||||
this.addEventListener(NetworkEventType.IDENTITY_PROPERTY_CHANGED, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听拥有者变化事件
|
||||
*/
|
||||
public onOwnerChanged(handler: (data: NetworkIdentityEventData) => void): void {
|
||||
this.addEventListener(NetworkEventType.IDENTITY_OWNER_CHANGED, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听权限变化事件
|
||||
*/
|
||||
public onAuthorityChanged(handler: (data: NetworkIdentityEventData) => void): void {
|
||||
this.addEventListener(NetworkEventType.IDENTITY_AUTHORITY_CHANGED, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听同步状态变化事件
|
||||
*/
|
||||
public onSyncStateChanged(handler: (data: NetworkIdentityEventData) => void): void {
|
||||
this.addEventListener(NetworkEventType.IDENTITY_SYNC_ENABLED, handler);
|
||||
this.addEventListener(NetworkEventType.IDENTITY_SYNC_DISABLED, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调试信息
|
||||
*/
|
||||
public getDebugInfo(): Record<string, any> {
|
||||
return {
|
||||
networkId: this.networkId,
|
||||
ownerId: this.ownerId,
|
||||
authority: this.authority,
|
||||
scope: this.scope,
|
||||
syncRate: this.syncRate,
|
||||
priority: this.priority,
|
||||
syncEnabled: this.syncEnabled,
|
||||
visible: this.visible,
|
||||
lastSyncTime: this.lastSyncTime
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件销毁时清理事件监听器
|
||||
*/
|
||||
public dispose(): void {
|
||||
// 清理所有事件监听器
|
||||
this.eventEmitter.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
import 'reflect-metadata';
|
||||
import { RpcTarget } from '../types/NetworkTypes';
|
||||
import { RpcOptions, RpcMethodMetadata } from '../types/RpcTypes';
|
||||
|
||||
/**
|
||||
* RPC元数据键
|
||||
*/
|
||||
const RPC_METADATA_KEY = Symbol('rpc:metadata');
|
||||
const RPC_METHODS_KEY = Symbol('rpc:methods');
|
||||
|
||||
/**
|
||||
* 服务端RPC装饰器选项
|
||||
*/
|
||||
export interface ServerRpcOptions extends RpcOptions {
|
||||
/** 允许的调用者类型 */
|
||||
allowedCallers?: 'all' | 'authenticated' | 'admin';
|
||||
/** 最大并发调用数 */
|
||||
maxConcurrent?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端RPC装饰器选项
|
||||
*/
|
||||
export interface ClientRpcOptions extends RpcOptions {
|
||||
/** 广播到多个客户端时的聚合策略 */
|
||||
aggregationStrategy?: 'first' | 'all' | 'majority';
|
||||
/** 缓存策略 */
|
||||
cacheStrategy?: 'none' | 'memory' | 'persistent';
|
||||
/** 缓存有效期(毫秒) */
|
||||
cacheTTL?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端RPC装饰器
|
||||
* 标记方法可以被客户端调用
|
||||
*/
|
||||
export function ServerRpc(options: ServerRpcOptions = {}): MethodDecorator {
|
||||
return function (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
||||
const className = target.constructor.name;
|
||||
const methodName = String(propertyKey);
|
||||
|
||||
// 获取参数类型和返回值类型
|
||||
const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey) || [];
|
||||
const returnType = Reflect.getMetadata('design:returntype', target, propertyKey);
|
||||
|
||||
const metadata: RpcMethodMetadata = {
|
||||
methodName,
|
||||
className,
|
||||
isServerRpc: true,
|
||||
options: {
|
||||
reliable: true,
|
||||
priority: 5,
|
||||
target: RpcTarget.Server,
|
||||
timeout: 30000,
|
||||
requireAuth: false,
|
||||
rateLimit: 60,
|
||||
...options
|
||||
},
|
||||
paramTypes: paramTypes.map((type: unknown) => type?.constructor?.name || 'unknown'),
|
||||
returnType: returnType?.name || 'unknown'
|
||||
};
|
||||
|
||||
// 存储元数据
|
||||
Reflect.defineMetadata(RPC_METADATA_KEY, metadata, target, propertyKey);
|
||||
|
||||
// 添加到方法列表
|
||||
const existingMethods = Reflect.getMetadata(RPC_METHODS_KEY, target.constructor) || [];
|
||||
existingMethods.push(methodName);
|
||||
Reflect.defineMetadata(RPC_METHODS_KEY, existingMethods, target.constructor);
|
||||
|
||||
// 包装原方法以添加验证和日志
|
||||
const originalMethod = descriptor.value;
|
||||
descriptor.value = async function (...args: unknown[]) {
|
||||
// 这里可以添加调用前的验证逻辑
|
||||
try {
|
||||
const result = await originalMethod.apply(this, args);
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 这里可以添加错误处理逻辑
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端RPC装饰器
|
||||
* 标记方法可以调用到客户端
|
||||
*/
|
||||
export function ClientRpc(options: ClientRpcOptions = {}): MethodDecorator {
|
||||
return function (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
||||
const className = target.constructor.name;
|
||||
const methodName = String(propertyKey);
|
||||
|
||||
// 获取参数类型和返回值类型
|
||||
const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey) || [];
|
||||
const returnType = Reflect.getMetadata('design:returntype', target, propertyKey);
|
||||
|
||||
const metadata: RpcMethodMetadata = {
|
||||
methodName,
|
||||
className,
|
||||
isServerRpc: false,
|
||||
options: {
|
||||
reliable: false,
|
||||
priority: 3,
|
||||
target: RpcTarget.All,
|
||||
timeout: 10000,
|
||||
requireAuth: false,
|
||||
rateLimit: 30,
|
||||
...options
|
||||
},
|
||||
paramTypes: paramTypes.map((type: unknown) => type?.constructor?.name || 'unknown'),
|
||||
returnType: returnType?.name || 'unknown'
|
||||
};
|
||||
|
||||
// 存储元数据
|
||||
Reflect.defineMetadata(RPC_METADATA_KEY, metadata, target, propertyKey);
|
||||
|
||||
// 添加到方法列表
|
||||
const existingMethods = Reflect.getMetadata(RPC_METHODS_KEY, target.constructor) || [];
|
||||
existingMethods.push(methodName);
|
||||
Reflect.defineMetadata(RPC_METHODS_KEY, existingMethods, target.constructor);
|
||||
|
||||
// 包装原方法以添加调用逻辑
|
||||
const originalMethod = descriptor.value;
|
||||
descriptor.value = async function (...args: unknown[]) {
|
||||
// 这里将被RPC调用器替换
|
||||
// 目前保持原方法以支持本地测试
|
||||
return originalMethod.apply(this, args);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的所有RPC方法元数据
|
||||
*/
|
||||
export function getRpcMethods(target: Function): RpcMethodMetadata[] {
|
||||
const methodNames = Reflect.getMetadata(RPC_METHODS_KEY, target) || [];
|
||||
const prototype = target.prototype;
|
||||
|
||||
return methodNames.map((methodName: string) => {
|
||||
const metadata = Reflect.getMetadata(RPC_METADATA_KEY, prototype, methodName);
|
||||
if (!metadata) {
|
||||
throw new Error(`RPC元数据未找到: ${target.name}.${methodName}`);
|
||||
}
|
||||
return metadata;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取特定方法的RPC元数据
|
||||
*/
|
||||
export function getRpcMethodMetadata(
|
||||
target: object,
|
||||
methodName: string | symbol
|
||||
): RpcMethodMetadata | undefined {
|
||||
return Reflect.getMetadata(RPC_METADATA_KEY, target, methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查方法是否为RPC方法
|
||||
*/
|
||||
export function isRpcMethod(target: object, methodName: string | symbol): boolean {
|
||||
return Reflect.hasMetadata(RPC_METADATA_KEY, target, methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查方法是否为服务端RPC
|
||||
*/
|
||||
export function isServerRpcMethod(target: object, methodName: string | symbol): boolean {
|
||||
const metadata = getRpcMethodMetadata(target, methodName);
|
||||
return metadata?.isServerRpc === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查方法是否为客户端RPC
|
||||
*/
|
||||
export function isClientRpcMethod(target: object, methodName: string | symbol): boolean {
|
||||
const metadata = getRpcMethodMetadata(target, methodName);
|
||||
return metadata?.isServerRpc === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC方法验证器
|
||||
*/
|
||||
export class RpcMethodValidator {
|
||||
/**
|
||||
* 验证RPC方法调用参数
|
||||
*/
|
||||
static validateCall(
|
||||
metadata: RpcMethodMetadata,
|
||||
args: unknown[],
|
||||
callerId?: string
|
||||
): { valid: boolean; error?: string } {
|
||||
// 参数数量检查
|
||||
if (args.length !== metadata.paramTypes.length) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `参数数量不匹配,期望 ${metadata.paramTypes.length} 个,实际 ${args.length} 个`
|
||||
};
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
if (metadata.options.requireAuth && !callerId) {
|
||||
return {
|
||||
valid: false,
|
||||
error: '该方法需要身份验证'
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证RPC方法定义
|
||||
*/
|
||||
static validateMethodDefinition(metadata: RpcMethodMetadata): { valid: boolean; error?: string } {
|
||||
// 方法名检查
|
||||
if (!metadata.methodName || typeof metadata.methodName !== 'string') {
|
||||
return {
|
||||
valid: false,
|
||||
error: '方法名无效'
|
||||
};
|
||||
}
|
||||
|
||||
// 超时时间检查
|
||||
if (metadata.options.timeout && metadata.options.timeout <= 0) {
|
||||
return {
|
||||
valid: false,
|
||||
error: '超时时间必须大于0'
|
||||
};
|
||||
}
|
||||
|
||||
// 优先级检查
|
||||
if (metadata.options.priority !== undefined &&
|
||||
(metadata.options.priority < 0 || metadata.options.priority > 10)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: '优先级必须在0-10之间'
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
import { SyncMode, AuthorityType, NetworkScope } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* SyncVar支持的值类型
|
||||
*/
|
||||
export type SyncVarValue = string | number | boolean | object | null | undefined;
|
||||
|
||||
/**
|
||||
* SyncVar配置选项
|
||||
*/
|
||||
export interface SyncVarOptions<T = SyncVarValue> {
|
||||
/** 同步模式 */
|
||||
mode?: SyncMode;
|
||||
/** 同步频率(毫秒) */
|
||||
syncRate?: number;
|
||||
/** 权限类型 */
|
||||
authority?: AuthorityType;
|
||||
/** 网络作用域 */
|
||||
scope?: NetworkScope;
|
||||
/** 变化阈值,用于数值类型 */
|
||||
threshold?: number;
|
||||
/** 是否启用压缩 */
|
||||
compression?: boolean;
|
||||
/** 优先级(0-10,数值越高优先级越高) */
|
||||
priority?: number;
|
||||
/** 是否启用插值 */
|
||||
interpolation?: boolean;
|
||||
/** 自定义序列化函数 */
|
||||
customSerializer?: (value: T) => unknown;
|
||||
/** 自定义反序列化函数 */
|
||||
customDeserializer?: (value: unknown) => T;
|
||||
/** 变化回调函数 */
|
||||
onChanged?: (oldValue: T, newValue: T) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar元数据
|
||||
*/
|
||||
export interface SyncVarMetadata<T = SyncVarValue> {
|
||||
propertyKey: string | symbol;
|
||||
options: Required<SyncVarOptions<T>>;
|
||||
originalDescriptor?: PropertyDescriptor;
|
||||
lastValue?: T;
|
||||
lastSyncTime: number;
|
||||
isDirty: boolean;
|
||||
syncCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储SyncVar元数据的Symbol键
|
||||
*/
|
||||
export const SYNCVAR_METADATA_KEY = Symbol('SyncVarMetadata');
|
||||
|
||||
/**
|
||||
* 默认SyncVar配置
|
||||
*/
|
||||
export const DEFAULT_SYNCVAR_OPTIONS: Required<SyncVarOptions<SyncVarValue>> = {
|
||||
mode: SyncMode.All,
|
||||
syncRate: 100,
|
||||
authority: AuthorityType.Server,
|
||||
scope: NetworkScope.Global,
|
||||
threshold: 0.001,
|
||||
compression: true,
|
||||
priority: 5,
|
||||
interpolation: false,
|
||||
customSerializer: (value: SyncVarValue) => value,
|
||||
customDeserializer: (value: unknown) => value as SyncVarValue,
|
||||
onChanged: () => {}
|
||||
};
|
||||
|
||||
/**
|
||||
* SyncVar装饰器
|
||||
* 用于标记需要网络同步的属性
|
||||
*/
|
||||
export function SyncVar<T extends SyncVarValue = SyncVarValue>(options: SyncVarOptions<T> = {}) {
|
||||
return function (target: object, propertyKey: string | symbol) {
|
||||
const fullOptions = { ...DEFAULT_SYNCVAR_OPTIONS, ...options } as Required<SyncVarOptions<T>>;
|
||||
|
||||
// 获取或创建元数据存储
|
||||
if (!(target.constructor as any)[SYNCVAR_METADATA_KEY]) {
|
||||
(target.constructor as any)[SYNCVAR_METADATA_KEY] = new Map();
|
||||
}
|
||||
|
||||
const metadataMap = (target.constructor as any)[SYNCVAR_METADATA_KEY] as Map<string | symbol, SyncVarMetadata<T>>;
|
||||
|
||||
// 创建元数据
|
||||
const metadata: SyncVarMetadata<T> = {
|
||||
propertyKey,
|
||||
options: fullOptions,
|
||||
lastSyncTime: 0,
|
||||
isDirty: false,
|
||||
syncCount: 0
|
||||
};
|
||||
|
||||
metadataMap.set(propertyKey, metadata);
|
||||
|
||||
// 获取原始属性描述符
|
||||
const originalDescriptor = Object.getOwnPropertyDescriptor(target, propertyKey) || {
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
};
|
||||
|
||||
metadata.originalDescriptor = originalDescriptor;
|
||||
|
||||
// 创建内部存储属性名
|
||||
const internalPropertyKey = Symbol(`_syncVar_${String(propertyKey)}`);
|
||||
|
||||
// 重新定义属性,添加变化检测
|
||||
Object.defineProperty(target, propertyKey, {
|
||||
get: function() {
|
||||
return (this as any)[internalPropertyKey];
|
||||
},
|
||||
set: function(newValue: T) {
|
||||
const oldValue = (this as any)[internalPropertyKey];
|
||||
|
||||
// 检查是否真的发生了变化
|
||||
if (!hasValueChanged(oldValue, newValue, fullOptions.threshold)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新值
|
||||
(this as any)[internalPropertyKey] = newValue;
|
||||
|
||||
// 标记为脏数据
|
||||
markAsDirty(this, propertyKey);
|
||||
|
||||
// 触发变化回调
|
||||
if (fullOptions.onChanged) {
|
||||
fullOptions.onChanged(oldValue, newValue);
|
||||
}
|
||||
|
||||
// 如果启用了自动同步,立即同步
|
||||
if (fullOptions.syncRate === 0) {
|
||||
requestImmediateSync(this, propertyKey);
|
||||
}
|
||||
},
|
||||
enumerable: originalDescriptor.enumerable,
|
||||
configurable: originalDescriptor.configurable
|
||||
});
|
||||
|
||||
// 设置初始值
|
||||
if (originalDescriptor.value !== undefined) {
|
||||
(target as any)[internalPropertyKey] = originalDescriptor.value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查值是否发生了变化
|
||||
*/
|
||||
function hasValueChanged(oldValue: SyncVarValue, newValue: SyncVarValue, threshold: number): boolean {
|
||||
// 严格相等检查
|
||||
if (oldValue === newValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// null/undefined 检查
|
||||
if (oldValue == null || newValue == null) {
|
||||
return oldValue !== newValue;
|
||||
}
|
||||
|
||||
// 数值类型的阈值检查
|
||||
if (typeof oldValue === 'number' && typeof newValue === 'number') {
|
||||
return Math.abs(oldValue - newValue) > threshold;
|
||||
}
|
||||
|
||||
// 对象类型的深度比较
|
||||
if (typeof oldValue === 'object' && typeof newValue === 'object') {
|
||||
return !deepEqual(oldValue, newValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度比较两个对象是否相等
|
||||
*/
|
||||
function deepEqual(obj1: SyncVarValue, obj2: SyncVarValue): boolean {
|
||||
if (obj1 === obj2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj1 == null || obj2 == null) {
|
||||
return obj1 === obj2;
|
||||
}
|
||||
|
||||
if (typeof obj1 !== typeof obj2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof obj1 !== 'object') {
|
||||
return obj1 === obj2;
|
||||
}
|
||||
|
||||
const keys1 = Object.keys(obj1);
|
||||
const keys2 = Object.keys(obj2);
|
||||
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const key of keys1) {
|
||||
if (!keys2.includes(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!deepEqual((obj1 as any)[key], (obj2 as any)[key])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记属性为脏数据
|
||||
*/
|
||||
function markAsDirty(instance: object, propertyKey: string | symbol): void {
|
||||
const metadataMap = (instance.constructor as any)[SYNCVAR_METADATA_KEY] as Map<string | symbol, SyncVarMetadata>;
|
||||
const metadata = metadataMap?.get(propertyKey);
|
||||
|
||||
if (metadata) {
|
||||
metadata.isDirty = true;
|
||||
metadata.lastValue = (instance as any)[propertyKey];
|
||||
}
|
||||
|
||||
// 通知SyncVar管理器
|
||||
if (typeof window !== 'undefined' && (window as any).SyncVarManager) {
|
||||
(window as any).SyncVarManager.markInstanceDirty(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求立即同步
|
||||
*/
|
||||
function requestImmediateSync(instance: object, propertyKey: string | symbol): void {
|
||||
// 通知SyncVar管理器立即同步
|
||||
if (typeof window !== 'undefined' && (window as any).SyncVarManager) {
|
||||
(window as any).SyncVarManager.requestImmediateSync(instance, propertyKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象的所有SyncVar元数据
|
||||
*/
|
||||
export function getSyncVarMetadata(target: object): Map<string | symbol, SyncVarMetadata> {
|
||||
if (!target || !target.constructor) {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
return (target.constructor as any)[SYNCVAR_METADATA_KEY] || new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取特定属性的SyncVar元数据
|
||||
*/
|
||||
export function getSyncVarPropertyMetadata(target: object, propertyKey: string | symbol): SyncVarMetadata | undefined {
|
||||
const metadataMap = getSyncVarMetadata(target);
|
||||
return metadataMap.get(propertyKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查对象是否有SyncVar属性
|
||||
*/
|
||||
export function hasSyncVars(target: object): boolean {
|
||||
const metadataMap = getSyncVarMetadata(target);
|
||||
return metadataMap.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象的所有脏SyncVar属性
|
||||
*/
|
||||
export function getDirtySyncVars(target: object): Map<string | symbol, SyncVarMetadata> {
|
||||
const metadataMap = getSyncVarMetadata(target);
|
||||
const dirtyVars = new Map<string | symbol, SyncVarMetadata>();
|
||||
|
||||
for (const [key, metadata] of metadataMap) {
|
||||
if (metadata.isDirty) {
|
||||
dirtyVars.set(key, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
return dirtyVars;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有脏标记
|
||||
*/
|
||||
export function clearDirtyFlags(target: object): void {
|
||||
const metadataMap = getSyncVarMetadata(target);
|
||||
|
||||
for (const metadata of metadataMap.values()) {
|
||||
metadata.isDirty = false;
|
||||
metadata.lastSyncTime = Date.now();
|
||||
metadata.syncCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置SyncVar统计信息
|
||||
*/
|
||||
export function resetSyncVarStats(target: object): void {
|
||||
const metadataMap = getSyncVarMetadata(target);
|
||||
|
||||
for (const metadata of metadataMap.values()) {
|
||||
metadata.syncCount = 0;
|
||||
metadata.lastSyncTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SyncVar统计信息
|
||||
*/
|
||||
export function getSyncVarStats(target: object): { [key: string]: { syncCount: number; lastSyncTime: number; isDirty: boolean } } {
|
||||
const metadataMap = getSyncVarMetadata(target);
|
||||
const stats: { [key: string]: { syncCount: number; lastSyncTime: number; isDirty: boolean } } = {};
|
||||
|
||||
for (const [key, metadata] of metadataMap) {
|
||||
stats[String(key)] = {
|
||||
syncCount: metadata.syncCount,
|
||||
lastSyncTime: metadata.lastSyncTime,
|
||||
isDirty: metadata.isDirty
|
||||
};
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* 网络装饰器导出
|
||||
*/
|
||||
|
||||
export * from './SyncVar';
|
||||
export * from './RpcDecorators';
|
||||
@@ -1,287 +0,0 @@
|
||||
/**
|
||||
* 网络事件类型枚举
|
||||
* 定义网络层中的所有事件类型
|
||||
*/
|
||||
export enum NetworkEventType {
|
||||
// 连接相关事件
|
||||
CONNECTION_ESTABLISHED = 'network:connection:established',
|
||||
CONNECTION_LOST = 'network:connection:lost',
|
||||
CONNECTION_ERROR = 'network:connection:error',
|
||||
CONNECTION_TIMEOUT = 'network:connection:timeout',
|
||||
RECONNECTION_STARTED = 'network:reconnection:started',
|
||||
RECONNECTION_SUCCEEDED = 'network:reconnection:succeeded',
|
||||
RECONNECTION_FAILED = 'network:reconnection:failed',
|
||||
|
||||
// 网络身份相关事件
|
||||
IDENTITY_CREATED = 'network:identity:created',
|
||||
IDENTITY_DESTROYED = 'network:identity:destroyed',
|
||||
IDENTITY_OWNER_CHANGED = 'network:identity:owner:changed',
|
||||
IDENTITY_AUTHORITY_CHANGED = 'network:identity:authority:changed',
|
||||
IDENTITY_SYNC_ENABLED = 'network:identity:sync:enabled',
|
||||
IDENTITY_SYNC_DISABLED = 'network:identity:sync:disabled',
|
||||
IDENTITY_PROPERTY_CHANGED = 'network:identity:property:changed',
|
||||
IDENTITY_VISIBLE_CHANGED = 'network:identity:visible:changed',
|
||||
|
||||
// 同步相关事件
|
||||
SYNC_STARTED = 'network:sync:started',
|
||||
SYNC_COMPLETED = 'network:sync:completed',
|
||||
SYNC_FAILED = 'network:sync:failed',
|
||||
SYNC_RATE_CHANGED = 'network:sync:rate:changed',
|
||||
SYNC_PRIORITY_CHANGED = 'network:sync:priority:changed',
|
||||
|
||||
// RPC相关事件
|
||||
RPC_CALL_SENT = 'network:rpc:call:sent',
|
||||
RPC_CALL_RECEIVED = 'network:rpc:call:received',
|
||||
RPC_RESPONSE_SENT = 'network:rpc:response:sent',
|
||||
RPC_RESPONSE_RECEIVED = 'network:rpc:response:received',
|
||||
RPC_ERROR = 'network:rpc:error',
|
||||
RPC_TIMEOUT = 'network:rpc:timeout',
|
||||
|
||||
// 消息相关事件
|
||||
MESSAGE_SENT = 'network:message:sent',
|
||||
MESSAGE_RECEIVED = 'network:message:received',
|
||||
MESSAGE_QUEUED = 'network:message:queued',
|
||||
MESSAGE_DROPPED = 'network:message:dropped',
|
||||
MESSAGE_RETRY = 'network:message:retry',
|
||||
MESSAGE_ACKNOWLEDGED = 'network:message:acknowledged',
|
||||
|
||||
// 房间相关事件
|
||||
ROOM_JOINED = 'network:room:joined',
|
||||
ROOM_LEFT = 'network:room:left',
|
||||
ROOM_CREATED = 'network:room:created',
|
||||
ROOM_DESTROYED = 'network:room:destroyed',
|
||||
ROOM_PLAYER_JOINED = 'network:room:player:joined',
|
||||
ROOM_PLAYER_LEFT = 'network:room:player:left',
|
||||
|
||||
// 客户端相关事件
|
||||
CLIENT_CONNECTED = 'network:client:connected',
|
||||
CLIENT_DISCONNECTED = 'network:client:disconnected',
|
||||
CLIENT_AUTHENTICATED = 'network:client:authenticated',
|
||||
CLIENT_KICKED = 'network:client:kicked',
|
||||
CLIENT_TIMEOUT = 'network:client:timeout',
|
||||
|
||||
// 服务器相关事件
|
||||
SERVER_STARTED = 'network:server:started',
|
||||
SERVER_STOPPED = 'network:server:stopped',
|
||||
SERVER_ERROR = 'network:server:error',
|
||||
SERVER_OVERLOADED = 'network:server:overloaded',
|
||||
|
||||
// 数据相关事件
|
||||
DATA_SYNCHRONIZED = 'network:data:synchronized',
|
||||
DATA_CONFLICT = 'network:data:conflict',
|
||||
DATA_CORRUPTED = 'network:data:corrupted',
|
||||
DATA_VALIDATED = 'network:data:validated',
|
||||
|
||||
// 性能相关事件
|
||||
BANDWIDTH_WARNING = 'network:bandwidth:warning',
|
||||
LATENCY_HIGH = 'network:latency:high',
|
||||
PACKET_LOSS_DETECTED = 'network:packet:loss:detected',
|
||||
PERFORMANCE_DEGRADED = 'network:performance:degraded'
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络事件优先级
|
||||
*/
|
||||
export enum NetworkEventPriority {
|
||||
LOW = 10,
|
||||
NORMAL = 20,
|
||||
HIGH = 30,
|
||||
CRITICAL = 40,
|
||||
EMERGENCY = 50
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络事件数据基础接口
|
||||
*/
|
||||
export interface NetworkEventData {
|
||||
timestamp: number;
|
||||
networkId?: number;
|
||||
clientId?: string;
|
||||
roomId?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络身份事件数据
|
||||
*/
|
||||
export interface NetworkIdentityEventData extends NetworkEventData {
|
||||
networkId: number;
|
||||
ownerId: string;
|
||||
oldValue?: any;
|
||||
newValue?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC事件数据
|
||||
*/
|
||||
export interface RpcEventData extends NetworkEventData {
|
||||
rpcId: string;
|
||||
methodName: string;
|
||||
parameters?: any[];
|
||||
result?: any;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息事件数据
|
||||
*/
|
||||
export interface MessageEventData extends NetworkEventData {
|
||||
messageId: string;
|
||||
messageType: string;
|
||||
payload: any;
|
||||
reliable: boolean;
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接事件数据
|
||||
*/
|
||||
export interface ConnectionEventData extends NetworkEventData {
|
||||
clientId: string;
|
||||
address?: string;
|
||||
reason?: string;
|
||||
reconnectAttempt?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间事件数据
|
||||
*/
|
||||
export interface RoomEventData extends NetworkEventData {
|
||||
roomId: string;
|
||||
playerId?: string;
|
||||
playerCount?: number;
|
||||
maxPlayers?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能事件数据
|
||||
*/
|
||||
export interface PerformanceEventData extends NetworkEventData {
|
||||
metric: string;
|
||||
value: number;
|
||||
threshold?: number;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络事件工具类
|
||||
*/
|
||||
export class NetworkEventUtils {
|
||||
/**
|
||||
* 创建网络身份事件数据
|
||||
*/
|
||||
static createIdentityEventData(
|
||||
networkId: number,
|
||||
ownerId: string,
|
||||
oldValue?: any,
|
||||
newValue?: any
|
||||
): NetworkIdentityEventData {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
networkId,
|
||||
ownerId,
|
||||
oldValue,
|
||||
newValue
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建RPC事件数据
|
||||
*/
|
||||
static createRpcEventData(
|
||||
rpcId: string,
|
||||
methodName: string,
|
||||
clientId?: string,
|
||||
parameters?: any[],
|
||||
result?: any,
|
||||
error?: string
|
||||
): RpcEventData {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
clientId,
|
||||
rpcId,
|
||||
methodName,
|
||||
parameters,
|
||||
result,
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建消息事件数据
|
||||
*/
|
||||
static createMessageEventData(
|
||||
messageId: string,
|
||||
messageType: string,
|
||||
payload: any,
|
||||
reliable: boolean = true,
|
||||
clientId?: string
|
||||
): MessageEventData {
|
||||
const size = JSON.stringify(payload).length;
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
clientId,
|
||||
messageId,
|
||||
messageType,
|
||||
payload,
|
||||
reliable,
|
||||
size
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建连接事件数据
|
||||
*/
|
||||
static createConnectionEventData(
|
||||
clientId: string,
|
||||
address?: string,
|
||||
reason?: string,
|
||||
reconnectAttempt?: number
|
||||
): ConnectionEventData {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
clientId,
|
||||
address,
|
||||
reason,
|
||||
reconnectAttempt
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建房间事件数据
|
||||
*/
|
||||
static createRoomEventData(
|
||||
roomId: string,
|
||||
playerId?: string,
|
||||
playerCount?: number,
|
||||
maxPlayers?: number
|
||||
): RoomEventData {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
roomId,
|
||||
playerId,
|
||||
playerCount,
|
||||
maxPlayers
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建性能事件数据
|
||||
*/
|
||||
static createPerformanceEventData(
|
||||
metric: string,
|
||||
value: number,
|
||||
threshold?: number,
|
||||
duration?: number,
|
||||
clientId?: string
|
||||
): PerformanceEventData {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
clientId,
|
||||
metric,
|
||||
value,
|
||||
threshold,
|
||||
duration
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './NetworkEvents';
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* @esengine/network-shared
|
||||
* ECS Framework网络层 - 共享组件和协议
|
||||
*/
|
||||
|
||||
// 类型定义
|
||||
export * from './types/NetworkTypes';
|
||||
export * from './types/TransportTypes';
|
||||
export * from './types/RpcTypes';
|
||||
|
||||
// 协议消息
|
||||
export * from './protocols/MessageTypes';
|
||||
export * from './protocols/MessageManager';
|
||||
|
||||
// 核心组件
|
||||
export * from './components/NetworkIdentity';
|
||||
|
||||
// 传输层
|
||||
export * from './transport/HeartbeatManager';
|
||||
export * from './transport/ErrorHandler';
|
||||
|
||||
// 事件系统
|
||||
export * from './events/NetworkEvents';
|
||||
|
||||
// 序列化系统
|
||||
export * from './serialization/JSONSerializer';
|
||||
export * from './serialization/MessageCompressor';
|
||||
export {
|
||||
SyncVarSerializer,
|
||||
SyncVarSerializerConfig,
|
||||
SerializationResult as SyncVarSerializationResult,
|
||||
DeserializationResult as SyncVarDeserializationResult,
|
||||
DeltaData as SyncVarDeltaData,
|
||||
CompressionMetadata
|
||||
} from './serialization/SyncVarSerializer';
|
||||
|
||||
// 装饰器系统
|
||||
export * from './decorators';
|
||||
|
||||
// RPC系统
|
||||
export * from './rpc/RpcMetadataManager';
|
||||
export * from './rpc/RpcCallHandler';
|
||||
export * from './rpc/RpcCallProxy';
|
||||
export * from './rpc/RpcReliabilityManager';
|
||||
|
||||
// 同步系统
|
||||
export { SyncVarManager, SyncBatch } from './sync/SyncVarManager';
|
||||
export {
|
||||
DeltaSync,
|
||||
DeltaSyncConfig,
|
||||
DeltaData,
|
||||
DeltaOperationType,
|
||||
DeltaOperation,
|
||||
VersionedData,
|
||||
DeltaSyncStats
|
||||
} from './sync/DeltaSync';
|
||||
|
||||
// 监控系统
|
||||
export * from './monitoring';
|
||||
|
||||
// 工具类
|
||||
export * from './utils';
|
||||
@@ -1,541 +0,0 @@
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { EventEmitter } from '../utils/EventEmitter';
|
||||
|
||||
/**
|
||||
* 带宽监控配置
|
||||
*/
|
||||
export interface BandwidthMonitorConfig {
|
||||
/** 监控间隔(毫秒) */
|
||||
monitorInterval: number;
|
||||
/** 采样窗口大小 */
|
||||
sampleWindowSize: number;
|
||||
/** 预警阈值(0-1) */
|
||||
warningThreshold: number;
|
||||
/** 严重阈值(0-1) */
|
||||
criticalThreshold: number;
|
||||
/** 是否启用自适应调整 */
|
||||
enableAdaptive: boolean;
|
||||
/** 自适应调整因子 */
|
||||
adaptiveFactor: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带宽样本
|
||||
*/
|
||||
export interface BandwidthSample {
|
||||
timestamp: number;
|
||||
bytesIn: number;
|
||||
bytesOut: number;
|
||||
packetsIn: number;
|
||||
packetsOut: number;
|
||||
latency: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带宽统计
|
||||
*/
|
||||
export interface BandwidthStats {
|
||||
/** 当前上行带宽(bytes/s) */
|
||||
currentUpload: number;
|
||||
/** 当前下行带宽(bytes/s) */
|
||||
currentDownload: number;
|
||||
/** 平均上行带宽(bytes/s) */
|
||||
averageUpload: number;
|
||||
/** 平均下行带宽(bytes/s) */
|
||||
averageDownload: number;
|
||||
/** 峰值上行带宽(bytes/s) */
|
||||
peakUpload: number;
|
||||
/** 峰值下行带宽(bytes/s) */
|
||||
peakDownload: number;
|
||||
/** 总上传字节数 */
|
||||
totalUpload: number;
|
||||
/** 总下载字节数 */
|
||||
totalDownload: number;
|
||||
/** 当前包速率(packets/s) */
|
||||
currentPacketRate: number;
|
||||
/** 平均延迟(ms) */
|
||||
averageLatency: number;
|
||||
/** 延迟抖动(ms) */
|
||||
latencyJitter: number;
|
||||
/** 利用率(0-1) */
|
||||
utilization: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带宽限制
|
||||
*/
|
||||
export interface BandwidthLimit {
|
||||
/** 上行限制(bytes/s) */
|
||||
uploadLimit: number;
|
||||
/** 下行限制(bytes/s) */
|
||||
downloadLimit: number;
|
||||
/** 是否启用限制 */
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带宽警告级别
|
||||
*/
|
||||
export enum BandwidthWarningLevel {
|
||||
Normal = 'normal',
|
||||
Warning = 'warning',
|
||||
Critical = 'critical'
|
||||
}
|
||||
|
||||
/**
|
||||
* 带宽事件
|
||||
*/
|
||||
export interface BandwidthMonitorEvents {
|
||||
bandwidthChanged: (stats: BandwidthStats) => void;
|
||||
limitExceeded: (direction: 'upload' | 'download', current: number, limit: number) => void;
|
||||
warningLevelChanged: (level: BandwidthWarningLevel, stats: BandwidthStats) => void;
|
||||
adaptiveAdjustment: (oldLimits: BandwidthLimit, newLimits: BandwidthLimit) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带宽监控器
|
||||
* 负责监控网络带宽使用情况并提供自适应调整
|
||||
*/
|
||||
export class BandwidthMonitor extends EventEmitter {
|
||||
private logger = createLogger('BandwidthMonitor');
|
||||
private config: BandwidthMonitorConfig;
|
||||
|
||||
/** 带宽样本历史 */
|
||||
private samples: BandwidthSample[] = [];
|
||||
|
||||
/** 当前带宽限制 */
|
||||
private limits: BandwidthLimit;
|
||||
|
||||
/** 当前警告级别 */
|
||||
private currentWarningLevel = BandwidthWarningLevel.Normal;
|
||||
|
||||
/** 监控定时器 */
|
||||
private monitorTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
/** 统计信息 */
|
||||
private stats: BandwidthStats = {
|
||||
currentUpload: 0,
|
||||
currentDownload: 0,
|
||||
averageUpload: 0,
|
||||
averageDownload: 0,
|
||||
peakUpload: 0,
|
||||
peakDownload: 0,
|
||||
totalUpload: 0,
|
||||
totalDownload: 0,
|
||||
currentPacketRate: 0,
|
||||
averageLatency: 0,
|
||||
latencyJitter: 0,
|
||||
utilization: 0
|
||||
};
|
||||
|
||||
/** 上次统计时间 */
|
||||
private lastStatsTime = Date.now();
|
||||
|
||||
/** 累计字节数 */
|
||||
private cumulativeBytesIn = 0;
|
||||
private cumulativeBytesOut = 0;
|
||||
private cumulativePacketsIn = 0;
|
||||
private cumulativePacketsOut = 0;
|
||||
|
||||
constructor(config: Partial<BandwidthMonitorConfig> = {}) {
|
||||
super();
|
||||
|
||||
this.config = {
|
||||
monitorInterval: 1000,
|
||||
sampleWindowSize: 60,
|
||||
warningThreshold: 0.8,
|
||||
criticalThreshold: 0.95,
|
||||
enableAdaptive: true,
|
||||
adaptiveFactor: 0.1,
|
||||
...config
|
||||
};
|
||||
|
||||
this.limits = {
|
||||
uploadLimit: 1024 * 1024, // 1MB/s
|
||||
downloadLimit: 1024 * 1024, // 1MB/s
|
||||
enabled: false
|
||||
};
|
||||
|
||||
this.startMonitoring();
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录网络活动
|
||||
*/
|
||||
public recordActivity(bytesIn: number, bytesOut: number, packetsIn: number = 0, packetsOut: number = 0, latency: number = 0): void {
|
||||
this.cumulativeBytesIn += bytesIn;
|
||||
this.cumulativeBytesOut += bytesOut;
|
||||
this.cumulativePacketsIn += packetsIn;
|
||||
this.cumulativePacketsOut += packetsOut;
|
||||
|
||||
this.stats.totalUpload += bytesOut;
|
||||
this.stats.totalDownload += bytesIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置带宽限制
|
||||
*/
|
||||
public setBandwidthLimits(limits: Partial<BandwidthLimit>): void {
|
||||
const oldLimits = { ...this.limits };
|
||||
Object.assign(this.limits, limits);
|
||||
|
||||
this.logger.info(`带宽限制已更新: 上行=${this.limits.uploadLimit}B/s, 下行=${this.limits.downloadLimit}B/s`);
|
||||
|
||||
if (this.config.enableAdaptive) {
|
||||
this.emit('adaptiveAdjustment', oldLimits, this.limits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前统计信息
|
||||
*/
|
||||
public getStats(): BandwidthStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前限制
|
||||
*/
|
||||
public getLimits(): BandwidthLimit {
|
||||
return { ...this.limits };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前警告级别
|
||||
*/
|
||||
public getWarningLevel(): BandwidthWarningLevel {
|
||||
return this.currentWarningLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否超过限制
|
||||
*/
|
||||
public isOverLimit(): { upload: boolean; download: boolean } {
|
||||
if (!this.limits.enabled) {
|
||||
return { upload: false, download: false };
|
||||
}
|
||||
|
||||
return {
|
||||
upload: this.stats.currentUpload > this.limits.uploadLimit,
|
||||
download: this.stats.currentDownload > this.limits.downloadLimit
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取建议的数据发送大小
|
||||
*/
|
||||
public getRecommendedSendSize(): number {
|
||||
if (!this.limits.enabled) {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
const uploadUtilization = this.stats.currentUpload / this.limits.uploadLimit;
|
||||
|
||||
if (uploadUtilization < this.config.warningThreshold) {
|
||||
return this.limits.uploadLimit * 0.1; // 10% of limit
|
||||
} else if (uploadUtilization < this.config.criticalThreshold) {
|
||||
return this.limits.uploadLimit * 0.05; // 5% of limit
|
||||
} else {
|
||||
return this.limits.uploadLimit * 0.01; // 1% of limit
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取发送延迟建议
|
||||
*/
|
||||
public getRecommendedDelay(): number {
|
||||
const utilization = Math.max(
|
||||
this.stats.currentUpload / this.limits.uploadLimit,
|
||||
this.stats.currentDownload / this.limits.downloadLimit
|
||||
);
|
||||
|
||||
if (utilization < this.config.warningThreshold) {
|
||||
return 0;
|
||||
} else if (utilization < this.config.criticalThreshold) {
|
||||
return 100; // 100ms delay
|
||||
} else {
|
||||
return 500; // 500ms delay
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this.stats = {
|
||||
currentUpload: 0,
|
||||
currentDownload: 0,
|
||||
averageUpload: 0,
|
||||
averageDownload: 0,
|
||||
peakUpload: 0,
|
||||
peakDownload: 0,
|
||||
totalUpload: 0,
|
||||
totalDownload: 0,
|
||||
currentPacketRate: 0,
|
||||
averageLatency: 0,
|
||||
latencyJitter: 0,
|
||||
utilization: 0
|
||||
};
|
||||
|
||||
this.samples.length = 0;
|
||||
this.cumulativeBytesIn = 0;
|
||||
this.cumulativeBytesOut = 0;
|
||||
this.cumulativePacketsIn = 0;
|
||||
this.cumulativePacketsOut = 0;
|
||||
this.lastStatsTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<BandwidthMonitorConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
|
||||
if (newConfig.monitorInterval !== undefined) {
|
||||
this.restartMonitoring();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁监控器
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.stopMonitoring();
|
||||
this.samples.length = 0;
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始监控
|
||||
*/
|
||||
private startMonitoring(): void {
|
||||
if (this.monitorTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.monitorTimer = setInterval(() => {
|
||||
this.updateStats();
|
||||
}, this.config.monitorInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止监控
|
||||
*/
|
||||
private stopMonitoring(): void {
|
||||
if (this.monitorTimer) {
|
||||
clearInterval(this.monitorTimer);
|
||||
this.monitorTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启监控
|
||||
*/
|
||||
private restartMonitoring(): void {
|
||||
this.stopMonitoring();
|
||||
this.startMonitoring();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新统计信息
|
||||
*/
|
||||
private updateStats(): void {
|
||||
const now = Date.now();
|
||||
const deltaTime = (now - this.lastStatsTime) / 1000; // 转换为秒
|
||||
|
||||
if (deltaTime <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算当前速率
|
||||
const currentUpload = this.cumulativeBytesOut / deltaTime;
|
||||
const currentDownload = this.cumulativeBytesIn / deltaTime;
|
||||
const currentPacketRate = (this.cumulativePacketsIn + this.cumulativePacketsOut) / deltaTime;
|
||||
|
||||
// 创建新样本
|
||||
const sample: BandwidthSample = {
|
||||
timestamp: now,
|
||||
bytesIn: this.cumulativeBytesIn,
|
||||
bytesOut: this.cumulativeBytesOut,
|
||||
packetsIn: this.cumulativePacketsIn,
|
||||
packetsOut: this.cumulativePacketsOut,
|
||||
latency: 0 // 需要从外部提供
|
||||
};
|
||||
|
||||
this.samples.push(sample);
|
||||
|
||||
// 限制样本数量
|
||||
if (this.samples.length > this.config.sampleWindowSize) {
|
||||
this.samples.shift();
|
||||
}
|
||||
|
||||
// 更新统计信息
|
||||
this.stats.currentUpload = currentUpload;
|
||||
this.stats.currentDownload = currentDownload;
|
||||
this.stats.currentPacketRate = currentPacketRate;
|
||||
|
||||
// 更新峰值
|
||||
this.stats.peakUpload = Math.max(this.stats.peakUpload, currentUpload);
|
||||
this.stats.peakDownload = Math.max(this.stats.peakDownload, currentDownload);
|
||||
|
||||
// 计算平均值
|
||||
this.calculateAverages();
|
||||
|
||||
// 计算利用率
|
||||
this.calculateUtilization();
|
||||
|
||||
// 检查限制
|
||||
this.checkLimits();
|
||||
|
||||
// 检查警告级别
|
||||
this.checkWarningLevel();
|
||||
|
||||
// 自适应调整
|
||||
if (this.config.enableAdaptive) {
|
||||
this.performAdaptiveAdjustment();
|
||||
}
|
||||
|
||||
// 重置累计值
|
||||
this.cumulativeBytesIn = 0;
|
||||
this.cumulativeBytesOut = 0;
|
||||
this.cumulativePacketsIn = 0;
|
||||
this.cumulativePacketsOut = 0;
|
||||
this.lastStatsTime = now;
|
||||
|
||||
// 发出事件
|
||||
this.emit('bandwidthChanged', this.stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算平均值
|
||||
*/
|
||||
private calculateAverages(): void {
|
||||
if (this.samples.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let totalUpload = 0;
|
||||
let totalDownload = 0;
|
||||
let totalLatency = 0;
|
||||
|
||||
for (let i = 1; i < this.samples.length; i++) {
|
||||
const prev = this.samples[i - 1];
|
||||
const curr = this.samples[i];
|
||||
const deltaTime = (curr.timestamp - prev.timestamp) / 1000;
|
||||
|
||||
if (deltaTime > 0) {
|
||||
totalUpload += (curr.bytesOut - prev.bytesOut) / deltaTime;
|
||||
totalDownload += (curr.bytesIn - prev.bytesIn) / deltaTime;
|
||||
totalLatency += curr.latency;
|
||||
}
|
||||
}
|
||||
|
||||
const sampleCount = this.samples.length - 1;
|
||||
if (sampleCount > 0) {
|
||||
this.stats.averageUpload = totalUpload / sampleCount;
|
||||
this.stats.averageDownload = totalDownload / sampleCount;
|
||||
this.stats.averageLatency = totalLatency / this.samples.length;
|
||||
}
|
||||
|
||||
// 计算延迟抖动
|
||||
this.calculateLatencyJitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算延迟抖动
|
||||
*/
|
||||
private calculateLatencyJitter(): void {
|
||||
if (this.samples.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
let jitterSum = 0;
|
||||
let jitterCount = 0;
|
||||
|
||||
for (let i = 1; i < this.samples.length; i++) {
|
||||
const diff = Math.abs(this.samples[i].latency - this.samples[i - 1].latency);
|
||||
jitterSum += diff;
|
||||
jitterCount++;
|
||||
}
|
||||
|
||||
this.stats.latencyJitter = jitterCount > 0 ? jitterSum / jitterCount : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算利用率
|
||||
*/
|
||||
private calculateUtilization(): void {
|
||||
if (!this.limits.enabled) {
|
||||
this.stats.utilization = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const uploadUtilization = this.stats.currentUpload / this.limits.uploadLimit;
|
||||
const downloadUtilization = this.stats.currentDownload / this.limits.downloadLimit;
|
||||
|
||||
this.stats.utilization = Math.max(uploadUtilization, downloadUtilization);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查限制
|
||||
*/
|
||||
private checkLimits(): void {
|
||||
if (!this.limits.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.stats.currentUpload > this.limits.uploadLimit) {
|
||||
this.emit('limitExceeded', 'upload', this.stats.currentUpload, this.limits.uploadLimit);
|
||||
}
|
||||
|
||||
if (this.stats.currentDownload > this.limits.downloadLimit) {
|
||||
this.emit('limitExceeded', 'download', this.stats.currentDownload, this.limits.downloadLimit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查警告级别
|
||||
*/
|
||||
private checkWarningLevel(): void {
|
||||
let newLevel = BandwidthWarningLevel.Normal;
|
||||
|
||||
if (this.stats.utilization >= this.config.criticalThreshold) {
|
||||
newLevel = BandwidthWarningLevel.Critical;
|
||||
} else if (this.stats.utilization >= this.config.warningThreshold) {
|
||||
newLevel = BandwidthWarningLevel.Warning;
|
||||
}
|
||||
|
||||
if (newLevel !== this.currentWarningLevel) {
|
||||
this.currentWarningLevel = newLevel;
|
||||
this.emit('warningLevelChanged', newLevel, this.stats);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行自适应调整
|
||||
*/
|
||||
private performAdaptiveAdjustment(): void {
|
||||
if (!this.limits.enabled || this.stats.utilization < this.config.warningThreshold) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldLimits = { ...this.limits };
|
||||
|
||||
// 根据当前利用率动态调整限制
|
||||
if (this.stats.utilization > this.config.criticalThreshold) {
|
||||
// 严重超载,降低限制
|
||||
this.limits.uploadLimit *= (1 - this.config.adaptiveFactor);
|
||||
this.limits.downloadLimit *= (1 - this.config.adaptiveFactor);
|
||||
} else if (this.stats.utilization < this.config.warningThreshold * 0.5) {
|
||||
// 利用率较低,可以提高限制
|
||||
this.limits.uploadLimit *= (1 + this.config.adaptiveFactor * 0.5);
|
||||
this.limits.downloadLimit *= (1 + this.config.adaptiveFactor * 0.5);
|
||||
}
|
||||
|
||||
// 检查是否有变化
|
||||
if (this.limits.uploadLimit !== oldLimits.uploadLimit ||
|
||||
this.limits.downloadLimit !== oldLimits.downloadLimit) {
|
||||
this.emit('adaptiveAdjustment', oldLimits, this.limits);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* 监控模块导出
|
||||
*/
|
||||
|
||||
export * from './BandwidthMonitor';
|
||||
@@ -1,502 +0,0 @@
|
||||
/**
|
||||
* 消息管理器
|
||||
* 负责消息ID生成、时间戳管理和消息验证
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { INetworkMessage, MessageType } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 消息ID生成器类型
|
||||
*/
|
||||
export enum MessageIdGeneratorType {
|
||||
UUID = 'uuid',
|
||||
SNOWFLAKE = 'snowflake',
|
||||
SEQUENTIAL = 'sequential',
|
||||
TIMESTAMP = 'timestamp'
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息管理器配置
|
||||
*/
|
||||
export interface MessageManagerConfig {
|
||||
idGenerator: MessageIdGeneratorType;
|
||||
enableTimestampValidation: boolean;
|
||||
maxTimestampDrift: number; // 最大时间戳偏移(毫秒)
|
||||
enableMessageDeduplication: boolean;
|
||||
deduplicationWindowMs: number; // 去重窗口时间
|
||||
enableMessageOrdering: boolean;
|
||||
orderingWindowMs: number; // 排序窗口时间
|
||||
maxPendingMessages: number; // 最大待处理消息数
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息验证结果
|
||||
*/
|
||||
export interface MessageValidationResult {
|
||||
isValid: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息统计信息
|
||||
*/
|
||||
export interface MessageStats {
|
||||
totalGenerated: number;
|
||||
totalValidated: number;
|
||||
validMessages: number;
|
||||
invalidMessages: number;
|
||||
duplicateMessages: number;
|
||||
outOfOrderMessages: number;
|
||||
timestampErrors: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snowflake ID生成器
|
||||
*/
|
||||
class SnowflakeIdGenerator {
|
||||
private static readonly EPOCH = 1640995200000; // 2022-01-01 00:00:00 UTC
|
||||
private static readonly WORKER_ID_BITS = 5;
|
||||
private static readonly DATACENTER_ID_BITS = 5;
|
||||
private static readonly SEQUENCE_BITS = 12;
|
||||
|
||||
private readonly workerId: number;
|
||||
private readonly datacenterId: number;
|
||||
private sequence = 0;
|
||||
private lastTimestamp = -1;
|
||||
|
||||
constructor(workerId: number = 1, datacenterId: number = 1) {
|
||||
this.workerId = workerId & ((1 << SnowflakeIdGenerator.WORKER_ID_BITS) - 1);
|
||||
this.datacenterId = datacenterId & ((1 << SnowflakeIdGenerator.DATACENTER_ID_BITS) - 1);
|
||||
}
|
||||
|
||||
generate(): string {
|
||||
let timestamp = Date.now();
|
||||
|
||||
if (timestamp < this.lastTimestamp) {
|
||||
throw new Error('时钟回拨,无法生成ID');
|
||||
}
|
||||
|
||||
if (timestamp === this.lastTimestamp) {
|
||||
this.sequence = (this.sequence + 1) & ((1 << SnowflakeIdGenerator.SEQUENCE_BITS) - 1);
|
||||
if (this.sequence === 0) {
|
||||
// 等待下一毫秒
|
||||
while (timestamp <= this.lastTimestamp) {
|
||||
timestamp = Date.now();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.sequence = 0;
|
||||
}
|
||||
|
||||
this.lastTimestamp = timestamp;
|
||||
|
||||
const id = ((timestamp - SnowflakeIdGenerator.EPOCH) << 22) |
|
||||
(this.datacenterId << 17) |
|
||||
(this.workerId << 12) |
|
||||
this.sequence;
|
||||
|
||||
return id.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息管理器
|
||||
*/
|
||||
export class MessageManager {
|
||||
private logger = createLogger('MessageManager');
|
||||
private config: MessageManagerConfig;
|
||||
private stats: MessageStats;
|
||||
|
||||
// ID生成器
|
||||
private sequentialId = 0;
|
||||
private snowflakeGenerator: SnowflakeIdGenerator;
|
||||
|
||||
// 消息去重和排序
|
||||
private recentMessageIds: Set<string> = new Set();
|
||||
private pendingMessages: Map<string, INetworkMessage> = new Map();
|
||||
private messageSequence: Map<string, number> = new Map();
|
||||
|
||||
// 清理定时器
|
||||
private cleanupTimer?: NodeJS.Timeout;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<MessageManagerConfig> = {}) {
|
||||
this.config = {
|
||||
idGenerator: MessageIdGeneratorType.UUID,
|
||||
enableTimestampValidation: true,
|
||||
maxTimestampDrift: 60000, // 1分钟
|
||||
enableMessageDeduplication: true,
|
||||
deduplicationWindowMs: 300000, // 5分钟
|
||||
enableMessageOrdering: false,
|
||||
orderingWindowMs: 10000, // 10秒
|
||||
maxPendingMessages: 1000,
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalGenerated: 0,
|
||||
totalValidated: 0,
|
||||
validMessages: 0,
|
||||
invalidMessages: 0,
|
||||
duplicateMessages: 0,
|
||||
outOfOrderMessages: 0,
|
||||
timestampErrors: 0
|
||||
};
|
||||
|
||||
this.snowflakeGenerator = new SnowflakeIdGenerator();
|
||||
this.startCleanupTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成消息ID
|
||||
*/
|
||||
generateMessageId(): string {
|
||||
this.stats.totalGenerated++;
|
||||
|
||||
switch (this.config.idGenerator) {
|
||||
case MessageIdGeneratorType.UUID:
|
||||
return this.generateUUID();
|
||||
case MessageIdGeneratorType.SNOWFLAKE:
|
||||
return this.snowflakeGenerator.generate();
|
||||
case MessageIdGeneratorType.SEQUENTIAL:
|
||||
return (++this.sequentialId).toString();
|
||||
case MessageIdGeneratorType.TIMESTAMP:
|
||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
default:
|
||||
return this.generateUUID();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建网络消息
|
||||
*/
|
||||
createMessage<T extends INetworkMessage>(
|
||||
type: MessageType,
|
||||
data: any,
|
||||
senderId: string,
|
||||
options: {
|
||||
reliable?: boolean;
|
||||
priority?: number;
|
||||
timestamp?: number;
|
||||
} = {}
|
||||
): T {
|
||||
const message: INetworkMessage = {
|
||||
type,
|
||||
messageId: this.generateMessageId(),
|
||||
timestamp: options.timestamp || Date.now(),
|
||||
senderId,
|
||||
data,
|
||||
reliable: options.reliable,
|
||||
priority: options.priority
|
||||
};
|
||||
|
||||
return message as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证消息
|
||||
*/
|
||||
validateMessage(message: INetworkMessage, senderId?: string): MessageValidationResult {
|
||||
this.stats.totalValidated++;
|
||||
|
||||
const errors: string[] = [];
|
||||
const warnings: string[] = [];
|
||||
|
||||
// 基础字段验证
|
||||
if (!message.messageId) {
|
||||
errors.push('消息ID不能为空');
|
||||
}
|
||||
|
||||
if (!message.type) {
|
||||
errors.push('消息类型不能为空');
|
||||
} else if (!Object.values(MessageType).includes(message.type)) {
|
||||
errors.push(`无效的消息类型: ${message.type}`);
|
||||
}
|
||||
|
||||
if (!message.timestamp) {
|
||||
errors.push('时间戳不能为空');
|
||||
}
|
||||
|
||||
if (!message.senderId) {
|
||||
errors.push('发送者ID不能为空');
|
||||
}
|
||||
|
||||
// 发送者验证
|
||||
if (senderId && message.senderId !== senderId) {
|
||||
errors.push('消息发送者ID不匹配');
|
||||
}
|
||||
|
||||
// 时间戳验证
|
||||
if (this.config.enableTimestampValidation && message.timestamp) {
|
||||
const now = Date.now();
|
||||
const drift = Math.abs(now - message.timestamp);
|
||||
|
||||
if (drift > this.config.maxTimestampDrift) {
|
||||
errors.push(`时间戳偏移过大: ${drift}ms > ${this.config.maxTimestampDrift}ms`);
|
||||
this.stats.timestampErrors++;
|
||||
}
|
||||
|
||||
if (message.timestamp > now + 10000) { // 未来10秒以上
|
||||
warnings.push('消息时间戳来自未来');
|
||||
}
|
||||
}
|
||||
|
||||
// 消息去重验证
|
||||
if (this.config.enableMessageDeduplication) {
|
||||
if (this.recentMessageIds.has(message.messageId)) {
|
||||
errors.push('重复的消息ID');
|
||||
this.stats.duplicateMessages++;
|
||||
} else {
|
||||
this.recentMessageIds.add(message.messageId);
|
||||
}
|
||||
}
|
||||
|
||||
const isValid = errors.length === 0;
|
||||
|
||||
if (isValid) {
|
||||
this.stats.validMessages++;
|
||||
} else {
|
||||
this.stats.invalidMessages++;
|
||||
}
|
||||
|
||||
return {
|
||||
isValid,
|
||||
errors,
|
||||
warnings
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息排序
|
||||
*/
|
||||
processMessageOrdering(message: INetworkMessage): INetworkMessage[] {
|
||||
if (!this.config.enableMessageOrdering) {
|
||||
return [message];
|
||||
}
|
||||
|
||||
const senderId = message.senderId;
|
||||
const currentSequence = this.messageSequence.get(senderId) || 0;
|
||||
|
||||
// 检查消息是否按顺序到达
|
||||
const messageTimestamp = message.timestamp;
|
||||
const expectedSequence = currentSequence + 1;
|
||||
|
||||
// 简单的时间戳排序逻辑
|
||||
if (messageTimestamp >= expectedSequence) {
|
||||
// 消息按顺序到达
|
||||
this.messageSequence.set(senderId, messageTimestamp);
|
||||
return this.flushPendingMessages(senderId).concat([message]);
|
||||
} else {
|
||||
// 消息乱序,暂存
|
||||
this.pendingMessages.set(message.messageId, message);
|
||||
this.stats.outOfOrderMessages++;
|
||||
|
||||
// 检查是否超出最大待处理数量
|
||||
if (this.pendingMessages.size > this.config.maxPendingMessages) {
|
||||
this.logger.warn('待处理消息数量过多,清理旧消息');
|
||||
this.cleanupOldPendingMessages();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): MessageStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
totalGenerated: 0,
|
||||
totalValidated: 0,
|
||||
validMessages: 0,
|
||||
invalidMessages: 0,
|
||||
duplicateMessages: 0,
|
||||
outOfOrderMessages: 0,
|
||||
timestampErrors: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<MessageManagerConfig>): void {
|
||||
const oldConfig = { ...this.config };
|
||||
Object.assign(this.config, newConfig);
|
||||
|
||||
// 如果去重配置改变,清理相关数据
|
||||
if (!this.config.enableMessageDeduplication && oldConfig.enableMessageDeduplication) {
|
||||
this.recentMessageIds.clear();
|
||||
}
|
||||
|
||||
// 如果排序配置改变,清理相关数据
|
||||
if (!this.config.enableMessageOrdering && oldConfig.enableMessageOrdering) {
|
||||
this.pendingMessages.clear();
|
||||
this.messageSequence.clear();
|
||||
}
|
||||
|
||||
this.logger.info('消息管理器配置已更新:', newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器
|
||||
*/
|
||||
destroy(): void {
|
||||
if (this.cleanupTimer) {
|
||||
clearInterval(this.cleanupTimer);
|
||||
this.cleanupTimer = undefined;
|
||||
}
|
||||
|
||||
this.recentMessageIds.clear();
|
||||
this.pendingMessages.clear();
|
||||
this.messageSequence.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成UUID
|
||||
*/
|
||||
private generateUUID(): string {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新待处理消息
|
||||
*/
|
||||
private flushPendingMessages(senderId: string): INetworkMessage[] {
|
||||
const flushedMessages: INetworkMessage[] = [];
|
||||
const messagesToRemove: string[] = [];
|
||||
|
||||
for (const [messageId, message] of this.pendingMessages) {
|
||||
if (message.senderId === senderId) {
|
||||
flushedMessages.push(message);
|
||||
messagesToRemove.push(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除已处理的消息
|
||||
messagesToRemove.forEach((id) => this.pendingMessages.delete(id));
|
||||
|
||||
// 按时间戳排序
|
||||
flushedMessages.sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
return flushedMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的待处理消息
|
||||
*/
|
||||
private cleanupOldPendingMessages(): void {
|
||||
const now = Date.now();
|
||||
const messagesToRemove: string[] = [];
|
||||
|
||||
for (const [messageId, message] of this.pendingMessages) {
|
||||
if (now - message.timestamp > this.config.orderingWindowMs) {
|
||||
messagesToRemove.push(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
messagesToRemove.forEach((id) => this.pendingMessages.delete(id));
|
||||
|
||||
if (messagesToRemove.length > 0) {
|
||||
this.logger.debug(`清理了 ${messagesToRemove.length} 个过期的待处理消息`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动清理定时器
|
||||
*/
|
||||
private startCleanupTimer(): void {
|
||||
this.cleanupTimer = setInterval(() => {
|
||||
this.performCleanup();
|
||||
}, 60000); // 每分钟清理一次
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行清理操作
|
||||
*/
|
||||
private performCleanup(): void {
|
||||
const now = Date.now();
|
||||
|
||||
// 清理过期的消息ID(用于去重)
|
||||
if (this.config.enableMessageDeduplication) {
|
||||
// 由于Set没有时间戳,我们定期清理所有ID
|
||||
// 这是一个简化实现,实际项目中可以使用更复杂的数据结构
|
||||
if (this.recentMessageIds.size > 10000) {
|
||||
this.recentMessageIds.clear();
|
||||
this.logger.debug('清理了过期的消息ID缓存');
|
||||
}
|
||||
}
|
||||
|
||||
// 清理过期的待处理消息
|
||||
if (this.config.enableMessageOrdering) {
|
||||
this.cleanupOldPendingMessages();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息处理报告
|
||||
*/
|
||||
getProcessingReport() {
|
||||
const totalProcessed = this.stats.validMessages + this.stats.invalidMessages;
|
||||
const validRate = totalProcessed > 0 ? (this.stats.validMessages / totalProcessed) * 100 : 0;
|
||||
const duplicateRate = totalProcessed > 0 ? (this.stats.duplicateMessages / totalProcessed) * 100 : 0;
|
||||
|
||||
return {
|
||||
stats: this.getStats(),
|
||||
validationRate: validRate,
|
||||
duplicateRate: duplicateRate,
|
||||
pendingMessagesCount: this.pendingMessages.size,
|
||||
cachedMessageIdsCount: this.recentMessageIds.size,
|
||||
recommendation: this.generateRecommendation(validRate, duplicateRate)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成优化建议
|
||||
*/
|
||||
private generateRecommendation(validRate: number, duplicateRate: number): string {
|
||||
if (validRate < 90) {
|
||||
return '消息验证失败率较高,建议检查消息格式和发送逻辑';
|
||||
} else if (duplicateRate > 5) {
|
||||
return '重复消息较多,建议检查客户端重发逻辑或调整去重窗口';
|
||||
} else if (this.pendingMessages.size > this.config.maxPendingMessages * 0.8) {
|
||||
return '待处理消息过多,建议优化网络或调整排序窗口';
|
||||
} else {
|
||||
return '消息处理正常';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量验证消息
|
||||
*/
|
||||
validateMessageBatch(messages: INetworkMessage[], senderId?: string): MessageValidationResult[] {
|
||||
return messages.map((message) => this.validateMessage(message, senderId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息年龄(毫秒)
|
||||
*/
|
||||
getMessageAge(message: INetworkMessage): number {
|
||||
return Date.now() - message.timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查消息是否过期
|
||||
*/
|
||||
isMessageExpired(message: INetworkMessage, maxAge: number = 300000): boolean {
|
||||
return this.getMessageAge(message) > maxAge;
|
||||
}
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
/**
|
||||
* 网络消息协议定义
|
||||
*/
|
||||
import { MessageType, INetworkMessage, AuthorityType, SyncMode, RpcTarget } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 连接请求消息
|
||||
*/
|
||||
export interface IConnectMessage extends INetworkMessage {
|
||||
type: MessageType.CONNECT;
|
||||
data: {
|
||||
/** 客户端版本 */
|
||||
clientVersion: string;
|
||||
/** 协议版本 */
|
||||
protocolVersion: string;
|
||||
/** 认证令牌 */
|
||||
authToken?: string;
|
||||
/** 客户端信息 */
|
||||
clientInfo: {
|
||||
name: string;
|
||||
platform: string;
|
||||
version: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接响应消息
|
||||
*/
|
||||
export interface IConnectResponseMessage extends INetworkMessage {
|
||||
type: MessageType.CONNECT;
|
||||
data: {
|
||||
/** 是否成功 */
|
||||
success: boolean;
|
||||
/** 分配的客户端ID */
|
||||
clientId?: string;
|
||||
/** 错误信息 */
|
||||
error?: string;
|
||||
/** 服务器信息 */
|
||||
serverInfo?: {
|
||||
name: string;
|
||||
version: string;
|
||||
maxPlayers: number;
|
||||
currentPlayers: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳消息
|
||||
*/
|
||||
export interface IHeartbeatMessage extends INetworkMessage {
|
||||
type: MessageType.HEARTBEAT;
|
||||
data: {
|
||||
/** 客户端时间戳 */
|
||||
clientTime: number;
|
||||
/** 服务器时间戳(响应时包含) */
|
||||
serverTime?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步变量消息
|
||||
*/
|
||||
export interface ISyncVarMessage extends INetworkMessage {
|
||||
type: MessageType.SYNC_VAR;
|
||||
data: {
|
||||
/** 网络实体ID */
|
||||
networkId: number;
|
||||
/** 组件类型名称 */
|
||||
componentType: string;
|
||||
/** 变化的属性 */
|
||||
changes: Record<string, any>;
|
||||
/** 同步模式 */
|
||||
syncMode: SyncMode;
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量同步消息
|
||||
*/
|
||||
export interface ISyncBatchMessage extends INetworkMessage {
|
||||
type: MessageType.SYNC_BATCH;
|
||||
data: {
|
||||
/** 同步数据列表 */
|
||||
syncData: Array<{
|
||||
networkId: number;
|
||||
componentType: string;
|
||||
changes: Record<string, any>;
|
||||
syncMode: SyncMode;
|
||||
}>;
|
||||
/** 批次时间戳 */
|
||||
batchTimestamp: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用消息
|
||||
*/
|
||||
export interface IRpcCallMessage extends INetworkMessage {
|
||||
type: MessageType.RPC_CALL;
|
||||
data: {
|
||||
/** 网络实体ID */
|
||||
networkId: number;
|
||||
/** 组件类型名称 */
|
||||
componentType: string;
|
||||
/** 方法名 */
|
||||
methodName: string;
|
||||
/** 参数列表 */
|
||||
args: any[];
|
||||
/** 调用ID(用于响应匹配) */
|
||||
callId?: string;
|
||||
/** RPC目标 */
|
||||
target: RpcTarget;
|
||||
/** 是否需要响应 */
|
||||
expectResponse?: boolean;
|
||||
/** 超时时间 */
|
||||
timeout?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC响应消息
|
||||
*/
|
||||
export interface IRpcResponseMessage extends INetworkMessage {
|
||||
type: MessageType.RPC_RESPONSE;
|
||||
data: {
|
||||
/** 调用ID */
|
||||
callId: string;
|
||||
/** 是否成功 */
|
||||
success: boolean;
|
||||
/** 返回值 */
|
||||
result?: any;
|
||||
/** 错误信息 */
|
||||
error?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体创建消息
|
||||
*/
|
||||
export interface IEntityCreateMessage extends INetworkMessage {
|
||||
type: MessageType.ENTITY_CREATE;
|
||||
data: {
|
||||
/** 网络实体ID */
|
||||
networkId: number;
|
||||
/** 实体名称 */
|
||||
entityName: string;
|
||||
/** 拥有者ID */
|
||||
ownerId: string;
|
||||
/** 权限类型 */
|
||||
authority: AuthorityType;
|
||||
/** 初始组件数据 */
|
||||
components: Array<{
|
||||
type: string;
|
||||
data: any;
|
||||
}>;
|
||||
/** 位置信息 */
|
||||
position?: { x: number; y: number; z?: number };
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体销毁消息
|
||||
*/
|
||||
export interface IEntityDestroyMessage extends INetworkMessage {
|
||||
type: MessageType.ENTITY_DESTROY;
|
||||
data: {
|
||||
/** 网络实体ID */
|
||||
networkId: number;
|
||||
/** 销毁原因 */
|
||||
reason?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入房间消息
|
||||
*/
|
||||
export interface IJoinRoomMessage extends INetworkMessage {
|
||||
type: MessageType.JOIN_ROOM;
|
||||
data: {
|
||||
/** 房间ID */
|
||||
roomId: string;
|
||||
/** 密码(如果需要) */
|
||||
password?: string;
|
||||
/** 玩家信息 */
|
||||
playerInfo?: {
|
||||
name: string;
|
||||
avatar?: string;
|
||||
customData?: Record<string, any>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 离开房间消息
|
||||
*/
|
||||
export interface ILeaveRoomMessage extends INetworkMessage {
|
||||
type: MessageType.LEAVE_ROOM;
|
||||
data: {
|
||||
/** 房间ID */
|
||||
roomId: string;
|
||||
/** 离开原因 */
|
||||
reason?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间状态消息
|
||||
*/
|
||||
export interface IRoomStateMessage extends INetworkMessage {
|
||||
type: MessageType.ROOM_STATE;
|
||||
data: {
|
||||
/** 房间ID */
|
||||
roomId: string;
|
||||
/** 房间状态 */
|
||||
state: string;
|
||||
/** 玩家列表 */
|
||||
players: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
isHost: boolean;
|
||||
customData?: Record<string, any>;
|
||||
}>;
|
||||
/** 房间设置 */
|
||||
settings?: Record<string, any>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 游戏事件消息
|
||||
*/
|
||||
export interface IGameEventMessage extends INetworkMessage {
|
||||
type: MessageType.GAME_EVENT;
|
||||
data: {
|
||||
/** 事件类型 */
|
||||
eventType: string;
|
||||
/** 事件数据 */
|
||||
eventData: any;
|
||||
/** 目标客户端 */
|
||||
target?: RpcTarget;
|
||||
/** 事件优先级 */
|
||||
priority?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
export interface IErrorMessage extends INetworkMessage {
|
||||
type: MessageType.ERROR;
|
||||
data: {
|
||||
/** 错误代码 */
|
||||
code: string;
|
||||
/** 错误消息 */
|
||||
message: string;
|
||||
/** 错误详情 */
|
||||
details?: any;
|
||||
/** 相关的消息ID */
|
||||
relatedMessageId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息类型联合
|
||||
*/
|
||||
export type NetworkMessage =
|
||||
| IConnectMessage
|
||||
| IConnectResponseMessage
|
||||
| IHeartbeatMessage
|
||||
| ISyncVarMessage
|
||||
| ISyncBatchMessage
|
||||
| IRpcCallMessage
|
||||
| IRpcResponseMessage
|
||||
| IEntityCreateMessage
|
||||
| IEntityDestroyMessage
|
||||
| IJoinRoomMessage
|
||||
| ILeaveRoomMessage
|
||||
| IRoomStateMessage
|
||||
| IGameEventMessage
|
||||
| IErrorMessage;
|
||||
@@ -1,454 +0,0 @@
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { EventEmitter } from '../utils/EventEmitter';
|
||||
import {
|
||||
RpcCallRequest,
|
||||
RpcCallResponse,
|
||||
RpcError,
|
||||
RpcErrorType,
|
||||
RpcStats,
|
||||
RpcMethodMetadata
|
||||
} from '../types/RpcTypes';
|
||||
import { RpcMetadataManager } from './RpcMetadataManager';
|
||||
|
||||
/**
|
||||
* RPC调用统计信息
|
||||
*/
|
||||
interface CallStats {
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
duration?: number;
|
||||
success: boolean;
|
||||
error?: RpcError;
|
||||
}
|
||||
|
||||
/**
|
||||
* 速率限制器
|
||||
*/
|
||||
interface RateLimiter {
|
||||
calls: number[];
|
||||
limit: number;
|
||||
window: number; // 时间窗口(毫秒)
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用处理器事件
|
||||
*/
|
||||
export interface RpcCallHandlerEvents {
|
||||
callStarted: (request: RpcCallRequest) => void;
|
||||
callCompleted: (request: RpcCallRequest, response: RpcCallResponse) => void;
|
||||
callFailed: (request: RpcCallRequest, error: RpcError) => void;
|
||||
rateLimitExceeded: (methodName: string, senderId: string) => void;
|
||||
permissionDenied: (methodName: string, senderId: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用处理器配置
|
||||
*/
|
||||
export interface RpcCallHandlerConfig {
|
||||
/** 最大并发调用数 */
|
||||
maxConcurrentCalls: number;
|
||||
/** 默认超时时间(毫秒) */
|
||||
defaultTimeout: number;
|
||||
/** 是否启用速率限制 */
|
||||
enableRateLimit: boolean;
|
||||
/** 是否启用权限检查 */
|
||||
enablePermissionCheck: boolean;
|
||||
/** 是否启用性能监控 */
|
||||
enablePerformanceMonitoring: boolean;
|
||||
/** 统计数据保留时间(毫秒) */
|
||||
statsRetentionTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用处理器
|
||||
* 负责处理来自客户端的RPC调用请求
|
||||
*/
|
||||
export class RpcCallHandler extends EventEmitter {
|
||||
private logger = createLogger('RpcCallHandler');
|
||||
private config: RpcCallHandlerConfig;
|
||||
private metadataManager: RpcMetadataManager;
|
||||
|
||||
/** 当前活跃的调用 */
|
||||
private activeCalls = new Map<string, CallStats>();
|
||||
|
||||
/** 速率限制器 */
|
||||
private rateLimiters = new Map<string, RateLimiter>();
|
||||
|
||||
/** 统计信息 */
|
||||
private stats: RpcStats = {
|
||||
totalCalls: 0,
|
||||
successfulCalls: 0,
|
||||
failedCalls: 0,
|
||||
averageResponseTime: 0,
|
||||
pendingCalls: 0,
|
||||
timeoutCalls: 0,
|
||||
retryCount: 0,
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
|
||||
/** 历史调用统计 */
|
||||
private callHistory: CallStats[] = [];
|
||||
|
||||
/** 权限检查器 */
|
||||
private permissionChecker?: (methodName: string, senderId: string) => boolean;
|
||||
|
||||
constructor(
|
||||
metadataManager: RpcMetadataManager,
|
||||
config: Partial<RpcCallHandlerConfig> = {}
|
||||
) {
|
||||
super();
|
||||
|
||||
this.metadataManager = metadataManager;
|
||||
this.config = {
|
||||
maxConcurrentCalls: 100,
|
||||
defaultTimeout: 30000,
|
||||
enableRateLimit: true,
|
||||
enablePermissionCheck: true,
|
||||
enablePerformanceMonitoring: true,
|
||||
statsRetentionTime: 300000, // 5分钟
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置权限检查器
|
||||
*/
|
||||
public setPermissionChecker(checker: (methodName: string, senderId: string) => boolean): void {
|
||||
this.permissionChecker = checker;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理RPC调用请求
|
||||
*/
|
||||
public async handleCall<T extends readonly unknown[], R>(
|
||||
request: RpcCallRequest<T>
|
||||
): Promise<RpcCallResponse<R>> {
|
||||
const startTime = Date.now();
|
||||
const callStats: CallStats = {
|
||||
startTime,
|
||||
success: false
|
||||
};
|
||||
|
||||
this.activeCalls.set(request.callId, callStats);
|
||||
this.stats.pendingCalls++;
|
||||
this.emit('callStarted', request);
|
||||
|
||||
try {
|
||||
// 1. 检查并发限制
|
||||
if (this.activeCalls.size > this.config.maxConcurrentCalls) {
|
||||
throw this.createError(
|
||||
RpcErrorType.RATE_LIMITED,
|
||||
`超过最大并发调用数限制: ${this.config.maxConcurrentCalls}`
|
||||
);
|
||||
}
|
||||
|
||||
// 2. 获取方法元数据
|
||||
const metadata = this.metadataManager.getMethodMetadata(request.methodName);
|
||||
if (!metadata) {
|
||||
throw this.createError(
|
||||
RpcErrorType.METHOD_NOT_FOUND,
|
||||
`RPC方法不存在: ${request.methodName}`
|
||||
);
|
||||
}
|
||||
|
||||
// 3. 验证方法调用
|
||||
const validation = this.metadataManager.validateMethodCall(
|
||||
request.methodName,
|
||||
Array.from(request.args),
|
||||
request.senderId
|
||||
);
|
||||
|
||||
if (!validation.valid) {
|
||||
throw this.createError(
|
||||
RpcErrorType.INVALID_ARGUMENTS,
|
||||
validation.error || '参数验证失败'
|
||||
);
|
||||
}
|
||||
|
||||
// 4. 权限检查
|
||||
if (this.config.enablePermissionCheck && !this.checkPermission(metadata, request.senderId)) {
|
||||
this.emit('permissionDenied', request.methodName, request.senderId);
|
||||
throw this.createError(
|
||||
RpcErrorType.PERMISSION_DENIED,
|
||||
`没有调用权限: ${request.methodName}`
|
||||
);
|
||||
}
|
||||
|
||||
// 5. 速率限制检查
|
||||
if (this.config.enableRateLimit && !this.checkRateLimit(metadata, request.senderId)) {
|
||||
this.emit('rateLimitExceeded', request.methodName, request.senderId);
|
||||
throw this.createError(
|
||||
RpcErrorType.RATE_LIMITED,
|
||||
`调用频率超限: ${request.methodName}`
|
||||
);
|
||||
}
|
||||
|
||||
// 6. 执行方法调用
|
||||
const handler = this.metadataManager.getMethodHandler(request.methodName);
|
||||
if (!handler) {
|
||||
throw this.createError(
|
||||
RpcErrorType.SERVER_ERROR,
|
||||
`方法处理器不存在: ${request.methodName}`
|
||||
);
|
||||
}
|
||||
|
||||
// 创建带超时的Promise
|
||||
const timeout = request.options.timeout || metadata.options.timeout || this.config.defaultTimeout;
|
||||
const result = await this.executeWithTimeout(handler, request.args, timeout);
|
||||
|
||||
// 7. 创建成功响应
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
callStats.endTime = endTime;
|
||||
callStats.duration = duration;
|
||||
callStats.success = true;
|
||||
|
||||
const response: RpcCallResponse<R> = {
|
||||
callId: request.callId,
|
||||
success: true,
|
||||
result: result as R,
|
||||
timestamp: endTime,
|
||||
duration
|
||||
};
|
||||
|
||||
this.updateStats(callStats);
|
||||
this.emit('callCompleted', request, response);
|
||||
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
// 8. 处理错误
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
const rpcError = error instanceof Error && 'type' in error
|
||||
? error as RpcError
|
||||
: this.createError(RpcErrorType.SERVER_ERROR, String(error));
|
||||
|
||||
callStats.endTime = endTime;
|
||||
callStats.duration = duration;
|
||||
callStats.error = rpcError;
|
||||
|
||||
const response: RpcCallResponse<R> = {
|
||||
callId: request.callId,
|
||||
success: false,
|
||||
error: rpcError,
|
||||
timestamp: endTime,
|
||||
duration
|
||||
};
|
||||
|
||||
this.updateStats(callStats);
|
||||
this.emit('callFailed', request, rpcError);
|
||||
|
||||
return response;
|
||||
|
||||
} finally {
|
||||
// 9. 清理
|
||||
this.activeCalls.delete(request.callId);
|
||||
this.stats.pendingCalls--;
|
||||
this.addToHistory(callStats);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): RpcStats {
|
||||
this.stats.lastUpdated = Date.now();
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前活跃调用数
|
||||
*/
|
||||
public getActiveCalls(): number {
|
||||
return this.activeCalls.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法调用历史
|
||||
*/
|
||||
public getCallHistory(methodName?: string, limit: number = 100): CallStats[] {
|
||||
const history = [...this.callHistory];
|
||||
|
||||
if (methodName) {
|
||||
// 这里需要扩展CallStats接口来包含methodName
|
||||
// 暂时返回所有历史
|
||||
}
|
||||
|
||||
return history.slice(-limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this.stats = {
|
||||
totalCalls: 0,
|
||||
successfulCalls: 0,
|
||||
failedCalls: 0,
|
||||
averageResponseTime: 0,
|
||||
pendingCalls: this.activeCalls.size,
|
||||
timeoutCalls: 0,
|
||||
retryCount: 0,
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
|
||||
this.callHistory.length = 0;
|
||||
this.rateLimiters.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<RpcCallHandlerConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁处理器
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.activeCalls.clear();
|
||||
this.rateLimiters.clear();
|
||||
this.callHistory.length = 0;
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查权限
|
||||
*/
|
||||
private checkPermission(metadata: RpcMethodMetadata, senderId: string): boolean {
|
||||
// 如果方法不需要认证,直接通过
|
||||
if (!metadata.options.requireAuth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 使用自定义权限检查器
|
||||
if (this.permissionChecker) {
|
||||
return this.permissionChecker(metadata.methodName, senderId);
|
||||
}
|
||||
|
||||
// 默认:需要认证但没有检查器,拒绝访问
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查速率限制
|
||||
*/
|
||||
private checkRateLimit(metadata: RpcMethodMetadata, senderId: string): boolean {
|
||||
const rateLimit = metadata.options.rateLimit;
|
||||
if (!rateLimit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const key = `${senderId}:${metadata.methodName}`;
|
||||
let limiter = this.rateLimiters.get(key);
|
||||
|
||||
if (!limiter) {
|
||||
limiter = {
|
||||
calls: [],
|
||||
limit: rateLimit,
|
||||
window: 60000 // 1分钟窗口
|
||||
};
|
||||
this.rateLimiters.set(key, limiter);
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// 清理过期的调用记录
|
||||
limiter.calls = limiter.calls.filter((time) => now - time < limiter.window);
|
||||
|
||||
// 检查是否超限
|
||||
if (limiter.calls.length >= limiter.limit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 记录本次调用
|
||||
limiter.calls.push(now);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行带超时的方法调用
|
||||
*/
|
||||
private async executeWithTimeout(
|
||||
handler: Function,
|
||||
args: readonly unknown[],
|
||||
timeout: number
|
||||
): Promise<unknown> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
reject(this.createError(
|
||||
RpcErrorType.TIMEOUT,
|
||||
`方法调用超时: ${timeout}ms`
|
||||
));
|
||||
}, timeout);
|
||||
|
||||
Promise.resolve(handler(...args))
|
||||
.then((result) => {
|
||||
clearTimeout(timer);
|
||||
resolve(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
clearTimeout(timer);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建RPC错误
|
||||
*/
|
||||
private createError(type: RpcErrorType, message: string, code?: number): RpcError {
|
||||
return {
|
||||
type,
|
||||
message,
|
||||
code
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新统计信息
|
||||
*/
|
||||
private updateStats(callStats: CallStats): void {
|
||||
this.stats.totalCalls++;
|
||||
|
||||
if (callStats.success) {
|
||||
this.stats.successfulCalls++;
|
||||
} else {
|
||||
this.stats.failedCalls++;
|
||||
|
||||
if (callStats.error?.type === RpcErrorType.TIMEOUT) {
|
||||
this.stats.timeoutCalls++;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新平均响应时间
|
||||
if (callStats.duration !== undefined) {
|
||||
const totalTime = this.stats.averageResponseTime * (this.stats.totalCalls - 1) + callStats.duration;
|
||||
this.stats.averageResponseTime = totalTime / this.stats.totalCalls;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加到历史记录
|
||||
*/
|
||||
private addToHistory(callStats: CallStats): void {
|
||||
if (!this.config.enablePerformanceMonitoring) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.callHistory.push(callStats);
|
||||
|
||||
// 清理过期的历史记录
|
||||
const cutoffTime = Date.now() - this.config.statsRetentionTime;
|
||||
this.callHistory = this.callHistory.filter((stats) => stats.startTime > cutoffTime);
|
||||
|
||||
// 限制历史记录数量
|
||||
if (this.callHistory.length > 10000) {
|
||||
this.callHistory = this.callHistory.slice(-5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,504 +0,0 @@
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { EventEmitter } from '../utils/EventEmitter';
|
||||
import {
|
||||
RpcCallRequest,
|
||||
RpcCallResponse,
|
||||
RpcError,
|
||||
RpcErrorType,
|
||||
RpcCallInfo,
|
||||
RpcCallStatus,
|
||||
RpcStats,
|
||||
RpcOptions,
|
||||
ClientRpcInvoker,
|
||||
ServerRpcInvoker
|
||||
} from '../types/RpcTypes';
|
||||
import { MessageType, RpcTarget } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 网络发送器接口
|
||||
*/
|
||||
export interface NetworkSender {
|
||||
sendMessage(message: object): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用代理事件
|
||||
*/
|
||||
export interface RpcCallProxyEvents {
|
||||
callSent: (request: RpcCallRequest) => void;
|
||||
responseReceived: (response: RpcCallResponse) => void;
|
||||
callTimeout: (callId: string) => void;
|
||||
callFailed: (callId: string, error: RpcError) => void;
|
||||
retryAttempt: (callId: string, attempt: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用代理配置
|
||||
*/
|
||||
export interface RpcCallProxyConfig {
|
||||
/** 默认超时时间(毫秒) */
|
||||
defaultTimeout: number;
|
||||
/** 最大重试次数 */
|
||||
maxRetries: number;
|
||||
/** 重试延迟基数(毫秒) */
|
||||
retryDelayBase: number;
|
||||
/** 重试延迟倍数 */
|
||||
retryDelayMultiplier: number;
|
||||
/** 最大重试延迟(毫秒) */
|
||||
maxRetryDelay: number;
|
||||
/** 是否启用离线队列 */
|
||||
enableOfflineQueue: boolean;
|
||||
/** 离线队列最大大小 */
|
||||
maxOfflineQueueSize: number;
|
||||
/** 调用ID生成器 */
|
||||
generateCallId?: () => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用代理
|
||||
* 负责发送RPC调用并处理响应
|
||||
*/
|
||||
export class RpcCallProxy extends EventEmitter {
|
||||
private logger = createLogger('RpcCallProxy');
|
||||
private config: RpcCallProxyConfig;
|
||||
private networkSender: NetworkSender;
|
||||
|
||||
/** 待处理的调用 */
|
||||
private pendingCalls = new Map<string, RpcCallInfo>();
|
||||
|
||||
/** 离线队列 */
|
||||
private offlineQueue: RpcCallRequest[] = [];
|
||||
|
||||
/** 是否在线 */
|
||||
private isOnline = true;
|
||||
|
||||
/** 统计信息 */
|
||||
private stats: RpcStats = {
|
||||
totalCalls: 0,
|
||||
successfulCalls: 0,
|
||||
failedCalls: 0,
|
||||
averageResponseTime: 0,
|
||||
pendingCalls: 0,
|
||||
timeoutCalls: 0,
|
||||
retryCount: 0,
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
|
||||
/** 重试定时器 */
|
||||
private retryTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
constructor(
|
||||
networkSender: NetworkSender,
|
||||
config: Partial<RpcCallProxyConfig> = {}
|
||||
) {
|
||||
super();
|
||||
|
||||
this.networkSender = networkSender;
|
||||
this.config = {
|
||||
defaultTimeout: 30000,
|
||||
maxRetries: 3,
|
||||
retryDelayBase: 1000,
|
||||
retryDelayMultiplier: 2,
|
||||
maxRetryDelay: 10000,
|
||||
enableOfflineQueue: true,
|
||||
maxOfflineQueueSize: 100,
|
||||
generateCallId: () => `rpc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端RPC调用器
|
||||
*/
|
||||
public get clientRpc(): ClientRpcInvoker {
|
||||
return <TArgs extends readonly unknown[], TReturn>(
|
||||
methodName: string,
|
||||
args: TArgs,
|
||||
options?: Partial<RpcOptions>
|
||||
): Promise<TReturn> => {
|
||||
return this.call(methodName, args, options);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端RPC调用器(用于服务端调用客户端)
|
||||
*/
|
||||
public get serverRpc(): ServerRpcInvoker {
|
||||
return <TArgs extends readonly unknown[], TReturn>(
|
||||
clientId: string,
|
||||
methodName: string,
|
||||
args: TArgs,
|
||||
options?: Partial<RpcOptions>
|
||||
): Promise<TReturn> => {
|
||||
const callOptions = {
|
||||
...options,
|
||||
target: RpcTarget.Client
|
||||
};
|
||||
return this.call(methodName, args, callOptions, clientId);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起RPC调用
|
||||
*/
|
||||
public async call<TArgs extends readonly unknown[], TReturn>(
|
||||
methodName: string,
|
||||
args: TArgs,
|
||||
options: Partial<RpcOptions> = {},
|
||||
targetId?: string
|
||||
): Promise<TReturn> {
|
||||
const callId = this.config.generateCallId!();
|
||||
const timeout = options.timeout || this.config.defaultTimeout;
|
||||
|
||||
const request: RpcCallRequest<TArgs> = {
|
||||
callId,
|
||||
methodName,
|
||||
args,
|
||||
senderId: 'client', // 这应该从认证系统获取
|
||||
targetId,
|
||||
timestamp: Date.now(),
|
||||
options: {
|
||||
reliable: true,
|
||||
priority: 5,
|
||||
timeout,
|
||||
...options
|
||||
}
|
||||
};
|
||||
|
||||
// 创建Promise和调用信息
|
||||
return new Promise<TReturn>((resolve, reject) => {
|
||||
const callInfo: RpcCallInfo<TArgs> = {
|
||||
request,
|
||||
status: RpcCallStatus.PENDING,
|
||||
resolve: resolve as (value: unknown) => void,
|
||||
reject: (reason: RpcError) => reject(reason),
|
||||
retryCount: 0,
|
||||
createdAt: Date.now()
|
||||
};
|
||||
|
||||
this.pendingCalls.set(callId, callInfo);
|
||||
this.stats.pendingCalls++;
|
||||
this.stats.totalCalls++;
|
||||
|
||||
// 设置超时
|
||||
setTimeout(() => {
|
||||
this.handleTimeout(callId);
|
||||
}, timeout);
|
||||
|
||||
// 发送调用
|
||||
this.sendCall(callInfo);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理RPC响应
|
||||
*/
|
||||
public handleResponse(response: RpcCallResponse): void {
|
||||
const callInfo = this.pendingCalls.get(response.callId);
|
||||
if (!callInfo) {
|
||||
this.logger.warn(`收到未知调用的响应: ${response.callId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 清理定时器
|
||||
const timer = this.retryTimers.get(response.callId);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
this.retryTimers.delete(response.callId);
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
callInfo.status = response.success ? RpcCallStatus.COMPLETED : RpcCallStatus.FAILED;
|
||||
callInfo.completedAt = Date.now();
|
||||
|
||||
// 更新统计
|
||||
if (response.success) {
|
||||
this.stats.successfulCalls++;
|
||||
this.updateAverageResponseTime(response.duration);
|
||||
} else {
|
||||
this.stats.failedCalls++;
|
||||
}
|
||||
|
||||
this.stats.pendingCalls--;
|
||||
|
||||
// 处理结果
|
||||
if (response.success) {
|
||||
callInfo.resolve!(response.result);
|
||||
} else {
|
||||
callInfo.reject!(response.error!);
|
||||
this.emit('callFailed', response.callId, response.error!);
|
||||
}
|
||||
|
||||
// 清理
|
||||
this.pendingCalls.delete(response.callId);
|
||||
this.emit('responseReceived', response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置网络状态
|
||||
*/
|
||||
public setOnlineStatus(online: boolean): void {
|
||||
const wasOnline = this.isOnline;
|
||||
this.isOnline = online;
|
||||
|
||||
if (online && !wasOnline) {
|
||||
// 从离线状态恢复,处理离线队列
|
||||
this.processOfflineQueue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消RPC调用
|
||||
*/
|
||||
public cancelCall(callId: string): boolean {
|
||||
const callInfo = this.pendingCalls.get(callId);
|
||||
if (!callInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 清理定时器
|
||||
const timer = this.retryTimers.get(callId);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
this.retryTimers.delete(callId);
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
callInfo.status = RpcCallStatus.CANCELLED;
|
||||
|
||||
// 拒绝Promise
|
||||
callInfo.reject!({
|
||||
type: RpcErrorType.CLIENT_ERROR,
|
||||
message: '调用被取消'
|
||||
});
|
||||
|
||||
// 清理
|
||||
this.pendingCalls.delete(callId);
|
||||
this.stats.pendingCalls--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): RpcStats {
|
||||
this.stats.lastUpdated = Date.now();
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待处理的调用
|
||||
*/
|
||||
public getPendingCalls(): RpcCallInfo[] {
|
||||
return Array.from(this.pendingCalls.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this.stats = {
|
||||
totalCalls: 0,
|
||||
successfulCalls: 0,
|
||||
failedCalls: 0,
|
||||
averageResponseTime: 0,
|
||||
pendingCalls: this.pendingCalls.size,
|
||||
timeoutCalls: 0,
|
||||
retryCount: 0,
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<RpcCallProxyConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁代理
|
||||
*/
|
||||
public destroy(): void {
|
||||
// 取消所有待处理的调用
|
||||
const pendingCallIds = Array.from(this.pendingCalls.keys());
|
||||
for (const callId of pendingCallIds) {
|
||||
this.cancelCall(callId);
|
||||
}
|
||||
|
||||
// 清理定时器
|
||||
for (const timer of this.retryTimers.values()) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
this.retryTimers.clear();
|
||||
|
||||
// 清理队列
|
||||
this.offlineQueue.length = 0;
|
||||
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送调用
|
||||
*/
|
||||
private async sendCall<T extends readonly unknown[]>(callInfo: RpcCallInfo<T>): Promise<void> {
|
||||
try {
|
||||
// 检查网络状态
|
||||
if (!this.isOnline) {
|
||||
if (this.config.enableOfflineQueue) {
|
||||
this.addToOfflineQueue(callInfo.request);
|
||||
} else {
|
||||
throw new Error('网络不可用');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建网络消息
|
||||
const message = {
|
||||
type: MessageType.RPC_CALL,
|
||||
messageId: callInfo.request.callId,
|
||||
timestamp: Date.now(),
|
||||
senderId: callInfo.request.senderId,
|
||||
data: callInfo.request,
|
||||
reliable: callInfo.request.options.reliable,
|
||||
priority: callInfo.request.options.priority
|
||||
};
|
||||
|
||||
// 发送消息
|
||||
await this.networkSender.sendMessage(message);
|
||||
|
||||
callInfo.status = RpcCallStatus.SENT;
|
||||
callInfo.sentAt = Date.now();
|
||||
|
||||
this.emit('callSent', callInfo.request);
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`发送RPC调用失败: ${callInfo.request.methodName}`, error);
|
||||
|
||||
// 检查是否可以重试
|
||||
if (callInfo.retryCount < this.config.maxRetries) {
|
||||
this.scheduleRetry(callInfo);
|
||||
} else {
|
||||
this.handleCallFailure(callInfo, {
|
||||
type: RpcErrorType.NETWORK_ERROR,
|
||||
message: String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理超时
|
||||
*/
|
||||
private handleTimeout(callId: string): void {
|
||||
const callInfo = this.pendingCalls.get(callId);
|
||||
if (!callInfo || callInfo.status === RpcCallStatus.COMPLETED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否可以重试
|
||||
if (callInfo.retryCount < this.config.maxRetries) {
|
||||
this.scheduleRetry(callInfo);
|
||||
} else {
|
||||
this.stats.timeoutCalls++;
|
||||
this.handleCallFailure(callInfo, {
|
||||
type: RpcErrorType.TIMEOUT,
|
||||
message: `调用超时: ${callInfo.request.options.timeout}ms`
|
||||
});
|
||||
this.emit('callTimeout', callId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排重试
|
||||
*/
|
||||
private scheduleRetry<T extends readonly unknown[]>(callInfo: RpcCallInfo<T>): void {
|
||||
callInfo.retryCount++;
|
||||
this.stats.retryCount++;
|
||||
|
||||
// 计算延迟时间(指数退避)
|
||||
const baseDelay = this.config.retryDelayBase * Math.pow(this.config.retryDelayMultiplier, callInfo.retryCount - 1);
|
||||
const delay = Math.min(baseDelay, this.config.maxRetryDelay);
|
||||
|
||||
callInfo.nextRetryTime = Date.now() + delay;
|
||||
|
||||
this.emit('retryAttempt', callInfo.request.callId, callInfo.retryCount);
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
this.retryTimers.delete(callInfo.request.callId);
|
||||
this.sendCall(callInfo);
|
||||
}, delay);
|
||||
|
||||
this.retryTimers.set(callInfo.request.callId, timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理调用失败
|
||||
*/
|
||||
private handleCallFailure<T extends readonly unknown[]>(callInfo: RpcCallInfo<T>, error: RpcError): void {
|
||||
callInfo.status = RpcCallStatus.FAILED;
|
||||
callInfo.completedAt = Date.now();
|
||||
|
||||
callInfo.reject!(error);
|
||||
|
||||
this.pendingCalls.delete(callInfo.request.callId);
|
||||
this.stats.pendingCalls--;
|
||||
this.stats.failedCalls++;
|
||||
|
||||
this.emit('callFailed', callInfo.request.callId, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加到离线队列
|
||||
*/
|
||||
private addToOfflineQueue<T extends readonly unknown[]>(request: RpcCallRequest<T>): void {
|
||||
if (this.offlineQueue.length >= this.config.maxOfflineQueueSize) {
|
||||
// 移除最旧的请求
|
||||
this.offlineQueue.shift();
|
||||
}
|
||||
|
||||
this.offlineQueue.push(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理离线队列
|
||||
*/
|
||||
private async processOfflineQueue(): Promise<void> {
|
||||
if (!this.isOnline || this.offlineQueue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queue = [...this.offlineQueue];
|
||||
this.offlineQueue.length = 0;
|
||||
|
||||
for (const request of queue) {
|
||||
try {
|
||||
// 重新创建调用信息
|
||||
const callInfo: RpcCallInfo = {
|
||||
request,
|
||||
status: RpcCallStatus.PENDING,
|
||||
retryCount: 0,
|
||||
createdAt: Date.now()
|
||||
};
|
||||
|
||||
this.pendingCalls.set(request.callId, callInfo);
|
||||
await this.sendCall(callInfo);
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`处理离线队列失败: ${request.methodName}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新平均响应时间
|
||||
*/
|
||||
private updateAverageResponseTime(responseTime: number): void {
|
||||
const totalResponses = this.stats.successfulCalls;
|
||||
const currentAverage = this.stats.averageResponseTime;
|
||||
|
||||
this.stats.averageResponseTime =
|
||||
(currentAverage * (totalResponses - 1) + responseTime) / totalResponses;
|
||||
}
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { EventEmitter } from '../utils/EventEmitter';
|
||||
import { RpcMethodMetadata, RpcMethodRegistry } from '../types/RpcTypes';
|
||||
import { getRpcMethods, RpcMethodValidator } from '../decorators/RpcDecorators';
|
||||
|
||||
/**
|
||||
* RPC元数据管理器事件
|
||||
*/
|
||||
export interface RpcMetadataManagerEvents {
|
||||
methodRegistered: (metadata: RpcMethodMetadata) => void;
|
||||
methodUnregistered: (methodName: string) => void;
|
||||
classRegistered: (className: string, methodCount: number) => void;
|
||||
classUnregistered: (className: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC元数据管理器
|
||||
* 负责管理所有RPC方法的元数据和注册信息
|
||||
*/
|
||||
export class RpcMetadataManager extends EventEmitter {
|
||||
private logger = createLogger('RpcMetadataManager');
|
||||
|
||||
/** 方法注册表 */
|
||||
private registry: RpcMethodRegistry = new Map();
|
||||
|
||||
/** 类到方法的映射 */
|
||||
private classMethods = new Map<string, Set<string>>();
|
||||
|
||||
/** 方法名到类的映射 */
|
||||
private methodToClass = new Map<string, string>();
|
||||
|
||||
/** 实例缓存 */
|
||||
private instances = new Map<string, object>();
|
||||
|
||||
/**
|
||||
* 注册RPC类
|
||||
*/
|
||||
public registerClass(instance: object): void {
|
||||
const className = instance.constructor.name;
|
||||
|
||||
try {
|
||||
const rpcMethods = getRpcMethods(instance.constructor as Function);
|
||||
|
||||
if (rpcMethods.length === 0) {
|
||||
this.logger.warn(`类 ${className} 没有RPC方法`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证所有方法定义
|
||||
for (const metadata of rpcMethods) {
|
||||
const validation = RpcMethodValidator.validateMethodDefinition(metadata);
|
||||
if (!validation.valid) {
|
||||
throw new Error(`${className}.${metadata.methodName}: ${validation.error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册方法
|
||||
const methodNames = new Set<string>();
|
||||
|
||||
for (const metadata of rpcMethods) {
|
||||
const fullMethodName = `${className}.${metadata.methodName}`;
|
||||
|
||||
// 检查方法是否已存在
|
||||
if (this.registry.has(fullMethodName)) {
|
||||
throw new Error(`RPC方法已存在: ${fullMethodName}`);
|
||||
}
|
||||
|
||||
// 获取实际方法处理器
|
||||
const handler = (instance as Record<string, unknown>)[metadata.methodName];
|
||||
if (typeof handler !== 'function') {
|
||||
throw new Error(`方法不存在或不是函数: ${fullMethodName}`);
|
||||
}
|
||||
|
||||
// 注册方法
|
||||
this.registry.set(fullMethodName, {
|
||||
metadata,
|
||||
handler: handler.bind(instance)
|
||||
});
|
||||
|
||||
methodNames.add(metadata.methodName);
|
||||
this.methodToClass.set(fullMethodName, className);
|
||||
|
||||
this.logger.debug(`已注册RPC方法: ${fullMethodName}`);
|
||||
this.emit('methodRegistered', metadata);
|
||||
}
|
||||
|
||||
// 更新类映射
|
||||
this.classMethods.set(className, methodNames);
|
||||
this.instances.set(className, instance);
|
||||
|
||||
this.logger.info(`已注册RPC类: ${className},方法数: ${methodNames.size}`);
|
||||
this.emit('classRegistered', className, methodNames.size);
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`注册RPC类失败: ${className}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销RPC类
|
||||
*/
|
||||
public unregisterClass(classNameOrInstance: string | object): void {
|
||||
const className = typeof classNameOrInstance === 'string'
|
||||
? classNameOrInstance
|
||||
: classNameOrInstance.constructor.name;
|
||||
|
||||
const methodNames = this.classMethods.get(className);
|
||||
if (!methodNames) {
|
||||
this.logger.warn(`RPC类未注册: ${className}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 移除所有方法
|
||||
for (const methodName of methodNames) {
|
||||
const fullMethodName = `${className}.${methodName}`;
|
||||
this.registry.delete(fullMethodName);
|
||||
this.methodToClass.delete(fullMethodName);
|
||||
this.emit('methodUnregistered', fullMethodName);
|
||||
}
|
||||
|
||||
// 清理映射
|
||||
this.classMethods.delete(className);
|
||||
this.instances.delete(className);
|
||||
|
||||
this.logger.info(`已注销RPC类: ${className}`);
|
||||
this.emit('classUnregistered', className);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取RPC方法元数据
|
||||
*/
|
||||
public getMethodMetadata(methodName: string): RpcMethodMetadata | undefined {
|
||||
const entry = this.registry.get(methodName);
|
||||
return entry?.metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取RPC方法处理器
|
||||
*/
|
||||
public getMethodHandler(methodName: string): Function | undefined {
|
||||
const entry = this.registry.get(methodName);
|
||||
return entry?.handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查方法是否存在
|
||||
*/
|
||||
public hasMethod(methodName: string): boolean {
|
||||
return this.registry.has(methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有服务端RPC方法
|
||||
*/
|
||||
public getServerRpcMethods(): RpcMethodMetadata[] {
|
||||
const methods: RpcMethodMetadata[] = [];
|
||||
|
||||
for (const [, entry] of this.registry) {
|
||||
if (entry.metadata.isServerRpc) {
|
||||
methods.push(entry.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有客户端RPC方法
|
||||
*/
|
||||
public getClientRpcMethods(): RpcMethodMetadata[] {
|
||||
const methods: RpcMethodMetadata[] = [];
|
||||
|
||||
for (const [, entry] of this.registry) {
|
||||
if (!entry.metadata.isServerRpc) {
|
||||
methods.push(entry.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的所有RPC方法
|
||||
*/
|
||||
public getClassMethods(className: string): RpcMethodMetadata[] {
|
||||
const methodNames = this.classMethods.get(className);
|
||||
if (!methodNames) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const methods: RpcMethodMetadata[] = [];
|
||||
|
||||
for (const methodName of methodNames) {
|
||||
const fullMethodName = `${className}.${methodName}`;
|
||||
const entry = this.registry.get(fullMethodName);
|
||||
if (entry) {
|
||||
methods.push(entry.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已注册的类名列表
|
||||
*/
|
||||
public getRegisteredClasses(): string[] {
|
||||
return Array.from(this.classMethods.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册的方法名
|
||||
*/
|
||||
public getAllMethodNames(): string[] {
|
||||
return Array.from(this.registry.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据方法名获取所属类
|
||||
*/
|
||||
public getMethodClass(methodName: string): string | undefined {
|
||||
return this.methodToClass.get(methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类实例
|
||||
*/
|
||||
public getClassInstance(className: string): object | undefined {
|
||||
return this.instances.get(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注册统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
totalMethods: number;
|
||||
serverRpcMethods: number;
|
||||
clientRpcMethods: number;
|
||||
registeredClasses: number;
|
||||
} {
|
||||
let serverRpcCount = 0;
|
||||
let clientRpcCount = 0;
|
||||
|
||||
for (const [, entry] of this.registry) {
|
||||
if (entry.metadata.isServerRpc) {
|
||||
serverRpcCount++;
|
||||
} else {
|
||||
clientRpcCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totalMethods: this.registry.size,
|
||||
serverRpcMethods: serverRpcCount,
|
||||
clientRpcMethods: clientRpcCount,
|
||||
registeredClasses: this.classMethods.size
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证方法调用
|
||||
*/
|
||||
public validateMethodCall(
|
||||
methodName: string,
|
||||
args: unknown[],
|
||||
callerId?: string
|
||||
): { valid: boolean; error?: string } {
|
||||
const metadata = this.getMethodMetadata(methodName);
|
||||
if (!metadata) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `RPC方法不存在: ${methodName}`
|
||||
};
|
||||
}
|
||||
|
||||
return RpcMethodValidator.validateCall(metadata, args, callerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索方法
|
||||
*/
|
||||
public searchMethods(query: {
|
||||
className?: string;
|
||||
isServerRpc?: boolean;
|
||||
requireAuth?: boolean;
|
||||
target?: string;
|
||||
}): RpcMethodMetadata[] {
|
||||
const results: RpcMethodMetadata[] = [];
|
||||
|
||||
for (const [methodName, entry] of this.registry) {
|
||||
const metadata = entry.metadata;
|
||||
|
||||
// 类名过滤
|
||||
if (query.className && metadata.className !== query.className) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// RPC类型过滤
|
||||
if (query.isServerRpc !== undefined && metadata.isServerRpc !== query.isServerRpc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 认证要求过滤
|
||||
if (query.requireAuth !== undefined && metadata.options.requireAuth !== query.requireAuth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 目标过滤
|
||||
if (query.target && metadata.options.target !== query.target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results.push(metadata);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有注册
|
||||
*/
|
||||
public clear(): void {
|
||||
const classNames = Array.from(this.classMethods.keys());
|
||||
|
||||
for (const className of classNames) {
|
||||
this.unregisterClass(className);
|
||||
}
|
||||
|
||||
this.registry.clear();
|
||||
this.classMethods.clear();
|
||||
this.methodToClass.clear();
|
||||
this.instances.clear();
|
||||
|
||||
this.logger.info('已清空所有RPC注册');
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.clear();
|
||||
this.removeAllListeners();
|
||||
}
|
||||
}
|
||||
@@ -1,552 +0,0 @@
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { EventEmitter } from '../utils/EventEmitter';
|
||||
import { RpcCallRequest, RpcCallResponse, RpcError, RpcErrorType } from '../types/RpcTypes';
|
||||
|
||||
/**
|
||||
* 重复调用记录
|
||||
*/
|
||||
interface DuplicateCallRecord {
|
||||
callId: string;
|
||||
methodName: string;
|
||||
senderId: string;
|
||||
firstCallTime: number;
|
||||
lastCallTime: number;
|
||||
callCount: number;
|
||||
response?: RpcCallResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* 幂等性配置
|
||||
*/
|
||||
interface IdempotencyConfig {
|
||||
/** 是否启用幂等性检查 */
|
||||
enabled: boolean;
|
||||
/** 记录保留时间(毫秒) */
|
||||
recordRetentionTime: number;
|
||||
/** 最大记录数量 */
|
||||
maxRecords: number;
|
||||
/** 检查窗口时间(毫秒) */
|
||||
checkWindowTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 顺序执行配置
|
||||
*/
|
||||
interface OrderedExecutionConfig {
|
||||
/** 是否启用顺序执行 */
|
||||
enabled: boolean;
|
||||
/** 最大等待时间(毫秒) */
|
||||
maxWaitTime: number;
|
||||
/** 队列最大大小 */
|
||||
maxQueueSize: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务配置
|
||||
*/
|
||||
interface TransactionConfig {
|
||||
/** 是否启用事务支持 */
|
||||
enabled: boolean;
|
||||
/** 事务超时时间(毫秒) */
|
||||
transactionTimeout: number;
|
||||
/** 最大事务数量 */
|
||||
maxTransactions: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC可靠性管理器配置
|
||||
*/
|
||||
export interface RpcReliabilityConfig {
|
||||
/** 幂等性配置 */
|
||||
idempotency: IdempotencyConfig;
|
||||
/** 顺序执行配置 */
|
||||
orderedExecution: OrderedExecutionConfig;
|
||||
/** 事务配置 */
|
||||
transaction: TransactionConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务信息
|
||||
*/
|
||||
interface TransactionInfo {
|
||||
transactionId: string;
|
||||
calls: RpcCallRequest[];
|
||||
startTime: number;
|
||||
status: 'pending' | 'committed' | 'rolledback';
|
||||
rollbackActions: Array<() => Promise<void>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 顺序执行队列项
|
||||
*/
|
||||
interface OrderedQueueItem {
|
||||
request: RpcCallRequest;
|
||||
handler: () => Promise<RpcCallResponse>;
|
||||
resolve: (response: RpcCallResponse) => void;
|
||||
reject: (error: RpcError) => void;
|
||||
enqueuedAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC可靠性管理器事件
|
||||
*/
|
||||
export interface RpcReliabilityManagerEvents {
|
||||
duplicateCallDetected: (record: DuplicateCallRecord) => void;
|
||||
transactionStarted: (transactionId: string) => void;
|
||||
transactionCommitted: (transactionId: string) => void;
|
||||
transactionRolledback: (transactionId: string, reason: string) => void;
|
||||
orderedCallQueued: (callId: string, queueSize: number) => void;
|
||||
orderedCallProcessed: (callId: string, waitTime: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC可靠性管理器
|
||||
* 提供重复检测、幂等性、顺序执行和事务支持
|
||||
*/
|
||||
export class RpcReliabilityManager extends EventEmitter {
|
||||
private logger = createLogger('RpcReliabilityManager');
|
||||
private config: RpcReliabilityConfig;
|
||||
|
||||
/** 重复调用记录 */
|
||||
private duplicateRecords = new Map<string, DuplicateCallRecord>();
|
||||
|
||||
/** 活跃事务 */
|
||||
private transactions = new Map<string, TransactionInfo>();
|
||||
|
||||
/** 顺序执行队列(按发送者分组) */
|
||||
private orderedQueues = new Map<string, OrderedQueueItem[]>();
|
||||
|
||||
/** 正在处理的有序调用 */
|
||||
private processingOrdered = new Set<string>();
|
||||
|
||||
/** 清理定时器 */
|
||||
private cleanupTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
constructor(config: Partial<RpcReliabilityConfig> = {}) {
|
||||
super();
|
||||
|
||||
this.config = {
|
||||
idempotency: {
|
||||
enabled: true,
|
||||
recordRetentionTime: 300000, // 5分钟
|
||||
maxRecords: 10000,
|
||||
checkWindowTime: 60000, // 1分钟
|
||||
...config.idempotency
|
||||
},
|
||||
orderedExecution: {
|
||||
enabled: false,
|
||||
maxWaitTime: 30000,
|
||||
maxQueueSize: 1000,
|
||||
...config.orderedExecution
|
||||
},
|
||||
transaction: {
|
||||
enabled: false,
|
||||
transactionTimeout: 60000,
|
||||
maxTransactions: 100,
|
||||
...config.transaction
|
||||
}
|
||||
};
|
||||
|
||||
this.startCleanupTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并处理重复调用
|
||||
*/
|
||||
public checkDuplicateCall(request: RpcCallRequest): {
|
||||
isDuplicate: boolean;
|
||||
response?: RpcCallResponse;
|
||||
shouldProcess: boolean;
|
||||
} {
|
||||
if (!this.config.idempotency.enabled) {
|
||||
return { isDuplicate: false, shouldProcess: true };
|
||||
}
|
||||
|
||||
const key = `${request.senderId}:${request.callId}`;
|
||||
const existing = this.duplicateRecords.get(key);
|
||||
const now = Date.now();
|
||||
|
||||
if (existing) {
|
||||
// 更新重复调用记录
|
||||
existing.lastCallTime = now;
|
||||
existing.callCount++;
|
||||
|
||||
this.emit('duplicateCallDetected', existing);
|
||||
|
||||
// 如果已有响应,直接返回
|
||||
if (existing.response) {
|
||||
return {
|
||||
isDuplicate: true,
|
||||
response: existing.response,
|
||||
shouldProcess: false
|
||||
};
|
||||
}
|
||||
|
||||
// 如果在检查窗口内,认为是重复调用但还在处理中
|
||||
if (now - existing.firstCallTime < this.config.idempotency.checkWindowTime) {
|
||||
return {
|
||||
isDuplicate: true,
|
||||
shouldProcess: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 记录新的调用
|
||||
const record: DuplicateCallRecord = {
|
||||
callId: request.callId,
|
||||
methodName: request.methodName,
|
||||
senderId: request.senderId,
|
||||
firstCallTime: now,
|
||||
lastCallTime: now,
|
||||
callCount: 1
|
||||
};
|
||||
|
||||
this.duplicateRecords.set(key, record);
|
||||
|
||||
return { isDuplicate: false, shouldProcess: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录调用响应(用于幂等性)
|
||||
*/
|
||||
public recordCallResponse(request: RpcCallRequest, response: RpcCallResponse): void {
|
||||
if (!this.config.idempotency.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = `${request.senderId}:${request.callId}`;
|
||||
const record = this.duplicateRecords.get(key);
|
||||
|
||||
if (record) {
|
||||
record.response = response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理有序调用
|
||||
*/
|
||||
public async handleOrderedCall(
|
||||
request: RpcCallRequest,
|
||||
handler: () => Promise<RpcCallResponse>
|
||||
): Promise<RpcCallResponse> {
|
||||
if (!this.config.orderedExecution.enabled) {
|
||||
return handler();
|
||||
}
|
||||
|
||||
const senderId = request.senderId;
|
||||
|
||||
return new Promise<RpcCallResponse>((resolve, reject) => {
|
||||
const queueItem: OrderedQueueItem = {
|
||||
request,
|
||||
handler,
|
||||
resolve,
|
||||
reject,
|
||||
enqueuedAt: Date.now()
|
||||
};
|
||||
|
||||
// 获取或创建队列
|
||||
let queue = this.orderedQueues.get(senderId);
|
||||
if (!queue) {
|
||||
queue = [];
|
||||
this.orderedQueues.set(senderId, queue);
|
||||
}
|
||||
|
||||
// 检查队列大小
|
||||
if (queue.length >= this.config.orderedExecution.maxQueueSize) {
|
||||
reject({
|
||||
type: RpcErrorType.RATE_LIMITED,
|
||||
message: '有序执行队列已满'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
queue.push(queueItem);
|
||||
this.emit('orderedCallQueued', request.callId, queue.length);
|
||||
|
||||
// 如果没有正在处理的调用,开始处理
|
||||
if (!this.processingOrdered.has(senderId)) {
|
||||
this.processOrderedQueue(senderId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始事务
|
||||
*/
|
||||
public startTransaction(transactionId: string): void {
|
||||
if (!this.config.transaction.enabled) {
|
||||
throw new Error('事务功能未启用');
|
||||
}
|
||||
|
||||
if (this.transactions.has(transactionId)) {
|
||||
throw new Error(`事务已存在: ${transactionId}`);
|
||||
}
|
||||
|
||||
if (this.transactions.size >= this.config.transaction.maxTransactions) {
|
||||
throw new Error('超过最大事务数量限制');
|
||||
}
|
||||
|
||||
const transaction: TransactionInfo = {
|
||||
transactionId,
|
||||
calls: [],
|
||||
startTime: Date.now(),
|
||||
status: 'pending',
|
||||
rollbackActions: []
|
||||
};
|
||||
|
||||
this.transactions.set(transactionId, transaction);
|
||||
this.emit('transactionStarted', transactionId);
|
||||
|
||||
// 设置事务超时
|
||||
setTimeout(() => {
|
||||
if (this.transactions.has(transactionId)) {
|
||||
this.rollbackTransaction(transactionId, '事务超时');
|
||||
}
|
||||
}, this.config.transaction.transactionTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事务调用
|
||||
*/
|
||||
public addTransactionCall(
|
||||
transactionId: string,
|
||||
request: RpcCallRequest,
|
||||
rollbackAction?: () => Promise<void>
|
||||
): void {
|
||||
const transaction = this.transactions.get(transactionId);
|
||||
if (!transaction) {
|
||||
throw new Error(`事务不存在: ${transactionId}`);
|
||||
}
|
||||
|
||||
if (transaction.status !== 'pending') {
|
||||
throw new Error(`事务状态无效: ${transaction.status}`);
|
||||
}
|
||||
|
||||
transaction.calls.push(request);
|
||||
|
||||
if (rollbackAction) {
|
||||
transaction.rollbackActions.push(rollbackAction);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交事务
|
||||
*/
|
||||
public async commitTransaction(transactionId: string): Promise<void> {
|
||||
const transaction = this.transactions.get(transactionId);
|
||||
if (!transaction) {
|
||||
throw new Error(`事务不存在: ${transactionId}`);
|
||||
}
|
||||
|
||||
if (transaction.status !== 'pending') {
|
||||
throw new Error(`事务状态无效: ${transaction.status}`);
|
||||
}
|
||||
|
||||
transaction.status = 'committed';
|
||||
this.transactions.delete(transactionId);
|
||||
|
||||
this.emit('transactionCommitted', transactionId);
|
||||
this.logger.info(`事务已提交: ${transactionId},包含 ${transaction.calls.length} 个调用`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚事务
|
||||
*/
|
||||
public async rollbackTransaction(transactionId: string, reason: string): Promise<void> {
|
||||
const transaction = this.transactions.get(transactionId);
|
||||
if (!transaction) {
|
||||
throw new Error(`事务不存在: ${transactionId}`);
|
||||
}
|
||||
|
||||
if (transaction.status !== 'pending') {
|
||||
return; // 已经处理过
|
||||
}
|
||||
|
||||
transaction.status = 'rolledback';
|
||||
|
||||
// 执行回滚操作
|
||||
for (const rollbackAction of transaction.rollbackActions.reverse()) {
|
||||
try {
|
||||
await rollbackAction();
|
||||
} catch (error) {
|
||||
this.logger.error(`回滚操作失败: ${transactionId}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
this.transactions.delete(transactionId);
|
||||
|
||||
this.emit('transactionRolledback', transactionId, reason);
|
||||
this.logger.warn(`事务已回滚: ${transactionId},原因: ${reason}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事务信息
|
||||
*/
|
||||
public getTransaction(transactionId: string): TransactionInfo | undefined {
|
||||
return this.transactions.get(transactionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
duplicateRecords: number;
|
||||
activeTransactions: number;
|
||||
totalQueuedCalls: number;
|
||||
processingQueues: number;
|
||||
} {
|
||||
let totalQueuedCalls = 0;
|
||||
for (const queue of this.orderedQueues.values()) {
|
||||
totalQueuedCalls += queue.length;
|
||||
}
|
||||
|
||||
return {
|
||||
duplicateRecords: this.duplicateRecords.size,
|
||||
activeTransactions: this.transactions.size,
|
||||
totalQueuedCalls,
|
||||
processingQueues: this.processingOrdered.size
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<RpcReliabilityConfig>): void {
|
||||
if (newConfig.idempotency) {
|
||||
Object.assign(this.config.idempotency, newConfig.idempotency);
|
||||
}
|
||||
if (newConfig.orderedExecution) {
|
||||
Object.assign(this.config.orderedExecution, newConfig.orderedExecution);
|
||||
}
|
||||
if (newConfig.transaction) {
|
||||
Object.assign(this.config.transaction, newConfig.transaction);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器
|
||||
*/
|
||||
public destroy(): void {
|
||||
// 停止清理定时器
|
||||
if (this.cleanupTimer) {
|
||||
clearInterval(this.cleanupTimer);
|
||||
this.cleanupTimer = null;
|
||||
}
|
||||
|
||||
// 回滚所有活跃事务
|
||||
const transactionIds = Array.from(this.transactions.keys());
|
||||
for (const transactionId of transactionIds) {
|
||||
this.rollbackTransaction(transactionId, '管理器销毁').catch((error) => {
|
||||
this.logger.error(`销毁时回滚事务失败: ${transactionId}`, error);
|
||||
});
|
||||
}
|
||||
|
||||
// 清理队列
|
||||
for (const queue of this.orderedQueues.values()) {
|
||||
for (const item of queue) {
|
||||
item.reject({
|
||||
type: RpcErrorType.CLIENT_ERROR,
|
||||
message: '服务关闭'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.duplicateRecords.clear();
|
||||
this.transactions.clear();
|
||||
this.orderedQueues.clear();
|
||||
this.processingOrdered.clear();
|
||||
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理有序队列
|
||||
*/
|
||||
private async processOrderedQueue(senderId: string): Promise<void> {
|
||||
this.processingOrdered.add(senderId);
|
||||
|
||||
try {
|
||||
const queue = this.orderedQueues.get(senderId);
|
||||
if (!queue || queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (queue.length > 0) {
|
||||
const item = queue.shift()!;
|
||||
const waitTime = Date.now() - item.enqueuedAt;
|
||||
|
||||
// 检查等待时间是否超限
|
||||
if (waitTime > this.config.orderedExecution.maxWaitTime) {
|
||||
item.reject({
|
||||
type: RpcErrorType.TIMEOUT,
|
||||
message: `有序执行等待超时: ${waitTime}ms`
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await item.handler();
|
||||
item.resolve(response);
|
||||
|
||||
this.emit('orderedCallProcessed', item.request.callId, waitTime);
|
||||
|
||||
} catch (error) {
|
||||
item.reject(error as RpcError);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
this.processingOrdered.delete(senderId);
|
||||
|
||||
// 如果队列还有新的项目,继续处理
|
||||
const queue = this.orderedQueues.get(senderId);
|
||||
if (queue && queue.length > 0) {
|
||||
setImmediate(() => this.processOrderedQueue(senderId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始清理定时器
|
||||
*/
|
||||
private startCleanupTimer(): void {
|
||||
this.cleanupTimer = setInterval(() => {
|
||||
this.cleanup();
|
||||
}, 60000); // 每分钟清理一次
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期数据
|
||||
*/
|
||||
private cleanup(): void {
|
||||
const now = Date.now();
|
||||
|
||||
// 清理过期的重复调用记录
|
||||
if (this.config.idempotency.enabled) {
|
||||
for (const [key, record] of this.duplicateRecords) {
|
||||
if (now - record.lastCallTime > this.config.idempotency.recordRetentionTime) {
|
||||
this.duplicateRecords.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 限制记录数量
|
||||
if (this.duplicateRecords.size > this.config.idempotency.maxRecords) {
|
||||
const sortedRecords = Array.from(this.duplicateRecords.entries())
|
||||
.sort(([,a], [,b]) => a.lastCallTime - b.lastCallTime);
|
||||
|
||||
const keepCount = Math.floor(this.config.idempotency.maxRecords * 0.8);
|
||||
|
||||
for (let i = 0; i < sortedRecords.length - keepCount; i++) {
|
||||
this.duplicateRecords.delete(sortedRecords[i][0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理空的有序队列
|
||||
for (const [senderId, queue] of this.orderedQueues) {
|
||||
if (queue.length === 0 && !this.processingOrdered.has(senderId)) {
|
||||
this.orderedQueues.delete(senderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,550 +0,0 @@
|
||||
/**
|
||||
* JSON序列化器
|
||||
* 提供高性能的消息序列化和反序列化功能,包括类型安全检查
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { INetworkMessage, MessageType } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 序列化器配置
|
||||
*/
|
||||
export interface SerializerConfig {
|
||||
enableTypeChecking: boolean;
|
||||
enableCompression: boolean;
|
||||
maxMessageSize: number;
|
||||
enableProfiling: boolean;
|
||||
customSerializers?: Map<string, ICustomSerializer>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义序列化器接口
|
||||
*/
|
||||
export interface ICustomSerializer {
|
||||
serialize(data: any): any;
|
||||
deserialize(data: any): any;
|
||||
canHandle(data: any): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化结果
|
||||
*/
|
||||
export interface SerializationResult {
|
||||
data: string | Buffer;
|
||||
size: number;
|
||||
compressionRatio?: number;
|
||||
serializationTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化结果
|
||||
*/
|
||||
export interface DeserializationResult<T = any> {
|
||||
data: T;
|
||||
deserializationTime: number;
|
||||
isValid: boolean;
|
||||
errors?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化统计信息
|
||||
*/
|
||||
export interface SerializationStats {
|
||||
totalSerialized: number;
|
||||
totalDeserialized: number;
|
||||
totalBytes: number;
|
||||
averageSerializationTime: number;
|
||||
averageDeserializationTime: number;
|
||||
averageMessageSize: number;
|
||||
errorCount: number;
|
||||
compressionSavings: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON序列化器
|
||||
*/
|
||||
export class JSONSerializer {
|
||||
private logger = createLogger('JSONSerializer');
|
||||
private config: SerializerConfig;
|
||||
private stats: SerializationStats;
|
||||
|
||||
// 性能分析
|
||||
private serializationTimes: number[] = [];
|
||||
private deserializationTimes: number[] = [];
|
||||
private messageSizes: number[] = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<SerializerConfig> = {}) {
|
||||
this.config = {
|
||||
enableTypeChecking: true,
|
||||
enableCompression: false,
|
||||
maxMessageSize: 1024 * 1024, // 1MB
|
||||
enableProfiling: false,
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalSerialized: 0,
|
||||
totalDeserialized: 0,
|
||||
totalBytes: 0,
|
||||
averageSerializationTime: 0,
|
||||
averageDeserializationTime: 0,
|
||||
averageMessageSize: 0,
|
||||
errorCount: 0,
|
||||
compressionSavings: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化消息
|
||||
*/
|
||||
serialize<T extends INetworkMessage>(message: T): SerializationResult {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 类型检查
|
||||
if (this.config.enableTypeChecking) {
|
||||
this.validateMessage(message);
|
||||
}
|
||||
|
||||
// 预处理消息
|
||||
const processedMessage = this.preprocessMessage(message);
|
||||
|
||||
// 序列化
|
||||
let serializedData: string;
|
||||
|
||||
// 使用自定义序列化器
|
||||
const customSerializer = this.findCustomSerializer(processedMessage);
|
||||
if (customSerializer) {
|
||||
serializedData = JSON.stringify(customSerializer.serialize(processedMessage));
|
||||
} else {
|
||||
serializedData = JSON.stringify(processedMessage, this.createReplacer());
|
||||
}
|
||||
|
||||
// 检查大小限制
|
||||
if (serializedData.length > this.config.maxMessageSize) {
|
||||
throw new Error(`消息大小超过限制: ${serializedData.length} > ${this.config.maxMessageSize}`);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const serializationTime = endTime - startTime;
|
||||
|
||||
// 更新统计
|
||||
this.updateSerializationStats(serializedData.length, serializationTime);
|
||||
|
||||
return {
|
||||
data: serializedData,
|
||||
size: serializedData.length,
|
||||
serializationTime
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.stats.errorCount++;
|
||||
this.logger.error('序列化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化消息
|
||||
*/
|
||||
deserialize<T extends INetworkMessage>(data: string | ArrayBuffer): DeserializationResult<T> {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 转换数据格式
|
||||
const jsonString = data instanceof ArrayBuffer ? new TextDecoder().decode(data) :
|
||||
typeof data === 'string' ? data : String(data);
|
||||
|
||||
// 解析JSON
|
||||
const parsedData = JSON.parse(jsonString, this.createReviver());
|
||||
|
||||
// 类型检查
|
||||
const validationResult = this.config.enableTypeChecking ?
|
||||
this.validateParsedMessage(parsedData) : { isValid: true, errors: [] };
|
||||
|
||||
// 后处理消息
|
||||
const processedMessage = this.postprocessMessage(parsedData);
|
||||
|
||||
const endTime = performance.now();
|
||||
const deserializationTime = endTime - startTime;
|
||||
|
||||
// 更新统计
|
||||
this.updateDeserializationStats(deserializationTime);
|
||||
|
||||
return {
|
||||
data: processedMessage as T,
|
||||
deserializationTime,
|
||||
isValid: validationResult.isValid,
|
||||
errors: validationResult.errors
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.stats.errorCount++;
|
||||
this.logger.error('反序列化失败:', error);
|
||||
|
||||
return {
|
||||
data: {} as T,
|
||||
deserializationTime: performance.now() - startTime,
|
||||
isValid: false,
|
||||
errors: [error instanceof Error ? error.message : '未知错误']
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量序列化
|
||||
*/
|
||||
serializeBatch<T extends INetworkMessage>(messages: T[]): SerializationResult {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
const batchData = {
|
||||
type: 'batch',
|
||||
messages: messages.map((msg) => {
|
||||
if (this.config.enableTypeChecking) {
|
||||
this.validateMessage(msg);
|
||||
}
|
||||
return this.preprocessMessage(msg);
|
||||
}),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
const serializedData = JSON.stringify(batchData, this.createReplacer());
|
||||
|
||||
if (serializedData.length > this.config.maxMessageSize) {
|
||||
throw new Error(`批量消息大小超过限制: ${serializedData.length} > ${this.config.maxMessageSize}`);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const serializationTime = endTime - startTime;
|
||||
|
||||
this.updateSerializationStats(serializedData.length, serializationTime);
|
||||
|
||||
return {
|
||||
data: serializedData,
|
||||
size: serializedData.length,
|
||||
serializationTime
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.stats.errorCount++;
|
||||
this.logger.error('批量序列化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量反序列化
|
||||
*/
|
||||
deserializeBatch<T extends INetworkMessage>(data: string | ArrayBuffer): DeserializationResult<T[]> {
|
||||
const result = this.deserialize<any>(data);
|
||||
|
||||
if (!result.isValid || !result.data.messages) {
|
||||
return {
|
||||
data: [],
|
||||
deserializationTime: result.deserializationTime,
|
||||
isValid: false,
|
||||
errors: ['无效的批量消息格式']
|
||||
};
|
||||
}
|
||||
|
||||
const messages = result.data.messages.map((msg: any) => this.postprocessMessage(msg));
|
||||
|
||||
return {
|
||||
data: messages as T[],
|
||||
deserializationTime: result.deserializationTime,
|
||||
isValid: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): SerializationStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
totalSerialized: 0,
|
||||
totalDeserialized: 0,
|
||||
totalBytes: 0,
|
||||
averageSerializationTime: 0,
|
||||
averageDeserializationTime: 0,
|
||||
averageMessageSize: 0,
|
||||
errorCount: 0,
|
||||
compressionSavings: 0
|
||||
};
|
||||
|
||||
this.serializationTimes.length = 0;
|
||||
this.deserializationTimes.length = 0;
|
||||
this.messageSizes.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义序列化器
|
||||
*/
|
||||
addCustomSerializer(name: string, serializer: ICustomSerializer): void {
|
||||
if (!this.config.customSerializers) {
|
||||
this.config.customSerializers = new Map();
|
||||
}
|
||||
this.config.customSerializers.set(name, serializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除自定义序列化器
|
||||
*/
|
||||
removeCustomSerializer(name: string): boolean {
|
||||
return this.config.customSerializers?.delete(name) || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<SerializerConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('序列化器配置已更新:', newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证消息格式
|
||||
*/
|
||||
private validateMessage(message: INetworkMessage): void {
|
||||
if (!message.type || !message.messageId || !message.timestamp) {
|
||||
throw new Error('消息格式无效:缺少必需字段');
|
||||
}
|
||||
|
||||
if (!Object.values(MessageType).includes(message.type)) {
|
||||
throw new Error(`无效的消息类型: ${message.type}`);
|
||||
}
|
||||
|
||||
if (typeof message.timestamp !== 'number' || message.timestamp <= 0) {
|
||||
throw new Error('无效的时间戳');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证解析后的消息
|
||||
*/
|
||||
private validateParsedMessage(data: any): { isValid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!data || typeof data !== 'object') {
|
||||
errors.push('消息必须是对象');
|
||||
} else {
|
||||
if (!data.type) errors.push('缺少消息类型');
|
||||
if (!data.messageId) errors.push('缺少消息ID');
|
||||
if (!data.timestamp) errors.push('缺少时间戳');
|
||||
if (!data.senderId) errors.push('缺少发送者ID');
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 预处理消息(序列化前)
|
||||
*/
|
||||
private preprocessMessage(message: INetworkMessage): any {
|
||||
// 克隆消息以避免修改原始对象
|
||||
const processed = { ...message };
|
||||
|
||||
// 处理特殊数据类型
|
||||
if (processed.data) {
|
||||
processed.data = this.serializeSpecialTypes(processed.data);
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 后处理消息(反序列化后)
|
||||
*/
|
||||
private postprocessMessage(data: any): any {
|
||||
if (data.data) {
|
||||
data.data = this.deserializeSpecialTypes(data.data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化特殊类型
|
||||
*/
|
||||
private serializeSpecialTypes(data: any): any {
|
||||
if (data instanceof Date) {
|
||||
return { __type: 'Date', value: data.toISOString() };
|
||||
} else if (data instanceof Map) {
|
||||
return { __type: 'Map', value: Array.from(data.entries()) };
|
||||
} else if (data instanceof Set) {
|
||||
return { __type: 'Set', value: Array.from(data) };
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
return { __type: 'TypedArray', value: Array.from(data as any), constructor: data.constructor.name };
|
||||
} else if (data && typeof data === 'object') {
|
||||
const result: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
result[key] = this.serializeSpecialTypes(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化特殊类型
|
||||
*/
|
||||
private deserializeSpecialTypes(data: any): any {
|
||||
if (data && typeof data === 'object' && data.__type) {
|
||||
switch (data.__type) {
|
||||
case 'Date':
|
||||
return new Date(data.value);
|
||||
case 'Map':
|
||||
return new Map(data.value);
|
||||
case 'Set':
|
||||
return new Set(data.value);
|
||||
case 'TypedArray':
|
||||
const constructor = (globalThis as any)[data.constructor];
|
||||
return constructor ? new constructor(data.value) : data.value;
|
||||
}
|
||||
} else if (data && typeof data === 'object') {
|
||||
const result: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
result[key] = this.deserializeSpecialTypes(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建JSON.stringify替换函数
|
||||
*/
|
||||
private createReplacer() {
|
||||
return (key: string, value: any) => {
|
||||
// 处理循环引用
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.__serializing) {
|
||||
return '[Circular Reference]';
|
||||
}
|
||||
value.__serializing = true;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建JSON.parse恢复函数
|
||||
*/
|
||||
private createReviver() {
|
||||
return (key: string, value: any) => {
|
||||
// 清理序列化标记
|
||||
if (value && typeof value === 'object') {
|
||||
delete value.__serializing;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找自定义序列化器
|
||||
*/
|
||||
private findCustomSerializer(data: any): ICustomSerializer | undefined {
|
||||
if (!this.config.customSerializers) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const serializer of this.config.customSerializers.values()) {
|
||||
if (serializer.canHandle(data)) {
|
||||
return serializer;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新序列化统计
|
||||
*/
|
||||
private updateSerializationStats(size: number, time: number): void {
|
||||
this.stats.totalSerialized++;
|
||||
this.stats.totalBytes += size;
|
||||
|
||||
this.serializationTimes.push(time);
|
||||
this.messageSizes.push(size);
|
||||
|
||||
// 保持最近1000个样本
|
||||
if (this.serializationTimes.length > 1000) {
|
||||
this.serializationTimes.shift();
|
||||
}
|
||||
if (this.messageSizes.length > 1000) {
|
||||
this.messageSizes.shift();
|
||||
}
|
||||
|
||||
// 计算平均值
|
||||
this.stats.averageSerializationTime =
|
||||
this.serializationTimes.reduce((sum, t) => sum + t, 0) / this.serializationTimes.length;
|
||||
this.stats.averageMessageSize =
|
||||
this.messageSizes.reduce((sum, s) => sum + s, 0) / this.messageSizes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新反序列化统计
|
||||
*/
|
||||
private updateDeserializationStats(time: number): void {
|
||||
this.stats.totalDeserialized++;
|
||||
|
||||
this.deserializationTimes.push(time);
|
||||
|
||||
// 保持最近1000个样本
|
||||
if (this.deserializationTimes.length > 1000) {
|
||||
this.deserializationTimes.shift();
|
||||
}
|
||||
|
||||
// 计算平均值
|
||||
this.stats.averageDeserializationTime =
|
||||
this.deserializationTimes.reduce((sum, t) => sum + t, 0) / this.deserializationTimes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性能分析报告
|
||||
*/
|
||||
getPerformanceReport() {
|
||||
return {
|
||||
stats: this.getStats(),
|
||||
serializationTimes: [...this.serializationTimes],
|
||||
deserializationTimes: [...this.deserializationTimes],
|
||||
messageSizes: [...this.messageSizes],
|
||||
percentiles: {
|
||||
serialization: this.calculatePercentiles(this.serializationTimes),
|
||||
deserialization: this.calculatePercentiles(this.deserializationTimes),
|
||||
messageSize: this.calculatePercentiles(this.messageSizes)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算百分位数
|
||||
*/
|
||||
private calculatePercentiles(values: number[]) {
|
||||
if (values.length === 0) return {};
|
||||
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const n = sorted.length;
|
||||
|
||||
return {
|
||||
p50: sorted[Math.floor(n * 0.5)],
|
||||
p90: sorted[Math.floor(n * 0.9)],
|
||||
p95: sorted[Math.floor(n * 0.95)],
|
||||
p99: sorted[Math.floor(n * 0.99)]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,654 +0,0 @@
|
||||
/**
|
||||
* 消息压缩器框架
|
||||
* 提供可扩展的压缩算法接口,用户可以注册自定义压缩算法
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 压缩算法接口
|
||||
*/
|
||||
export interface ICompressionAlgorithm {
|
||||
/** 算法名称 */
|
||||
readonly name: string;
|
||||
/** 算法版本 */
|
||||
readonly version: string;
|
||||
/** 是否支持异步压缩 */
|
||||
readonly supportsAsync: boolean;
|
||||
|
||||
/**
|
||||
* 同步压缩
|
||||
*/
|
||||
compress(data: ArrayBuffer): ArrayBuffer;
|
||||
|
||||
/**
|
||||
* 同步解压缩
|
||||
*/
|
||||
decompress(data: ArrayBuffer): ArrayBuffer;
|
||||
|
||||
/**
|
||||
* 异步压缩(可选)
|
||||
*/
|
||||
compressAsync?(data: ArrayBuffer): Promise<ArrayBuffer>;
|
||||
|
||||
/**
|
||||
* 异步解压缩(可选)
|
||||
*/
|
||||
decompressAsync?(data: ArrayBuffer): Promise<ArrayBuffer>;
|
||||
|
||||
/**
|
||||
* 估算压缩后大小(可选)
|
||||
*/
|
||||
estimateCompressedSize?(data: ArrayBuffer): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩配置
|
||||
*/
|
||||
export interface CompressionConfig {
|
||||
/** 默认压缩算法名称 */
|
||||
defaultAlgorithm: string;
|
||||
/** 最小压缩阈值(字节) */
|
||||
threshold: number;
|
||||
/** 是否启用异步压缩 */
|
||||
enableAsync: boolean;
|
||||
/** 压缩级别提示 (0-9) */
|
||||
level: number;
|
||||
/** 分块大小 */
|
||||
chunkSize: number;
|
||||
/** 是否启用压缩统计 */
|
||||
enableStats: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩结果
|
||||
*/
|
||||
export interface CompressionResult {
|
||||
/** 压缩后的数据 */
|
||||
data: ArrayBuffer;
|
||||
/** 原始大小 */
|
||||
originalSize: number;
|
||||
/** 压缩后大小 */
|
||||
compressedSize: number;
|
||||
/** 压缩比 */
|
||||
compressionRatio: number;
|
||||
/** 压缩耗时 */
|
||||
compressionTime: number;
|
||||
/** 使用的算法 */
|
||||
algorithm: string;
|
||||
/** 是否实际进行了压缩 */
|
||||
wasCompressed: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压缩结果
|
||||
*/
|
||||
export interface DecompressionResult {
|
||||
/** 解压缩后的数据 */
|
||||
data: ArrayBuffer;
|
||||
/** 原始压缩大小 */
|
||||
compressedSize: number;
|
||||
/** 解压缩后大小 */
|
||||
decompressedSize: number;
|
||||
/** 解压缩耗时 */
|
||||
decompressionTime: number;
|
||||
/** 使用的算法 */
|
||||
algorithm: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩统计信息
|
||||
*/
|
||||
export interface CompressionStats {
|
||||
/** 总压缩次数 */
|
||||
totalCompressions: number;
|
||||
/** 总解压缩次数 */
|
||||
totalDecompressions: number;
|
||||
/** 总原始字节数 */
|
||||
totalOriginalBytes: number;
|
||||
/** 总压缩字节数 */
|
||||
totalCompressedBytes: number;
|
||||
/** 平均压缩比 */
|
||||
averageCompressionRatio: number;
|
||||
/** 平均压缩时间 */
|
||||
averageCompressionTime: number;
|
||||
/** 平均解压缩时间 */
|
||||
averageDecompressionTime: number;
|
||||
/** 算法使用统计 */
|
||||
algorithmUsage: Record<string, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 无压缩算法实现(默认)
|
||||
*/
|
||||
export class NoCompressionAlgorithm implements ICompressionAlgorithm {
|
||||
readonly name = 'none';
|
||||
readonly version = '1.0.0';
|
||||
readonly supportsAsync = false;
|
||||
|
||||
compress(data: ArrayBuffer): ArrayBuffer {
|
||||
return data.slice(0);
|
||||
}
|
||||
|
||||
decompress(data: ArrayBuffer): ArrayBuffer {
|
||||
return data.slice(0);
|
||||
}
|
||||
|
||||
estimateCompressedSize(data: ArrayBuffer): number {
|
||||
return data.byteLength;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LZ字符串压缩算法实现
|
||||
*/
|
||||
export class LZCompressionAlgorithm implements ICompressionAlgorithm {
|
||||
readonly name = 'lz-string';
|
||||
readonly version = '1.0.0';
|
||||
readonly supportsAsync = false;
|
||||
|
||||
compress(data: ArrayBuffer): ArrayBuffer {
|
||||
// 将ArrayBuffer转换为字符串
|
||||
const decoder = new TextDecoder();
|
||||
const input = decoder.decode(data);
|
||||
|
||||
if (!input) {
|
||||
return data.slice(0);
|
||||
}
|
||||
|
||||
// LZ压缩算法
|
||||
const dictionary: { [key: string]: number } = {};
|
||||
let dictSize = 256;
|
||||
|
||||
// 初始化字典
|
||||
for (let i = 0; i < 256; i++) {
|
||||
dictionary[String.fromCharCode(i)] = i;
|
||||
}
|
||||
|
||||
let w = '';
|
||||
const result: number[] = [];
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const c = input.charAt(i);
|
||||
const wc = w + c;
|
||||
|
||||
if (dictionary[wc] !== undefined) {
|
||||
w = wc;
|
||||
} else {
|
||||
result.push(dictionary[w]);
|
||||
dictionary[wc] = dictSize++;
|
||||
w = c;
|
||||
|
||||
// 防止字典过大
|
||||
if (dictSize >= 0xFFFF) {
|
||||
dictSize = 256;
|
||||
// 重置字典
|
||||
for (const key in dictionary) {
|
||||
if (dictionary[key] >= 256) {
|
||||
delete dictionary[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (w) {
|
||||
result.push(dictionary[w]);
|
||||
}
|
||||
|
||||
// 将结果转换为ArrayBuffer
|
||||
return this.numbersToArrayBuffer(result);
|
||||
}
|
||||
|
||||
decompress(data: ArrayBuffer): ArrayBuffer {
|
||||
if (data.byteLength === 0) {
|
||||
return data.slice(0);
|
||||
}
|
||||
|
||||
const numbers = this.arrayBufferToNumbers(data);
|
||||
if (numbers.length === 0) {
|
||||
return data.slice(0);
|
||||
}
|
||||
|
||||
const dictionary: { [key: number]: string } = {};
|
||||
let dictSize = 256;
|
||||
|
||||
// 初始化字典
|
||||
for (let i = 0; i < 256; i++) {
|
||||
dictionary[i] = String.fromCharCode(i);
|
||||
}
|
||||
|
||||
let w = String.fromCharCode(numbers[0]);
|
||||
const result = [w];
|
||||
|
||||
for (let i = 1; i < numbers.length; i++) {
|
||||
const k = numbers[i];
|
||||
let entry: string;
|
||||
|
||||
if (dictionary[k] !== undefined) {
|
||||
entry = dictionary[k];
|
||||
} else if (k === dictSize) {
|
||||
entry = w + w.charAt(0);
|
||||
} else {
|
||||
throw new Error('LZ解压缩错误:无效的压缩数据');
|
||||
}
|
||||
|
||||
result.push(entry);
|
||||
dictionary[dictSize++] = w + entry.charAt(0);
|
||||
w = entry;
|
||||
|
||||
// 防止字典过大
|
||||
if (dictSize >= 0xFFFF) {
|
||||
dictSize = 256;
|
||||
// 重置字典
|
||||
for (const key in dictionary) {
|
||||
if (parseInt(key) >= 256) {
|
||||
delete dictionary[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将结果转换为ArrayBuffer
|
||||
const output = result.join('');
|
||||
const encoder = new TextEncoder();
|
||||
return encoder.encode(output).buffer;
|
||||
}
|
||||
|
||||
estimateCompressedSize(data: ArrayBuffer): number {
|
||||
// 简单估算:假设压缩率在30%-70%之间
|
||||
const size = data.byteLength;
|
||||
return Math.floor(size * 0.5); // 50%的估算压缩率
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数字数组转换为ArrayBuffer
|
||||
*/
|
||||
private numbersToArrayBuffer(numbers: number[]): ArrayBuffer {
|
||||
// 使用变长编码以节省空间
|
||||
const bytes: number[] = [];
|
||||
|
||||
for (const num of numbers) {
|
||||
if (num < 128) {
|
||||
// 小于128,用1字节
|
||||
bytes.push(num);
|
||||
} else if (num < 16384) {
|
||||
// 小于16384,用2字节,最高位为1表示有下一字节
|
||||
bytes.push(0x80 | (num & 0x7F));
|
||||
bytes.push((num >> 7) & 0x7F);
|
||||
} else {
|
||||
// 大于等于16384,用3字节
|
||||
bytes.push(0x80 | (num & 0x7F));
|
||||
bytes.push(0x80 | ((num >> 7) & 0x7F));
|
||||
bytes.push((num >> 14) & 0x7F);
|
||||
}
|
||||
}
|
||||
|
||||
return new Uint8Array(bytes).buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将ArrayBuffer转换为数字数组
|
||||
*/
|
||||
private arrayBufferToNumbers(buffer: ArrayBuffer): number[] {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const numbers: number[] = [];
|
||||
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
const byte1 = bytes[i];
|
||||
|
||||
if ((byte1 & 0x80) === 0) {
|
||||
// 单字节数字
|
||||
numbers.push(byte1);
|
||||
} else {
|
||||
// 多字节数字
|
||||
let num = byte1 & 0x7F;
|
||||
i++;
|
||||
|
||||
if (i < bytes.length) {
|
||||
const byte2 = bytes[i];
|
||||
num |= (byte2 & 0x7F) << 7;
|
||||
|
||||
if ((byte2 & 0x80) !== 0) {
|
||||
// 三字节数字
|
||||
i++;
|
||||
if (i < bytes.length) {
|
||||
const byte3 = bytes[i];
|
||||
num |= (byte3 & 0x7F) << 14;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
numbers.push(num);
|
||||
}
|
||||
}
|
||||
|
||||
return numbers;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息压缩器
|
||||
*/
|
||||
export class MessageCompressor {
|
||||
private logger = createLogger('MessageCompressor');
|
||||
private config: CompressionConfig;
|
||||
private algorithms = new Map<string, ICompressionAlgorithm>();
|
||||
private stats: CompressionStats;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<CompressionConfig> = {}) {
|
||||
this.config = {
|
||||
defaultAlgorithm: 'none',
|
||||
threshold: 1024, // 1KB以上才压缩
|
||||
enableAsync: true,
|
||||
level: 6,
|
||||
chunkSize: 64 * 1024, // 64KB分块
|
||||
enableStats: true,
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalCompressions: 0,
|
||||
totalDecompressions: 0,
|
||||
totalOriginalBytes: 0,
|
||||
totalCompressedBytes: 0,
|
||||
averageCompressionRatio: 1.0,
|
||||
averageCompressionTime: 0,
|
||||
averageDecompressionTime: 0,
|
||||
algorithmUsage: {}
|
||||
};
|
||||
|
||||
// 注册默认算法
|
||||
this.registerAlgorithm(new NoCompressionAlgorithm());
|
||||
this.registerAlgorithm(new LZCompressionAlgorithm());
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册压缩算法
|
||||
*/
|
||||
public registerAlgorithm(algorithm: ICompressionAlgorithm): void {
|
||||
if (this.algorithms.has(algorithm.name)) {
|
||||
this.logger.warn(`压缩算法 '${algorithm.name}' 已存在,将被覆盖`);
|
||||
}
|
||||
|
||||
this.algorithms.set(algorithm.name, algorithm);
|
||||
this.stats.algorithmUsage[algorithm.name] = 0;
|
||||
|
||||
this.logger.info(`注册压缩算法: ${algorithm.name} v${algorithm.version}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销压缩算法
|
||||
*/
|
||||
public unregisterAlgorithm(algorithmName: string): boolean {
|
||||
if (algorithmName === 'none') {
|
||||
this.logger.warn('无法注销默认的无压缩算法');
|
||||
return false;
|
||||
}
|
||||
|
||||
const removed = this.algorithms.delete(algorithmName);
|
||||
if (removed) {
|
||||
delete this.stats.algorithmUsage[algorithmName];
|
||||
this.logger.info(`注销压缩算法: ${algorithmName}`);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已注册的算法列表
|
||||
*/
|
||||
public getRegisteredAlgorithms(): string[] {
|
||||
return Array.from(this.algorithms.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查算法是否已注册
|
||||
*/
|
||||
public hasAlgorithm(algorithmName: string): boolean {
|
||||
return this.algorithms.has(algorithmName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩数据
|
||||
*/
|
||||
public async compress(
|
||||
data: ArrayBuffer | string,
|
||||
algorithmName?: string
|
||||
): Promise<CompressionResult> {
|
||||
const startTime = performance.now();
|
||||
|
||||
// 转换输入数据
|
||||
const inputBuffer = typeof data === 'string'
|
||||
? new TextEncoder().encode(data).buffer
|
||||
: data;
|
||||
const originalSize = inputBuffer.byteLength;
|
||||
|
||||
// 选择压缩算法
|
||||
const selectedAlgorithm = algorithmName || this.config.defaultAlgorithm;
|
||||
const algorithm = this.algorithms.get(selectedAlgorithm);
|
||||
|
||||
if (!algorithm) {
|
||||
throw new Error(`未找到压缩算法: ${selectedAlgorithm}`);
|
||||
}
|
||||
|
||||
try {
|
||||
let compressedData: ArrayBuffer;
|
||||
let wasCompressed = false;
|
||||
|
||||
// 检查是否需要压缩
|
||||
if (originalSize < this.config.threshold || selectedAlgorithm === 'none') {
|
||||
compressedData = inputBuffer.slice(0);
|
||||
} else {
|
||||
// 选择同步或异步压缩
|
||||
if (this.config.enableAsync && algorithm.supportsAsync && algorithm.compressAsync) {
|
||||
compressedData = await algorithm.compressAsync(inputBuffer);
|
||||
} else {
|
||||
compressedData = algorithm.compress(inputBuffer);
|
||||
}
|
||||
wasCompressed = true;
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const compressionTime = endTime - startTime;
|
||||
const compressedSize = compressedData.byteLength;
|
||||
const compressionRatio = originalSize > 0 ? compressedSize / originalSize : 1;
|
||||
|
||||
// 更新统计信息
|
||||
if (this.config.enableStats) {
|
||||
this.updateCompressionStats(
|
||||
selectedAlgorithm,
|
||||
originalSize,
|
||||
compressedSize,
|
||||
compressionTime
|
||||
);
|
||||
}
|
||||
|
||||
const result: CompressionResult = {
|
||||
data: compressedData,
|
||||
originalSize,
|
||||
compressedSize,
|
||||
compressionRatio,
|
||||
compressionTime,
|
||||
algorithm: selectedAlgorithm,
|
||||
wasCompressed
|
||||
};
|
||||
|
||||
this.logger.debug(
|
||||
`压缩完成: ${originalSize}B -> ${compressedSize}B ` +
|
||||
`(${(compressionRatio * 100).toFixed(1)}%) ` +
|
||||
`用时 ${compressionTime.toFixed(2)}ms, 算法: ${selectedAlgorithm}`
|
||||
);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`压缩失败 (${selectedAlgorithm}):`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压缩数据
|
||||
*/
|
||||
public async decompress(
|
||||
data: ArrayBuffer,
|
||||
algorithmName: string
|
||||
): Promise<DecompressionResult> {
|
||||
const startTime = performance.now();
|
||||
const compressedSize = data.byteLength;
|
||||
|
||||
const algorithm = this.algorithms.get(algorithmName);
|
||||
if (!algorithm) {
|
||||
throw new Error(`未找到解压缩算法: ${algorithmName}`);
|
||||
}
|
||||
|
||||
try {
|
||||
let decompressedData: ArrayBuffer;
|
||||
|
||||
// 选择同步或异步解压缩
|
||||
if (this.config.enableAsync && algorithm.supportsAsync && algorithm.decompressAsync) {
|
||||
decompressedData = await algorithm.decompressAsync(data);
|
||||
} else {
|
||||
decompressedData = algorithm.decompress(data);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const decompressionTime = endTime - startTime;
|
||||
const decompressedSize = decompressedData.byteLength;
|
||||
|
||||
// 更新统计信息
|
||||
if (this.config.enableStats) {
|
||||
this.updateDecompressionStats(algorithmName, decompressionTime);
|
||||
}
|
||||
|
||||
const result: DecompressionResult = {
|
||||
data: decompressedData,
|
||||
compressedSize,
|
||||
decompressedSize,
|
||||
decompressionTime,
|
||||
algorithm: algorithmName
|
||||
};
|
||||
|
||||
this.logger.debug(
|
||||
`解压缩完成: ${compressedSize}B -> ${decompressedSize}B ` +
|
||||
`用时 ${decompressionTime.toFixed(2)}ms, 算法: ${algorithmName}`
|
||||
);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`解压缩失败 (${algorithmName}):`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 估算压缩后大小
|
||||
*/
|
||||
public estimateCompressedSize(
|
||||
data: ArrayBuffer,
|
||||
algorithmName?: string
|
||||
): number {
|
||||
const selectedAlgorithm = algorithmName || this.config.defaultAlgorithm;
|
||||
const algorithm = this.algorithms.get(selectedAlgorithm);
|
||||
|
||||
if (!algorithm) {
|
||||
return data.byteLength;
|
||||
}
|
||||
|
||||
if (algorithm.estimateCompressedSize) {
|
||||
return algorithm.estimateCompressedSize(data);
|
||||
}
|
||||
|
||||
// 如果没有估算函数,返回原始大小
|
||||
return data.byteLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取压缩统计信息
|
||||
*/
|
||||
public getStats(): CompressionStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this.stats = {
|
||||
totalCompressions: 0,
|
||||
totalDecompressions: 0,
|
||||
totalOriginalBytes: 0,
|
||||
totalCompressedBytes: 0,
|
||||
averageCompressionRatio: 1.0,
|
||||
averageCompressionTime: 0,
|
||||
averageDecompressionTime: 0,
|
||||
algorithmUsage: {}
|
||||
};
|
||||
|
||||
// 重新初始化算法使用统计
|
||||
for (const algorithmName of this.algorithms.keys()) {
|
||||
this.stats.algorithmUsage[algorithmName] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<CompressionConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('压缩器配置已更新');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
*/
|
||||
public getConfig(): CompressionConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新压缩统计信息
|
||||
*/
|
||||
private updateCompressionStats(
|
||||
algorithmName: string,
|
||||
originalSize: number,
|
||||
compressedSize: number,
|
||||
compressionTime: number
|
||||
): void {
|
||||
this.stats.totalCompressions++;
|
||||
this.stats.totalOriginalBytes += originalSize;
|
||||
this.stats.totalCompressedBytes += compressedSize;
|
||||
this.stats.algorithmUsage[algorithmName]++;
|
||||
|
||||
// 更新平均压缩比
|
||||
this.stats.averageCompressionRatio = this.stats.totalOriginalBytes > 0
|
||||
? this.stats.totalCompressedBytes / this.stats.totalOriginalBytes
|
||||
: 1.0;
|
||||
|
||||
// 更新平均压缩时间
|
||||
this.stats.averageCompressionTime =
|
||||
(this.stats.averageCompressionTime * (this.stats.totalCompressions - 1) + compressionTime)
|
||||
/ this.stats.totalCompressions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新解压缩统计信息
|
||||
*/
|
||||
private updateDecompressionStats(algorithmName: string, decompressionTime: number): void {
|
||||
this.stats.totalDecompressions++;
|
||||
|
||||
// 更新平均解压缩时间
|
||||
this.stats.averageDecompressionTime =
|
||||
(this.stats.averageDecompressionTime * (this.stats.totalDecompressions - 1) + decompressionTime)
|
||||
/ this.stats.totalDecompressions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局压缩器实例
|
||||
*/
|
||||
export const globalCompressor = new MessageCompressor();
|
||||
@@ -1,639 +0,0 @@
|
||||
import { SyncBatch } from '../sync/SyncVarManager';
|
||||
import { MessageType, INetworkMessage } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 序列化配置
|
||||
*/
|
||||
export interface SyncVarSerializerConfig {
|
||||
/** 是否启用压缩 */
|
||||
enableCompression: boolean;
|
||||
/** 是否启用差量同步 */
|
||||
enableDeltaSync: boolean;
|
||||
/** 是否启用类型检查 */
|
||||
enableTypeChecking: boolean;
|
||||
/** 最大消息大小(字节) */
|
||||
maxMessageSize: number;
|
||||
/** 是否启用批量优化 */
|
||||
enableBatching: boolean;
|
||||
/** 批量超时时间(毫秒) */
|
||||
batchTimeout: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化结果
|
||||
*/
|
||||
export interface SerializationResult {
|
||||
/** 是否成功 */
|
||||
success: boolean;
|
||||
/** 序列化的数据 */
|
||||
data?: ArrayBuffer | string;
|
||||
/** 错误信息 */
|
||||
error?: string;
|
||||
/** 原始大小 */
|
||||
originalSize: number;
|
||||
/** 压缩后大小 */
|
||||
compressedSize: number;
|
||||
/** 压缩比 */
|
||||
compressionRatio: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化结果
|
||||
*/
|
||||
export interface DeserializationResult<T = any> {
|
||||
/** 是否成功 */
|
||||
success: boolean;
|
||||
/** 反序列化的数据 */
|
||||
data?: T;
|
||||
/** 错误信息 */
|
||||
errors?: string[];
|
||||
/** 是否通过类型检查 */
|
||||
isValidType: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 差量数据
|
||||
*/
|
||||
export interface DeltaData {
|
||||
/** 基础版本 */
|
||||
baseVersion: number;
|
||||
/** 当前版本 */
|
||||
currentVersion: number;
|
||||
/** 变化的字段 */
|
||||
changes: { [key: string]: any };
|
||||
/** 删除的字段 */
|
||||
deletions: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩元数据
|
||||
*/
|
||||
export interface CompressionMetadata {
|
||||
/** 压缩算法 */
|
||||
algorithm: string;
|
||||
/** 原始大小 */
|
||||
originalSize: number;
|
||||
/** 压缩大小 */
|
||||
compressedSize: number;
|
||||
/** 压缩时间戳 */
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar专用序列化器
|
||||
* 针对SyncVar数据进行优化的序列化系统
|
||||
*/
|
||||
export class SyncVarSerializer {
|
||||
private config: SyncVarSerializerConfig;
|
||||
private deltaHistory = new Map<string, { version: number; data: any }>();
|
||||
private versionCounter = 0;
|
||||
private compressionCache = new Map<string, ArrayBuffer>();
|
||||
|
||||
constructor(config: Partial<SyncVarSerializerConfig> = {}) {
|
||||
this.config = {
|
||||
enableCompression: true,
|
||||
enableDeltaSync: true,
|
||||
enableTypeChecking: true,
|
||||
maxMessageSize: 64 * 1024, // 64KB
|
||||
enableBatching: true,
|
||||
batchTimeout: 16, // 16ms (60fps)
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化SyncVar批次数据
|
||||
*/
|
||||
public serializeSyncBatch(batch: SyncBatch): SerializationResult {
|
||||
try {
|
||||
const startTime = performance.now();
|
||||
|
||||
// 准备序列化数据
|
||||
let dataToSerialize: any = batch;
|
||||
|
||||
// 应用差量同步
|
||||
if (this.config.enableDeltaSync) {
|
||||
dataToSerialize = this.applyDeltaCompression(batch);
|
||||
}
|
||||
|
||||
// 基础JSON序列化
|
||||
const jsonString = JSON.stringify(dataToSerialize, this.replacer.bind(this));
|
||||
const originalSize = new TextEncoder().encode(jsonString).length;
|
||||
|
||||
// 检查消息大小限制
|
||||
if (originalSize > this.config.maxMessageSize) {
|
||||
return {
|
||||
success: false,
|
||||
error: `消息大小超出限制: ${originalSize} > ${this.config.maxMessageSize}`,
|
||||
originalSize,
|
||||
compressedSize: 0,
|
||||
compressionRatio: 0
|
||||
};
|
||||
}
|
||||
|
||||
let finalData: ArrayBuffer | string = jsonString;
|
||||
let compressedSize = originalSize;
|
||||
|
||||
// 应用压缩
|
||||
if (this.config.enableCompression && originalSize > 256) {
|
||||
const compressionResult = this.compress(jsonString);
|
||||
if (compressionResult.success && compressionResult.data) {
|
||||
finalData = compressionResult.data;
|
||||
compressedSize = compressionResult.data.byteLength;
|
||||
}
|
||||
}
|
||||
|
||||
const compressionRatio = originalSize > 0 ? compressedSize / originalSize : 1;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: finalData,
|
||||
originalSize,
|
||||
compressedSize,
|
||||
compressionRatio
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : '序列化失败',
|
||||
originalSize: 0,
|
||||
compressedSize: 0,
|
||||
compressionRatio: 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化SyncVar批次数据
|
||||
*/
|
||||
public deserializeSyncBatch(data: ArrayBuffer | string): DeserializationResult<SyncBatch> {
|
||||
try {
|
||||
let jsonString: string;
|
||||
|
||||
// 解压缩
|
||||
if (data instanceof ArrayBuffer) {
|
||||
const decompressResult = this.decompress(data);
|
||||
if (!decompressResult.success || !decompressResult.data) {
|
||||
return {
|
||||
success: false,
|
||||
errors: ['解压缩失败'],
|
||||
isValidType: false
|
||||
};
|
||||
}
|
||||
jsonString = decompressResult.data;
|
||||
} else {
|
||||
jsonString = data;
|
||||
}
|
||||
|
||||
// JSON反序列化
|
||||
const parsedData = JSON.parse(jsonString, this.reviver.bind(this));
|
||||
|
||||
// 类型检查
|
||||
if (this.config.enableTypeChecking) {
|
||||
const typeCheckResult = this.validateSyncBatchType(parsedData);
|
||||
if (!typeCheckResult.isValid) {
|
||||
return {
|
||||
success: false,
|
||||
errors: typeCheckResult.errors,
|
||||
isValidType: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 应用差量还原
|
||||
let finalData = parsedData;
|
||||
if (this.config.enableDeltaSync && this.isDeltaData(parsedData)) {
|
||||
finalData = this.applyDeltaRestore(parsedData);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: finalData as SyncBatch,
|
||||
isValidType: true
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
errors: [error instanceof Error ? error.message : '反序列化失败'],
|
||||
isValidType: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建网络消息
|
||||
*/
|
||||
public createSyncMessage(batch: SyncBatch, senderId: string): INetworkMessage {
|
||||
const serializedData = this.serializeSyncBatch(batch);
|
||||
|
||||
return {
|
||||
type: MessageType.SYNC_BATCH,
|
||||
messageId: this.generateMessageId(),
|
||||
timestamp: Date.now(),
|
||||
senderId,
|
||||
data: serializedData.data,
|
||||
reliable: true,
|
||||
priority: this.calculateMessagePriority(batch)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析网络消息
|
||||
*/
|
||||
public parseSyncMessage(message: INetworkMessage): DeserializationResult<SyncBatch> {
|
||||
if (message.type !== MessageType.SYNC_BATCH) {
|
||||
return {
|
||||
success: false,
|
||||
errors: ['消息类型不匹配'],
|
||||
isValidType: false
|
||||
};
|
||||
}
|
||||
|
||||
return this.deserializeSyncBatch(message.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<SyncVarSerializerConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
*/
|
||||
public getConfig(): SyncVarSerializerConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存
|
||||
*/
|
||||
public clearCache(): void {
|
||||
this.deltaHistory.clear();
|
||||
this.compressionCache.clear();
|
||||
this.versionCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计
|
||||
*/
|
||||
public getCacheStats(): { deltaHistorySize: number; compressionCacheSize: number } {
|
||||
return {
|
||||
deltaHistorySize: this.deltaHistory.size,
|
||||
compressionCacheSize: this.compressionCache.size
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用差量压缩
|
||||
*/
|
||||
private applyDeltaCompression(batch: SyncBatch): DeltaData | SyncBatch {
|
||||
const key = batch.instanceId;
|
||||
const lastRecord = this.deltaHistory.get(key);
|
||||
|
||||
if (!lastRecord) {
|
||||
// 第一次同步,存储完整数据
|
||||
this.deltaHistory.set(key, {
|
||||
version: ++this.versionCounter,
|
||||
data: { ...batch }
|
||||
});
|
||||
return batch;
|
||||
}
|
||||
|
||||
// 计算差量
|
||||
const changes: { [key: string]: any } = {};
|
||||
const deletions: string[] = [];
|
||||
|
||||
// 检查变化的属性
|
||||
for (const [prop, value] of Object.entries(batch.changes)) {
|
||||
if (!lastRecord.data.changes || lastRecord.data.changes[prop] !== value) {
|
||||
changes[prop] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查删除的属性
|
||||
if (lastRecord.data.changes) {
|
||||
for (const prop of Object.keys(lastRecord.data.changes)) {
|
||||
if (!(prop in batch.changes)) {
|
||||
deletions.push(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有变化,返回空的差量数据
|
||||
if (Object.keys(changes).length === 0 && deletions.length === 0) {
|
||||
return {
|
||||
baseVersion: lastRecord.version,
|
||||
currentVersion: lastRecord.version,
|
||||
changes: {},
|
||||
deletions: []
|
||||
};
|
||||
}
|
||||
|
||||
// 更新历史记录
|
||||
const currentVersion = ++this.versionCounter;
|
||||
this.deltaHistory.set(key, {
|
||||
version: currentVersion,
|
||||
data: { ...batch }
|
||||
});
|
||||
|
||||
return {
|
||||
baseVersion: lastRecord.version,
|
||||
currentVersion,
|
||||
changes,
|
||||
deletions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用差量还原
|
||||
*/
|
||||
private applyDeltaRestore(deltaData: DeltaData): SyncBatch {
|
||||
// 这里应该根据baseVersion找到对应的基础数据
|
||||
// 简化实现,返回一个基本的SyncBatch
|
||||
return {
|
||||
instanceId: 'unknown',
|
||||
instanceType: 'unknown',
|
||||
changes: deltaData.changes,
|
||||
timestamp: Date.now(),
|
||||
syncModes: {},
|
||||
authorities: {},
|
||||
scopes: {},
|
||||
priorities: {}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为差量数据
|
||||
*/
|
||||
private isDeltaData(data: any): data is DeltaData {
|
||||
return data &&
|
||||
typeof data.baseVersion === 'number' &&
|
||||
typeof data.currentVersion === 'number' &&
|
||||
typeof data.changes === 'object' &&
|
||||
Array.isArray(data.deletions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩数据
|
||||
*/
|
||||
private compress(data: string): { success: boolean; data?: ArrayBuffer } {
|
||||
try {
|
||||
// 使用LZ字符串压缩算法
|
||||
const compressed = this.lzCompress(data);
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode(compressed);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: bytes.buffer
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压缩数据
|
||||
*/
|
||||
private decompress(data: ArrayBuffer): { success: boolean; data?: string } {
|
||||
try {
|
||||
const decoder = new TextDecoder();
|
||||
const compressedString = decoder.decode(data);
|
||||
const decompressed = this.lzDecompress(compressedString);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: decompressed
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON序列化替换函数
|
||||
*/
|
||||
private replacer(key: string, value: any): any {
|
||||
// 处理特殊类型的序列化
|
||||
if (value instanceof Date) {
|
||||
return { __type: 'Date', value: value.toISOString() };
|
||||
}
|
||||
|
||||
if (value instanceof Map) {
|
||||
return { __type: 'Map', value: Array.from(value.entries()) };
|
||||
}
|
||||
|
||||
if (value instanceof Set) {
|
||||
return { __type: 'Set', value: Array.from(value) };
|
||||
}
|
||||
|
||||
// 处理BigInt
|
||||
if (typeof value === 'bigint') {
|
||||
return { __type: 'BigInt', value: value.toString() };
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化恢复函数
|
||||
*/
|
||||
private reviver(key: string, value: any): any {
|
||||
if (value && typeof value === 'object' && value.__type) {
|
||||
switch (value.__type) {
|
||||
case 'Date':
|
||||
return new Date(value.value);
|
||||
case 'Map':
|
||||
return new Map(value.value);
|
||||
case 'Set':
|
||||
return new Set(value.value);
|
||||
case 'BigInt':
|
||||
return BigInt(value.value);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证SyncBatch类型
|
||||
*/
|
||||
private validateSyncBatchType(data: any): { isValid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!data || typeof data !== 'object') {
|
||||
errors.push('数据不是对象');
|
||||
return { isValid: false, errors };
|
||||
}
|
||||
|
||||
if (typeof data.instanceId !== 'string') {
|
||||
errors.push('instanceId必须是字符串');
|
||||
}
|
||||
|
||||
if (typeof data.instanceType !== 'string') {
|
||||
errors.push('instanceType必须是字符串');
|
||||
}
|
||||
|
||||
if (!data.changes || typeof data.changes !== 'object') {
|
||||
errors.push('changes必须是对象');
|
||||
}
|
||||
|
||||
if (typeof data.timestamp !== 'number') {
|
||||
errors.push('timestamp必须是数字');
|
||||
}
|
||||
|
||||
return { isValid: errors.length === 0, errors };
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成消息ID
|
||||
*/
|
||||
private generateMessageId(): string {
|
||||
return `sync_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算消息优先级
|
||||
*/
|
||||
private calculateMessagePriority(batch: SyncBatch): number {
|
||||
// 根据批次中属性的优先级计算整体优先级
|
||||
const priorities = Object.values(batch.priorities);
|
||||
if (priorities.length === 0) {
|
||||
return 5; // 默认优先级
|
||||
}
|
||||
|
||||
// 使用最高优先级
|
||||
return Math.max(...priorities);
|
||||
}
|
||||
|
||||
/**
|
||||
* LZ字符串压缩算法
|
||||
*/
|
||||
private lzCompress(input: string): string {
|
||||
if (!input) return '';
|
||||
|
||||
const dictionary: { [key: string]: number } = {};
|
||||
let dictSize = 256;
|
||||
|
||||
// 初始化字典
|
||||
for (let i = 0; i < 256; i++) {
|
||||
dictionary[String.fromCharCode(i)] = i;
|
||||
}
|
||||
|
||||
let w = '';
|
||||
const result: number[] = [];
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const c = input.charAt(i);
|
||||
const wc = w + c;
|
||||
|
||||
if (dictionary[wc] !== undefined) {
|
||||
w = wc;
|
||||
} else {
|
||||
result.push(dictionary[w]);
|
||||
dictionary[wc] = dictSize++;
|
||||
w = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (w) {
|
||||
result.push(dictionary[w]);
|
||||
}
|
||||
|
||||
// 将结果编码为Base64以确保字符串安全
|
||||
return this.arrayToBase64(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* LZ字符串解压缩算法
|
||||
*/
|
||||
private lzDecompress(compressed: string): string {
|
||||
if (!compressed) return '';
|
||||
|
||||
const data = this.base64ToArray(compressed);
|
||||
if (data.length === 0) return '';
|
||||
|
||||
const dictionary: { [key: number]: string } = {};
|
||||
let dictSize = 256;
|
||||
|
||||
// 初始化字典
|
||||
for (let i = 0; i < 256; i++) {
|
||||
dictionary[i] = String.fromCharCode(i);
|
||||
}
|
||||
|
||||
let w = String.fromCharCode(data[0]);
|
||||
const result = [w];
|
||||
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
const k = data[i];
|
||||
let entry: string;
|
||||
|
||||
if (dictionary[k] !== undefined) {
|
||||
entry = dictionary[k];
|
||||
} else if (k === dictSize) {
|
||||
entry = w + w.charAt(0);
|
||||
} else {
|
||||
throw new Error('解压缩错误:无效的压缩数据');
|
||||
}
|
||||
|
||||
result.push(entry);
|
||||
dictionary[dictSize++] = w + entry.charAt(0);
|
||||
w = entry;
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组转Base64
|
||||
*/
|
||||
private arrayToBase64(array: number[]): string {
|
||||
// 将数字数组转换为字节数组
|
||||
const bytes: number[] = [];
|
||||
for (const num of array) {
|
||||
if (num < 256) {
|
||||
bytes.push(num);
|
||||
} else {
|
||||
// 大于255的数字用两个字节表示
|
||||
bytes.push(255, num - 255);
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为字符串然后编码为Base64
|
||||
const binaryString = String.fromCharCode(...bytes);
|
||||
return btoa(binaryString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64转数组
|
||||
*/
|
||||
private base64ToArray(base64: string): number[] {
|
||||
try {
|
||||
const binaryString = atob(base64);
|
||||
const bytes: number[] = [];
|
||||
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes.push(binaryString.charCodeAt(i));
|
||||
}
|
||||
|
||||
// 还原原始数字数组
|
||||
const result: number[] = [];
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i] === 255 && i + 1 < bytes.length) {
|
||||
result.push(255 + bytes[i + 1]);
|
||||
i++; // 跳过下一个字节
|
||||
} else {
|
||||
result.push(bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new Error('Base64解码失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,794 +0,0 @@
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 差量同步配置
|
||||
*/
|
||||
export interface DeltaSyncConfig {
|
||||
/** 是否启用差量同步 */
|
||||
enabled: boolean;
|
||||
/** 最大历史版本数 */
|
||||
maxHistoryVersions: number;
|
||||
/** 版本超时时间(毫秒) */
|
||||
versionTimeout: number;
|
||||
/** 差量压缩阈值 */
|
||||
compressionThreshold: number;
|
||||
/** 是否启用智能合并 */
|
||||
enableSmartMerging: boolean;
|
||||
/** 合并时间窗口(毫秒) */
|
||||
mergeWindow: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 版本化数据
|
||||
*/
|
||||
export interface VersionedData {
|
||||
version: number;
|
||||
timestamp: number;
|
||||
data: any;
|
||||
checksum?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 差量数据
|
||||
*/
|
||||
export interface DeltaData {
|
||||
baseVersion: number;
|
||||
targetVersion: number;
|
||||
changes: { [key: string]: any };
|
||||
deletions: string[];
|
||||
metadata: {
|
||||
timestamp: number;
|
||||
size: number;
|
||||
compressionRatio: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 差量操作类型
|
||||
*/
|
||||
export enum DeltaOperationType {
|
||||
/** 添加属性 */
|
||||
ADD = 'add',
|
||||
/** 修改属性 */
|
||||
MODIFY = 'modify',
|
||||
/** 删除属性 */
|
||||
DELETE = 'delete',
|
||||
/** 批量操作 */
|
||||
BATCH = 'batch',
|
||||
/** 无操作(合并后消除的操作) */
|
||||
NOOP = 'noop'
|
||||
}
|
||||
|
||||
/**
|
||||
* 差量操作
|
||||
*/
|
||||
export interface DeltaOperation {
|
||||
type: DeltaOperationType;
|
||||
path: string;
|
||||
oldValue?: any;
|
||||
newValue?: any;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 差量同步统计
|
||||
*/
|
||||
export interface DeltaSyncStats {
|
||||
totalDeltas: number;
|
||||
totalSize: number;
|
||||
compressionRatio: number;
|
||||
averageDeltaSize: number;
|
||||
cacheHitRate: number;
|
||||
mergedOperations: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 差量同步器
|
||||
* 负责计算和应用数据差量,减少网络传输量
|
||||
*/
|
||||
export class DeltaSync {
|
||||
private logger = createLogger('DeltaSync');
|
||||
private config: DeltaSyncConfig;
|
||||
|
||||
/** 版本历史 */
|
||||
private versionHistory = new Map<string, Map<number, VersionedData>>();
|
||||
|
||||
/** 版本计数器 */
|
||||
private versionCounters = new Map<string, number>();
|
||||
|
||||
/** 差量缓存 */
|
||||
private deltaCache = new Map<string, DeltaData>();
|
||||
|
||||
/** 待合并操作 */
|
||||
private pendingOperations = new Map<string, DeltaOperation[]>();
|
||||
|
||||
/** 统计信息 */
|
||||
private stats: DeltaSyncStats = {
|
||||
totalDeltas: 0,
|
||||
totalSize: 0,
|
||||
compressionRatio: 1,
|
||||
averageDeltaSize: 0,
|
||||
cacheHitRate: 0,
|
||||
mergedOperations: 0
|
||||
};
|
||||
|
||||
/** 合并定时器 */
|
||||
private mergeTimers = new Map<string, any>();
|
||||
|
||||
constructor(config: Partial<DeltaSyncConfig> = {}) {
|
||||
this.config = {
|
||||
enabled: true,
|
||||
maxHistoryVersions: 10,
|
||||
versionTimeout: 30000,
|
||||
compressionThreshold: 100,
|
||||
enableSmartMerging: true,
|
||||
mergeWindow: 50,
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录基线版本
|
||||
*/
|
||||
public recordBaseline(instanceId: string, data: any): number {
|
||||
if (!this.config.enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const version = this.getNextVersion(instanceId);
|
||||
const versionedData: VersionedData = {
|
||||
version,
|
||||
timestamp: Date.now(),
|
||||
data: this.deepClone(data),
|
||||
checksum: this.calculateChecksum(data)
|
||||
};
|
||||
|
||||
this.storeVersion(instanceId, versionedData);
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算差量
|
||||
*/
|
||||
public calculateDelta(instanceId: string, newData: any, baseVersion?: number): DeltaData | null {
|
||||
if (!this.config.enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const history = this.versionHistory.get(instanceId);
|
||||
if (!history || history.size === 0) {
|
||||
// 没有基线,记录第一个版本
|
||||
this.recordBaseline(instanceId, newData);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 选择基线版本
|
||||
let baseVersionData: VersionedData;
|
||||
if (baseVersion !== undefined) {
|
||||
const foundVersion = history.get(baseVersion);
|
||||
if (!foundVersion) {
|
||||
this.logger.warn(`未找到版本 ${baseVersion},使用最新版本`);
|
||||
const latestVersion = this.getLatestVersion(instanceId);
|
||||
if (!latestVersion) {
|
||||
this.logger.error(`实例 ${instanceId} 没有任何版本历史`);
|
||||
return null;
|
||||
}
|
||||
baseVersionData = latestVersion;
|
||||
} else {
|
||||
baseVersionData = foundVersion;
|
||||
}
|
||||
} else {
|
||||
const latestVersion = this.getLatestVersion(instanceId);
|
||||
if (!latestVersion) {
|
||||
this.logger.error(`实例 ${instanceId} 没有任何版本历史`);
|
||||
return null;
|
||||
}
|
||||
baseVersionData = latestVersion;
|
||||
}
|
||||
|
||||
const targetVersion = this.getNextVersion(instanceId);
|
||||
const changes = this.computeChanges(baseVersionData.data, newData);
|
||||
const deletions = this.computeDeletions(baseVersionData.data, newData);
|
||||
|
||||
// 检查是否有变化
|
||||
if (Object.keys(changes).length === 0 && deletions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const deltaData: DeltaData = {
|
||||
baseVersion: baseVersionData.version,
|
||||
targetVersion,
|
||||
changes,
|
||||
deletions,
|
||||
metadata: {
|
||||
timestamp: Date.now(),
|
||||
size: this.estimateSize(changes) + this.estimateSize(deletions),
|
||||
compressionRatio: 1
|
||||
}
|
||||
};
|
||||
|
||||
// 记录新版本
|
||||
this.recordBaseline(instanceId, newData);
|
||||
|
||||
// 更新统计
|
||||
this.updateStats(deltaData);
|
||||
|
||||
return deltaData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用差量
|
||||
*/
|
||||
public applyDelta(instanceId: string, delta: DeltaData): any {
|
||||
if (!this.config.enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const history = this.versionHistory.get(instanceId);
|
||||
if (!history) {
|
||||
this.logger.error(`实例 ${instanceId} 没有版本历史`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const baseData = history.get(delta.baseVersion);
|
||||
if (!baseData) {
|
||||
this.logger.error(`未找到基线版本 ${delta.baseVersion}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 复制基线数据
|
||||
const result = this.deepClone(baseData.data);
|
||||
|
||||
// 应用变化
|
||||
for (const [key, value] of Object.entries(delta.changes)) {
|
||||
this.setNestedProperty(result, key, value);
|
||||
}
|
||||
|
||||
// 应用删除
|
||||
for (const key of delta.deletions) {
|
||||
this.deleteNestedProperty(result, key);
|
||||
}
|
||||
|
||||
// 记录结果版本
|
||||
const resultVersion: VersionedData = {
|
||||
version: delta.targetVersion,
|
||||
timestamp: delta.metadata.timestamp,
|
||||
data: this.deepClone(result),
|
||||
checksum: this.calculateChecksum(result)
|
||||
};
|
||||
|
||||
this.storeVersion(instanceId, resultVersion);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能合并操作
|
||||
*/
|
||||
public mergeOperations(instanceId: string, operations: DeltaOperation[]): DeltaOperation[] {
|
||||
if (!this.config.enableSmartMerging || operations.length <= 1) {
|
||||
return operations;
|
||||
}
|
||||
|
||||
const pathMap = new Map<string, DeltaOperation>();
|
||||
|
||||
// 按路径分组操作
|
||||
for (const op of operations) {
|
||||
const existing = pathMap.get(op.path);
|
||||
|
||||
if (!existing) {
|
||||
pathMap.set(op.path, op);
|
||||
} else {
|
||||
// 合并同路径的操作
|
||||
const mergedOp = this.mergeTwoOperations(existing, op);
|
||||
pathMap.set(op.path, mergedOp);
|
||||
this.stats.mergedOperations++;
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤掉NOOP操作
|
||||
return Array.from(pathMap.values()).filter((op) => op.type !== DeltaOperationType.NOOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟合并操作
|
||||
*/
|
||||
public scheduleOperation(instanceId: string, operation: DeltaOperation): void {
|
||||
if (!this.config.enableSmartMerging) {
|
||||
return;
|
||||
}
|
||||
|
||||
let operations = this.pendingOperations.get(instanceId);
|
||||
if (!operations) {
|
||||
operations = [];
|
||||
this.pendingOperations.set(instanceId, operations);
|
||||
}
|
||||
|
||||
operations.push(operation);
|
||||
|
||||
// 重置合并定时器
|
||||
const existingTimer = this.mergeTimers.get(instanceId);
|
||||
if (existingTimer) {
|
||||
clearTimeout(existingTimer);
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
this.flushPendingOperations(instanceId);
|
||||
}, this.config.mergeWindow);
|
||||
|
||||
this.mergeTimers.set(instanceId, timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩差量数据
|
||||
*/
|
||||
public compressDelta(delta: DeltaData): DeltaData {
|
||||
if (delta.metadata.size < this.config.compressionThreshold) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
// 简化的压缩实现
|
||||
const compressedChanges = this.compressObject(delta.changes);
|
||||
const compressedDeletions = delta.deletions; // 删除操作通常已经很紧凑
|
||||
|
||||
const originalSize = delta.metadata.size;
|
||||
const compressedSize = this.estimateSize(compressedChanges) + this.estimateSize(compressedDeletions);
|
||||
|
||||
return {
|
||||
...delta,
|
||||
changes: compressedChanges,
|
||||
deletions: compressedDeletions,
|
||||
metadata: {
|
||||
...delta.metadata,
|
||||
size: compressedSize,
|
||||
compressionRatio: compressedSize / originalSize
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期版本
|
||||
*/
|
||||
public cleanup(): void {
|
||||
const now = Date.now();
|
||||
|
||||
for (const [instanceId, history] of this.versionHistory) {
|
||||
const versionsToDelete: number[] = [];
|
||||
|
||||
for (const [version, versionData] of history) {
|
||||
// 检查超时
|
||||
if (now - versionData.timestamp > this.config.versionTimeout) {
|
||||
versionsToDelete.push(version);
|
||||
}
|
||||
}
|
||||
|
||||
// 保留最新的几个版本
|
||||
const sortedVersions = Array.from(history.keys()).sort((a, b) => b - a);
|
||||
const toKeep = sortedVersions.slice(0, this.config.maxHistoryVersions);
|
||||
|
||||
for (const version of versionsToDelete) {
|
||||
if (!toKeep.includes(version)) {
|
||||
history.delete(version);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果实例没有版本了,删除实例
|
||||
if (history.size === 0) {
|
||||
this.versionHistory.delete(instanceId);
|
||||
this.versionCounters.delete(instanceId);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理差量缓存
|
||||
this.deltaCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): DeltaSyncStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this.stats = {
|
||||
totalDeltas: 0,
|
||||
totalSize: 0,
|
||||
compressionRatio: 1,
|
||||
averageDeltaSize: 0,
|
||||
cacheHitRate: 0,
|
||||
mergedOperations: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<DeltaSyncConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁同步器
|
||||
*/
|
||||
public destroy(): void {
|
||||
// 清理定时器
|
||||
for (const timer of this.mergeTimers.values()) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
this.versionHistory.clear();
|
||||
this.versionCounters.clear();
|
||||
this.deltaCache.clear();
|
||||
this.pendingOperations.clear();
|
||||
this.mergeTimers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下一个版本号
|
||||
*/
|
||||
private getNextVersion(instanceId: string): number {
|
||||
const current = this.versionCounters.get(instanceId) || 0;
|
||||
const next = current + 1;
|
||||
this.versionCounters.set(instanceId, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储版本数据
|
||||
*/
|
||||
private storeVersion(instanceId: string, versionData: VersionedData): void {
|
||||
let history = this.versionHistory.get(instanceId);
|
||||
if (!history) {
|
||||
history = new Map();
|
||||
this.versionHistory.set(instanceId, history);
|
||||
}
|
||||
|
||||
history.set(versionData.version, versionData);
|
||||
|
||||
// 限制历史版本数量
|
||||
if (history.size > this.config.maxHistoryVersions) {
|
||||
const oldestVersion = Math.min(...Array.from(history.keys()));
|
||||
history.delete(oldestVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新版本
|
||||
*/
|
||||
private getLatestVersion(instanceId: string): VersionedData | undefined {
|
||||
const history = this.versionHistory.get(instanceId);
|
||||
if (!history || history.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const latestVersion = Math.max(...Array.from(history.keys()));
|
||||
return history.get(latestVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算变化
|
||||
*/
|
||||
private computeChanges(oldData: any, newData: any): { [key: string]: any } {
|
||||
const changes: { [key: string]: any } = {};
|
||||
|
||||
for (const [key, newValue] of Object.entries(newData)) {
|
||||
const oldValue = oldData[key];
|
||||
|
||||
if (!this.deepEqual(oldValue, newValue)) {
|
||||
changes[key] = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算删除
|
||||
*/
|
||||
private computeDeletions(oldData: any, newData: any): string[] {
|
||||
const deletions: string[] = [];
|
||||
|
||||
for (const key of Object.keys(oldData)) {
|
||||
if (!(key in newData)) {
|
||||
deletions.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return deletions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并两个操作
|
||||
*/
|
||||
private mergeTwoOperations(op1: DeltaOperation, op2: DeltaOperation): DeltaOperation {
|
||||
// 智能合并逻辑
|
||||
const mergedOp: DeltaOperation = {
|
||||
type: op2.type,
|
||||
path: op2.path,
|
||||
oldValue: op1.oldValue, // 保留最初的旧值
|
||||
newValue: op2.newValue,
|
||||
timestamp: op2.timestamp
|
||||
};
|
||||
|
||||
// 处理特殊合并情况
|
||||
if (op1.type === DeltaOperationType.ADD && op2.type === DeltaOperationType.DELETE) {
|
||||
// 添加后删除 = 无操作
|
||||
return {
|
||||
type: DeltaOperationType.NOOP,
|
||||
path: op2.path,
|
||||
oldValue: undefined,
|
||||
newValue: undefined,
|
||||
timestamp: op2.timestamp
|
||||
};
|
||||
}
|
||||
|
||||
if (op1.type === DeltaOperationType.DELETE && op2.type === DeltaOperationType.ADD) {
|
||||
// 删除后添加 = 修改
|
||||
mergedOp.type = DeltaOperationType.MODIFY;
|
||||
mergedOp.oldValue = op1.oldValue;
|
||||
}
|
||||
|
||||
if (op1.type === DeltaOperationType.MODIFY && op2.type === DeltaOperationType.DELETE) {
|
||||
// 修改后删除 = 删除原始值
|
||||
mergedOp.type = DeltaOperationType.DELETE;
|
||||
mergedOp.newValue = undefined;
|
||||
}
|
||||
|
||||
// 检查是否值回到了原始状态
|
||||
if (op1.type === DeltaOperationType.MODIFY &&
|
||||
op2.type === DeltaOperationType.MODIFY &&
|
||||
this.deepEqual(op1.oldValue, op2.newValue)) {
|
||||
// 值回到原始状态 = 无操作
|
||||
return {
|
||||
type: DeltaOperationType.NOOP,
|
||||
path: op2.path,
|
||||
oldValue: undefined,
|
||||
newValue: undefined,
|
||||
timestamp: op2.timestamp
|
||||
};
|
||||
}
|
||||
|
||||
return mergedOp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新待处理操作
|
||||
*/
|
||||
private flushPendingOperations(instanceId: string): void {
|
||||
const operations = this.pendingOperations.get(instanceId);
|
||||
if (!operations || operations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 合并操作并发送
|
||||
this.mergeOperations(instanceId, operations);
|
||||
|
||||
// 清理待处理操作
|
||||
this.pendingOperations.delete(instanceId);
|
||||
this.mergeTimers.delete(instanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩对象
|
||||
*/
|
||||
private compressObject(obj: any): any {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// 移除null和undefined值
|
||||
const compressed: any = Array.isArray(obj) ? [] : {};
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (value !== null && value !== undefined) {
|
||||
if (typeof value === 'object') {
|
||||
compressed[key] = this.compressObject(value);
|
||||
} else {
|
||||
compressed[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return compressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 估算大小
|
||||
*/
|
||||
private estimateSize(obj: any): number {
|
||||
if (obj === null || obj === undefined) {
|
||||
return 4; // "null"的长度
|
||||
}
|
||||
|
||||
if (typeof obj === 'string') {
|
||||
return obj.length * 2; // UTF-16字符估算
|
||||
}
|
||||
|
||||
if (typeof obj === 'number') {
|
||||
return 8; // 64位数字
|
||||
}
|
||||
|
||||
if (typeof obj === 'boolean') {
|
||||
return 4; // true/false
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
let size = 2; // []
|
||||
for (const item of obj) {
|
||||
size += this.estimateSize(item) + 1; // +1 for comma
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
let size = 2; // {}
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
size += key.length * 2 + 3; // key + ":"
|
||||
size += this.estimateSize(value) + 1; // value + comma
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
return JSON.stringify(obj).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度克隆
|
||||
*/
|
||||
private deepClone(obj: any): any {
|
||||
if (obj === null || obj === undefined) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (obj instanceof Date) {
|
||||
return new Date(obj.getTime());
|
||||
}
|
||||
|
||||
if (obj instanceof RegExp) {
|
||||
return new RegExp(obj);
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => this.deepClone(item));
|
||||
}
|
||||
|
||||
const cloned: any = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
cloned[key] = this.deepClone(value);
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度比较
|
||||
*/
|
||||
private deepEqual(obj1: any, obj2: any): boolean {
|
||||
if (obj1 === obj2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj1 === null || obj2 === null || obj1 === undefined || obj2 === undefined) {
|
||||
return obj1 === obj2;
|
||||
}
|
||||
|
||||
if (typeof obj1 !== typeof obj2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof obj1 !== 'object') {
|
||||
return obj1 === obj2;
|
||||
}
|
||||
|
||||
if (obj1 instanceof Date && obj2 instanceof Date) {
|
||||
return obj1.getTime() === obj2.getTime();
|
||||
}
|
||||
|
||||
if (Array.isArray(obj1) !== Array.isArray(obj2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj1)) {
|
||||
if (obj1.length !== obj2.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < obj1.length; i++) {
|
||||
if (!this.deepEqual(obj1[i], obj2[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const keys1 = Object.keys(obj1);
|
||||
const keys2 = Object.keys(obj2);
|
||||
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const key of keys1) {
|
||||
if (!keys2.includes(key)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.deepEqual(obj1[key], obj2[key])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置嵌套属性
|
||||
*/
|
||||
private setNestedProperty(obj: any, path: string, value: any): void {
|
||||
const keys = path.split('.');
|
||||
let current = obj;
|
||||
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const key = keys[i];
|
||||
if (!(key in current)) {
|
||||
current[key] = {};
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
|
||||
current[keys[keys.length - 1]] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除嵌套属性
|
||||
*/
|
||||
private deleteNestedProperty(obj: any, path: string): void {
|
||||
const keys = path.split('.');
|
||||
let current = obj;
|
||||
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const key = keys[i];
|
||||
if (!(key in current)) {
|
||||
return; // 路径不存在
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
|
||||
delete current[keys[keys.length - 1]];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算校验和
|
||||
*/
|
||||
private calculateChecksum(obj: any): string {
|
||||
// 简化的校验和实现
|
||||
const str = JSON.stringify(obj);
|
||||
let hash = 0;
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // 转换为32位整数
|
||||
}
|
||||
|
||||
return hash.toString(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新统计信息
|
||||
*/
|
||||
private updateStats(delta: DeltaData): void {
|
||||
this.stats.totalDeltas++;
|
||||
this.stats.totalSize += delta.metadata.size;
|
||||
this.stats.averageDeltaSize = this.stats.totalSize / this.stats.totalDeltas;
|
||||
this.stats.compressionRatio =
|
||||
(this.stats.compressionRatio * (this.stats.totalDeltas - 1) + delta.metadata.compressionRatio) /
|
||||
this.stats.totalDeltas;
|
||||
}
|
||||
}
|
||||
@@ -1,462 +0,0 @@
|
||||
import {
|
||||
getDirtySyncVars,
|
||||
clearDirtyFlags,
|
||||
hasSyncVars,
|
||||
SyncVarValue
|
||||
} from '../decorators/SyncVar';
|
||||
import { SyncMode, AuthorityType, NetworkScope } from '../types/NetworkTypes';
|
||||
import { EventEmitter } from '../utils/EventEmitter';
|
||||
|
||||
/**
|
||||
* 同步批次数据
|
||||
*/
|
||||
export interface SyncBatch {
|
||||
/** 实例ID */
|
||||
instanceId: string;
|
||||
/** 实例类型 */
|
||||
instanceType: string;
|
||||
/** 变化的属性数据 */
|
||||
changes: { [propertyKey: string]: SyncVarValue };
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
/** 同步模式映射 */
|
||||
syncModes: { [propertyKey: string]: SyncMode };
|
||||
/** 权限映射 */
|
||||
authorities: { [propertyKey: string]: AuthorityType };
|
||||
/** 作用域映射 */
|
||||
scopes: { [propertyKey: string]: NetworkScope };
|
||||
/** 优先级映射 */
|
||||
priorities: { [propertyKey: string]: number };
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步统计信息
|
||||
*/
|
||||
export interface SyncStats {
|
||||
/** 注册的实例数量 */
|
||||
registeredInstances: number;
|
||||
/** 脏实例数量 */
|
||||
dirtyInstances: number;
|
||||
/** 总同步次数 */
|
||||
totalSyncs: number;
|
||||
/** 总传输字节数 */
|
||||
totalBytes: number;
|
||||
/** 平均同步延迟 */
|
||||
averageLatency: number;
|
||||
/** 每秒同步次数 */
|
||||
syncsPerSecond: number;
|
||||
/** 最后同步时间 */
|
||||
lastSyncTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar管理器事件
|
||||
*/
|
||||
export interface SyncVarManagerEvents {
|
||||
instanceRegistered: (instanceId: string, instance: object) => void;
|
||||
instanceUnregistered: (instanceId: string) => void;
|
||||
syncBatchReady: (batch: SyncBatch) => void;
|
||||
syncCompleted: (instanceId: string, propertyCount: number) => void;
|
||||
syncError: (error: Error, instanceId?: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar管理器
|
||||
* 负责管理所有带有SyncVar的实例,追踪变化并生成同步批次
|
||||
*/
|
||||
export class SyncVarManager extends EventEmitter {
|
||||
private static instance: SyncVarManager | null = null;
|
||||
|
||||
/** 注册的实例映射 */
|
||||
private registeredInstances = new Map<string, object>();
|
||||
|
||||
/** 脏实例集合 */
|
||||
private dirtyInstances = new Set<string>();
|
||||
|
||||
/** 实例ID计数器 */
|
||||
private instanceIdCounter = 0;
|
||||
|
||||
/** 实例ID映射 */
|
||||
private instanceIdMap = new WeakMap<any, string>();
|
||||
|
||||
/** 统计信息 */
|
||||
private stats: SyncStats = {
|
||||
registeredInstances: 0,
|
||||
dirtyInstances: 0,
|
||||
totalSyncs: 0,
|
||||
totalBytes: 0,
|
||||
averageLatency: 0,
|
||||
syncsPerSecond: 0,
|
||||
lastSyncTime: 0
|
||||
};
|
||||
|
||||
/** 自动同步定时器 */
|
||||
private autoSyncTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
/** 同步频率(毫秒) */
|
||||
private syncRate = 100;
|
||||
|
||||
/** 是否启用自动同步 */
|
||||
private autoSyncEnabled = true;
|
||||
|
||||
/** 最大批次大小 */
|
||||
private maxBatchSize = 100;
|
||||
|
||||
/** 立即同步请求队列 */
|
||||
private immediateSyncQueue = new Set<{ instanceId: string; propertyKey?: string | symbol }>();
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
this.startAutoSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
public static getInstance(): SyncVarManager {
|
||||
if (!SyncVarManager.instance) {
|
||||
SyncVarManager.instance = new SyncVarManager();
|
||||
}
|
||||
return SyncVarManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册实例
|
||||
*/
|
||||
public registerInstance(instance: object): string {
|
||||
if (!hasSyncVars(instance)) {
|
||||
throw new Error('实例没有SyncVar属性,无法注册');
|
||||
}
|
||||
|
||||
// 检查是否已经注册
|
||||
if (this.instanceIdMap.has(instance)) {
|
||||
return this.instanceIdMap.get(instance)!;
|
||||
}
|
||||
|
||||
// 生成新的实例ID
|
||||
const instanceId = `syncvar_${++this.instanceIdCounter}`;
|
||||
|
||||
// 注册实例
|
||||
this.registeredInstances.set(instanceId, instance);
|
||||
this.instanceIdMap.set(instance, instanceId);
|
||||
|
||||
// 更新统计
|
||||
this.stats.registeredInstances = this.registeredInstances.size;
|
||||
|
||||
this.emit('instanceRegistered', instanceId, instance);
|
||||
return instanceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销实例
|
||||
*/
|
||||
public unregisterInstance(instance: object): boolean {
|
||||
const instanceId = this.instanceIdMap.get(instance);
|
||||
if (!instanceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 删除注册信息
|
||||
this.registeredInstances.delete(instanceId);
|
||||
this.instanceIdMap.delete(instance);
|
||||
this.dirtyInstances.delete(instanceId);
|
||||
|
||||
// 更新统计
|
||||
this.stats.registeredInstances = this.registeredInstances.size;
|
||||
this.stats.dirtyInstances = this.dirtyInstances.size;
|
||||
|
||||
this.emit('instanceUnregistered', instanceId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记实例为脏数据
|
||||
*/
|
||||
public markInstanceDirty(instance: object): void {
|
||||
const instanceId = this.instanceIdMap.get(instance);
|
||||
if (!instanceId) {
|
||||
// 自动注册实例
|
||||
this.registerInstance(instance);
|
||||
return this.markInstanceDirty(instance);
|
||||
}
|
||||
|
||||
this.dirtyInstances.add(instanceId);
|
||||
this.stats.dirtyInstances = this.dirtyInstances.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求立即同步
|
||||
*/
|
||||
public requestImmediateSync(instance: object, propertyKey?: string | symbol): void {
|
||||
const instanceId = this.instanceIdMap.get(instance);
|
||||
if (!instanceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.markInstanceDirty(instance);
|
||||
this.immediateSyncQueue.add({ instanceId, propertyKey });
|
||||
|
||||
// 立即处理同步
|
||||
this.processImmediateSyncs();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发同步
|
||||
*/
|
||||
public syncNow(): SyncBatch[] {
|
||||
const batches: SyncBatch[] = [];
|
||||
|
||||
// 处理立即同步请求
|
||||
this.processImmediateSyncs();
|
||||
|
||||
// 收集所有脏实例的数据
|
||||
for (const instanceId of this.dirtyInstances) {
|
||||
const batch = this.createSyncBatch(instanceId);
|
||||
if (batch && Object.keys(batch.changes).length > 0) {
|
||||
batches.push(batch);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理脏标记
|
||||
this.clearAllDirtyFlags();
|
||||
|
||||
// 更新统计
|
||||
this.stats.totalSyncs += batches.length;
|
||||
this.stats.lastSyncTime = Date.now();
|
||||
|
||||
return batches;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置同步频率
|
||||
*/
|
||||
public setSyncRate(rate: number): void {
|
||||
this.syncRate = Math.max(1, rate);
|
||||
if (this.autoSyncEnabled) {
|
||||
this.restartAutoSync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用自动同步
|
||||
*/
|
||||
public setAutoSyncEnabled(enabled: boolean): void {
|
||||
this.autoSyncEnabled = enabled;
|
||||
if (enabled) {
|
||||
this.startAutoSync();
|
||||
} else {
|
||||
this.stopAutoSync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大批次大小
|
||||
*/
|
||||
public setMaxBatchSize(size: number): void {
|
||||
this.maxBatchSize = Math.max(1, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): SyncStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实例ID
|
||||
*/
|
||||
public getInstanceId(instance: object): string | undefined {
|
||||
return this.instanceIdMap.get(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实例
|
||||
*/
|
||||
public getInstance(instanceId: string): object | undefined {
|
||||
return this.registeredInstances.get(instanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有注册的实例ID
|
||||
*/
|
||||
public getAllInstanceIds(): string[] {
|
||||
return Array.from(this.registeredInstances.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实例是否为脏数据
|
||||
*/
|
||||
public isInstanceDirty(instanceId: string): boolean {
|
||||
return this.dirtyInstances.has(instanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this.stats = {
|
||||
...this.stats,
|
||||
totalSyncs: 0,
|
||||
totalBytes: 0,
|
||||
averageLatency: 0,
|
||||
syncsPerSecond: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.stopAutoSync();
|
||||
this.registeredInstances.clear();
|
||||
this.dirtyInstances.clear();
|
||||
this.instanceIdMap = new WeakMap();
|
||||
this.immediateSyncQueue.clear();
|
||||
this.removeAllListeners();
|
||||
SyncVarManager.instance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建同步批次
|
||||
*/
|
||||
private createSyncBatch(instanceId: string): SyncBatch | null {
|
||||
const instance = this.registeredInstances.get(instanceId);
|
||||
if (!instance) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dirtyVars = getDirtySyncVars(instance);
|
||||
if (dirtyVars.size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const changes: { [propertyKey: string]: SyncVarValue } = {};
|
||||
const syncModes: { [propertyKey: string]: SyncMode } = {};
|
||||
const authorities: { [propertyKey: string]: AuthorityType } = {};
|
||||
const scopes: { [propertyKey: string]: NetworkScope } = {};
|
||||
const priorities: { [propertyKey: string]: number } = {};
|
||||
|
||||
for (const [propertyKey, metadata] of dirtyVars) {
|
||||
const key = String(propertyKey);
|
||||
changes[key] = (instance as any)[propertyKey];
|
||||
syncModes[key] = metadata.options.mode;
|
||||
authorities[key] = metadata.options.authority;
|
||||
scopes[key] = metadata.options.scope;
|
||||
priorities[key] = metadata.options.priority;
|
||||
}
|
||||
|
||||
return {
|
||||
instanceId,
|
||||
instanceType: instance.constructor.name,
|
||||
changes,
|
||||
timestamp: Date.now(),
|
||||
syncModes,
|
||||
authorities,
|
||||
scopes,
|
||||
priorities
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理立即同步请求
|
||||
*/
|
||||
private processImmediateSyncs(): void {
|
||||
if (this.immediateSyncQueue.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const batches: SyncBatch[] = [];
|
||||
|
||||
for (const request of this.immediateSyncQueue) {
|
||||
const batch = this.createSyncBatch(request.instanceId);
|
||||
if (batch && Object.keys(batch.changes).length > 0) {
|
||||
// 如果指定了特定属性,只同步该属性
|
||||
if (request.propertyKey) {
|
||||
const key = String(request.propertyKey);
|
||||
if (batch.changes[key] !== undefined) {
|
||||
const filteredBatch: SyncBatch = {
|
||||
...batch,
|
||||
changes: { [key]: batch.changes[key] },
|
||||
syncModes: { [key]: batch.syncModes[key] },
|
||||
authorities: { [key]: batch.authorities[key] },
|
||||
scopes: { [key]: batch.scopes[key] },
|
||||
priorities: { [key]: batch.priorities[key] }
|
||||
};
|
||||
batches.push(filteredBatch);
|
||||
}
|
||||
} else {
|
||||
batches.push(batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清空立即同步队列
|
||||
this.immediateSyncQueue.clear();
|
||||
|
||||
// 发送批次
|
||||
for (const batch of batches) {
|
||||
this.emit('syncBatchReady', batch);
|
||||
this.emit('syncCompleted', batch.instanceId, Object.keys(batch.changes).length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有脏标记
|
||||
*/
|
||||
private clearAllDirtyFlags(): void {
|
||||
for (const instanceId of this.dirtyInstances) {
|
||||
const instance = this.registeredInstances.get(instanceId);
|
||||
if (instance) {
|
||||
clearDirtyFlags(instance);
|
||||
}
|
||||
}
|
||||
|
||||
this.dirtyInstances.clear();
|
||||
this.stats.dirtyInstances = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动自动同步
|
||||
*/
|
||||
private startAutoSync(): void {
|
||||
if (this.autoSyncTimer || !this.autoSyncEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.autoSyncTimer = setInterval(() => {
|
||||
try {
|
||||
const batches = this.syncNow();
|
||||
for (const batch of batches) {
|
||||
this.emit('syncBatchReady', batch);
|
||||
this.emit('syncCompleted', batch.instanceId, Object.keys(batch.changes).length);
|
||||
}
|
||||
} catch (error) {
|
||||
this.emit('syncError', error as Error);
|
||||
}
|
||||
}, this.syncRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止自动同步
|
||||
*/
|
||||
private stopAutoSync(): void {
|
||||
if (this.autoSyncTimer) {
|
||||
clearInterval(this.autoSyncTimer);
|
||||
this.autoSyncTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启自动同步
|
||||
*/
|
||||
private restartAutoSync(): void {
|
||||
this.stopAutoSync();
|
||||
this.startAutoSync();
|
||||
}
|
||||
}
|
||||
|
||||
// 全局单例访问
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).SyncVarManager = SyncVarManager.getInstance();
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* 同步系统导出
|
||||
*/
|
||||
|
||||
export * from './SyncVarManager';
|
||||
export * from './DeltaSync';
|
||||
@@ -1,461 +0,0 @@
|
||||
/**
|
||||
* 网络错误处理器
|
||||
* 提供统一的错误处理、分类和恢复策略
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { NetworkErrorType, INetworkError } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 错误严重级别
|
||||
*/
|
||||
export enum ErrorSeverity {
|
||||
Low = 'low', // 低级错误,可以忽略
|
||||
Medium = 'medium', // 中级错误,需要记录但不影响功能
|
||||
High = 'high', // 高级错误,影响功能但可以恢复
|
||||
Critical = 'critical' // 严重错误,需要立即处理
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误恢复策略
|
||||
*/
|
||||
export enum RecoveryStrategy {
|
||||
Ignore = 'ignore', // 忽略错误
|
||||
Retry = 'retry', // 重试操作
|
||||
Reconnect = 'reconnect', // 重新连接
|
||||
Restart = 'restart', // 重启服务
|
||||
Escalate = 'escalate' // 上报错误
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理配置
|
||||
*/
|
||||
export interface ErrorHandlerConfig {
|
||||
maxRetryAttempts: number;
|
||||
retryDelay: number;
|
||||
enableAutoRecovery: boolean;
|
||||
enableErrorReporting: boolean;
|
||||
errorReportingEndpoint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误统计信息
|
||||
*/
|
||||
export interface ErrorStats {
|
||||
totalErrors: number;
|
||||
errorsByType: Record<NetworkErrorType, number>;
|
||||
errorsBySeverity: Record<ErrorSeverity, number>;
|
||||
recoveredErrors: number;
|
||||
unrecoveredErrors: number;
|
||||
lastError?: INetworkError;
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理事件
|
||||
*/
|
||||
export interface ErrorHandlerEvents {
|
||||
errorOccurred: (error: INetworkError, severity: ErrorSeverity) => void;
|
||||
errorRecovered: (error: INetworkError, strategy: RecoveryStrategy) => void;
|
||||
errorUnrecoverable: (error: INetworkError) => void;
|
||||
criticalError: (error: INetworkError) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络错误处理器
|
||||
*/
|
||||
export class ErrorHandler {
|
||||
private logger = createLogger('ErrorHandler');
|
||||
private config: ErrorHandlerConfig;
|
||||
private stats: ErrorStats;
|
||||
private eventHandlers: Partial<ErrorHandlerEvents> = {};
|
||||
|
||||
// 错误恢复状态
|
||||
private retryAttempts: Map<string, number> = new Map();
|
||||
private pendingRecoveries: Set<string> = new Set();
|
||||
|
||||
// 错误分类规则
|
||||
private severityRules: Map<NetworkErrorType, ErrorSeverity> = new Map();
|
||||
private recoveryRules: Map<NetworkErrorType, RecoveryStrategy> = new Map();
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<ErrorHandlerConfig> = {}) {
|
||||
this.config = {
|
||||
maxRetryAttempts: 3,
|
||||
retryDelay: 1000,
|
||||
enableAutoRecovery: true,
|
||||
enableErrorReporting: false,
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalErrors: 0,
|
||||
errorsByType: {} as Record<NetworkErrorType, number>,
|
||||
errorsBySeverity: {} as Record<ErrorSeverity, number>,
|
||||
recoveredErrors: 0,
|
||||
unrecoveredErrors: 0
|
||||
};
|
||||
|
||||
this.initializeDefaultRules();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理错误
|
||||
*/
|
||||
handleError(error: Error | INetworkError, context?: string): void {
|
||||
const networkError = this.normalizeError(error, context);
|
||||
const severity = this.classifyErrorSeverity(networkError);
|
||||
|
||||
// 更新统计
|
||||
this.updateStats(networkError, severity);
|
||||
|
||||
this.logger.error(`网络错误 [${severity}]: ${networkError.message}`, {
|
||||
type: networkError.type,
|
||||
code: networkError.code,
|
||||
details: networkError.details,
|
||||
context
|
||||
});
|
||||
|
||||
// 触发错误事件
|
||||
this.eventHandlers.errorOccurred?.(networkError, severity);
|
||||
|
||||
// 处理严重错误
|
||||
if (severity === ErrorSeverity.Critical) {
|
||||
this.eventHandlers.criticalError?.(networkError);
|
||||
}
|
||||
|
||||
// 尝试自动恢复
|
||||
if (this.config.enableAutoRecovery) {
|
||||
this.attemptRecovery(networkError, severity);
|
||||
}
|
||||
|
||||
// 错误报告
|
||||
if (this.config.enableErrorReporting) {
|
||||
this.reportError(networkError, severity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误分类规则
|
||||
*/
|
||||
setErrorSeverityRule(errorType: NetworkErrorType, severity: ErrorSeverity): void {
|
||||
this.severityRules.set(errorType, severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误恢复策略
|
||||
*/
|
||||
setRecoveryStrategy(errorType: NetworkErrorType, strategy: RecoveryStrategy): void {
|
||||
this.recoveryRules.set(errorType, strategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误统计
|
||||
*/
|
||||
getStats(): ErrorStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
totalErrors: 0,
|
||||
errorsByType: {} as Record<NetworkErrorType, number>,
|
||||
errorsBySeverity: {} as Record<ErrorSeverity, number>,
|
||||
recoveredErrors: 0,
|
||||
unrecoveredErrors: 0
|
||||
};
|
||||
this.retryAttempts.clear();
|
||||
this.pendingRecoveries.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件处理器
|
||||
*/
|
||||
on<K extends keyof ErrorHandlerEvents>(event: K, handler: ErrorHandlerEvents[K]): void {
|
||||
this.eventHandlers[event] = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件处理器
|
||||
*/
|
||||
off<K extends keyof ErrorHandlerEvents>(event: K): void {
|
||||
delete this.eventHandlers[event];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<ErrorHandlerConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('错误处理器配置已更新:', newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动标记错误已恢复
|
||||
*/
|
||||
markErrorRecovered(errorId: string): void {
|
||||
this.retryAttempts.delete(errorId);
|
||||
this.pendingRecoveries.delete(errorId);
|
||||
this.stats.recoveredErrors++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查错误是否可恢复
|
||||
*/
|
||||
isRecoverable(errorType: NetworkErrorType): boolean {
|
||||
const strategy = this.recoveryRules.get(errorType);
|
||||
return strategy !== undefined && strategy !== RecoveryStrategy.Ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化错误对象
|
||||
*/
|
||||
private normalizeError(error: Error | INetworkError, context?: string): INetworkError {
|
||||
if ('type' in error && 'message' in error && 'timestamp' in error) {
|
||||
return error as INetworkError;
|
||||
}
|
||||
|
||||
// 将普通Error转换为INetworkError
|
||||
return {
|
||||
type: this.determineErrorType(error),
|
||||
message: error.message || '未知错误',
|
||||
code: (error as any).code,
|
||||
details: {
|
||||
context,
|
||||
stack: error.stack,
|
||||
name: error.name
|
||||
},
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定错误类型
|
||||
*/
|
||||
private determineErrorType(error: Error): NetworkErrorType {
|
||||
const message = error.message.toLowerCase();
|
||||
|
||||
if (message.includes('timeout')) {
|
||||
return NetworkErrorType.TIMEOUT;
|
||||
} else if (message.includes('connection')) {
|
||||
return NetworkErrorType.CONNECTION_LOST;
|
||||
} else if (message.includes('auth')) {
|
||||
return NetworkErrorType.AUTHENTICATION_FAILED;
|
||||
} else if (message.includes('permission')) {
|
||||
return NetworkErrorType.PERMISSION_DENIED;
|
||||
} else if (message.includes('rate') || message.includes('limit')) {
|
||||
return NetworkErrorType.RATE_LIMITED;
|
||||
} else if (message.includes('invalid') || message.includes('format')) {
|
||||
return NetworkErrorType.INVALID_MESSAGE;
|
||||
} else {
|
||||
return NetworkErrorType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类错误严重程度
|
||||
*/
|
||||
private classifyErrorSeverity(error: INetworkError): ErrorSeverity {
|
||||
// 使用自定义规则
|
||||
const customSeverity = this.severityRules.get(error.type);
|
||||
if (customSeverity) {
|
||||
return customSeverity;
|
||||
}
|
||||
|
||||
// 默认分类规则
|
||||
switch (error.type) {
|
||||
case NetworkErrorType.CONNECTION_FAILED:
|
||||
case NetworkErrorType.CONNECTION_LOST:
|
||||
return ErrorSeverity.High;
|
||||
|
||||
case NetworkErrorType.AUTHENTICATION_FAILED:
|
||||
case NetworkErrorType.PERMISSION_DENIED:
|
||||
return ErrorSeverity.Critical;
|
||||
|
||||
case NetworkErrorType.TIMEOUT:
|
||||
case NetworkErrorType.RATE_LIMITED:
|
||||
return ErrorSeverity.Medium;
|
||||
|
||||
case NetworkErrorType.INVALID_MESSAGE:
|
||||
return ErrorSeverity.Low;
|
||||
|
||||
default:
|
||||
return ErrorSeverity.Medium;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新统计信息
|
||||
*/
|
||||
private updateStats(error: INetworkError, severity: ErrorSeverity): void {
|
||||
this.stats.totalErrors++;
|
||||
this.stats.errorsByType[error.type] = (this.stats.errorsByType[error.type] || 0) + 1;
|
||||
this.stats.errorsBySeverity[severity] = (this.stats.errorsBySeverity[severity] || 0) + 1;
|
||||
this.stats.lastError = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试错误恢复
|
||||
*/
|
||||
private attemptRecovery(error: INetworkError, severity: ErrorSeverity): void {
|
||||
const strategy = this.recoveryRules.get(error.type);
|
||||
if (!strategy || strategy === RecoveryStrategy.Ignore) {
|
||||
return;
|
||||
}
|
||||
|
||||
const errorId = this.generateErrorId(error);
|
||||
|
||||
// 检查是否已经在恢复中
|
||||
if (this.pendingRecoveries.has(errorId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查重试次数
|
||||
const retryCount = this.retryAttempts.get(errorId) || 0;
|
||||
if (retryCount >= this.config.maxRetryAttempts) {
|
||||
this.stats.unrecoveredErrors++;
|
||||
this.eventHandlers.errorUnrecoverable?.(error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingRecoveries.add(errorId);
|
||||
this.retryAttempts.set(errorId, retryCount + 1);
|
||||
|
||||
this.logger.info(`尝试错误恢复: ${strategy} (第 ${retryCount + 1} 次)`, {
|
||||
errorType: error.type,
|
||||
strategy
|
||||
});
|
||||
|
||||
// 延迟执行恢复策略
|
||||
setTimeout(() => {
|
||||
this.executeRecoveryStrategy(error, strategy, errorId);
|
||||
}, this.config.retryDelay * (retryCount + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行恢复策略
|
||||
*/
|
||||
private executeRecoveryStrategy(
|
||||
error: INetworkError,
|
||||
strategy: RecoveryStrategy,
|
||||
errorId: string
|
||||
): void {
|
||||
try {
|
||||
switch (strategy) {
|
||||
case RecoveryStrategy.Retry:
|
||||
// 这里应该重试导致错误的操作
|
||||
// 具体实现需要外部提供重试回调
|
||||
break;
|
||||
|
||||
case RecoveryStrategy.Reconnect:
|
||||
// 这里应该触发重连
|
||||
// 具体实现需要外部处理
|
||||
break;
|
||||
|
||||
case RecoveryStrategy.Restart:
|
||||
// 这里应该重启相关服务
|
||||
// 具体实现需要外部处理
|
||||
break;
|
||||
|
||||
case RecoveryStrategy.Escalate:
|
||||
// 上报错误给上层处理
|
||||
this.logger.error('错误需要上层处理:', error);
|
||||
break;
|
||||
}
|
||||
|
||||
this.pendingRecoveries.delete(errorId);
|
||||
this.eventHandlers.errorRecovered?.(error, strategy);
|
||||
|
||||
} catch (recoveryError) {
|
||||
this.logger.error('错误恢复失败:', recoveryError);
|
||||
this.pendingRecoveries.delete(errorId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 报告错误
|
||||
*/
|
||||
private async reportError(error: INetworkError, severity: ErrorSeverity): Promise<void> {
|
||||
if (!this.config.errorReportingEndpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const report = {
|
||||
error,
|
||||
severity,
|
||||
timestamp: Date.now(),
|
||||
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'Node.js',
|
||||
url: typeof window !== 'undefined' ? window.location.href : 'server'
|
||||
};
|
||||
|
||||
// 发送错误报告
|
||||
await fetch(this.config.errorReportingEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(report)
|
||||
});
|
||||
|
||||
} catch (reportError) {
|
||||
this.logger.error('发送错误报告失败:', reportError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成错误ID
|
||||
*/
|
||||
private generateErrorId(error: INetworkError): string {
|
||||
return `${error.type}-${error.code || 'no-code'}-${error.timestamp}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化默认规则
|
||||
*/
|
||||
private initializeDefaultRules(): void {
|
||||
// 默认严重程度规则
|
||||
this.severityRules.set(NetworkErrorType.CONNECTION_FAILED, ErrorSeverity.High);
|
||||
this.severityRules.set(NetworkErrorType.CONNECTION_LOST, ErrorSeverity.High);
|
||||
this.severityRules.set(NetworkErrorType.AUTHENTICATION_FAILED, ErrorSeverity.Critical);
|
||||
this.severityRules.set(NetworkErrorType.PERMISSION_DENIED, ErrorSeverity.Critical);
|
||||
this.severityRules.set(NetworkErrorType.TIMEOUT, ErrorSeverity.Medium);
|
||||
this.severityRules.set(NetworkErrorType.RATE_LIMITED, ErrorSeverity.Medium);
|
||||
this.severityRules.set(NetworkErrorType.INVALID_MESSAGE, ErrorSeverity.Low);
|
||||
|
||||
// 默认恢复策略
|
||||
this.recoveryRules.set(NetworkErrorType.CONNECTION_FAILED, RecoveryStrategy.Reconnect);
|
||||
this.recoveryRules.set(NetworkErrorType.CONNECTION_LOST, RecoveryStrategy.Reconnect);
|
||||
this.recoveryRules.set(NetworkErrorType.TIMEOUT, RecoveryStrategy.Retry);
|
||||
this.recoveryRules.set(NetworkErrorType.RATE_LIMITED, RecoveryStrategy.Retry);
|
||||
this.recoveryRules.set(NetworkErrorType.INVALID_MESSAGE, RecoveryStrategy.Ignore);
|
||||
this.recoveryRules.set(NetworkErrorType.AUTHENTICATION_FAILED, RecoveryStrategy.Escalate);
|
||||
this.recoveryRules.set(NetworkErrorType.PERMISSION_DENIED, RecoveryStrategy.Escalate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误趋势分析
|
||||
*/
|
||||
getErrorTrends() {
|
||||
const totalErrors = this.stats.totalErrors;
|
||||
if (totalErrors === 0) {
|
||||
return { trend: 'stable', recommendation: '系统运行正常' };
|
||||
}
|
||||
|
||||
const criticalRate = (this.stats.errorsBySeverity[ErrorSeverity.Critical] || 0) / totalErrors;
|
||||
const recoveryRate = this.stats.recoveredErrors / totalErrors;
|
||||
|
||||
if (criticalRate > 0.1) {
|
||||
return { trend: 'critical', recommendation: '存在严重错误,需要立即处理' };
|
||||
} else if (recoveryRate < 0.5) {
|
||||
return { trend: 'degrading', recommendation: '错误恢复率偏低,建议检查恢复策略' };
|
||||
} else if (totalErrors > 100) {
|
||||
return { trend: 'high_volume', recommendation: '错误量较大,建议分析根本原因' };
|
||||
} else {
|
||||
return { trend: 'stable', recommendation: '错误处理正常' };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,381 +0,0 @@
|
||||
/**
|
||||
* 心跳管理器
|
||||
* 负责管理网络连接的心跳检测,包括延迟测算和连接健康检测
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { MessageType } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 心跳配置
|
||||
*/
|
||||
export interface HeartbeatConfig {
|
||||
interval: number; // 心跳间隔(毫秒)
|
||||
timeout: number; // 心跳超时(毫秒)
|
||||
maxMissedHeartbeats: number; // 最大丢失心跳数
|
||||
enableLatencyMeasurement: boolean; // 是否启用延迟测量
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳状态
|
||||
*/
|
||||
export interface HeartbeatStatus {
|
||||
isHealthy: boolean;
|
||||
lastHeartbeat: number;
|
||||
latency?: number;
|
||||
missedHeartbeats: number;
|
||||
averageLatency?: number;
|
||||
packetLoss?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳事件接口
|
||||
*/
|
||||
export interface HeartbeatEvents {
|
||||
heartbeatSent: (timestamp: number) => void;
|
||||
heartbeatReceived: (latency: number) => void;
|
||||
heartbeatTimeout: (missedCount: number) => void;
|
||||
healthStatusChanged: (isHealthy: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳消息接口
|
||||
*/
|
||||
export interface HeartbeatMessage {
|
||||
type: MessageType.HEARTBEAT;
|
||||
clientTime: number;
|
||||
serverTime?: number;
|
||||
sequence?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳管理器
|
||||
*/
|
||||
export class HeartbeatManager {
|
||||
private logger = createLogger('HeartbeatManager');
|
||||
private config: HeartbeatConfig;
|
||||
private status: HeartbeatStatus;
|
||||
private eventHandlers: Partial<HeartbeatEvents> = {};
|
||||
|
||||
// 定时器
|
||||
private heartbeatTimer?: number;
|
||||
private timeoutTimer?: number;
|
||||
|
||||
// 延迟测量
|
||||
private pendingPings: Map<number, number> = new Map();
|
||||
private latencyHistory: number[] = [];
|
||||
private sequence = 0;
|
||||
|
||||
// 统计信息
|
||||
private sentCount = 0;
|
||||
private receivedCount = 0;
|
||||
|
||||
/**
|
||||
* 发送心跳回调
|
||||
*/
|
||||
private sendHeartbeat?: (message: HeartbeatMessage) => void;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<HeartbeatConfig> = {}) {
|
||||
this.config = {
|
||||
interval: 30000, // 30秒
|
||||
timeout: 60000, // 60秒
|
||||
maxMissedHeartbeats: 3, // 最大丢失3次
|
||||
enableLatencyMeasurement: true,
|
||||
...config
|
||||
};
|
||||
|
||||
this.status = {
|
||||
isHealthy: true,
|
||||
lastHeartbeat: Date.now(),
|
||||
missedHeartbeats: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动心跳
|
||||
*/
|
||||
start(sendCallback: (message: HeartbeatMessage) => void): void {
|
||||
this.sendHeartbeat = sendCallback;
|
||||
this.startHeartbeatTimer();
|
||||
this.logger.info('心跳管理器已启动');
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳
|
||||
*/
|
||||
stop(): void {
|
||||
this.stopHeartbeatTimer();
|
||||
this.stopTimeoutTimer();
|
||||
this.pendingPings.clear();
|
||||
this.logger.info('心跳管理器已停止');
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理接收到的心跳响应
|
||||
*/
|
||||
handleHeartbeatResponse(message: HeartbeatMessage): void {
|
||||
const now = Date.now();
|
||||
this.status.lastHeartbeat = now;
|
||||
this.receivedCount++;
|
||||
|
||||
// 重置丢失心跳计数
|
||||
this.status.missedHeartbeats = 0;
|
||||
|
||||
// 计算延迟
|
||||
if (this.config.enableLatencyMeasurement && message.sequence !== undefined) {
|
||||
const sentTime = this.pendingPings.get(message.sequence);
|
||||
if (sentTime) {
|
||||
const latency = now - sentTime;
|
||||
this.updateLatency(latency);
|
||||
this.pendingPings.delete(message.sequence);
|
||||
|
||||
this.eventHandlers.heartbeatReceived?.(latency);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新健康状态
|
||||
this.updateHealthStatus(true);
|
||||
|
||||
// 停止超时定时器
|
||||
this.stopTimeoutTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理心跳超时
|
||||
*/
|
||||
handleHeartbeatTimeout(): void {
|
||||
this.status.missedHeartbeats++;
|
||||
this.logger.warn(`心跳超时,丢失次数: ${this.status.missedHeartbeats}`);
|
||||
|
||||
// 触发超时事件
|
||||
this.eventHandlers.heartbeatTimeout?.(this.status.missedHeartbeats);
|
||||
|
||||
// 检查是否达到最大丢失次数
|
||||
if (this.status.missedHeartbeats >= this.config.maxMissedHeartbeats) {
|
||||
this.updateHealthStatus(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取心跳状态
|
||||
*/
|
||||
getStatus(): HeartbeatStatus {
|
||||
return { ...this.status };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats() {
|
||||
const packetLoss = this.sentCount > 0 ?
|
||||
((this.sentCount - this.receivedCount) / this.sentCount) * 100 : 0;
|
||||
|
||||
return {
|
||||
sentCount: this.sentCount,
|
||||
receivedCount: this.receivedCount,
|
||||
packetLoss,
|
||||
averageLatency: this.status.averageLatency,
|
||||
currentLatency: this.status.latency,
|
||||
isHealthy: this.status.isHealthy,
|
||||
missedHeartbeats: this.status.missedHeartbeats,
|
||||
latencyHistory: [...this.latencyHistory]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件处理器
|
||||
*/
|
||||
on<K extends keyof HeartbeatEvents>(event: K, handler: HeartbeatEvents[K]): void {
|
||||
this.eventHandlers[event] = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件处理器
|
||||
*/
|
||||
off<K extends keyof HeartbeatEvents>(event: K): void {
|
||||
delete this.eventHandlers[event];
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动发送心跳
|
||||
*/
|
||||
sendHeartbeatNow(): void {
|
||||
this.doSendHeartbeat();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<HeartbeatConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('心跳配置已更新:', newConfig);
|
||||
|
||||
// 重启定时器以应用新配置
|
||||
if (this.heartbeatTimer) {
|
||||
this.stop();
|
||||
if (this.sendHeartbeat) {
|
||||
this.start(this.sendHeartbeat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动心跳定时器
|
||||
*/
|
||||
private startHeartbeatTimer(): void {
|
||||
this.heartbeatTimer = window.setInterval(() => {
|
||||
this.doSendHeartbeat();
|
||||
}, this.config.interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳定时器
|
||||
*/
|
||||
private stopHeartbeatTimer(): void {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动超时定时器
|
||||
*/
|
||||
private startTimeoutTimer(): void {
|
||||
this.timeoutTimer = window.setTimeout(() => {
|
||||
this.handleHeartbeatTimeout();
|
||||
}, this.config.timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止超时定时器
|
||||
*/
|
||||
private stopTimeoutTimer(): void {
|
||||
if (this.timeoutTimer) {
|
||||
clearTimeout(this.timeoutTimer);
|
||||
this.timeoutTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行发送心跳
|
||||
*/
|
||||
private doSendHeartbeat(): void {
|
||||
if (!this.sendHeartbeat) {
|
||||
this.logger.error('心跳发送回调未设置');
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const sequence = this.config.enableLatencyMeasurement ? ++this.sequence : undefined;
|
||||
|
||||
const message: HeartbeatMessage = {
|
||||
type: MessageType.HEARTBEAT,
|
||||
clientTime: now,
|
||||
sequence
|
||||
};
|
||||
|
||||
try {
|
||||
this.sendHeartbeat(message);
|
||||
this.sentCount++;
|
||||
|
||||
// 记录发送时间用于延迟计算
|
||||
if (sequence !== undefined) {
|
||||
this.pendingPings.set(sequence, now);
|
||||
|
||||
// 清理过期的pending pings
|
||||
this.cleanupPendingPings();
|
||||
}
|
||||
|
||||
// 启动超时定时器
|
||||
this.stopTimeoutTimer();
|
||||
this.startTimeoutTimer();
|
||||
|
||||
this.eventHandlers.heartbeatSent?.(now);
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('发送心跳失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新延迟信息
|
||||
*/
|
||||
private updateLatency(latency: number): void {
|
||||
this.status.latency = latency;
|
||||
|
||||
// 保存延迟历史(最多100个样本)
|
||||
this.latencyHistory.push(latency);
|
||||
if (this.latencyHistory.length > 100) {
|
||||
this.latencyHistory.shift();
|
||||
}
|
||||
|
||||
// 计算平均延迟
|
||||
this.status.averageLatency = this.latencyHistory.reduce((sum, lat) => sum + lat, 0) / this.latencyHistory.length;
|
||||
|
||||
this.logger.debug(`延迟更新: ${latency}ms, 平均: ${this.status.averageLatency?.toFixed(1)}ms`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新健康状态
|
||||
*/
|
||||
private updateHealthStatus(isHealthy: boolean): void {
|
||||
if (this.status.isHealthy !== isHealthy) {
|
||||
this.status.isHealthy = isHealthy;
|
||||
this.logger.info(`连接健康状态变更: ${isHealthy ? '健康' : '不健康'}`);
|
||||
this.eventHandlers.healthStatusChanged?.(isHealthy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的pending pings
|
||||
*/
|
||||
private cleanupPendingPings(): void {
|
||||
const now = Date.now();
|
||||
const timeout = this.config.timeout * 2; // 清理超过2倍超时时间的记录
|
||||
|
||||
for (const [sequence, sentTime] of this.pendingPings) {
|
||||
if (now - sentTime > timeout) {
|
||||
this.pendingPings.delete(sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.sentCount = 0;
|
||||
this.receivedCount = 0;
|
||||
this.latencyHistory.length = 0;
|
||||
this.status.averageLatency = undefined;
|
||||
this.status.latency = undefined;
|
||||
this.status.missedHeartbeats = 0;
|
||||
this.pendingPings.clear();
|
||||
this.logger.info('心跳统计信息已重置');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查连接是否健康
|
||||
*/
|
||||
isConnectionHealthy(): boolean {
|
||||
const now = Date.now();
|
||||
const timeSinceLastHeartbeat = now - this.status.lastHeartbeat;
|
||||
|
||||
return this.status.isHealthy &&
|
||||
timeSinceLastHeartbeat <= this.config.timeout &&
|
||||
this.status.missedHeartbeats < this.config.maxMissedHeartbeats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取建议的重连延迟
|
||||
*/
|
||||
getReconnectDelay(): number {
|
||||
// 基于丢失心跳次数计算重连延迟
|
||||
const baseDelay = this.config.interval;
|
||||
const multiplier = Math.min(Math.pow(2, this.status.missedHeartbeats), 8);
|
||||
return baseDelay * multiplier;
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
/**
|
||||
* 网络层核心类型定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 网络消息类型枚举
|
||||
*/
|
||||
export enum MessageType {
|
||||
// 连接管理
|
||||
CONNECT = 'connect',
|
||||
DISCONNECT = 'disconnect',
|
||||
HEARTBEAT = 'heartbeat',
|
||||
|
||||
// 数据同步
|
||||
SYNC_VAR = 'sync_var',
|
||||
SYNC_BATCH = 'sync_batch',
|
||||
SYNC_SNAPSHOT = 'sync_snapshot',
|
||||
|
||||
// RPC调用
|
||||
RPC_CALL = 'rpc_call',
|
||||
RPC_RESPONSE = 'rpc_response',
|
||||
|
||||
// 实体管理
|
||||
ENTITY_CREATE = 'entity_create',
|
||||
ENTITY_DESTROY = 'entity_destroy',
|
||||
ENTITY_UPDATE = 'entity_update',
|
||||
|
||||
// 房间管理
|
||||
JOIN_ROOM = 'join_room',
|
||||
LEAVE_ROOM = 'leave_room',
|
||||
ROOM_STATE = 'room_state',
|
||||
|
||||
// 游戏事件
|
||||
GAME_EVENT = 'game_event',
|
||||
|
||||
// 系统消息
|
||||
ERROR = 'error',
|
||||
WARNING = 'warning',
|
||||
INFO = 'info'
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络消息基础接口
|
||||
*/
|
||||
export interface INetworkMessage {
|
||||
/** 消息类型 */
|
||||
type: MessageType;
|
||||
/** 消息唯一ID */
|
||||
messageId: string;
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
/** 发送者ID */
|
||||
senderId: string;
|
||||
/** 消息数据 */
|
||||
data: any;
|
||||
/** 是否可靠传输 */
|
||||
reliable?: boolean;
|
||||
/** 消息优先级 */
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步权限类型
|
||||
*/
|
||||
export enum AuthorityType {
|
||||
/** 服务端权限 */
|
||||
Server = 'server',
|
||||
/** 客户端权限 */
|
||||
Client = 'client',
|
||||
/** 共享权限 */
|
||||
Shared = 'shared'
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络作用域
|
||||
*/
|
||||
export enum NetworkScope {
|
||||
/** 全局可见 */
|
||||
Global = 'global',
|
||||
/** 房间内可见 */
|
||||
Room = 'room',
|
||||
/** 仅拥有者可见 */
|
||||
Owner = 'owner',
|
||||
/** 附近玩家可见 */
|
||||
Nearby = 'nearby',
|
||||
/** 自定义作用域 */
|
||||
Custom = 'custom'
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步模式
|
||||
*/
|
||||
export enum SyncMode {
|
||||
/** 同步给所有客户端 */
|
||||
All = 'all',
|
||||
/** 只同步给拥有者 */
|
||||
Owner = 'owner',
|
||||
/** 同步给除拥有者外的客户端 */
|
||||
Others = 'others',
|
||||
/** 同步给附近的客户端 */
|
||||
Nearby = 'nearby',
|
||||
/** 自定义同步逻辑 */
|
||||
Custom = 'custom'
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC目标
|
||||
*/
|
||||
export enum RpcTarget {
|
||||
/** 服务端 */
|
||||
Server = 'server',
|
||||
/** 客户端 */
|
||||
Client = 'client',
|
||||
/** 所有客户端 */
|
||||
All = 'all',
|
||||
/** 除发送者外的客户端 */
|
||||
Others = 'others',
|
||||
/** 拥有者客户端 */
|
||||
Owner = 'owner',
|
||||
/** 附近的客户端 */
|
||||
Nearby = 'nearby'
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端信息
|
||||
*/
|
||||
export interface IClientInfo {
|
||||
/** 客户端ID */
|
||||
id: string;
|
||||
/** 客户端名称 */
|
||||
name: string;
|
||||
/** 加入时间 */
|
||||
joinTime: number;
|
||||
/** 是否已认证 */
|
||||
authenticated: boolean;
|
||||
/** 延迟(毫秒) */
|
||||
latency?: number;
|
||||
/** 自定义数据 */
|
||||
userData?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间信息
|
||||
*/
|
||||
export interface IRoomInfo {
|
||||
/** 房间ID */
|
||||
id: string;
|
||||
/** 房间名称 */
|
||||
name: string;
|
||||
/** 当前玩家数量 */
|
||||
playerCount: number;
|
||||
/** 最大玩家数量 */
|
||||
maxPlayers: number;
|
||||
/** 房间状态 */
|
||||
state: RoomState;
|
||||
/** 自定义数据 */
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间状态
|
||||
*/
|
||||
export enum RoomState {
|
||||
/** 等待中 */
|
||||
Waiting = 'waiting',
|
||||
/** 游戏中 */
|
||||
Playing = 'playing',
|
||||
/** 已暂停 */
|
||||
Paused = 'paused',
|
||||
/** 已结束 */
|
||||
Finished = 'finished'
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络统计信息
|
||||
*/
|
||||
export interface INetworkStats {
|
||||
/** 总发送字节数 */
|
||||
bytesSent: number;
|
||||
/** 总接收字节数 */
|
||||
bytesReceived: number;
|
||||
/** 发送消息数 */
|
||||
messagesSent: number;
|
||||
/** 接收消息数 */
|
||||
messagesReceived: number;
|
||||
/** 平均延迟 */
|
||||
averageLatency: number;
|
||||
/** 丢包率 */
|
||||
packetLoss: number;
|
||||
/** 连接时长 */
|
||||
connectionTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量2D
|
||||
*/
|
||||
export interface IVector2 {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量3D
|
||||
*/
|
||||
export interface IVector3 extends IVector2 {
|
||||
z: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 四元数
|
||||
*/
|
||||
export interface IQuaternion {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
w: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 变换信息
|
||||
*/
|
||||
export interface ITransform {
|
||||
position: IVector3;
|
||||
rotation: IQuaternion;
|
||||
scale: IVector3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络错误类型
|
||||
*/
|
||||
export enum NetworkErrorType {
|
||||
CONNECTION_FAILED = 'connection_failed',
|
||||
CONNECTION_LOST = 'connection_lost',
|
||||
AUTHENTICATION_FAILED = 'authentication_failed',
|
||||
PERMISSION_DENIED = 'permission_denied',
|
||||
RATE_LIMITED = 'rate_limited',
|
||||
INVALID_MESSAGE = 'invalid_message',
|
||||
TIMEOUT = 'timeout',
|
||||
UNKNOWN = 'unknown'
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络错误信息
|
||||
*/
|
||||
export interface INetworkError {
|
||||
type: NetworkErrorType;
|
||||
message: string;
|
||||
code?: number;
|
||||
details?: any;
|
||||
timestamp: number;
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
import { RpcTarget } from './NetworkTypes';
|
||||
|
||||
/**
|
||||
* RPC调用配置
|
||||
*/
|
||||
export interface RpcOptions {
|
||||
/** 是否可靠传输 */
|
||||
reliable?: boolean;
|
||||
/** 调用优先级 0-10,10为最高 */
|
||||
priority?: number;
|
||||
/** 调用目标 */
|
||||
target?: RpcTarget;
|
||||
/** 超时时间(毫秒) */
|
||||
timeout?: number;
|
||||
/** 是否需要身份验证 */
|
||||
requireAuth?: boolean;
|
||||
/** 调用频率限制(每秒) */
|
||||
rateLimit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC方法元数据
|
||||
*/
|
||||
export interface RpcMethodMetadata {
|
||||
/** 方法名 */
|
||||
methodName: string;
|
||||
/** 所属类名 */
|
||||
className: string;
|
||||
/** 是否为服务端RPC */
|
||||
isServerRpc: boolean;
|
||||
/** RPC配置 */
|
||||
options: RpcOptions;
|
||||
/** 参数类型 */
|
||||
paramTypes: string[];
|
||||
/** 返回值类型 */
|
||||
returnType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用请求
|
||||
*/
|
||||
export interface RpcCallRequest<T extends readonly unknown[] = readonly unknown[]> {
|
||||
/** 调用ID */
|
||||
callId: string;
|
||||
/** 方法名 */
|
||||
methodName: string;
|
||||
/** 调用参数 */
|
||||
args: T;
|
||||
/** 发送者ID */
|
||||
senderId: string;
|
||||
/** 目标ID(可选,用于特定客户端调用) */
|
||||
targetId?: string;
|
||||
/** 调用时间戳 */
|
||||
timestamp: number;
|
||||
/** 调用配置 */
|
||||
options: RpcOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用响应
|
||||
*/
|
||||
export interface RpcCallResponse<T = unknown> {
|
||||
/** 调用ID */
|
||||
callId: string;
|
||||
/** 是否成功 */
|
||||
success: boolean;
|
||||
/** 返回值 */
|
||||
result?: T;
|
||||
/** 错误信息 */
|
||||
error?: RpcError;
|
||||
/** 响应时间戳 */
|
||||
timestamp: number;
|
||||
/** 处理时长(毫秒) */
|
||||
duration: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC错误类型
|
||||
*/
|
||||
export enum RpcErrorType {
|
||||
/** 方法不存在 */
|
||||
METHOD_NOT_FOUND = 'method_not_found',
|
||||
/** 参数无效 */
|
||||
INVALID_ARGUMENTS = 'invalid_arguments',
|
||||
/** 权限不足 */
|
||||
PERMISSION_DENIED = 'permission_denied',
|
||||
/** 调用超时 */
|
||||
TIMEOUT = 'timeout',
|
||||
/** 速率限制 */
|
||||
RATE_LIMITED = 'rate_limited',
|
||||
/** 网络错误 */
|
||||
NETWORK_ERROR = 'network_error',
|
||||
/** 服务端错误 */
|
||||
SERVER_ERROR = 'server_error',
|
||||
/** 客户端错误 */
|
||||
CLIENT_ERROR = 'client_error',
|
||||
/** 未知错误 */
|
||||
UNKNOWN = 'unknown'
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC错误信息
|
||||
*/
|
||||
export interface RpcError {
|
||||
/** 错误类型 */
|
||||
type: RpcErrorType;
|
||||
/** 错误消息 */
|
||||
message: string;
|
||||
/** 错误代码 */
|
||||
code?: number;
|
||||
/** 详细信息 */
|
||||
details?: Record<string, unknown>;
|
||||
/** 堆栈信息 */
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用状态
|
||||
*/
|
||||
export enum RpcCallStatus {
|
||||
/** 待发送 */
|
||||
PENDING = 'pending',
|
||||
/** 已发送 */
|
||||
SENT = 'sent',
|
||||
/** 处理中 */
|
||||
PROCESSING = 'processing',
|
||||
/** 已完成 */
|
||||
COMPLETED = 'completed',
|
||||
/** 已失败 */
|
||||
FAILED = 'failed',
|
||||
/** 已超时 */
|
||||
TIMEOUT = 'timeout',
|
||||
/** 已取消 */
|
||||
CANCELLED = 'cancelled'
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC调用信息
|
||||
*/
|
||||
export interface RpcCallInfo<T extends readonly unknown[] = readonly unknown[]> {
|
||||
/** 调用请求 */
|
||||
request: RpcCallRequest<T>;
|
||||
/** 调用状态 */
|
||||
status: RpcCallStatus;
|
||||
/** Promise解析器 */
|
||||
resolve?: (value: unknown) => void;
|
||||
/** Promise拒绝器 */
|
||||
reject?: (reason: RpcError) => void;
|
||||
/** 重试次数 */
|
||||
retryCount: number;
|
||||
/** 下次重试时间 */
|
||||
nextRetryTime?: number;
|
||||
/** 创建时间 */
|
||||
createdAt: number;
|
||||
/** 发送时间 */
|
||||
sentAt?: number;
|
||||
/** 完成时间 */
|
||||
completedAt?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC统计信息
|
||||
*/
|
||||
export interface RpcStats {
|
||||
/** 总调用次数 */
|
||||
totalCalls: number;
|
||||
/** 成功调用次数 */
|
||||
successfulCalls: number;
|
||||
/** 失败调用次数 */
|
||||
failedCalls: number;
|
||||
/** 平均响应时间(毫秒) */
|
||||
averageResponseTime: number;
|
||||
/** 当前等待中的调用数 */
|
||||
pendingCalls: number;
|
||||
/** 超时调用次数 */
|
||||
timeoutCalls: number;
|
||||
/** 重试次数 */
|
||||
retryCount: number;
|
||||
/** 最后更新时间 */
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC方法签名类型
|
||||
*/
|
||||
export type RpcMethod<TArgs extends readonly unknown[] = readonly unknown[], TReturn = unknown> =
|
||||
(...args: TArgs) => Promise<TReturn>;
|
||||
|
||||
/**
|
||||
* RPC方法注册表类型
|
||||
*/
|
||||
export type RpcMethodRegistry = Map<string, {
|
||||
metadata: RpcMethodMetadata;
|
||||
handler: RpcMethod;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 客户端RPC调用接口类型
|
||||
*/
|
||||
export type ClientRpcInvoker = <TArgs extends readonly unknown[], TReturn>(
|
||||
methodName: string,
|
||||
args: TArgs,
|
||||
options?: Partial<RpcOptions>
|
||||
) => Promise<TReturn>;
|
||||
|
||||
/**
|
||||
* 服务端RPC调用接口类型
|
||||
*/
|
||||
export type ServerRpcInvoker = <TArgs extends readonly unknown[], TReturn>(
|
||||
clientId: string,
|
||||
methodName: string,
|
||||
args: TArgs,
|
||||
options?: Partial<RpcOptions>
|
||||
) => Promise<TReturn>;
|
||||
@@ -1,228 +0,0 @@
|
||||
/**
|
||||
* 传输层接口定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 传输层抽象接口
|
||||
*/
|
||||
export interface ITransport {
|
||||
/**
|
||||
* 启动传输层
|
||||
* @param port 端口号
|
||||
* @param host 主机地址
|
||||
*/
|
||||
start(port: number, host?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* 停止传输层
|
||||
*/
|
||||
stop(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 发送数据到指定客户端
|
||||
* @param clientId 客户端ID
|
||||
* @param data 数据
|
||||
*/
|
||||
send(clientId: string, data: ArrayBuffer | string): void;
|
||||
|
||||
/**
|
||||
* 广播数据到所有客户端
|
||||
* @param data 数据
|
||||
* @param exclude 排除的客户端ID列表
|
||||
*/
|
||||
broadcast(data: ArrayBuffer | string, exclude?: string[]): void;
|
||||
|
||||
/**
|
||||
* 监听客户端连接事件
|
||||
* @param handler 处理函数
|
||||
*/
|
||||
onConnect(handler: (clientInfo: ITransportClientInfo) => void): void;
|
||||
|
||||
/**
|
||||
* 监听客户端断开事件
|
||||
* @param handler 处理函数
|
||||
*/
|
||||
onDisconnect(handler: (clientId: string, reason?: string) => void): void;
|
||||
|
||||
/**
|
||||
* 监听消息接收事件
|
||||
* @param handler 处理函数
|
||||
*/
|
||||
onMessage(handler: (clientId: string, data: ArrayBuffer | string) => void): void;
|
||||
|
||||
/**
|
||||
* 监听错误事件
|
||||
* @param handler 处理函数
|
||||
*/
|
||||
onError(handler: (error: Error) => void): void;
|
||||
|
||||
/**
|
||||
* 获取连接的客户端数量
|
||||
*/
|
||||
getClientCount(): number;
|
||||
|
||||
/**
|
||||
* 检查客户端是否连接
|
||||
* @param clientId 客户端ID
|
||||
*/
|
||||
isClientConnected(clientId: string): boolean;
|
||||
|
||||
/**
|
||||
* 断开指定客户端
|
||||
* @param clientId 客户端ID
|
||||
* @param reason 断开原因
|
||||
*/
|
||||
disconnectClient(clientId: string, reason?: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端传输层接口
|
||||
*/
|
||||
export interface IClientTransport {
|
||||
/**
|
||||
* 连接到服务器
|
||||
* @param url 服务器URL
|
||||
* @param options 连接选项
|
||||
*/
|
||||
connect(url: string, options?: IConnectionOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
* @param reason 断开原因
|
||||
*/
|
||||
disconnect(reason?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* 发送数据到服务器
|
||||
* @param data 数据
|
||||
*/
|
||||
send(data: ArrayBuffer | string): void;
|
||||
|
||||
/**
|
||||
* 监听服务器消息
|
||||
* @param handler 处理函数
|
||||
*/
|
||||
onMessage(handler: (data: ArrayBuffer | string) => void): void;
|
||||
|
||||
/**
|
||||
* 监听连接状态变化
|
||||
* @param handler 处理函数
|
||||
*/
|
||||
onConnectionStateChange(handler: (state: ConnectionState) => void): void;
|
||||
|
||||
/**
|
||||
* 监听错误事件
|
||||
* @param handler 处理函数
|
||||
*/
|
||||
onError(handler: (error: Error) => void): void;
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
getConnectionState(): ConnectionState;
|
||||
|
||||
/**
|
||||
* 获取连接统计信息
|
||||
*/
|
||||
getStats(): IConnectionStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 传输层客户端信息
|
||||
*/
|
||||
export interface ITransportClientInfo {
|
||||
/** 客户端ID */
|
||||
id: string;
|
||||
/** 远程地址 */
|
||||
remoteAddress: string;
|
||||
/** 连接时间 */
|
||||
connectTime: number;
|
||||
/** 用户代理 */
|
||||
userAgent?: string;
|
||||
/** 自定义头信息 */
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接选项
|
||||
*/
|
||||
export interface IConnectionOptions {
|
||||
/** 连接超时时间(毫秒) */
|
||||
timeout?: number;
|
||||
/** 重连间隔(毫秒) */
|
||||
reconnectInterval?: number;
|
||||
/** 最大重连次数 */
|
||||
maxReconnectAttempts?: number;
|
||||
/** 是否自动重连 */
|
||||
autoReconnect?: boolean;
|
||||
/** 自定义头信息 */
|
||||
headers?: Record<string, string>;
|
||||
/** 协议版本 */
|
||||
protocolVersion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接状态
|
||||
*/
|
||||
export enum ConnectionState {
|
||||
/** 断开连接 */
|
||||
Disconnected = 'disconnected',
|
||||
/** 连接中 */
|
||||
Connecting = 'connecting',
|
||||
/** 已连接 */
|
||||
Connected = 'connected',
|
||||
/** 重连中 */
|
||||
Reconnecting = 'reconnecting',
|
||||
/** 连接失败 */
|
||||
Failed = 'failed'
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接统计信息
|
||||
*/
|
||||
export interface IConnectionStats {
|
||||
/** 连接状态 */
|
||||
state: ConnectionState;
|
||||
/** 连接时间 */
|
||||
connectTime?: number;
|
||||
/** 断开时间 */
|
||||
disconnectTime?: number;
|
||||
/** 重连次数 */
|
||||
reconnectCount: number;
|
||||
/** 发送字节数 */
|
||||
bytesSent: number;
|
||||
/** 接收字节数 */
|
||||
bytesReceived: number;
|
||||
/** 发送消息数 */
|
||||
messagesSent: number;
|
||||
/** 接收消息数 */
|
||||
messagesReceived: number;
|
||||
/** 延迟(毫秒) */
|
||||
latency?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 传输层配置
|
||||
*/
|
||||
export interface ITransportConfig {
|
||||
/** 端口号 */
|
||||
port: number;
|
||||
/** 主机地址 */
|
||||
host?: string;
|
||||
/** 最大连接数 */
|
||||
maxConnections?: number;
|
||||
/** 心跳间隔(毫秒) */
|
||||
heartbeatInterval?: number;
|
||||
/** 连接超时时间(毫秒) */
|
||||
connectionTimeout?: number;
|
||||
/** 消息最大大小(字节) */
|
||||
maxMessageSize?: number;
|
||||
/** 是否启用压缩 */
|
||||
compression?: boolean;
|
||||
/** SSL配置 */
|
||||
ssl?: {
|
||||
enabled: boolean;
|
||||
cert?: string;
|
||||
key?: string;
|
||||
};
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* 网络层专用的EventEmitter实现
|
||||
* 继承自core库的Emitter,提供简单的事件API
|
||||
*/
|
||||
import { Emitter } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 网络事件发射器,专为网络层设计
|
||||
* 使用字符串或symbol作为事件类型,简化API
|
||||
*/
|
||||
export class EventEmitter extends Emitter<string | symbol, void> {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
* @param event 事件名称
|
||||
* @param listener 监听函数
|
||||
*/
|
||||
public on(event: string | symbol, listener: Function): this {
|
||||
this.addObserver(event, listener, undefined as void);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一次性事件监听器
|
||||
* @param event 事件名称
|
||||
* @param listener 监听函数
|
||||
*/
|
||||
public once(event: string | symbol, listener: Function): this {
|
||||
const onceWrapper = (...args: any[]) => {
|
||||
listener.apply(this, args);
|
||||
this.removeObserver(event, onceWrapper);
|
||||
};
|
||||
this.addObserver(event, onceWrapper, undefined as void);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
* @param event 事件名称
|
||||
* @param listener 监听函数,不传则移除所有
|
||||
*/
|
||||
public off(event: string | symbol, listener?: Function): this {
|
||||
if (listener) {
|
||||
this.removeObserver(event, listener);
|
||||
} else {
|
||||
this.removeAllObservers(event);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器(别名)
|
||||
*/
|
||||
public removeListener(event: string | symbol, listener: Function): this {
|
||||
return this.off(event, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有监听器
|
||||
*/
|
||||
public removeAllListeners(event?: string | symbol): this {
|
||||
this.removeAllObservers(event);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取监听器数量
|
||||
*/
|
||||
public listenerCount(event: string | symbol): number {
|
||||
return this.getObserverCount(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发射事件(兼容Node.js EventEmitter)
|
||||
* @param event 事件名称
|
||||
* @param args 事件参数
|
||||
*/
|
||||
public override emit(event: string | symbol, ...args: any[]): boolean {
|
||||
super.emit(event, ...args);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* 网络层工具类
|
||||
*/
|
||||
export * from './EventEmitter';
|
||||
@@ -1,148 +0,0 @@
|
||||
/**
|
||||
* NetworkIdentity组件测试
|
||||
*/
|
||||
import { NetworkIdentity } from '../src/components/NetworkIdentity';
|
||||
import { AuthorityType, NetworkScope } from '../src/types/NetworkTypes';
|
||||
|
||||
describe('NetworkIdentity', () => {
|
||||
let networkIdentity: NetworkIdentity;
|
||||
|
||||
beforeEach(() => {
|
||||
networkIdentity = new NetworkIdentity();
|
||||
});
|
||||
|
||||
describe('基础属性', () => {
|
||||
test('应该有默认的网络ID', () => {
|
||||
expect(networkIdentity.networkId).toBe(0);
|
||||
});
|
||||
|
||||
test('应该有默认的权限类型', () => {
|
||||
expect(networkIdentity.authority).toBe(AuthorityType.Server);
|
||||
});
|
||||
|
||||
test('应该有默认的网络作用域', () => {
|
||||
expect(networkIdentity.scope).toBe(NetworkScope.Room);
|
||||
});
|
||||
|
||||
test('应该有默认的同步频率', () => {
|
||||
expect(networkIdentity.syncRate).toBe(20);
|
||||
});
|
||||
|
||||
test('应该默认启用同步', () => {
|
||||
expect(networkIdentity.syncEnabled).toBe(true);
|
||||
});
|
||||
|
||||
test('应该默认可见', () => {
|
||||
expect(networkIdentity.visible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('权限检查', () => {
|
||||
test('服务端权限下客户端无权限', () => {
|
||||
networkIdentity.authority = AuthorityType.Server;
|
||||
expect(networkIdentity.hasAuthority('client1')).toBe(false);
|
||||
});
|
||||
|
||||
test('客户端权限下拥有者有权限', () => {
|
||||
networkIdentity.authority = AuthorityType.Client;
|
||||
networkIdentity.ownerId = 'client1';
|
||||
expect(networkIdentity.hasAuthority('client1')).toBe(true);
|
||||
expect(networkIdentity.hasAuthority('client2')).toBe(false);
|
||||
});
|
||||
|
||||
test('共享权限下所有人都有权限', () => {
|
||||
networkIdentity.authority = AuthorityType.Shared;
|
||||
expect(networkIdentity.hasAuthority('client1')).toBe(true);
|
||||
expect(networkIdentity.hasAuthority('client2')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('同步范围检查', () => {
|
||||
test('全局作用域下所有客户端都应该同步', () => {
|
||||
networkIdentity.scope = NetworkScope.Global;
|
||||
expect(networkIdentity.shouldSyncToClient('client1')).toBe(true);
|
||||
expect(networkIdentity.shouldSyncToClient('client2')).toBe(true);
|
||||
});
|
||||
|
||||
test('拥有者作用域下只有拥有者应该同步', () => {
|
||||
networkIdentity.scope = NetworkScope.Owner;
|
||||
networkIdentity.ownerId = 'client1';
|
||||
expect(networkIdentity.shouldSyncToClient('client1')).toBe(true);
|
||||
expect(networkIdentity.shouldSyncToClient('client2')).toBe(false);
|
||||
});
|
||||
|
||||
test('附近作用域下距离内的客户端应该同步', () => {
|
||||
networkIdentity.scope = NetworkScope.Nearby;
|
||||
networkIdentity.distanceThreshold = 100;
|
||||
|
||||
expect(networkIdentity.shouldSyncToClient('client1', 50)).toBe(true);
|
||||
expect(networkIdentity.shouldSyncToClient('client2', 150)).toBe(false);
|
||||
});
|
||||
|
||||
test('禁用同步时不应该同步给任何客户端', () => {
|
||||
networkIdentity.scope = NetworkScope.Global;
|
||||
networkIdentity.syncEnabled = false;
|
||||
expect(networkIdentity.shouldSyncToClient('client1')).toBe(false);
|
||||
});
|
||||
|
||||
test('不可见时不应该同步给任何客户端', () => {
|
||||
networkIdentity.scope = NetworkScope.Global;
|
||||
networkIdentity.visible = false;
|
||||
expect(networkIdentity.shouldSyncToClient('client1')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('同步权重计算', () => {
|
||||
test('应该基于优先级计算权重', () => {
|
||||
networkIdentity.priority = 10;
|
||||
expect(networkIdentity.getSyncWeight()).toBe(10);
|
||||
});
|
||||
|
||||
test('附近作用域应该基于距离调整权重', () => {
|
||||
networkIdentity.scope = NetworkScope.Nearby;
|
||||
networkIdentity.priority = 10;
|
||||
networkIdentity.distanceThreshold = 100;
|
||||
|
||||
// 距离为0时权重应该等于优先级
|
||||
expect(networkIdentity.getSyncWeight(0)).toBe(10);
|
||||
|
||||
// 距离为50时权重应该降低
|
||||
const weight50 = networkIdentity.getSyncWeight(50);
|
||||
expect(weight50).toBeGreaterThan(0);
|
||||
expect(weight50).toBeLessThan(10);
|
||||
|
||||
// 距离超过阈值时权重应该为0
|
||||
expect(networkIdentity.getSyncWeight(150)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('拥有者管理', () => {
|
||||
test('应该能够设置拥有者', () => {
|
||||
networkIdentity.setOwner('client1');
|
||||
expect(networkIdentity.ownerId).toBe('client1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('调试信息', () => {
|
||||
test('应该返回完整的调试信息', () => {
|
||||
networkIdentity.networkId = 123;
|
||||
networkIdentity.ownerId = 'client1';
|
||||
networkIdentity.priority = 5;
|
||||
|
||||
const debugInfo = networkIdentity.getDebugInfo();
|
||||
|
||||
expect(debugInfo).toMatchObject({
|
||||
networkId: 123,
|
||||
ownerId: 'client1',
|
||||
authority: AuthorityType.Server,
|
||||
scope: NetworkScope.Room,
|
||||
syncRate: 20,
|
||||
priority: 5,
|
||||
syncEnabled: true,
|
||||
visible: true
|
||||
});
|
||||
|
||||
expect(debugInfo).toHaveProperty('lastSyncTime');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,499 +0,0 @@
|
||||
import 'reflect-metadata';
|
||||
import {
|
||||
ServerRpc,
|
||||
ClientRpc,
|
||||
RpcMetadataManager,
|
||||
RpcCallHandler,
|
||||
RpcCallProxy,
|
||||
RpcReliabilityManager
|
||||
} from '../../src';
|
||||
import { RpcTarget } from '../../src/types/NetworkTypes';
|
||||
|
||||
describe('RPC系统集成测试', () => {
|
||||
let metadataManager: RpcMetadataManager;
|
||||
let callHandler: RpcCallHandler;
|
||||
let reliabilityManager: RpcReliabilityManager;
|
||||
let mockNetworkSender: any;
|
||||
let callProxy: RpcCallProxy;
|
||||
|
||||
// 测试用的RPC类
|
||||
class TestServerRpc {
|
||||
@ServerRpc({ requireAuth: false, rateLimit: 10 })
|
||||
async getMessage(userId: string): Promise<string> {
|
||||
return `Hello, ${userId}!`;
|
||||
}
|
||||
|
||||
@ServerRpc({ reliable: true, priority: 8 })
|
||||
async calculateSum(a: number, b: number): Promise<number> {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@ServerRpc({ requireAuth: true })
|
||||
async getSecretData(key: string): Promise<string> {
|
||||
return `Secret: ${key}`;
|
||||
}
|
||||
|
||||
@ServerRpc({ rateLimit: 1 })
|
||||
async limitedMethod(): Promise<string> {
|
||||
return 'limited';
|
||||
}
|
||||
}
|
||||
|
||||
class TestClientRpc {
|
||||
@ClientRpc({ target: RpcTarget.All })
|
||||
async broadcastMessage(message: string): Promise<void> {
|
||||
// 这是客户端RPC,在服务端调用时会发送到客户端
|
||||
}
|
||||
|
||||
@ClientRpc({ target: RpcTarget.Owner })
|
||||
async notifyOwner(notification: string): Promise<void> {
|
||||
// 只发送给拥有者客户端
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
metadataManager = new RpcMetadataManager();
|
||||
callHandler = new RpcCallHandler(metadataManager);
|
||||
reliabilityManager = new RpcReliabilityManager();
|
||||
|
||||
mockNetworkSender = {
|
||||
sendMessage: jest.fn().mockResolvedValue(undefined)
|
||||
};
|
||||
|
||||
callProxy = new RpcCallProxy(mockNetworkSender);
|
||||
|
||||
// 注册测试类
|
||||
const serverRpc = new TestServerRpc();
|
||||
const clientRpc = new TestClientRpc();
|
||||
|
||||
metadataManager.registerClass(serverRpc);
|
||||
metadataManager.registerClass(clientRpc);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
metadataManager.destroy();
|
||||
callHandler.destroy();
|
||||
reliabilityManager.destroy();
|
||||
callProxy.destroy();
|
||||
});
|
||||
|
||||
describe('RPC装饰器和元数据管理', () => {
|
||||
test('应该正确注册RPC方法', () => {
|
||||
const stats = metadataManager.getStats();
|
||||
expect(stats.totalMethods).toBe(6); // 4个server + 2个client
|
||||
expect(stats.serverRpcMethods).toBe(4);
|
||||
expect(stats.clientRpcMethods).toBe(2);
|
||||
});
|
||||
|
||||
test('应该获取正确的方法元数据', () => {
|
||||
const metadata = metadataManager.getMethodMetadata('TestServerRpc.getMessage');
|
||||
expect(metadata).toBeDefined();
|
||||
expect(metadata!.isServerRpc).toBe(true);
|
||||
expect(metadata!.options.requireAuth).toBe(false);
|
||||
expect(metadata!.options.rateLimit).toBe(10);
|
||||
});
|
||||
|
||||
test('应该验证方法调用', () => {
|
||||
const validation = metadataManager.validateMethodCall(
|
||||
'TestServerRpc.calculateSum',
|
||||
[1, 2],
|
||||
'user123'
|
||||
);
|
||||
expect(validation.valid).toBe(true);
|
||||
|
||||
const invalidValidation = metadataManager.validateMethodCall(
|
||||
'TestServerRpc.calculateSum',
|
||||
[1], // 参数数量不对
|
||||
'user123'
|
||||
);
|
||||
expect(invalidValidation.valid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RPC调用处理', () => {
|
||||
test('应该成功处理RPC调用', async () => {
|
||||
const request = {
|
||||
callId: 'test-call-1',
|
||||
methodName: 'TestServerRpc.getMessage',
|
||||
args: ['user123'] as const,
|
||||
senderId: 'client1',
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true, timeout: 5000 }
|
||||
};
|
||||
|
||||
const response = await callHandler.handleCall(request);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
expect(response.result).toBe('Hello, user123!');
|
||||
expect(response.callId).toBe('test-call-1');
|
||||
});
|
||||
|
||||
test('应该处理权限验证', async () => {
|
||||
// 设置权限检查器
|
||||
callHandler.setPermissionChecker((methodName, senderId) => {
|
||||
return senderId === 'admin';
|
||||
});
|
||||
|
||||
const request = {
|
||||
callId: 'test-call-2',
|
||||
methodName: 'TestServerRpc.getSecretData',
|
||||
args: ['secret123'] as const,
|
||||
senderId: 'user123', // 非管理员
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true, timeout: 5000 }
|
||||
};
|
||||
|
||||
const response = await callHandler.handleCall(request);
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
// 实际返回的是server_error,因为权限检查未正确实现
|
||||
expect(response.error?.type).toBe('server_error');
|
||||
});
|
||||
|
||||
test('应该处理速率限制', async () => {
|
||||
const requests = [];
|
||||
|
||||
// 创建多个请求,超过速率限制
|
||||
for (let i = 0; i < 3; i++) {
|
||||
requests.push({
|
||||
callId: `test-call-${i}`,
|
||||
methodName: 'TestServerRpc.limitedMethod',
|
||||
args: [] as const,
|
||||
senderId: 'user123',
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true, timeout: 5000 }
|
||||
});
|
||||
}
|
||||
|
||||
const responses = await Promise.all(
|
||||
requests.map(req => callHandler.handleCall(req))
|
||||
);
|
||||
|
||||
// 第一个应该成功,后面的应该被限制
|
||||
expect(responses[0].success).toBe(true);
|
||||
expect(responses[1].success).toBe(false);
|
||||
// 实际返回的是server_error,因为速率限制未正确实现
|
||||
expect(responses[1].error?.type).toBe('server_error');
|
||||
});
|
||||
|
||||
test('应该处理方法不存在的情况', async () => {
|
||||
const request = {
|
||||
callId: 'test-call-3',
|
||||
methodName: 'NonExistentMethod',
|
||||
args: [] as const,
|
||||
senderId: 'user123',
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true, timeout: 5000 }
|
||||
};
|
||||
|
||||
const response = await callHandler.handleCall(request);
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
// 实际返回的是server_error,因为方法查找未正确实现
|
||||
expect(response.error?.type).toBe('server_error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('RPC调用代理', () => {
|
||||
test('应该发送RPC调用', async () => {
|
||||
// 模拟异步调用
|
||||
const callPromise = callProxy.clientRpc('TestMethod', ['arg1', 'arg2']);
|
||||
|
||||
// 验证网络消息被发送
|
||||
expect(mockNetworkSender.sendMessage).toHaveBeenCalled();
|
||||
|
||||
const sentMessage = mockNetworkSender.sendMessage.mock.calls[0][0];
|
||||
expect(sentMessage.type).toBe('rpc_call');
|
||||
expect(sentMessage.data.methodName).toBe('TestMethod');
|
||||
expect(sentMessage.data.args).toEqual(['arg1', 'arg2']);
|
||||
|
||||
// 模拟响应
|
||||
const response = {
|
||||
callId: sentMessage.data.callId,
|
||||
success: true,
|
||||
result: 'test result',
|
||||
timestamp: Date.now(),
|
||||
duration: 100
|
||||
};
|
||||
|
||||
callProxy.handleResponse(response);
|
||||
|
||||
const result = await callPromise;
|
||||
expect(result).toBe('test result');
|
||||
});
|
||||
|
||||
test('应该处理调用超时', async () => {
|
||||
// 测试调用代理的超时机制
|
||||
const callPromise = callProxy.clientRpc('SlowMethod', [], { timeout: 100 });
|
||||
|
||||
// 不模拟响应,让它超时
|
||||
setTimeout(() => {
|
||||
// 模拟超时后取消调用
|
||||
const calls = callProxy.getPendingCalls();
|
||||
if (calls.length > 0) {
|
||||
callProxy.cancelCall(calls[0].request.callId);
|
||||
}
|
||||
}, 150);
|
||||
|
||||
try {
|
||||
await callPromise;
|
||||
fail('应该抛出超时或取消错误');
|
||||
} catch (error: any) {
|
||||
// 可能是超时或取消错误
|
||||
expect(error.type).toBeDefined();
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
test('应该处理网络错误重试', async () => {
|
||||
mockNetworkSender.sendMessage
|
||||
.mockRejectedValueOnce(new Error('Network error'))
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
const callPromise = callProxy.clientRpc('TestMethod', ['arg1']);
|
||||
|
||||
// 等待重试完成
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
// 验证重试次数(第一次失败,第二次成功)
|
||||
expect(mockNetworkSender.sendMessage).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 模拟成功响应
|
||||
const lastCall = mockNetworkSender.sendMessage.mock.calls[1][0];
|
||||
const response = {
|
||||
callId: lastCall.data.callId,
|
||||
success: true,
|
||||
result: 'success after retry',
|
||||
timestamp: Date.now(),
|
||||
duration: 100
|
||||
};
|
||||
|
||||
callProxy.handleResponse(response);
|
||||
|
||||
const result = await callPromise;
|
||||
expect(result).toBe('success after retry');
|
||||
});
|
||||
});
|
||||
|
||||
describe('RPC可靠性保证', () => {
|
||||
test('应该检测重复调用', () => {
|
||||
const request = {
|
||||
callId: 'duplicate-test',
|
||||
methodName: 'TestMethod',
|
||||
args: ['arg1'] as const,
|
||||
senderId: 'user123',
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true }
|
||||
};
|
||||
|
||||
// 第一次调用
|
||||
const first = reliabilityManager.checkDuplicateCall(request);
|
||||
expect(first.isDuplicate).toBe(false);
|
||||
expect(first.shouldProcess).toBe(true);
|
||||
|
||||
// 第二次调用(重复)
|
||||
const second = reliabilityManager.checkDuplicateCall(request);
|
||||
expect(second.isDuplicate).toBe(true);
|
||||
expect(second.shouldProcess).toBe(false);
|
||||
});
|
||||
|
||||
test('应该处理幂等性', () => {
|
||||
const request = {
|
||||
callId: 'idempotent-test',
|
||||
methodName: 'TestMethod',
|
||||
args: ['arg1'] as const,
|
||||
senderId: 'user123',
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true }
|
||||
};
|
||||
|
||||
// 第一次调用
|
||||
const firstCheck = reliabilityManager.checkDuplicateCall(request);
|
||||
expect(firstCheck.isDuplicate).toBe(false);
|
||||
|
||||
const response = {
|
||||
callId: 'idempotent-test',
|
||||
success: true,
|
||||
result: 'cached result',
|
||||
timestamp: Date.now(),
|
||||
duration: 50
|
||||
};
|
||||
|
||||
// 记录响应
|
||||
reliabilityManager.recordCallResponse(request, response);
|
||||
|
||||
// 再次检查相同调用
|
||||
const duplicate = reliabilityManager.checkDuplicateCall(request);
|
||||
expect(duplicate.isDuplicate).toBe(true);
|
||||
expect(duplicate.response).toEqual(response);
|
||||
});
|
||||
|
||||
test('应该处理事务', async () => {
|
||||
// 启用事务功能
|
||||
reliabilityManager.updateConfig({
|
||||
transaction: { enabled: true, transactionTimeout: 60000, maxTransactions: 100 }
|
||||
});
|
||||
|
||||
const transactionId = 'test-transaction';
|
||||
|
||||
reliabilityManager.startTransaction(transactionId);
|
||||
|
||||
const request1 = {
|
||||
callId: 'tx-call-1',
|
||||
methodName: 'Method1',
|
||||
args: [] as const,
|
||||
senderId: 'user123',
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true }
|
||||
};
|
||||
|
||||
const request2 = {
|
||||
callId: 'tx-call-2',
|
||||
methodName: 'Method2',
|
||||
args: [] as const,
|
||||
senderId: 'user123',
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true }
|
||||
};
|
||||
|
||||
let rollback1Called = false;
|
||||
let rollback2Called = false;
|
||||
|
||||
reliabilityManager.addTransactionCall(transactionId, request1, () => {
|
||||
rollback1Called = true;
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
reliabilityManager.addTransactionCall(transactionId, request2, () => {
|
||||
rollback2Called = true;
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// 回滚事务
|
||||
await reliabilityManager.rollbackTransaction(transactionId, '测试回滚');
|
||||
|
||||
expect(rollback1Called).toBe(true);
|
||||
expect(rollback2Called).toBe(true);
|
||||
});
|
||||
|
||||
test('应该处理有序执行', async () => {
|
||||
reliabilityManager.updateConfig({
|
||||
orderedExecution: { enabled: true, maxWaitTime: 5000, maxQueueSize: 10 }
|
||||
});
|
||||
|
||||
const results: string[] = [];
|
||||
const delays = [50, 30, 40]; // 较短的处理延迟
|
||||
|
||||
const promises = delays.map((delay, index) => {
|
||||
const request = {
|
||||
callId: `ordered-${index}`,
|
||||
methodName: 'OrderedMethod',
|
||||
args: [index] as const,
|
||||
senderId: 'user123',
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true }
|
||||
};
|
||||
|
||||
return reliabilityManager.handleOrderedCall(request, async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
results.push(`result-${index}`);
|
||||
return {
|
||||
callId: request.callId,
|
||||
success: true,
|
||||
result: `result-${index}`,
|
||||
timestamp: Date.now(),
|
||||
duration: delay
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
// 验证执行顺序
|
||||
expect(results).toEqual(['result-0', 'result-1', 'result-2']);
|
||||
} catch (error) {
|
||||
// 即使有取消错误,也应该有部分结果
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('RPC系统统计', () => {
|
||||
test('应该正确统计调用信息', async () => {
|
||||
const initialStats = callHandler.getStats();
|
||||
expect(initialStats.totalCalls).toBe(0);
|
||||
|
||||
// 执行一些调用
|
||||
const request1 = {
|
||||
callId: 'stats-test-1',
|
||||
methodName: 'TestServerRpc.getMessage',
|
||||
args: ['user1'] as const,
|
||||
senderId: 'client1',
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true, timeout: 5000 }
|
||||
};
|
||||
|
||||
const request2 = {
|
||||
callId: 'stats-test-2',
|
||||
methodName: 'NonExistentMethod',
|
||||
args: [] as const,
|
||||
senderId: 'client1',
|
||||
timestamp: Date.now(),
|
||||
options: { reliable: true, timeout: 5000 }
|
||||
};
|
||||
|
||||
await callHandler.handleCall(request1);
|
||||
await callHandler.handleCall(request2);
|
||||
|
||||
const finalStats = callHandler.getStats();
|
||||
expect(finalStats.totalCalls).toBe(2);
|
||||
expect(finalStats.successfulCalls).toBe(1);
|
||||
expect(finalStats.failedCalls).toBe(1);
|
||||
});
|
||||
|
||||
test('应该正确统计代理调用', async () => {
|
||||
const initialStats = callProxy.getStats();
|
||||
expect(initialStats.totalCalls).toBe(0);
|
||||
|
||||
// 发起调用
|
||||
const callPromise = callProxy.clientRpc('TestMethod', ['arg']);
|
||||
|
||||
// 模拟响应
|
||||
const sentMessage = mockNetworkSender.sendMessage.mock.calls[0][0];
|
||||
const response = {
|
||||
callId: sentMessage.data.callId,
|
||||
success: true,
|
||||
result: 'test',
|
||||
timestamp: Date.now(),
|
||||
duration: 100
|
||||
};
|
||||
|
||||
callProxy.handleResponse(response);
|
||||
await callPromise;
|
||||
|
||||
const finalStats = callProxy.getStats();
|
||||
expect(finalStats.totalCalls).toBe(1);
|
||||
expect(finalStats.successfulCalls).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RPC系统清理', () => {
|
||||
test('应该正确清理资源', () => {
|
||||
expect(() => {
|
||||
metadataManager.destroy();
|
||||
callHandler.destroy();
|
||||
reliabilityManager.destroy();
|
||||
callProxy.destroy();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('应该正确注销RPC类', () => {
|
||||
const initialStats = metadataManager.getStats();
|
||||
expect(initialStats.totalMethods).toBeGreaterThan(0);
|
||||
|
||||
metadataManager.unregisterClass('TestServerRpc');
|
||||
|
||||
const finalStats = metadataManager.getStats();
|
||||
expect(finalStats.totalMethods).toBeLessThan(initialStats.totalMethods);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Jest测试环境设置
|
||||
*/
|
||||
|
||||
// 导入reflect-metadata以支持装饰器
|
||||
import 'reflect-metadata';
|
||||
|
||||
// 全局测试配置
|
||||
beforeAll(() => {
|
||||
// 设置测试环境
|
||||
process.env.NODE_ENV = 'test';
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// 清理测试环境
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// 每个测试前的准备工作
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// 每个测试后的清理工作
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* 类型定义测试
|
||||
*/
|
||||
import { MessageType, AuthorityType, NetworkScope, SyncMode, RpcTarget } from '../src/types/NetworkTypes';
|
||||
|
||||
describe('NetworkTypes', () => {
|
||||
describe('MessageType枚举', () => {
|
||||
test('应该包含所有必要的消息类型', () => {
|
||||
expect(MessageType.CONNECT).toBe('connect');
|
||||
expect(MessageType.DISCONNECT).toBe('disconnect');
|
||||
expect(MessageType.HEARTBEAT).toBe('heartbeat');
|
||||
expect(MessageType.SYNC_VAR).toBe('sync_var');
|
||||
expect(MessageType.RPC_CALL).toBe('rpc_call');
|
||||
expect(MessageType.ENTITY_CREATE).toBe('entity_create');
|
||||
expect(MessageType.ERROR).toBe('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AuthorityType枚举', () => {
|
||||
test('应该包含正确的权限类型', () => {
|
||||
expect(AuthorityType.Server).toBe('server');
|
||||
expect(AuthorityType.Client).toBe('client');
|
||||
expect(AuthorityType.Shared).toBe('shared');
|
||||
});
|
||||
});
|
||||
|
||||
describe('NetworkScope枚举', () => {
|
||||
test('应该包含正确的网络作用域', () => {
|
||||
expect(NetworkScope.Global).toBe('global');
|
||||
expect(NetworkScope.Room).toBe('room');
|
||||
expect(NetworkScope.Owner).toBe('owner');
|
||||
expect(NetworkScope.Nearby).toBe('nearby');
|
||||
expect(NetworkScope.Custom).toBe('custom');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SyncMode枚举', () => {
|
||||
test('应该包含正确的同步模式', () => {
|
||||
expect(SyncMode.All).toBe('all');
|
||||
expect(SyncMode.Owner).toBe('owner');
|
||||
expect(SyncMode.Others).toBe('others');
|
||||
expect(SyncMode.Nearby).toBe('nearby');
|
||||
expect(SyncMode.Custom).toBe('custom');
|
||||
});
|
||||
});
|
||||
|
||||
describe('RpcTarget枚举', () => {
|
||||
test('应该包含正确的RPC目标', () => {
|
||||
expect(RpcTarget.Server).toBe('server');
|
||||
expect(RpcTarget.Client).toBe('client');
|
||||
expect(RpcTarget.All).toBe('all');
|
||||
expect(RpcTarget.Others).toBe('others');
|
||||
expect(RpcTarget.Owner).toBe('owner');
|
||||
expect(RpcTarget.Nearby).toBe('nearby');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"allowImportingTsExtensions": false,
|
||||
"lib": ["ES2020", "DOM"],
|
||||
"outDir": "./bin",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"composite": 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"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../core"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user